IAP/MQ Device Interaction Example

In the previous section, you provisioned an application device that is programmed to respond to a wink command issued by the CMS.  In this section, we build on the SID.js application, and write an application that looks for devices of a targeted PID type that are provisioned and attached to the network, and then issue a wink command.  You will use the Device resource in the IAP/MQ API.

An MQTT topic is a '/' delimited string that is used to access resources of the IAP/MQ API.  The structure of the topics are defined the the IAP/MQ reference guide under IAP/MQ Topic Syntax.  The example uses a small wrapper class to manage and replace parts of the topic to access specific resources and to handle substituting one or more elements in the topic string.  In most topics, you must provide the SID as the third element in the topic.  In other topics, you will provide handles that are handled as a parameter.   

The following code block only touches a small part of the topic space for IAP/MQ.  Use this as an example for you to begin your development.

IAP/MQ Wrapper Class
'use strict'
/*
*  IAP/MQ Topics and messages with substitution parameters
*/
const TOPIC_SID =  'glp/0/././sid';
const TOPIC_ABOUT = function(sid) {
    return 'glp/0/' + sid + '/fb/about';
}
const TOPIC_ALARM = function(sid){
    return 'glp/0' + sid + '/rq/alarm/' + handle
}
const TOPIC_DEV_STS = function(sid, dev) {
    return 'glp/0/' + sid + '/fb/dev/lon/' + dev  + '/sts';
}
const TOPIC_DEV_IOX_STS = function(sid, dev, deviceBlk ) {
    return 'glp/0/' + sid + '/fb/dev/' + dev + '/' + deviceBlk + '/sts';
}
const TOPIC_EVENT_ERROR = function (sid) {
    return 'glp/0/' + sid + '/ev/error';
}
const TOPIC_DEVICE_DO = function (sid, dev) {
    return 'glp/0/' + sid + '/rq/dev/lon/' + dev + '/do';
}       
const TOPIC_FB_INTERFACE = function (sid, dev, fbname, index) {
    return 'glp/0/' + sid + '/fb/dev/lon/' + dev + '/if/' + fbname + '/' + index.toString();
}
const TOPIC_FB_DP_WRITE = function (sid, dev, fbname, index) {
    return 'glp/0/' + sid + '/rq/dev/lon/' + dev + '/if/' + fbname + '/' + index.toString();    
}
const MSG_valueMsg_str_asc = function (pointName, valueStr) {
    return {[pointName] : {value: {ascii: valueStr.substr(0,20)}}};
}
const TOPIC_ALLOCATE_HANDLE = function (sid) {
    return 'glp/0/' + sid + '/rq/=system/handle';
}
const TOPIC_ALLOCATE_HANDLE_FB = function (sid) {
    return 'glp/0/' + sid + '/fb/=system/handle';
}
const TOPIC_DO = function (sid) {
    return 'glp/0/' + sid + '/rq/do';
}
const TOPIC_DP_MONITOR = function (sid, dev, pointName) {
    return 'glp/0/' + sid + '/rq/dev/lon/' + dev + '/if/' + pointName + '/monitor';
}
const TOPIC_DP_UPDATE = function (sid) {
    return 'glp/0/' + sid + '/ev/data';
}
const TOPIC_CPU_STATS = function (sid) {
    return 'glp/0/' + sid + '/=alarm/system/cpu/+';
}
//const TOPIC
 
module.exports.TOPIC_SID = TOPIC_SID;
module.exports.TOPIC_ABOUT = TOPIC_ABOUT;
module.exports.TOPIC_DEV_STS = TOPIC_DEV_STS;
module.exports.TOPIC_EVENT_ERROR = TOPIC_EVENT_ERROR;
module.exports.TOPIC_DEV_DO = TOPIC_DEVICE_DO;
module.exports.TOPIC_FB_INTERFACE = TOPIC_FB_INTERFACE;
module.exports.TOPIC_FB_DP_WRITE = TOPIC_FB_DP_WRITE;
module.exports.MSG_valueMsg_str_asc = MSG_valueMsg_str_asc;
module.exports.TOPIC_ALLOCATE_HANDLE = TOPIC_ALLOCATE_HANDLE;
module.exports.TOPIC_ALLOCATE_HANDLE_FB = TOPIC_ALLOCATE_HANDLE_FB;
module.exports.TOPIC_DO = TOPIC_DO;
module.exports.TOPIC_DP_MONITOR = TOPIC_DP_MONITOR;
module.exports.TOPIC_DP_UPDATE = TOPIC_DP_UPDATE;
module.exports.TOPIC_DEV_IOX_STS = TOPIC_DEV_IOX_STS;
module.exports.TOPIC_CPU_STATS = TOPIC_CPU_STATS; 


