Assuming you have completed the first two tutorials you should have your gateway connected to your Pi and the app.js program displaying messages. 

basic sensor startup messages
basic sensor startup messages

Here is a screen shot of the messages you should see on your Raspberry Pi when you first power up your new Basic Sensor. The first thing to understand is that all messages from your Arduino gateway are formatted as a single text string with the parameters separated by a semi-colon 

Understanding the mySensors messages

All messages use the following format. Note that the \n is on non printed character called ‘carriage return’ that signals the end of the message and starts a new line on the Pi’s terminal window.

node-id;child-sensor-id;message-type;ack;sub-type;payload\n

The first parameter we will describe is the message-type, an integer between 0-4 which defines the type of message as described below. Messages of type 0,3 and 4 are generated by the mySensors software and are useful for troubleshooting. Message type 1 identifies the message that contains the sensor data that you sent from your Arduino sensor using the send() method. This is the message that we need to decode as it contains the sensor data (payload) that we are interested in. If you would like to better understand the system (Internal) messages then a full description is available on the mySensors web site.

basic sensor startup messages
basic sensor startup messages

The send() method we used in the Arduino created the message shown in the last line above. “18;0;1;0;16;1”. Using the message format we know that this message came from node 18, child-sensor 0. The message sub-type was type 16, which is the value of the variable V_TRIPPED that we set when we created the sensor object in the Arduino

“MyMessage msgGateStatus(CHILD_ID_SWITCH, V_TRIPPED);”

The final message parameter 1, the payload, is the status of our switch that we sent with the command,

send(msgGateStatus.set(switchState));

Where 1 represents the switch being in the open state, and 0 closed.

Note that there is also an associated preceding message of type 3 (Internal) that is helpful for debugging as it contains more information. “0;255;3;0;9;TSF:MSG:READ,18-18-0,s=0,c=1,t=16,pt=2,l=2,sg=0:1” Check out the mySensors debug web page to interpret this message.

Node.js program to parse the message

The code that follows parses the mySensors message to extract the payload and store the information in a sensorMsg object. We will be modifying the modules we wrote in the previous post ‘Setting up your system’ (main.js and serialMonitor.js) and create two new node.js modules (sensors.js and SensorMsg.js) as shown in the diagram below. If you are new to Node.js or want a quick refresher on modules and the use of the module.exports parameter then checkout our overview ‘node.js modules explained‘ that can be found under our Articles menu.

node.js modules 1
node.js modules 1

./myModules/Sensor.js

This new module is a constructor function that we will use to create sensorMsg objects. These objects contain two types of properties. Static properties are set when the object is created, e.g. nodeID and childID match these same parameters in the Ardiono sensor. Dynamic parameters e.g. data and dateTime will be updated every time we receive a new sensor message.

// constructor for message objects.
// 		Only name, nodeID and childID are required. 
// 		params is an object containing optional paramaters

module.exports = class sensorMsg {

	constructor(name, nodeID, childID, params ) {

		var d = new Date();

		// static  data
		this.name        = name;                    // This must match the sensor property name that instance is connected to 
		this.nodeID      = nodeID;                  // node ID of the Arduino Sensor
		this.childID     = childID;                 // child ID of the Arduino Sensor 
		this.ID          = nodeID + ":" + childID;  // We create this unique ID format to make it easier to search messages 

		this.type        = params.type || "null";         // type is used yto format data correctly on web pages (e.g. "Boolean")
		this.description = params.description || "null";  // free format text field used on web pages. (Avoid special characters)
		this.units       = params.units || "null"         // examples "Gal/hr", "DegF"
		this.trueText    = params.trueText  || "1";       // text that describes condition when data=true (e.g. "OPEN")
		this.falseText   = params.falseText || "0";       // text that describes condition when data=false (e.g. "CLOSED")


		// dymamic data. This is where we record data received from sensors

		this.data        = 0;
		this.msgType     = 0;
		this.ack         = 0;
		this.sigType     = 0;
		this.dateTime    = d.toTimeString();
		this.dateDay     = d.toDateString();
	}

};

/myModules/sensors.js

This next module will create and provide reference links to all of the sensorMsg objects we plan to create. For now we will just have one sensorMsg object ‘side_gate_status‘. Thus we create a property sensors.side_gate_status that will point to a new sensorMsg object. We will add properties and expand this section of the code as we build new sensors and create their associated sensorMsg objects.

