S5D9 Equipment Anomaly Detection with Zendesk Integration – Quick Start Guide


This tutorial will show you how to attach the S5D9 fast prototyping board to a fan and monitor its vibration on the Renesas IoT Sandbox.  We will use workflows in the Renesas IoT Sandbox to learn the vibration pattern as events are being streamed.

When a new vibration pattern is detected the yellow LED on the board will flash 5 times.  When an anomaly is detected, the red LED on the board will flash 5 times and a Zendesk ticket will be automatically created.  

Note: The Zendesk ticket is created if you choose to link your account to the Renesas IoT Sandbox.

Also Note: This tutorial will exceed the allotted free daily credits provided by the Renesas IoT Sandbox if you run it 24 hours.  You can reduce the credits used by slowing the data transmission from per 5 seconds.  You can also upgrade to the paid plan for more credits on www.mediumone.com.

What you need to get started:


  • S5D9 Quick Start Guide (insert link)
  • How to link Zendesk to Renesas IoT Sandbox (here)



If you don’t have the latest image, program the S5D9 image to the S5D9 board following the instructions in the “How to update an application binary on the S5D9 Board” tutorial.


Step 2: Attach S5D9 board to fan

We selected this fan because it has fine resolution fan settings (100 settings) and a remote.  We also attached the board to the fan using outdoor strength double-sided tape to provide a secure attachment.  This is important to detect the vibration.  Attach the board to the fan as shown in this image.

Note: The board will need to be placed on a flat surface or horizontal position since it currently only analyzes the X and Y axis.



You will need a windows PC to complete this step.  Click here to activate the kit and complete the registration process to create a Renesas IoT Sandbox account preconfigured for this board. Once activation is complete, you will receive an email with your account credentials needed to provision the kit and access the cloud portal.  

This email will include your:

  • Renesas IoT Sandbox web login
  • API Key
  • MQTT Project ID
  • MQTT Registration User ID
  • Registration User password

The email will also include a .txt attachment that you will drag to your board to auto-enroll it.

Follow these steps to connect your board to the Renesas IoT Sandbox

  1. Plug your S5D9 board into your computer and it should show up as an USB Drive
  2. Open the folder. There should be these files in the USB Drive: you may not be able to see loaded.txt

  1. Download the file named “m1config.txt” from your welcome email in Step 2. Do not change the file name. The email should have these credentials on each line:
    1. On the first line, the API Key
    2. On the second line, the MQTT Project ID
    3. On the third line, the MQTT Registration User ID
    4. On the last line, the Registration User Password

Sample m1config.txt:

  1. Drag this file into the board directory. Wait 2 seconds, then unplug the board and plug it back in. Make sure it is connected to Ethernet.

  1. At first, both the green and yellow LED will be on. This signifies that the board is trying to connect to ethernet and your Renesas IoT Sandbox credentials (m1config.txt file). After a few minutes, only the green LED light on the board should be on. This signifies that the board has successfully connected. If the board can’t connect for some reason, all 3 lights will simultaneously blink on and off. If this happens, verify the “m1config.txt” file is correct and your ethernet is working.



Go to Dashboard add Widget Real Time Events Stream.  

You will see a widget appear.  Select the user on the drop down menu (Note: Your device will have a unique user id not matching that shown in the picture).  Click on the save icon on the top right to save the dashboard view.

Plug the USB cable into the board with the power supplied.  If your board was already powered on, restart it by unplugging and plugging the USB cable.

Observe the Real Time Events Stream widget, you should see at least one event appear after a few seconds.  If you do not see any event appear, go back and repeat this step, your board isn’t connecting to this account.


Step 4: Create Workflow “Update Sample Periods and Thresholds”

In this step, you will create a workflow that will set the sample rate and thresholds on the board.  This workflow will trigger when an event with tag “connect” is sent to this Renesas IoT Sandbox project.

Login into the your Renesas IoT Sandbox account. Click on Workflow Studio on the left navigation bar and then “Create” workflow.

Name your workflow “Update Sample Periods and Thresholds”.  

On the right navigation bar, select the top icon to expand the tag to reveal the tags and triggers.  Expand raw and drag connected to the main canvas.

Next, click on the Modules icon on the right navigation and expand Foundation.  Drag Base Python to the canvas.

