Intro
Securely and easily deploying an IOT software solution to multiple gateways across the world can be a challenge. However, for gateways running Wind River Helix* Device Cloud there is a clear path to follow that diminishes the challenge. The Wind River Helix* Device Cloud allows for complete device lifecycle management, from deploying, to monitoring, to updating, to decommissioning. It has telemetry capabilities as well, allowing it to receive and store data in the cloud, as well as act on it using rules and alerts. This article will explore a proof of concept that deploys software to vending machine gateways using the Helix Device Cloud (HDC).
To learn more about the Helix Device Cloud:
https://www.helixdevicecloud.com
Figure 1: High level component diagram with Arduino 101* (branded Genuino 101* outside the U.S.) and Intel® NUC
Set-up
This article assumes that chocolate bar vending machines have been deployed in various locations, that they’re controlled by a gateway with the HDC agent installed, and that they are properly configured. The POC uses the Intel® NUC (NUC5I3MYHE) running Ubuntu* 16.04 as the gateway with HDC 2.2.1 installed and an Arduino 101 on a USB port with Grove* sensors from Seeed* Studio acting as the vending machine sensors. The Arduino 101 has a touch sensor to indicate a purchase of the product; a green LED turns on when purchase is successful and a red LED turns on when the product is out of stock. A temperature sensor will monitor the vending machine’s temperature to see if the chocolate bars are in danger of melting. In addition, it has a motion sensor to count traffic passing by the vending machine which turns on a blue LED when motion is detected. The software for the vending machine is written in Python* and uses the HDC iot_python module.
For instructions on how to install and configure the HDC Agent on Ubuntu, refer to this guide in the Wind River Knowledge Library:
To interface the Arduino 101 board’s sensors with the gateway, MRAA needs to be installed on the gateway:
sudo add-apt-repository ppa:mraa/mraa sudo apt-get update sudo apt-get install libmraa1 libmraa-dev mraa-tools python-mraa python3-mraa
Code 1: commands to install MRAA on Ubuntu
The Arduino 101 must also be running the StandardFirmata sketch. That sketch comes with the Arduino IDE under ExamplesàFirmata à StandardFirmata.
Vending Machine Telemetry
The data collected from the vending machine is where the real value comes into play. The gateway application will collect motion, temperature, and inventory data, and send it to the Helix Device Cloud. The application is a python script ‘VendingMachine.py’ that will be turned into a service. Then in HDC, a variety of rules and alerts can be set up to handle the values coming in. For example, if inventory runs out, a rule can trigger more inventory to be sent out to the machine.
The Arduino 101’s sensors will supply the data to upload. To interface with it through the USB port add the line below in the code to tie into MRAA and Firmata*. Firmata will allow the board to talk to the gateway and MRAA handles to IO pin communications. Note that root access is required to access the USB port by default, so when running the python script locally, it must be ‘sudo python VendingMachine.py’.
# Interface with Arduino 101 board mraa.addSubplatform(mraa.GENERIC_FIRMATA, "/dev/ttyACM0")
Code 2: line to have MRAA use Firmata
Using Firmata will shift all the pin numbers by 512, so pin A3 for the temperature sensor is really pin 512 + 3.
Arduino 101 pins:
Temperature sensor: A3
Touch sensor: D3
Motion sensor: D7
Blue motion indicator LED: D2
Red out of stock indicator LED: D5
Green purchase indicator LED: D6
temperature_sensor = mraa.Aio(512 + 3) touch_sensor = mraa.Gpio(512 + 3) touch_sensor.dir(mraa.DIR_IN) motion_sensor = mraa.Gpio(512 + 7) motion_sensor.dir(mraa.DIR_IN) blue_motion_led = mraa.Gpio(512 + 2) blue_motion_led.dir(mraa.DIR_OUT) red_stock_led = mraa.Gpio(512 + 5) red_stock_led.dir(mraa.DIR_OUT) green_stock_led = mraa.Gpio(512 + 6) green_stock_led.dir(mraa.DIR_OUT)
Code 3: Arduino 101 sensor initialization code
The program’s loop will compile the sensor data, handle items being purchased, and then send that data to HDC every minute.
count = 0 while ( running ): #motion sensor current_motion = motion_sensor.read() if (current_motion): print "Detecting moving object" blue_motion_led.write(1) motion += 1 else: blue_motion_led.write(0) #temperature sensor fahrenheit = 0 raw_Temp = temperature_sensor.read() if raw_Temp> 0 : resistance = (1023-raw_Temp)*10000.0/raw_Temp celsius = 1/(math.log(resistance/10000.0)/B+1/298.15)-273.15 fahrenheit = (1.8 * celsius) + 32 if fahrenheit > temperature: temperature = fahrenheit #purchase flow green_stock_led.write(0) customer_purchase = touch_sensor.read() if (num_chocobars > 0): red_stock_led.write(0) if (customer_purchase): print "Customer purchasing item" green_stock_led.write(1) num_chocobars -= 1 else: red_stock_led.write(1) #send telemetery every 10 seconds if (count%POLL_INTERVAL_SEC==0): send_telemetry_sample() count += 1 sleep(1)
Code 4: The main loop of the program
To send telemetry to HDC, there are three required steps in the code for each metric: local memory needs to be allocated for it, the metric must be registered with the HDC agent, and the data needs to be sent. Refer to the condensed code below. In the actual program, the code will allocate and initialize all the sensors in the initialize() method and submit the telemetry data in the send_telemetry_sample() method. Refer to the end of the article for the full code. Following HDC’s recommendations for sending telemetry, data is only sent once every minute and only if the value has changed. This will also prevent alerts from being triggered multiple times unnecessarily.
telemetry_motion = None telemetry_motion = iot_telemetry_allocate( iot_lib_hdl, "motion", IOT_TYPE_INT64 ) iot_telemetry_register( telemetry_motion, None, 0 ) iot_telemetry_publish( telemetry_motion, None, 0, IOT_TYPE_INT64, motion )
Code 5: code needed for each telemetry metric
Registered telemetry items can be seen in the device’s dashboard in the Helix Device Cloud and can be viewed in graph form by expanding each metric.
Figure 2: Helix Device Cloud’s device dashboard
Expanding each telemetry item, the data can be viewed in graph form.
Figure 3: Temperature data graph in Helix Device Cloud
Rules and Alerts
Now that the data is being sent to the cloud, the rules and alerts feature can be leveraged. These will help monitor the vending machine when data is received for conditions that require attention.
The vending machine needs to send out an alert if the temperature gets too high, as the chocolate inside might melt. To create a new rule, go to the ‘Rules’ tab and click ‘CREATE NEW RULE’.
Figure 4: Create a new rule
1) Name the rule and select the device or devices to deploy the rule on. To deploy to a large group of devices at once, say all the vending machine gateways, use the more generic device variables on the left.
Figure 5: select devices for the rule
2) From there select the ‘temp’ telemetry item. Note that the telemetry gathering program must be running at the time of rule creation, otherwise the telemetry choices will be blank.
Figure 6: select telemetry metric to monitor
3) Once selected, set the conditions to greater than or equal to 90, as chocolate melts at 90 degrees.
Figure 7: set conditions for the telemetry metric
4) Then set up the rule response, in this case it will create a priority one alert that the chocolate is melting.
Figure 8: set up an alert
Now when the temperature gets to 90 degrees, an alert will be created in the ‘Alerts’ tab.
Figure 9: alerts in Helix Device Cloud
The condition also gives the option of sending an email or forwarding the data to a specified MQTT topic. Additionally it could trigger a device action which will be used in the next example alert for low inventory.
While the other rule responses can be completely managed in HDC, a device action requires additional code on the device side. The gateway application code initiates and receives the action sent from HDC. The action in this case will be a simple integer called ‘action_restock’, however HDC can also handle triggering a script or other system command.
1) To begin, allocate and register the action_restock:
# Allocate action restock_cmd = iot_action_allocate( iot_lib_hdl, "action_restock" ) # Restock action iot_action_parameter_add( restock_cmd, PARAM_STOCK_NAME, IOT_PARAMETER_IN, IOT_TYPE_INT32, 0 )
Code 6: code for an HDC action
2) Next, add a callback defining what to do when the action is received from the gateway. Here it is mimicking restocking the chocolate bars, so the sent number will be added to the current stock value.
def on_action_restock( request ): '''Callback function for testing parameters''' result = IOT_STATUS_SUCCESS status = IOT_STATUS_FAILURE global num_chocobars chocobarShipment = 0 IOT_LOG( iot_lib_hdl, IOT_LOG_INFO, "on_action_restock invoked\n\n") # int if ( result == IOT_STATUS_SUCCESS ): ( status, chocobarShipment ) = iot_action_parameter_get( request, PARAM_STOCK_NAME, False, IOT_TYPE_INT32 ) if ( status != IOT_STATUS_SUCCESS ): result = IOT_STATUS_BAD_PARAMETER IOT_LOG( iot_lib_hdl, IOT_LOG_ERROR, "get param failed for {}\n".format( PARAM_STOCK_NAME ) ) else: IOT_LOG( iot_lib_hdl, IOT_LOG_INFO, "{} success, value = {}\n".format( PARAM_STOCK_NAME, chocobarShipment ) ) num_chocobars = num_chocobars + chocobarShipment return result
Code 7: code to handle action received from HDC
3) Next, the action needs to be configured in HDC as well. Follow the above telemetry steps but choose ‘Add a Device Action’ instead. And like telemetry, actions are only available when the program is running on the device.
Figure 10: action conditions and responses
Now the gateway has two active rules applied to it: Restock Machine and ChocolateMeltingPoint.
Figure 11: Rules in HDC
Deployment
With the code complete, it can be turned into a service running continuously on the gateway, which can be deployed using the Helix Device Cloud to all the vending machine gateways. To start, create a new package under the ‘Updates’ tab.
Figure 12: Create an update package
1) Name the package, enter the version number, and select the device compatibility criteria to narrow down the list of devices to deploy to. The files for the program need to be uploaded and attached to the package as well: VendingMachine.py and HDC_VendingMachine.service.
HDC_VendingMachine.service file should look like the below code. The service should start after the iot.service starts as that is the HDC agent on the gateway and will start the python code. Note that the python file will need to be moved out of the initial download location as it will be erased by any subsequent package deployments. In addition, the first line of the VendingMachine.py file needs to be ‘#!/usr/bin/python’ for the service to be able to ExecStart it.
Unit] Description=HDC POC After=iot.service [Service] ExecStart=/home/whitney/Desktop/VendingMachineApp/VendingMachine.py Restart=always User=root TimeoutStartSec=240 TimeoutStopSec=15 KillMode=process KillSignal=SIGINT [Install] WantedBy=multi-user.target
Code 8: HDC_Vendingmachine.service file
Figure 13: Parameters of the update package
The cloud package can also execute commands at various parts of the install.
2) For pre-install the directory to store the code needs to be created.
sudo mkdir /usr/bin/VendingMachineApp
Code 9: Pre-install command in HDC
3) During the install, make the python file executable, and move all the files to their final destination. Note that HDC does commands as user ‘iot’, however the python script needs to run as root to have access to USB. The HDC_VendingMachine.service file already has the user as root. To avoid permission conflicts, the chmod must be done as user iot while the file is owned by user iot. Then after the sudo cp takes place the owner will change to root.
chmod +x /var/lib/iot/update/download/VendingMachine.py sudo cp /var/lib/iot/update/download/VendingMachine.py /usr/bin/VendingMachineApp/ sudo cp /var/lib/iot/update/download/HDC_VendingMachine.service /lib/systemd/system/
Code 10: Install commands in HDC
4) Post-install commands will enable and start the service.
sudo systemctl enable HDC_VendingMachine.service sudo systemctl start HDC_VendingMachine.service
Code 11: Post-install commands in HDC
Figure 14: Install commands in HDC
5) Save the package and wait for it to finish. Then it is ready to be deployed by clicking on ‘Deploy’.
Figure 15: Saved update package in HDC
6) The Compatible Device list is pre-populated based on the device conditions specified in the package. Select and add the desired devices for the deployment, then click ‘Deploy’.
Figure 16: Select devices to deploy package to
7) The status will show as ‘In Progress’ and then to change to ‘Completed’.
Figure 17: Completed deployment
8) On the gateway, check the status of the service with the command below and refer to the syslog in ‘/var/log/syslog’ for any errors starting the service and ‘var/lib/iot/update/iot_install_updates.log’ for errors with the install itself.
systemctl status HDC_VendingMachine
Code 12: Check HDC_VendingMachine service status
Full Code
#!/usr/bin/python import os import sys import signal import inspect import math import mraa from time import sleep sys.path.append( "../lib" ) from iot_python import * B=3975 # Interface with Arduino 101 board mraa.addSubplatform(mraa.GENERIC_FIRMATA, "/dev/ttyACM0") temperature_sensor = mraa.Aio(512 + 3) touch_sensor = mraa.Gpio(512 + 3) touch_sensor.dir(mraa.DIR_IN) motion_sensor = mraa.Gpio(512 + 7) motion_sensor.dir(mraa.DIR_IN) blue_motion_led = mraa.Gpio(512 + 2) blue_motion_led.dir(mraa.DIR_OUT) red_stock_led = mraa.Gpio(512 + 5) red_stock_led.dir(mraa.DIR_OUT) green_stock_led = mraa.Gpio(512 + 6) green_stock_led.dir(mraa.DIR_OUT) POLL_INTERVAL_SEC = 60 MAX_LOOP_ITERATIONS = 360 TAG_MAX_LEN = 128 # Set up named parameters for a sample action to validate actions with # parameters PARAM_STOCK_NAME = "# Chocobars to Ship" # telemetry data telemetry_motion = None telemetry_temp = None telemetry_stock_chocobars = None previous_numchocobars= 0 previous_motion = 1000 previous_temperature= 0 running = True iot_lib_hdl = None restock_cmd = None def debug_log( log_level, source, msg ): '''Debug log wrapper for printing, used for callbacks''' i = 0 prefix = ["FATAL","ALERT","CRITICAL","ERROR","WARNING", "NOTICE","INFO","DEBUG","TRACE"] # ensure log level is a valid enumeration value if ( log_level <= IOT_LOG_TRACE ): i = log_level print( "{}: {}".format( prefix[i], msg ) ) def IOT_LOG( handle, level, msg ): '''Logging function with support for call location''' # previous function call callerframerecord = inspect.stack()[1] # callerframrecord : 1 = function, 3 = file, 2 = line iot_log( handle, level, callerframerecord[1], callerframerecord[3], callerframerecord[2], msg ) def initialize(): '''Connects to the agent and registers all actions and telemetry''' global telemetry_motion, telemetry_temp, telemetry_stock_chocobars global iot_lib_hdl global restock_cmd result = False status = IOT_STATUS_FAILURE iot_lib_hdl = iot_initialize( "complete-app-py", None, 0 ) iot_log_callback_set( iot_lib_hdl, debug_log ) status = iot_connect( iot_lib_hdl, 0 ) if ( status == IOT_STATUS_SUCCESS ): IOT_LOG( iot_lib_hdl, IOT_LOG_INFO, "Connected" ) # Allocate telemetry items telemetry_motion = iot_telemetry_allocate( iot_lib_hdl, "motion", IOT_TYPE_INT64 ) telemetry_temp = iot_telemetry_allocate( iot_lib_hdl, "temp", IOT_TYPE_FLOAT64 ) iot_telemetry_attribute_set( telemetry_temp, "udmp:units", IOT_TYPE_STRING, "Fahrenheit" ) telemetry_stock_chocobars = iot_telemetry_allocate( iot_lib_hdl, "stock chocobars", IOT_TYPE_INT64 ) # Register telemetry items IOT_LOG( iot_lib_hdl, IOT_LOG_INFO, "Registering telemetry: {}".format( "motion" ) ) iot_telemetry_register( telemetry_motion, None, 0 ) IOT_LOG( iot_lib_hdl, IOT_LOG_INFO, "Registering telemetry: {}".format( "temp" ) ) iot_telemetry_register( telemetry_temp, None, 0 ) IOT_LOG( iot_lib_hdl, IOT_LOG_INFO, "Registering telemetry: {}".format( "stock chocobars" ) ) iot_telemetry_register( telemetry_stock_chocobars, None, 0 ) # Allocate action IOT_LOG( iot_lib_hdl, IOT_LOG_INFO, "Registering action test_parameters\n" ) restock_cmd = iot_action_allocate( iot_lib_hdl, "action_restock" ) # Restock action iot_action_parameter_add( restock_cmd, PARAM_STOCK_NAME, IOT_PARAMETER_IN, IOT_TYPE_INT32, 0 ) #validate action registration status = iot_action_register_callback(restock_cmd, on_action_restock, None, 0 ) if ( status != IOT_STATUS_SUCCESS ): IOT_LOG( iot_lib_hdl, IOT_LOG_ERROR, "Failed to register command. Reason: {}".format( iot_error( status ) ) ) else: IOT_LOG( iot_lib_hdl, IOT_LOG_ERROR, "Failed to connect" ) if ( status == IOT_STATUS_SUCCESS ): result = True return result def on_action_restock( request ): '''Callback function for testing parameters''' result = IOT_STATUS_SUCCESS status = IOT_STATUS_FAILURE global num_chocobars chocobarShipment = 0 IOT_LOG( iot_lib_hdl, IOT_LOG_INFO, "on_action_restock invoked\n\n") # int if ( result == IOT_STATUS_SUCCESS ): ( status, chocobarShipment ) = iot_action_parameter_get( request, PARAM_STOCK_NAME, False, IOT_TYPE_INT32 ) if ( status != IOT_STATUS_SUCCESS ): result = IOT_STATUS_BAD_PARAMETER IOT_LOG( iot_lib_hdl, IOT_LOG_ERROR, "get param failed for {}\n".format( PARAM_STOCK_NAME ) ) else: IOT_LOG( iot_lib_hdl, IOT_LOG_INFO, "{} success, value = {}\n".format( PARAM_STOCK_NAME, chocobarShipment ) ) num_chocobars = num_chocobars + chocobarShipment return result def send_telemetry_sample(): '''Send telemetry data to the agent''' global num_chocobars, motion, temperature global previous_numchocobars, previous_motion, previous_temperature IOT_LOG( iot_lib_hdl, IOT_LOG_INFO, "{}\n".format("+--------------------------------------------------------+")) if previous_motion != motion: IOT_LOG( iot_lib_hdl, IOT_LOG_INFO, "Sending motion : {}".format(motion) ); iot_telemetry_publish( telemetry_motion, None, 0, IOT_TYPE_INT64, motion ) previous_motion = motion motion = 0 if previous_temperature != temperature: IOT_LOG( iot_lib_hdl, IOT_LOG_INFO, "Sending temp : {}".format(temperature) ); iot_telemetry_publish( telemetry_temp, None, 0, IOT_TYPE_FLOAT64, temperature ) previous_temperature = temperature temperature = 0 if previous_numchocobars != num_chocobars: IOT_LOG( iot_lib_hdl, IOT_LOG_INFO, "Sending chocobars stock : {}".format(num_chocobars) ); iot_telemetry_publish( telemetry_stock_chocobars, None, 0, IOT_TYPE_INT64, num_chocobars ) previous_numchocobars = num_chocobars def sig_handler( signo, frame ): '''Handles terminatation signal and tears down gracefully''' global running if ( signo == signal.SIGINT ): IOT_LOG( iot_lib_hdl, IOT_LOG_INFO, "Received termination signal...\n" ) running = False if ( __name__ == '__main__' ): global motion, num_chocobars, temperature motion = 0 num_chocobars = 2 temperature = 0 if ( initialize() == IOT_TRUE ): signal.signal( signal.SIGINT, sig_handler ) IOT_LOG( iot_lib_hdl, IOT_LOG_INFO, "Sending telemetry..." ) count = 0 while ( running ): #motion sensor current_motion = motion_sensor.read() if (current_motion): print "Detecting moving object" blue_motion_led.write(1) motion += 1 else: blue_motion_led.write(0) #temperature sensor fahrenheit = 0 raw_Temp = temperature_sensor.read() if raw_Temp> 0 : resistance = (1023-raw_Temp)*10000.0/raw_Temp celsius = 1/(math.log(resistance/10000.0)/B+1/298.15)-273.15 fahrenheit = (1.8 * celsius) + 32 if fahrenheit > temperature: temperature = fahrenheit #purchase flow green_stock_led.write(0) customer_purchase = touch_sensor.read() if (num_chocobars > 0): red_stock_led.write(0) if (customer_purchase): print "Customer purchasing item" green_stock_led.write(1) num_chocobars -= 1 else: red_stock_led.write(1) #send telemetery every 10 seconds if (count%POLL_INTERVAL_SEC==0): send_telemetry_sample() count += 1 sleep(1) # Terminate IOT_LOG( iot_lib_hdl, IOT_LOG_INFO, "Exiting..." ) iot_terminate( iot_lib_hdl, 0 ) exit( 0 )
Code 13: VendingMachine.py file
Summary
Our vending machine code has now been successfully deployed using HDC. Temperature and stock data is being monitored with automated rules. Motion data can be referenced as time goes on to monitor foot traffic around the vending machine. Any future updates to the program and overall gateway health can be deployed and monitored using HDC.
To purchase HDC visit https://www.windriver.com/company/contact/index.html or email sales@windriver.com
References
https://www.windriver.com/products/helix/device-cloud/
About the author
Whitney Foster is a software engineer at Intel in the Software Solutions Group working on scale enabling projects for Internet of Things.
Notices
You may not use or facilitate the use of this document in connection with any infringement or other legal analysis concerning Intel products described herein. You agree to grant Intel a non-exclusive, royalty-free license to any patent claim thereafter drafted which includes subject matter disclosed herein.
No license (express or implied, by estoppel or otherwise) to any intellectual property rights is granted by this document.
Intel disclaims all express and implied warranties, including without limitation, the implied warranties of merchantability, fitness for a particular purpose, and non-infringement, as well as any warranty arising from course of performance, course of dealing, or usage in trade.
This document contains information on products, services and/or processes in development. All information provided here is subject to change without notice. Contact your Intel representative to obtain the latest forecast, schedule, specifications and roadmaps.
The products and services described may contain defects or errors known as errata which may cause deviations from published specifications. Current characterized errata are available on request.
Copies of documents which have an order number and are referenced in this document may be obtained by calling 1-800-548-4725 or by visiting www.intel.com/design/literature.htm.
Intel, Intel RealSense, Intel Edison. and the Intel logo are trademarks of Intel Corporation in the U.S. and/or other countries.
*Other names and brands may be claimed as the property of others
© 2017 Intel Corporation.