To create a fresh VS code project, follow these steps:

  1. Create a new folder in a place of your choice.  For this example use:  Documents\nodeDev\winkonce 
     
  2. In VS code, use File > Open Folder and select the new folder you created in the previous step.

  3. In VS code, select File > New

  4. Copy the code block above, and paste it in the new file you created in the previous step.  Save the file as iapmq.js.

  5. In VS code, Select Terminal > New Terminal.

  6. Type npm init and provide the inputs when you are prompted. 
    The following screenshot shows an example highlighting the inputs that were provided that are not the default values.




  7. This application needs the mqtt.js npm module.  Use the following command in the terminal window:  npm install mqtt --save 

  8. This application uses the npm collections module.  Use the following command in the terminal window:  npm install collections --save 

Simple Device Interaction Example

The following application does the following:

  1. Determine the segment ID of the target SmartServer.
  2. Subscribe to the feedback channel to monitor device status.
  3. If the reported device matches a targeted device type, publish a device/do action on the request channel to issue a wink command.
  4. Do this once for each provisioned device.

Winkonce.js Complete Source Code
const mqtt = require('mqtt');
const set = require ('collections/set'); //collections.js www.collectionsjs.com

var winkComplete = new Set();
var sid = ''; 
const iap = require('./iapmq');
var  subscribtionsActive = false;

/* environ returns the value of a named environment
 * variable, if it exists, or returns the default
 * value otherwise.
 */
function environ(variable, defaultValue) {
    return process.env.hasOwnProperty(variable) ?
        process.env[variable] : defaultValue;
}
// PIDs for EVB 5000, and EVB 6000 versions of the application.  Other PIDs could be added to support
// other devices that may implement a wink behavior.
var targetPids = [
    {pid: '9FFFFF0501840450'},
    {pid: '9FFFFF0501840460'}
];

 // Set up a MQTT client for the duration of the app lifetime.
var client = mqtt.connect('mqtt://' + environ('DEV_TARGET', '127.0.0.1') + ':1883');

// Subscribe to the segment ID topic.
client.subscribe(
    iap.TOPIC_SID,
    (error) => {
        if (error) {
            console.log(error);
        }
    }
);