sensors.js has three methods that can be used to get/set sensorMsg objects. getById(ID) and getCopyByID(ID) return the sensorMsg object that matches the ID parameter passed as an argument. ID is a unique identification made up of the nodeID and childID. The difference between these two methods is that one returns a reference to the mySenors object, the other returns a copy of the mySensors object. The setSensorMsg(sensorMsg) updates the dynamic properties with passed as an argument object. Every time we receive new sensor data on our PI’s USB interface we will use this method to keep the ‘sensor.side_gate_status‘ object updated.

const Sensor       = require('./SensorMsg');  // Constructor used to create new message objects

function Sensors(){

  // this is where we define our sensor objects
  this.side_gate_status  	= new Sensor("side_gate_status", 18, 0, {type:"Boolean", description:"Side Gate", units:"status", trueText:"OPEN", falseText:"CLOSED"}); 

 }
 
 
// ========== returns a REFERENCE to the sensor object that matches ID ============

Sensors.prototype.getByID = function(ID){ 
	for(var sensorObj in this){
		if (this[sensorObj].ID == ID){
			return this[sensorObj];        // return sensor object
		}
	}
	return (new Sensor());               // no match found, return object with null params
} 
 
// ========== returns a COPY of the sensor object that matches ID ============

Sensors.prototype.getCopyByID = function(ID){                        
	for(var sensorObj in this){
		if (this[sensorObj].ID == ID){
			return JSON.parse(JSON.stringify(this[sensorObj]));      // return a copy of the sensor object
		}
	}
	return (new Sensor("null",0,0,{}));               // no match found, return object with null params
}  
  
//   ========= Stores/Saves new dynamic message data ============

 Sensors.prototype.setSensorMsg = function(sensorMsg){

  var s = this.getByID(sensorMsg.ID);  // get reference to sensor object

  s.data      = sensorMsg.data;

  s.msgType   = sensorMsg.msgType;       // update dynamic sensor object properties
  s.ack       = sensorMsg.ack;
  s.sigType   = sensorMsg.sigType;
  s.dateTime  = sensorMsg.dateTime; 
  s.dateDay   = sensorMsg.dateDay;

}

module.exports = new Sensors();

/myModules/serialMonitor.js

We have expanded the original serialMonitor.js module written in the ‘Setting up your system’ tutorial.

processMsg() is a method that first parses the text string received from the Arduino gateway into an array. Line 18 of our module checks to see if the message is sensor message and not an internal mySensors generated message. It then calls parseSensorMsg() which performs the following,

  • Retrieve a copy of the relevant sensorMsg object from our sensors object.
  • Return this copy with the dynamic data updated to match the data received on the USB serial interface.

processMsg() then uses a command this.emit(‘newMsg’,sensorMsg) . This is an event/subscribe model that is used extensively in node.js applications. If you are unfamiliar with this programming architecture check out this reference. In our code the serialMonitor.js module will emit a sensorMsg object every time it process a new message received on the USB port. The main.js module that we will cover next explains how we will subscribe to these messages

Message Format
Message Format
const SerialPort   = require("serialport");
const Readline     = require('@serialport/parser-readline');
const util         = require('util');
const EventEmitter = require('events');
const sensors      = require('./sensors');

function mySensorMonitor() { }; // constructor function, used in last line of code to create object that is exported.

util.inherits(mySensorMonitor, EventEmitter);

//  ========== Process incoming messages ==========

mySensorMonitor.prototype.processMsg = function(msg){      // msg is the line of text read from USB port 

	var d = new Date();
	var dataArray = msg.split(';');

	if (dataArray[0] != 0 && dataArray[2] == 1){     			 // nodeID must not be 0 (system message), and msgType must be SET  
		var sensorMsg = this.parseSensorMsg(dataArray);  			 // parse message and return a new sensor object holding the message information
		this.emit('newMsg',sensorMsg);
		console.log(d.toTimeString() + " , " + msg + " [" + sensorMsg.name +"]" );    // print serial data to console with tag name 
	} 
	else {
		console.log(d.toTimeString() + " , "+ msg);    			    // print serial data to console 
	}     
}	
  
//  ========== open USB data port and monitor for mew messages ==========

