Resources for developers and hackers tinkering with MultiTech gear.
This series is intended for developers and hackers looking to test out a LoRaWAN network using MultiTech’s Conduit and mDot RF modules. The Things Network has an extensive forum and support for public gateways and nodes in Europe, but little documentation exists for those wanting to test a private network in the US.This is the third post in a four-part guide which covers:
1. Configuring the Conduit & Starter Kit
2. Node-RED & Bypassing IBM Bluemix
3. Programming MultiTech Developer Kit & Adding Sensors
4. Using the mDot, xbee shield, and Arduino
This post will focus on programming the developer board and adding sensors to test out a LoRaWAN network. Since MultiTech runs on an ARM processor, you can program the device on the ARM mbed IoT Device Platform.
To start programming the developer board, align the mDot with the board as shown below and connect to a computer via USB.
You will need an account on ARMmbed to access their development portal. Once you connect via USB, open MBED.HTM to open up the portal.
As with any programming task, we will first try to send “Hello World” to the gateway. The easiest way to accomplish this is to import the MultiTech example code into the compiler. All you have to do is to edit the network name and password to match the LoRa settings on the Conduit in main.cpp:
// these options must match the settings on your Conduit
// uncomment the following lines and edit their values to match your configuration
static std::string config_network_name = "<lora network id>";
static std::string config_network_pass = "<lora network key>";
Now just hit compile and drag the binary file onto the MultiTech device. It will automatically restart the developer board and run the program.
If the board complains about not having enough space, make sure the mDot is correctly installed on the developer board and clean out the trashcan (if this isn’t the first time loading binaries onto the board).
The Things Network provides an example program to read data and send data to the gateway. The documentation is bit incomplete, but the key thing it to use AnalogIn/DigitalIn with the correct micro pin numbers. For example, if you connect a wire to D15 on the mDot Arduino Shield, use pin PA_8 (notice the underscore).
From the example “Hello World” program, all you need to do is modify the data coming in, push it to the vector, and send it to the gateway:
/* EXAMPLE PROGRAM READING LIGHT SENSOR DATA */
// PB_0 = Analog pin 1
AnalogIn pinn(PB_0);
// Read voltage
uint16_t light = (int)(pinn.read()*1023);
// Put the data to byte array/vector
std::vector<uint8_t> data;
for (int i = 0; i <= sizeof(light); i++)
data.push_back((uint8_t)(light >> ((8*i-8) & 0xFF)));
// Send data to the gateway
ret = dot->send(data);
If you want a more complicated example, look at the MTDOT-BOX-EVB-Factory-Firmware code or mDotEVBM2X demo by James Maki.
The last part is programming the Node-RED to decide what to do with the sensor data and send it to the cloud appropriately. LoRa node is set to show bytes by default, but if you are sending strings like the Hello World example, you can change to display UTF-8 encoded messaged.
In case your Node-RED doesn’t have EVB-parsing code, you can import the following flow into Node-RED to check it out:
[
{
"id":"f0cdc142.a2f8b",
"type":"mqtt-broker",
"broker":"quickstart.messaging.internetofthings.ibmcloud.com",
"port":"1883",
"clientid":"a:quickstart:myApplicationId"
},
{
"id":"23099a38.cdf696",
"type":"function",
"name":"Post xDotEtc Data",
"func":"var mac=context.global.evb_info.macAddress;\nmsg.payload={ \n d: {\n light: msg[\"evb\"].light.lux|0,\n moisture:msg.evb.barometer.pa, \n temperature:(msg.evb.temperature.c) ,\n x_acc: msg.evb.accelerometer.x, \n y_acc: msg.evb.accelerometer.y, \n z_acc: msg.evb.accelerometer.z\n }\n}\n\nif (context.global.evb_info[msg.eui].msg_counter == null){\n context.global.evb_info[msg.eui].msg_counter = 0;\n}\n// iot-2/type/mosquitto/id/myDeviceId/evt/helloworld/fmt/json\nmsg.topic = \"iot-2/type/mosquitto/id/\" + \n mac + \n \"/evt/datapoint/\" + \n \"fmt/json\";\n\ncontext.global.evb_info[msg.eui][\"msg_counter\"] += 1.0 ;\nreturn msg;\n",
"outputs":"1",
"noerr":0,
"x":576.5,
"y":492,
"z":"adc5f34d.d57ba",
"wires":[
[
"5e8f1c51.228e64",
"1f4b704b.77c5f"
]
]
},
{
"id":"1f4b704b.77c5f",
"type":"debug",
"name":"Posted xDotEtc Data",
"active":false,
"console":"false",
"complete":"payload",
"x":914.5,
"y":564,
"z":"adc5f34d.d57ba",
"wires":[
]
},
{
"id":"b1b96995.81ebb8",
"type":"lora in",
"name":"",
"datatype":"bytes",
"x":86.92855834960938,
"y":493.0000047683716,
"z":"adc5f34d.d57ba",
"wires":[
[
"15fa773a.44bcd9",
"ec934d31.0b016"
]
]
},
{
"id":"15fa773a.44bcd9",
"type":"function",
"name":"Lora Payload Process",
"func":"if ( context.global.evb_info[msg.eui] == null) {\n context.global.evb_info[msg.eui] = { msg_counter: 0 };\n \n}\n\nvar evb_info = context.global.evb_info ;\nvar evb_sensors = {};\n\n/*\n * Evaluation board properties.\n */\nvar EVB_TYPE = {\n none: 0,\n led_1: 1,\n led_2: 2,\n lux_max: 3,\n lux_min: 4,\n lux: 5,\n barometer_max: 6,\n barometer_min: 7,\n barometer: 8,\n temperature_max: 9,\n temperature__min: 10,\n temperature: 11,\n accelerometer_max: 12,\n accelerometer_min: 13,\n accelerometer: 14,\n tx_interval: 15,\n amps_max: 16,\n amps_min: 17,\n amps: 18,\n m2x_device: 19,\n m2x_key: 20,\n};\n\n/*\n * Process the EVB LoRa payload.\n *\n * EVB payload contains one or more TLV fields.\n *\n * [<type: accelerometer><length: 6><x-high><x-low><y-high><y-low><z-high><z-low>]\n * [<type: barometer><length: 3><byte2><byte1><byte0>]\n * [<type: temperature><length: 2><byte-high><byte-low>]\n * \n */\nfor (var index = 0; index < msg.payload.length; ) {\n var type = msg.payload[index++];\n// var length = msg.payload[index++];\n var value;\n console.log(\"type: \" + type + \" length: \" );\n\n switch (type) {\n case EVB_TYPE.lux:\n if (typeof(evb_sensors.light) == \"undefined\") {\n evb_sensors.light = {};\n }\n\n value = msg.payload[index++] << 8;\n value |= msg.payload[index++];\n value = value * 0.24;\n\n evb_sensors.light.lux = value;\n break;\n case EVB_TYPE.barometer:\n if (typeof(evb_sensors.barometer) == \"undefined\") {\n evb_sensors.barometer = {};\n }\n\n value = msg.payload[index++] << 16;\n value |= msg.payload[index++] << 8;\n value |= msg.payload[index++];\n value = value * 0.00025;\n\n evb_sensors.barometer.pa = value;\n break;\n case EVB_TYPE.accelerometer:\n if (typeof(evb_sensors.accelerometer) == \"undefined\") {\n evb_sensors.accelerometer = {};\n }\n // evb_sensors.accelerometer.x = (msg.payload[index++] << 24) >> 16;\n var x1 = evb_sensors.accelerometer.x = msg.payload[index++] ;\n // x1 = ~x1 ; \n // x1 = ( x1 + 1 ) % 256; \n evb_sensors.accelerometer.x = x1 * 0.0625//; / 15;\n // evb_sensors.accelerometer.y = (msg.payload[index++] << 24) >> 16;\n var y1 = evb_sensors.accelerometer.y = msg.payload[index++] ;\n // y1 = ~ y1 ; \n // y1 = ( y1 + 1 ) % 256;\n \n var y1 = evb_sensors.accelerometer.y = y1 * 0.0625 ; // / 15 ;\n\n // evb_sensors.accelerometer.z = (msg.payload[index++] << 24) >> 16;\n var z1 = evb_sensors.accelerometer.z = msg.payload[index++] ;\n // z1 = ~ z1 ; \n // z1 = ( z1 + 1 ) % 256; \n // z1 = z1 - 128;\n var z1 = evb_sensors.accelerometer.z = z1 * 0.0625; // / 15;\n break;\n case EVB_TYPE.temperature:\n if (typeof(evb_sensors.temperature) == \"undefined\") {\n evb_sensors.temperature = {};\n }\n\n value = (msg.payload[index++] << 24) >> 16;\n value |= msg.payload[index++];\n value = value * 0.0625;\n\n evb_sensors.temperature.c = value;\n break;\n case EVB_TYPE.tx_interval:\n evb_sensors.tx_timer = msg.payload[index++];\n break;\n case EVB_TYPE.m2x_device:\n value = msg.payload.slice(index, index + length);\n evb_info[msg.eui].m2x_device = \"\";\n for (var j = 0; j < length; j++) {\n evb_info[msg.eui].m2x_device += String.fromCharCode(value[j]);\n }\n break;\n case EVB_TYPE.m2x_key:\n value = msg.payload.slice(index, index + length);\n evb_info[msg.eui].m2x_key = \"\";\n for (var j = 0; j < length; j++) {\n evb_info[msg.eui].m2x_key += String.fromCharCode(value[j]);\n }\n break;\n default:\n index += length;\n break;\n }\n}\n\nif (typeof(evb_info[msg.eui].m2x_device) == \"undefined\") {\n console.log(\"No m2x_device registered for \" + msg.eui);\n// return null;\n}\nif (typeof(evb_info[msg.eui].m2x_key) == \"undefined\") {\n console.log(\"No m2x_key registered for \" + msg.eui);\n// return null;\n}\nmsg.m2x_device = evb_info[msg.eui].m2x_device;\nmsg.m2x_key = evb_info[msg.eui].m2x_key;\nmsg.evb = evb_sensors;\n\n/*\n * Return msg to continue the flow\n */\nif (typeof msg.evb != \"undefined\"){\n return msg; \n}\n",
"outputs":1,
"noerr":0,
"x":307.49999237060547,
"y":494.5714330673218,
"z":"adc5f34d.d57ba",
"wires":[
[
"23099a38.cdf696"
]
]
},
{
"id":"205c0fb.be38ff",
"type":"comment",
"name":"Start Here",
"info":"Welcome to the Multitech Conduit Lora Starter Kit\nNode Red flow \n\nThis \"flow\" is actually a small javascript \napplication. \n\nOn the left is a lora node. This node listens to\nthe LoRa radio for incoming messages. When it\nreceives a lora packet, it sends it from its output which\nis connected to the \"Lora Payload Process\",\nwhere the original binary messages is translated\ninto a javascript object for easier use.\n\nThe output of this function is then fed into the node\n\"Post xDotEtc Data\" \n\nThe output of this node is connected to the IOT Foundation\nxDotEtc node, which uses the MQTT protocol to send the \nprepared packet of data to IBM's Bluemix. \n\n\n\n\n",
"x":587.5,
"y":74,
"z":"adc5f34d.d57ba",
"wires":[
]
},
{
"id":"1be0b5aa.c3d60a",
"type":"inject",
"name":"Run once at Startup",
"topic":"",
"payload":"",
"payloadType":"date",
"repeat":"",
"crontab":"",
"once":true,
"x":92.5,
"y":227,
"z":"adc5f34d.d57ba",
"wires":[
[
"d23545a.7ddd9b8"
]
]
},
{
"id":"22d9859c.9d77ca",
"type":"function",
"name":"Initialization",
"func":"var system=JSON.parse(msg.payload).result;\n\n// create \nif (typeof context.global.evb_info == \"undefined\") {\n context.global.evb_info = {};\n}\nwhile (system.macAddress.indexOf(\":\") > -1 ){\n system.macAddress=system.macAddress.replace(\":\",\"-\") \n}\ncontext.global.evb_info['macAddress']=system.macAddress.replace(\":\",\"-\"); \nif ( context.global.evb_info[\"simulated\"]==null ){\n context.global.evb_info[\"simulated\"] = {};\n}\nmsg=system;\nreturn msg;",
"outputs":1,
"noerr":0,
"x":500.5,
"y":229,
"z":"adc5f34d.d57ba",
"wires":[
[
"b93d9baf.52cdb8"
]
]
},
{
"id":"5e8f1c51.228e64",
"type":"mqtt out",
"name":"MQTT Quickstart",
"topic":"",
"qos":"",
"retain":"",
"broker":"f0cdc142.a2f8b",
"x":917.5,
"y":389,
"z":"adc5f34d.d57ba",
"wires":[
]
},
{
"id":"d23545a.7ddd9b8",
"type":"http request",
"name":"get mac address",
"method":"GET",
"ret":"txt",
"url":"http://localhost/api/system",
"x":289.5,
"y":228,
"z":"adc5f34d.d57ba",
"wires":[
[
"22d9859c.9d77ca"
]
]
},
{
"id":"b93d9baf.52cdb8",
"type":"debug",
"name":"",
"active":true,
"console":"false",
"complete":"true",
"x":736.5,
"y":229,
"z":"adc5f34d.d57ba",
"wires":[
]
},
{
"id":"ec934d31.0b016",
"type":"debug",
"name":"",
"active":true,
"console":"false",
"complete":"false",
"x":307.85714285714283,
"y":695.1428571428571,
"z":"adc5f34d.d57ba",
"wires":[
]
}
]