client.on('message', (topic, message) => {
    try {
        const payload = JSON.parse(message);
        var nowTs = new Date(); // Seconds TS good enough
        if (topic === iap.TOPIC_SID) {
            // Assuming the SID topic is a string payload
            if (typeof(payload) === typeof('xyz')) {
                if (payload.length > 0) {
                    sid = payload;
                    console.log(nowTs.toISOString() + ' - SmartServer SID: ' + payload);
                    // Note how the '+' wild card is used to monitor device status for 
                    // any lon device.  Note, once we know the SID, we need to avoid
                    // adding multiple listeners.  Subscribe once and only once
                    if (!subscribtionsActive) {
                        client.subscribe (iap.TOPIC_DEV_STS(sid, '+'));
                        client.unsubscribe (iap.TOPIC_SID);
                        subscribtionsActive = true;
                    } 
                } else {
                    // We are not provisioned. 
                    sid = undefined;
                    cosole.log(nowTs.toISOString() +' - [%s] Problem with SID payload.' + payload);
                }
            }
        } 
        // topic expected glp/0/[sid]/fb/dev/lon/{handle}/sts
        if (topic.endsWith ('/sts')) {  
            var devHandle;  
            var dpTopic;
            // qualify the device as a targeted pid
            for ( i = 0; i < targetPids.length; i++)
                if (payload.type === targetPids[i].pid)
                    break;
            if (i === targetPids.length)
                return; 
                         
            //topic expected glp/0/[sid]/fb/dev/lon/{handle}/sts, extract the device handle
            devHandle = topic.split('/')[6];       
            // Monitor device health
            if (payload.state === 'provisioned') {
                if (payload.health === 'normal' ) {
                    if (!winkComplete.has(devHandle)) {
                        const doWink = { action: 'wink' };
                        client.publish (iap.TOPIC_DEV_DO(sid, devHandle), 
                            JSON.stringify(doWink),
                            {qos:1,retain:false},
                            (err) => {
                                if (err != null) { // issue with publish
                                    console.log(err.message); 
                                } else {
                                    console.log('Device: %s - Publish Wink Action', devHandle);
                                    winkComplete.add(devHandle);
                                } // publish success
                            } // (err) => callback
                        ); // client.publish
                    } else {
                        // Note that the publish device/do {action:'wink'} will generate two messages in the fb/dev.../sts
                        // channel. The first will report the action:'wink' request, and the second will report
                        // action:null.  Also note that actions from other client applications may generate message
                        // on the fb/dev/lon/+/sts channel.  See what messages show up when you issue a device.Test from
                        // the CMS device.widget.
                        console.log('Device: %s - Action: %s, State: %s, Health: %s', devHandle, 
                             payload.action, payload.state, payload.health);
                    }                                                                                                                                                                                                
                } else { // Handle case when the device is OFFLINE
                    console.log('Device: %s, State: %s, health: %s', devHandle, payload.state, payload.health);
                }                  
            } else {
                console.log ('Device: %s - State: %s, health: %s', devHandle, payload.state, payload.health ); 
            }
            // Remove from winkComplete set if the device is deprovisioned
            if (payload.state === 'unprovisioned')
                winkComplete.delete(devHandle);
        }
    }  catch(error) {
        console.error(nowTs.toISOString() + ' - MQTT Message: ' + error);
    }
});

Exploring this Application

This application does not do anything particularly important, but it does serve as a guide on using the IAP/MQ API.  You can see how the publish/subscribe message based APIs are used to interact with a device.

Let's explore this application in detail:  

  • Lines 1 and 2 pull in the mqtt and collections npm modules. 

  • The collections module set object is used to track whether the application has issued a wink command to each of the discovered devices that are defined in the targetPids object array at line 19. 

  • On line 25, the object client is defined to connect to a local or remote broker as defined by the environment variable DEV_TARGET.  When running the application from VS code, you need to have this environment variable define in the file .vcode/lauch.json, and the targeted SmartServer must have a ufw rule defined to allow connections to port 1883. 

  • On line 28, the application subscribes to iap.TOPIC_SID ('glp/0/././sid') to determine the SID for the SmartServer defined in DEV_TARGET.

  • On line 37, MQTT messages are processed in the client.on('message'... event.

  • Once the SmartServer broker reports the SID, the application subscribes to iap.TOPIC_DEV_STS (glp/0/{sid}/fb/dev/lon/+/sts).

  • The message broker will provide the status of all defined devices on the smartServer.  You may find it helpful to use mosquitto_sub to grasp this idea.  From an SSH console on the SmartServer, type this command: mosquitto_sub -v -t glp/0/+/fb/dev/lon/+/sts 

  • On line 63, the messages for the ../fb/dev/lon/+/sts are processed. 

  • Lines 67-71 are used to determine whether the device program ID matches one in the targetPids object array. 

  • Lines 76 and 77 determine whether the device is provisioned, and it's health is normal.  If both tests pass, the application publishes the message {'action':'wink'} to iap.TOPIC_DEV_DO (glp/0/{sid}/rq/dev/lon/{handle}/do). 

  • On lines 90-91, the set winkComplete is used to capture the fact that this device has been issued a wink command .  Your device toggles the blue LED on the bottom edge of the board as a result.

  • The application uses console.log to report key information from other messages on the glp/0/{sid}/fb/dev/lon/+/sts channel.  

Next Step

Now that you have seen how a simple device interaction application works, you can move on to the next step in this tutorial.  Continue with Data Monitoring Application.