Wind River Helix Device Cloud Application Deployment: POC Retail Vending Machine

0
129

Get access to the new Intel® IoT Developer Kit, a complete hardware and software solution that allows developers to create exciting new solutions with the Intel® Galileo and Intel® Edison boards. Visit the Intel® Developer Zone for IoT.

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:

http://knowledge.windriver.com/en-us/000_Products/040/050/020/000_Wind_River_Helix_Device_Cloud_Getting_Started/060

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/

http://knowledge.windriver.com/en-us/000_Products/040/050/020/000_Wind_River_Helix_Device_Cloud_Getting_Started/060

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.

LEAVE A REPLY