Next, connect the trigger to the base python as shown below by clicking and dragging a line between the two dots.

Double click on Base Python to reveal the python editor.  Paste the following code in the editor.  Click “Check Syntax” and make sure you see the “Pass!” message appear.  Next, click “Save and Activate”.


Update Sample Period and Thresholds Workflow

This workflow is for updating the settings for sample periods and thresholds. If any of these values
are changed, the board must be reset.

This is the time before a 'summary event' is sent from the board to the cloud. This 'summary event' 
includes the average values across all sensors on the board and will be sent as long as the board is

This is the threshold a certain sensor much reach before a 'threshold event' is sent. 
For example, the thresholds are represented as below:
"x_accel", 1, 0.2  
- The first string is the name of the sensor
- The second value is to specify 0 for "relative" or 1 "absolute"
- The third value is the threshold

Last Updated: May 1, 2017

Author: Medium One


import MQTT

# Sample Period set to 5 seconds
period = 5 * TICKS_PER_SECOND

x = "x_accel", 1, 4  # absolute, g
y = "y_accel", 1, 4  # absolute, g
z = "z_accel", 1, 4  # absolute, g
x_mag = "x_mag", 0, 10000  # absolute, uT
y_mag = "y_mag", 0, 10000  # absolute, uT
z_mag = "z_mag", 0, 10000  # absolute, uT
temp = "temp1", 0, 100  # relative, %
temp2 = "temp2", 0, 100  # relative, %
temp3 = "temp3", 0, 100  # relative, %
humidity = "humidity", 0, 100  # relative, %
pressure = "pressure", 0, 100  # relative, %
mic = "mic", 1, 102400  # absolute, units
sensors = [x, y, z, x_mag, y_mag, z_mag, temp, temp2, temp3, humidity, pressure, mic]
threshold_string = ';'.join('T{}'.format(':'.join(str(settings) for settings in sensor)) for sensor in sensors)
MQTT.publish_event_to_client('s5d9', 'S{};{}'.format(5 * TICKS_PER_SECOND, threshold_string))

Congrats! You just created your first workflow.



Reset the board. Go to the dashboard page and observe the Real Time Events Stream widget.  You should notice events appearing from the board once every 5 seconds.  This continuous data feed includes vibration and sensor measurements from the board.


Step 5: Create alerts stream

Next, we will create a new events stream called “alerts” which will be used to save alert events.

Click on Config on the left panel and Data Streams.  Then click on Create New Stream.

On the next screen, type alerts in the name and click on Save Data Stream.



You should see the newly created alerts stream on your list of data streams on the following screen.


Step 6: Create Workflow “Vibration Monitoring”

Next, you will create a workflow to monitor the vibration data that is being received once every 5 seconds. This workflow will detect the vibration pattern and monitor any anomaly.

Similar to Step 4, go to Workflow Studio and click on Create Workflow.  Drag the Base Python module to the canvas.

Double click on the Base Python box.  Expand Inputs/Outputs label to reveal additional buttons.  Click “Add Input” until you see a total 6 inputs on the row.  Click on “Add Output” once to add a second output.  Then click Save at the bottom on the Base Python window.

Next, add two output event modules to the canvas. Click on “Outputs” on the right and drag “Processed Stream – Single” to the canvas.  Repeat so there are two of them side by side.

Click on one Output box and edit the label and data streams as shown below.  Be sure to click “Save”.  


Next, add the triggers and connect the workflow as shown.  Important: Be sure to connect the right modules in order as shown below, this is important for the workflow to function properly.

Double click on Base Python and add the following python code.  Click “Save and Activate”.

import Store
import time
import json
import Analytics
import datetime
import DateConversion
import MQTT

#  Parameters
# Set anomaly sensitivity, the lower the number, the more sensitive.  Must be 0 or greater

# Set anomaly sensitivity based on number of bits in pattern change.  Must be 0 or greater

# Set pattern sensitivity, the lower the number, the more sensitive.  Must be 0 or greater. 
# This determines how sensitive the program will be to "lock" to a new pattern

# Set sensitivity to determine if device is "ON", based on the x or y amplitude.  
# The lower the number the number the more sensitive, this is based on G force