mySensorMonitor.prototype.openPort = function(usbID, baud) {

	var parentThis = this;	// create reference for this back to parent mySensorMonitor object	
		
	const port   = new SerialPort('/dev/tty' + usbID, {baudRate: baud, autoOpen:false, });	
	const parser = port.pipe(new Readline({delimiter: '\n'})); 

	port.open(function(err){
		if(err){
			return console.log('Error opening USB port: ', err.message); 		
		}
		else{
			console.log("\n Serial port /dev/tty" + usbID + " opened \n" );
			// read data and process the message
			parser.on('data', function(data){  
				parentThis.processMsg(data);	// note we use 'parentThis' instead of 'this', as 'this' points to the 'port' object.
			});
		}		
	});
}




mySensorMonitor.prototype.parseSensorMsg = function(dataArray){

	var nodeID    = parseInt(dataArray[0]);
	var childID   = parseInt(dataArray[1]);
	var ID        = nodeID + ":" + childID;
	var sensorMsg = sensors.getCopyByID(ID);       // retrieve  a copy of the current message object from memory

	sensorMsg.msgType = parseInt(dataArray[2]);
	sensorMsg.ack     = parseInt(dataArray[3]);
	sensorMsg.sigType = parseInt(dataArray[4]);

	if(sensorMsg.sigType == this.sigTypes.V_STATUS || sensorMsg.sigType == this.sigTypes.V_ARMED || sensorMsg.sigType == this.sigTypes.V_TRIPPED ){
		sensorMsg.data    = parseInt(dataArray[5]);         // signal is boolean
	}
	else {
		sensorMsg.data    = parseFloat(dataArray[5]);       // signal is real
	}

	var d = new Date();
	sensorMsg.dateTime = d.toTimeString();          // time stamp used for data sent to client via websocket
	sensorMsg.dateDay  = d.toDateString();          // time stamp used for data sent to client via websocket

	return sensorMsg
} 

//  ========== mySensors data types. Used when parsing message to identify boolean vs real/integer data types.

mySensorMonitor.prototype.sigTypes = {        // used to convert data to real, or boolean
	V_TEMP       : 0,
	V_HUM        : 1,
	V_STATUS     : 2,    // boolean
	V_PERCENTAGE : 3,
	V_PRESSURE   : 4,
	V_FORECAST   : 5,
	V_RAIN       : 6,
	V_RAINRATE   : 7,
	V_WIND       : 8,
	V_GUST       : 9,
	V_DIRECTION  : 10,
	V_UV         : 11,
	V_WEAIGHT    : 12,
	V_DISTANCE   : 13,
	V_IMPEDANCE  : 14,
	V_ARMED      : 15,     // boolean
	V_TRIPPED    : 16,     // boolean
	V_WATT       : 17
}

module.exports = new mySensorMonitor();  // create object and export

/main.js

This is the primary module that we initially wrote in our ‘Setting up yur system’ tutorial. The original module simple instructed serialMonitor.js to start monitoring the USB port. We have now expanded main.js by adding a new method activate(). This method directs serialMonitor.js to monitor the USB port then uses the mySensorsMonitor.on() format to subscribe to messages emitted by the serialMonitor.js module. When a new sensorMsg object is received we execute the processNewMsg() method which calls sensors.setSensorMsg() to store this new data. This may seam like a lot of coding to achieve a simple result, but keeping the code moduler will make it easier to expand, e.g. by creating modules to send SMS messages that we can add to the processNewMsg() method.

var mySensorMonitor = require("./myModules/serialMonitor");
var sensors         = require("./myModules/sensors");

function Main(){};	// constructor

//   ========= main loop to process new messages ============

Main.prototype.processNewMsg = function(sensorMsg){   // 
	sensors.setSensorMsg(sensorMsg);			// store new message data
	
	// we will expand this section later to take actions on sensors.
	
}

//   ========= event handlers that detect new message events ============

Main.prototype.activate = function(){  // 
	
	parentThis = this;

	mySensorMonitor.openPort('USB0',115200);		// activate USB monitoring module

	mySensorMonitor.on('newMsg',function(sensorMsg){		// event handler for new USB messages
		parentThis.processNewMsg(sensorMsg);
	});

}

//   ========= start our automation application ============

const main = new Main();
main.activate();

CONCLUSION

OK, that’s it for now. If you now run your application using the command line

node main.js

It will instantiate an object sensors that will hold sensorMsg objects for each sensor on our network. In this case sensors.side_gate_status. Each time we receive new sensor data this object will be updated. This will allow us to develop further modules that can access parameters such as sensors.side_gate_status.data to check if our side gate is open of closed and sensors.side_gate_status.dateDay and sensors.side_gate_status.dateTime to check the last time the gate status switch changes status.

Leave a Reply

Your email address will not be published. Required fields are marked *