# Set the minimum amount to generate new ticket between anomalies
# This is to limit to number of tickets created (send_ticket output tag)

#  Setting initial values
# set flag to no locking by default
new_lock = False

# determine how many bits of the pattern has changed
pattern_bit_change = 0

# set default status message to OK
status_message = "OK"

# set default anomaly flag to FALSE
anomaly_detected = False

# default anomaly message
anomaly_message = "None detected"

# default anomaly output tag value
anomaly_flag = 0

# default send_ticket tag value
send_ticket = 0

# default to minimal minutes
time_since_last_ticket = MINIMUM_MINS_BTW_TICKETS

# initialize debug message
debug_message = ""

#  Functions
# This function returns the time different in seconds between two iso timestamps
def iso_time_delta(a,b):
    a_datetime = DateConversion.to_py_datetime(a)
    b_datetime = DateConversion.to_py_datetime(b)
    return (b_datetime - a_datetime).total_seconds()

# This function returns current time in iso format
def get_current_iso_time():
    dtnow = datetime.datetime.now()
    dtutcnow = datetime.datetime.utcnow()
    delta = dtnow - dtutcnow
    hh,mm = divmod((delta.days * 24*60*60 + delta.seconds + 30) // 60, 60)
    return "%s%+03d:%02d" % (dtnow.isoformat(), hh, mm)

#  Start Program
# calculate vibration amplitude from peak values on x and y axis
x_diff = IONode.get_input('in1')['event_data']['value'] - IONode.get_input('in2')['event_data']['value']
y_diff = IONode.get_input('in3')['event_data']['value'] - IONode.get_input('in4')['event_data']['value']

# get approximate oscillation cycles on x and y axis
x_zero_cross = IONode.get_input('in5')['event_data']['value'] 
y_zero_cross = IONode.get_input('in6')['event_data']['value'] 

# Bin x amplitude in a 1 of 9 bins
if x_diff < 0.1:
    xd_bin = 1
elif x_diff < 0.150:
    xd_bin = 2
elif x_diff < 0.200:
    xd_bin = 3
elif x_diff < 0.250:
    xd_bin = 4
elif x_diff < 0.300:
    xd_bin = 5
elif x_diff < 0.350:
    xd_bin = 6
elif x_diff < 0.400:
    xd_bin = 7
elif x_diff < 0.450:
    xd_bin = 8
    xd_bin = 9
# Bin y amplitude in a 1 of 9 bins
if y_diff < 0.1:
    yd_bin = 1
elif y_diff < 0.150:
    yd_bin = 2
elif y_diff < 0.200:
    yd_bin = 3
elif y_diff < 0.250:
    yd_bin = 4
elif y_diff < 0.300:
    yd_bin = 5
elif y_diff < 0.350:
    yd_bin = 6
elif y_diff < 0.400:
    yd_bin = 7
elif y_diff < 0.450:
    yd_bin = 8
    yd_bin = 9

# Bin x oscillation in a 1 of 9 bins
if x_zero_cross < 50:
    xc_bin = 1
elif x_zero_cross < 100:
    xc_bin = 2
elif x_zero_cross < 150:
    xc_bin = 3
elif x_zero_cross < 200:
    xc_bin = 4
elif x_zero_cross < 250:
    xc_bin = 5
elif x_zero_cross < 300:
    xc_bin = 6
elif x_zero_cross < 350:
    xc_bin = 7
elif x_zero_cross < 400:
    xc_bin = 8
    xc_bin = 9

# Bin y oscillation in a 1 of 9 bins
if y_zero_cross < 50:
    yc_bin = 1
elif y_zero_cross < 100:
    yc_bin = 2
elif y_zero_cross < 150:
    yc_bin = 3
elif y_zero_cross < 200:
    yc_bin = 4
elif y_zero_cross < 250:
    yc_bin = 5
elif y_zero_cross < 300:
    yc_bin = 6
elif y_zero_cross < 350:
    yc_bin = 7
elif y_zero_cross < 400:
    yc_bin = 8
    yc_bin = 9

# get last 5 bins from Analytics
    recent_xdbin = Analytics.last_n_values('processed.xd_bin', 5)
    recent_ydbin = Analytics.last_n_values('processed.yd_bin', 5)
    recent_xcbin = Analytics.last_n_values('processed.xc_bin', 5)
    recent_ycbin = Analytics.last_n_values('processed.yc_bin', 5)
except Exception:

# get total count per bin (up to 5)
count_xdbin = len(recent_xdbin) + 1
count_ydbin = len(recent_ydbin) + 1
count_xcbin = len(recent_xcbin) + 1
count_ycbin = len(recent_ycbin) + 1

# calculate average bin over last 5 bin values
total_xdbin = xd_bin 
for i in recent_xdbin:
    total_xdbin += i['processed.xd_bin']
total_ydbin = yd_bin 
for i in recent_ydbin:
    total_ydbin += i['processed.yd_bin']    

total_xcbin = xc_bin
for i in recent_xcbin:
    total_xcbin += i['processed.xc_bin']   

total_ycbin = yc_bin
for i in recent_ycbin:
    total_ycbin += i['processed.yc_bin']   

# The bin averages will be used to determine the vibration pattern
xd_bin_avg = total_xdbin/count_xdbin
yd_bin_avg = total_ydbin/count_ydbin   
xc_bin_avg = total_xcbin/count_xcbin
yc_bin_avg = total_ycbin/count_ycbin

# calculate current vibration pattern
pattern = xd_bin_avg*1000 + yd_bin_avg*100 + xc_bin_avg*10 + yc_bin_avg
current_pattern = xd_bin*1000 + yd_bin*100 + xc_bin*10 + yc_bin

# Get last locked pattern from Store
# If no prior lock pattern, set to 1111 as default
# Used for very first time WF is ran
locked_pattern = Store.get("locked_pattern")
if locked_pattern is None:
    locked_pattern = “1111”
    new_lock = True
prior_locked_pattern = Store.get("prior_locked_pattern")
if prior_locked_pattern is None:
    prior_locked_pattern = 0
    new_lock = True

# Get last anomaly status from Store, this is used to determine if anomaly is "slightly sensed" 
# v.s. "strong sense" which will be considered an true anomaly
# If no prior anomaly status, initialize to "none"
anomaly_status = Store.get("anomaly_status")
if anomaly_status is None:
    anomaly_status = "none"
# Get pattern of last anomaly, used to prevent multiple tickets being files for same pattern
# If no prior anomaly pattern, initialize to 0
locked_pattern_anomaly = Store.get("locked_pattern_anomaly")
if locked_pattern_anomaly is None:
    locked_pattern_anomaly = 0

# Get last time ticket was filed, used to limit time between tickets
last_ticket_filed = Store.get("last_ticket_filed")
if last_ticket_filed != None:
    time_since_last_ticket = iso_time_delta(last_ticket_filed,get_current_iso_time()) / 60

# decompose pattern into bins
locked_xd_bin_avg = locked_pattern[0]
locked_yd_bin_avg = locked_pattern[1]
locked_xc_bin_avg = locked_pattern[2]
locked_yc_bin_avg = locked_pattern[3]

# lock new pattern is any bin changes by pattern sensitivity threshold
if abs(int(locked_xd_bin_avg) - xd_bin_avg) >= PATTERN_SENSITIVITY:
    new_lock = True
elif abs(int(locked_yd_bin_avg) - yd_bin_avg) >= PATTERN_SENSITIVITY:
    new_lock = True
elif abs(int(locked_xc_bin_avg) - xc_bin_avg) >= PATTERN_SENSITIVITY:
    new_lock = True
elif abs(int(locked_yc_bin_avg) - yc_bin_avg) >= PATTERN_SENSITIVITY:
    new_lock = True

# determine how many bits in pattern has changed
if abs(int(locked_xd_bin_avg) - xd_bin_avg) > 0:
    pattern_bit_change = pattern_bit_change + 1
if abs(int(locked_yd_bin_avg) - yd_bin_avg) > 0:
    pattern_bit_change = pattern_bit_change + 1
if abs(int(locked_xc_bin_avg) - xc_bin_avg) > 0:
    pattern_bit_change = pattern_bit_change + 1
if abs(int(locked_yc_bin_avg) - yc_bin_avg) > 0:
    pattern_bit_change = pattern_bit_change + 1

# lock new pattern if more enough bits has changed
if pattern_bit_change == 2 and int(prior_locked_pattern) == int(pattern):
    debug_message = "Blocked pattern oscillation: "+str(prior_locked_pattern)
elif pattern_bit_change >= PATTERN_SENSITIVITY:
    new_lock = True

# Lock new pattern
if new_lock:
    status_message = "LOCKED new pattern ("+str(locked_pattern)+" -> "+str(pattern)+")"
    locked_pattern = pattern
# Detect Anomaly 
anomaly_xd = abs(int(locked_xd_bin_avg) - xd_bin)
anomaly_yd = abs(int(locked_yd_bin_avg) - yd_bin)
anomaly_xc = abs(int(locked_xc_bin_avg) - xc_bin)
anomaly_yc = abs(int(locked_yc_bin_avg) - yc_bin)

if anomaly_xd > ANOMALY_SENSITIVITY:
    anomaly_detected = True
    debug_message = "Type XD"
elif anomaly_yd > ANOMALY_SENSITIVITY:
    anomaly_detected = True
    debug_message = "Type YD"
elif anomaly_xc > ANOMALY_SENSITIVITY:
    anomaly_detected = True
    debug_message = "Type XC"
elif anomaly_yc > ANOMALY_SENSITIVITY:
    anomaly_detected = True
    debug_message = "Type YC"
elif anomaly_xd + anomaly_yd + anomaly_xc + anomaly_yc > 2*ANOMALY_SENSITIVITY:
    anomaly_detected = True
    debug_message = "Type SUM"
elif pattern_bit_change >= ANOMALY_SENSITIVITY_BIT_CHG:
    debug_message = "Type BIT_CHG"
    anomaly_detected = True

if anomaly_detected == True and int(locked_pattern_anomaly) != int(locked_pattern):
    # if this is first time sensed, set to "warning" status
    if anomaly_status == "none" and new_lock != True:
        anomaly_message = "Warning: Potential anomaly sensed"
    # if second consecutive anomaly or new pattern lock, trigger true anomaly
    elif anomaly_status == "sensed" or new_lock == True:
        anomaly_message = "Warning: Anomaly detected"
        anomaly_flag = 1
        debug_message = debug_message + ":" + str(locked_pattern_anomaly) + " " + str(locked_pattern)

        if new_lock == True:
            # blink both yellow and red LED
            MQTT.publish_event_to_client('s5d9', 'B6:5')
            # blink red LED
            MQTT.publish_event_to_client('s5d9', 'B4:5')
        # Determine if a ticket should be filed
        if (time_since_last_ticket >= MINIMUM_MINS_BTW_TICKETS):
            debug_message = debug_message + "(last filed:"+str(time_since_last_ticket)+" mins ago) "
            send_ticket = 1

    elif new_lock == True:
        # blink only yellow LED
        MQTT.publish_event_to_client('s5d9', 'B2:5')
    if new_lock == True:
        # blink only yellow LED
        MQTT.publish_event_to_client('s5d9', 'B2:5')

device_status = "on"
        device_status = "off"

IONode.set_output('out1', {"xd_bin":xd_bin,

if new_lock:
    if send_ticket:
        IONode.set_output('out2', {"locked_pattern":locked_pattern,
        IONode.set_output('out2', {"locked_pattern":locked_pattern,


This workflow monitors the vibration data and detects vibration patterns.  Once a vibration pattern is LOCKED, new vibration patterns will be compared to see if there’s an anomaly.  The pattern locking and anomaly sensitivity can be adjusted in the workflow below.

Once an Anomaly is detected an alert event is sent to trigger a downstream Zendesk ticket creation.

Note: This workflow has been tuned to receive vibration data once every 5-10 seconds.



Go to Dashboard, observe the Real Time Event Stream widget.  Right after the next “raw” event, you should see a “processed” event.  The processed event is generated by the newly created workflow.  

Turn on the fan to level 10 and let it run for 30 seconds.  Then increase the speed to 75.  Within 30 seconds you should see the yellow and red LEDs flash.  This means the workflow is detecting the pattern change and anomaly.  If you do not see this, don’t move forward.


Step 7: Create Dashboard

Next, we will display the vibration pattern on the dashboard.  Important: Refresh the dashboard page, this will force the browser to load any newly created tags from the workflows.

Add the Real Time Gauge, select the device in the drop down, click on the gear box.  On the popup, click the box beside:


Click save.  You should see the gauges as shown below.  


alerts.locked_pattern is the most recent vibration pattern locked.  
processed.current_pattern is the most recent vibration detected
raw.humidity_avg is the most recent humidity measurement
raw.temp3.avg is the most recent temperature measurement


Next, add a “Last Value Table” widget, select your device and click on the gear button.  Similar to the prior step, check the following tags below.


processed.anomaly_message is the latest anomaly message generated by the Vibration Monitoring workflow
processed.locked_pattern is the most recent locked pattern, same as above
processed.status_message is the latest board status


Lastly, add the “Table” widget and add the following tags below.  This allows you to see the changes in the message over time.  Note, send_ticket is used for the Zendesk ticket.



Save your dashboard.



Turn on the fan and change the fan setting to level 10.  Wait up to 30 seconds to observe a new locked_pattern.  This may take time since the workflow is looking for a stable pattern.  

Next change the fan level to 60.  Wait up to 30 seconds, you should see yellow and red LEDs flash.  The red LED means an anomaly was detected.  Anomalies are not always detected with small changes in pattern.  You can adjust the fan levels by a small amount or large amount.

Observe the widgets being updated on the dashboard.  Click the refresh button in the Table widget to see the anomaly, lock and send ticket messages on the dashboard.

Note: send_ticket events are only generated up to once per minute.


Step 8: Linking Zendesk (Optional)

You can now link your Zendesk account to the Renesas IoT Sandbox so a ticket is automatically created when an anomaly is detected.  The workflow in Step 6 will send an event with “send_ticket”:1 when a ticket should be created.

If you haven’t done so: follow this tutorial to link your Zendesk account account to the Renesas IoT Sandbox (here).  (Note the Zendesk Credentials ID from this guide, you will need this in the step below.)

Once done, create a workflow called “Create Zendesk Ticket”.  Construct the workflow as follows in the diagram below.  Note, you may need to refresh your browser if you don’t see all the tags to force a reload.

Important, note that only one tag is a trigger (alerts.send_ticket).  This means that this workflow is only triggered when this specific tag is received and will not be triggered if the other tags are received.  We only want this workflow to run when a send_ticket event is seen.  Also make sure you preserve the ordering of the tags.

Disable the trigger for the other tags: double click on raw:ip_address and uncheck the trigger box.  Click Save.  Repeat for the other tags.

Double click on Base Python and add the following code.  Make sure you put your Zendesk External API Credentials in the code below.

This workflow creates a Zendesk ticket.
import Zendesk
import Credentials
import Analytics

def get_API_user_id():
    result = Analytics.events('raw', None, None, 1, None)
    return result[0]["user"]

ip_address = IONode.get_input('in2')['event_data']['value']
temperature = IONode.get_input('in3')['event_data']['value']
pressure = IONode.get_input('in4')['event_data']['value']
humidity = IONode.get_input('in5')['event_data']['value']
current_pattern = IONode.get_input('in6')['event_data']['value']
locked_pattern = IONode.get_input('in7')['event_data']['value']
user_id = get_API_user_id()

message = '''Anomaly Detected.  Detected experiencing unexpected vibration pattern {{current_pattern}}
Usual vibration pattern is {{locked_pattern}}
Device ID: {{user_id}}
IP Address: {{ip_address}}
Temperatures: {{temperature}}
Pressure: {{pressure}}'''.replace('{{current_pattern}}', str(current_pattern)) \
                    .replace('{{locked_pattern}}', str(locked_pattern)) \
                    .replace('{{user_id}}', user_id) \
                    .replace('{{ip_address}}', str(ip_address)) \
                    .replace('{{temperature}}', str(temperature)) \
                    .replace('{{pressure}}', str(pressure))

creds = Credentials.use('<insert your zendesk credentials ID here>')
Zendesk.authenticate(creds['URL'], creds['username'], creds['API Key'])
Zendesk.create_ticket("Automated Ticket", message)


Congrats, you just connected your anomaly detection to Zendesk.



Now change the fan settings enough to generate an anomaly.  Log into your Zendesk account and see the new ticket created.