scriptr.io simple client for Mutitech Conduit device

Target audience

This flow is intended to developers using a Multitech® mDot-Box device in combination with a Multitech® Conduit gateway, and leveraging the scriptr.io IoT platform.

Purpose

Send sensor data to scriptr.io to be leveraged in IoT applications.

Details

The flow receives data sent by the mDot-Box device through the LoRa protocol. The received binary payload is parsed into a JSON structure then sent to the developer's scriptr.io account via HTTP.

A simple simulator is also available in the flow in case the developer does not have a Multitech mDot-Box device.

mDot-Box supported modes

  • GPS Survey (temperature, GPS location)
  • LoRa Demo (temperature, luminosity, pressure, GPS location)

How to install

  • Log in to the dashboard of your MultiConnect® ConduitTM
  • Click on Apps, in the menu on the left
  • Click on "Launch Node-Red". You will have to sign-in to the Node-Red editor
  • In the Node-Red editor, click on the menu (top-right corner), select Import > Clipboard
  • In the Import node section, paste the content of the flow below
  • Deploy the Node-Red flow

How to configure

  • From the scriptr.io web IDE, click on the username on the top-right corner, then click on Device directory.
  • Copy the token of the device that is listed
  • Log in to the dashboard of the MultiConnect® ConduitTM
  • Click on Apps, in the menu on the left
  • Click on "Launch Node-Red"
  • Double-click on the "config" node of the Node-Red flow. Set the value of the auh_token variable to the token obtained from scriptr.io
[{"id":"cab85130.622b6","type":"tab","label":"mDot Box > Scriptr.io"},{"id":"fafef485.d15c78","type":"inject","z":"cab85130.622b6","name":"cron trigger","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"x":133,"y":112.00000190734863,"wires":[["9ddbd55b.9cf608"]]},{"id":"9ddbd55b.9cf608","type":"function","z":"cab85130.622b6","name":"data simulator","func":"// Generate simulated data\nvar temperature = Math.round(Math.random() * 5) + 20;\nvar luminosity = Math.round(Math.random() * 10) + 80;\nvar loc = {\n    lat: getRandomInRange(39, 40, 5),\n    lon: getRandomInRange(69, 70, 5)\n};\n\nvar pressure = Math.round(Math.random() * 4) + 1;\nvar timestamp = msg.payload;\n\n// Prepare request payload\nmsg.timestamp = timestamp;\nmsg.temperature = temperature;\nmsg.luminosity = luminosity;\nmsg.lat_deg = loc.lat;\nmsg.long_deg = loc.lon;\nmsg.pressure = pressure;\n\nreturn msg;\n\nfunction getRandomInRange(from, to, fixed) {\n    return (Math.random() * (to - from) + from).toFixed(fixed) * 1;\n    // .toFixed() returns string, so ' * 1' is a trick to convert to number\n}","outputs":1,"noerr":0,"x":181,"y":224,"wires":[["b73ed3d1.f0fd3"]]},{"id":"c231374a.661908","type":"comment","z":"cab85130.622b6","name":"click for simulation","info":"You can use this simulator in case your Conduit device is not connected to any sensor.\nWhen using a real mdot box device, connect the lora node","x":120.00001525878906,"y":69.00000762939453,"wires":[]},{"id":"b73ed3d1.f0fd3","type":"function","z":"cab85130.622b6","name":"config","func":"var auth_token = \"WjVCRkY2OTZDOTpteURldmljZTpDQzJBMEQyN0E2RjFCODk2QjhEREE1RDU3REU2MDZFRQ==\";\nvar url = \"https://api.scriptrapps.io/app/api/subscription/subscriber\";\n\nmsg.auth_token = auth_token,\nmsg.url = url;\n\n// fill your default device location\n// it will be used if your device cannot lock\n// a GPS location\nmsg.default_location = {\n    lat: 40.6976701, // replace with your lat\n    lon: -74.2598681 // replace with your lon\n}\nreturn msg;","outputs":1,"noerr":0,"x":444,"y":287.0000305175781,"wires":[["7125973b.5e74b8"]]},{"id":"f6e6f307.db737","type":"http request","z":"cab85130.622b6","name":"Send data to scriptr.io","method":"POST","ret":"txt","url":"","x":640.0000610351562,"y":480.00006675720215,"wires":[["13aeaca5.d9aa93"]]},{"id":"13aeaca5.d9aa93","type":"debug","z":"cab85130.622b6","name":"Response received","active":true,"console":"false","complete":"true","x":736.0000190734863,"y":577.0000400543213,"wires":[]},{"id":"1c0cf9f3.6d9ce6","type":"comment","z":"cab85130.622b6","name":"Customize me","info":"This config file has to be customized with the adequest auth token and URL.\n","x":445,"y":246.00003051757812,"wires":[]},{"id":"f04ed5ff.e8c018","type":"lora in","z":"cab85130.622b6","name":"mdotbox","datatype":"bytes","x":87,"y":345.0000057220459,"wires":[["4a85fc66.99f854"]]},{"id":"7125973b.5e74b8","type":"function","z":"cab85130.622b6","name":"Configure request","func":"var newMsg = {};\nnewMsg.payload = {\n    temperature: msg.temperature,\n    luminosity: msg.lux,\n    pressure: msg.baro_pressure,\n    location: {\n        lat: msg.lat_deg ? msg.lat_deg : msg.default_location.lat,\n        lon: msg.long_deg ? msg.long_deg : msg.default_location.lon\n    },\n    gps_status: msg.gps_status\n};\n\n// Prepare request headers\nnewMsg.headers = {\n    \"Content-Type\": \"application/json\",\n    \"Authorization\": \"Bearer \" + msg.auth_token \n};\n\n// Prepare request url and method\nnewMsg.url = msg.url;\nnewMsg.method = \"POST\";\n\nreturn newMsg;","outputs":1,"noerr":0,"x":542.0000610351562,"y":374.0000648498535,"wires":[["f6e6f307.db737","20fcdc49.4354e4"]]},{"id":"20fcdc49.4354e4","type":"debug","z":"cab85130.622b6","name":"","active":true,"console":"false","complete":"true","x":728,"y":323.2727355957031,"wires":[]},{"id":"4a85fc66.99f854","type":"function","z":"cab85130.622b6","name":"Lora Payload Process","func":"if (msg.payload) {\n    \n    switch (msg.payload.length) {\n        \n        case 11: return parseGPSSurvey(msg);\n        case 14: return parseLoRADemo(msg);\n    }\n    \n    return msg;\n}\n\nfunction parseGPSSurvey(msg) {\n    \n    //byte location in the incoming data packets\n    var data_type = {\n        none_0 : 0,\n        temp_curr : 1,\n        none_1 : 2,\n        gps_latitude : 3,\n        gps_longitude : 7\n    };\n\n    var gps_decode_index = 2147483647; //converting from HEX to Earch geo location\n    \n    var data_struc = {\n        temperature : 0,\n        lat_deg : 0 ,\n        long_deg : 0,\n    };\n   \n    pData = data_struc;\n    var msg_pntr = 0;\n\n    while (msg_pntr < msg.payload.length){\n        switch (msg_pntr){\n            case data_type.none_0:\n                msg_pntr++;\n                break;\n            case data_type.none_1:\n                msg_pntr++;\n                break;\n            case data_type.temp_curr:\n                pData.temperature = parseInt(msg.payload.toString('hex', 1, 2), 16);\n                msg_pntr++;\n                break;\n            case data_type.gps_latitude:\n                pData.lat_deg = parseInt(msg.payload.toString('hex', 3, 7), 16);\n                pData.lat_deg = pData.lat_deg/gps_decode_index * 90\n                if (pData.lat_deg > 90) {\n                    pData.lat_deg = pData.lat_deg - 90;\n                }\n                msg_pntr += 4;\n                break;\n            case data_type.gps_longitude:\n                pData.long_deg = parseInt(msg.payload.toString('hex', 7, 11), 16);\n                pData.long_deg = pData.long_deg/gps_decode_index * 180;\n                if (pData.long_deg > 180) {\n                    pData.long_deg = pData.long_deg - 360\n                }\n                msg_pntr += 4;\n                break;\n            default:\n                msg_pntr++;\n        }\n    }\n    \n    delete msg.payload;\n    for (var key in pData){\n        msg[key] = pData[key];\n    }\n    \n    return msg;\n}\n\nfunction parseLoRADemo(msg) {\n    \n    var data_type = {\n    \tnone : 0x00,\n    \tled1 : 0x01,\n    \tled2 : 0x02,\n    \tlux_max : 0x03,\n    \tlux_min : 0x04,\n    \tlux_curr : 0x05,\n    \tbaro_max : 0x06,\n    \tbaro_min : 0x07,\n    \tbaro_curr : 0x08,\n    \ttemp_max : 0x09,\n    \ttemp__min : 0x0A,\n    \ttemp_curr : 0x0B,\n    \taccel_max : 0x0C,\n    \taccel_min : 0x0D,\n    \taccel_curr : 0x0E,\n    \tconfiguration : 0x0F,\n    \tgpio_in : 0x10,\n    \tgpio_out : 0x11,\n    \tcurrent_max : 0x12,\n    \tcurrent_min : 0x13,\n    \tcurrent_curr : 0x14,\n     \n    \tgps_time : 0x17,\n    \tgps_date : 0x18,\n    \tgps_lock : 0x19,\n    \tqos_up : 0x1A,\n    \tqos_dwn : 0x1B,\n    \trf_out : 0x1C,\n    \tdata_mark : 0x1D,\n    };\n    \n    var data_struc = {\n    \tdata_valid : 0,\n    \tblock_start :0,\n    \ttemperature : 0,\n    \tx_pos : 0,\n    \ty_pos : 0,\n    \tz_pos : 0,\n    \tbaro_pressure : 0,\n    \tlux : 0,\n    \tpkt_timer :0,\n    \trf_pwr : 0,\n    \tsf_val : 0,\n    \tgateways : 0,\n    \tmargin_up : 0,\n    \trssi_dwn : 0,\n    \tsnr_dwn :0 ,\n    \tlat_deg : 0 ,\n    \tlat_min : 0,\n    \tlong_deg : 0,\n    \tlong_min : 0,\n    \tnum_sats : 0 ,\n    \tgps_status : 0,\n    };\n    \n    context.global.data_out = context.global.data_out || data_struc;\n    var pData = context.global.data_out;\n    \n    var msg_pntr = 0;\n    var temp = 0;\n\n    while (msg_pntr < msg.payload.length){\n    \tswitch (msg.payload[msg_pntr]){\n    \tcase data_type.lux_max:\n    \tcase data_type.lux_min:\n    \tcase data_type.lux_curr:\n    \t\tpData.lux = msg.payload[++msg_pntr] << 8 \n    \t\tpData.lux |= msg.payload[++msg_pntr];\n    \t\tpData.lux = pData.lux * 0.24;\n    \t\tmsg_pntr++;\n    \t\tbreak;\n    \tcase data_type.baro_max:\n    \tcase data_type.baro_min:\n    \tcase data_type.baro_curr:\n    \t\tpData.baro_pressure = msg.payload[++msg_pntr]<<16;\n    \t\tpData.baro_pressure |= msg.payload[++msg_pntr]<<8;\n    \t\tpData.baro_pressure |= msg.payload[++msg_pntr];\n    \t\tpData.baro_pressure = pData.baro_pressure * 0.25;\n    \t\tmsg_pntr++;\n    \t\tbreak;\n    \tcase data_type.accel_max:\n    \tcase data_type.accel_min:\n    \tcase data_type.accel_curr:\n    \t\tpData.x_pos = ((msg.payload[++msg_pntr] << 24) >> 24) * 0.0625;\n    \t\tpData.y_pos = ((msg.payload[++msg_pntr] << 24) >> 24) * 0.0625;\n    \t\tpData.z_pos = ((msg.payload[++msg_pntr] << 24) >> 24) * 0.0625;\n    \t\tmsg_pntr++;\n    \t\tbreak;\n    \tcase data_type.temp_min:\n    \tcase data_type.temp_max:\n    \tcase data_type.temp_curr:\n    \t\tpData.temperature = msg.payload[++msg_pntr] << 24;\n    \t\tpData.temperature |= msg.payload[++msg_pntr] << 16;\n    \t\tpData.temperature = (pData.temperature >> 16) * .0625;\n    \t\tmsg_pntr++;\n    \t\tbreak;\n    \tcase data_type.configuration:\n    \t\tpData.pkt_timer = msg.payload[++msg_pntr];\n    \t\tmsg_pntr++\n    \t\tbreak;\n    \tcase data_type.current_max:\n    \tcase data_type.current_min:\n    \tcase data_type.current_curr:\n    \t\tmsg_pntr++;\n    \t\tmsg_pntr++;\n    \t\tmsg_pntr++;\n    \t\tbreak;\n    \tcase data_type.gps_latitude:\n    \t\tpData.lat_deg = (msg.payload[++msg_pntr] << 24) >> 24;\n    \t\tpData.lat_min = msg.payload[++msg_pntr];\n    \t\ttemp = msg.payload[++msg_pntr] << 8 \n    \t\ttemp |= msg.payload[++msg_pntr];\n    \t\tpData.lat_min = pData.lat_min + (temp * 0.0001);\n    \t\tmsg_pntr++;\n    \t\tbreak;\n    \tcase data_type.gps_longitude:\n    \t\tpData.long_deg = (msg.payload[++msg_pntr] << 24);\n    \t\tpData.long_deg |= (msg.payload[++msg_pntr] << 16);\n    \t\tpData.long_deg = pData.long_deg >> 16;\n    \t\tpData.long_min = msg.payload[++msg_pntr];\n    \t\ttemp = msg.payload[++msg_pntr] << 8 \n    \t\ttemp |= msg.payload[++msg_pntr];\n    \t\tpData.long_min = pData.long_min + (temp * 0.0001);\n    \t\tmsg_pntr++;\n    \t\tbreak;\n    \tcase data_type.gps_time:\n    \t\tmsg_pntr++;\n    \t\tmsg_pntr++;\n    \t\tmsg_pntr++;\n    \t\tmsg_pntr++;\n    \t\tbreak;\n    \tcase data_type.gps_date:\n    \t\tmsg_pntr++;\n    \t\tmsg_pntr++;\n    \t\tmsg_pntr++;\n    \t\tmsg_pntr++;\n    \t\tbreak;\n    \tcase data_type.gps_lock:\n    \t\tmsg_pntr++;\n    \t\tpData.gps_status = msg.payload[msg_pntr] & 0x0F;\n    \t\tpData.num_sats = msg.payload[msg_pntr++] >> 4;\n    \t\tbreak;\n    \tcase data_type.qos_up:\n    \t\tpData.gateways = msg.payload[++msg_pntr] << 24;\n    \t\tpData.gateways |= msg.payload[++msg_pntr] << 16;\n    \t\tpData.gateways = pData.gateways >> 16;\n    \t\tpData.margin_up = (msg.payload[++msg_pntr] << 24) >> 24;\n    \t\tpData.rf_pwr = (msg.payload[++msg_pntr] << 24) >> 24;\n    \t\tmsg_pntr++;\n    \t\tbreak;\n    \tcase data_type.qos_dwn:\n    \t\tpData.rssi_dwn = msg.payload[++msg_pntr] << 24;\n    \t\tpData.rssi_dwn |= msg.payload[++msg_pntr] << 16;\n    \t\tpData.rssi_dwn = pData.rssi_dwn >> 16;\n    \t\tpData.snr_dwn = ((msg.payload[++msg_pntr] << 24) >> 24);\n    \t\tmsg_pntr++;\n    \t\tbreak;\n    \tcase data_type.rf_out:\n    \t\tpData.rf_pwr = (msg.payload[++msg_pntr] << 24) >> 24;\n    \t\tmsg_pntr++;\n    \t\tbreak;\n    \tcase data_type.data_mark:\n    \t\tif (msg_pntr == 0) {\n    \t\t\tpData = data_struc;\n    \t\t\tpData.block_start = 1;\n    \t\t\tmsg_pntr++;\n    \t\t}\n    \t\telse if (msg_pntr == (msg.payload.length - 1) && (pData.block_start === 1)) {\n    \t\t\t\tpData.data_valid = 1;\n    \t\t\t\tmsg_pntr++;\n    \t\t\t}\n    \t\t\telse {\n    \t\t\t\tpData = data_struc;\n    \t\t\t\tmsg_pntr = msg.payload.length;\n    \t\t\t\t}\n    \t\tbreak;\n    \tdefault:\n    \t\tpData = data_struc;\n    \t\tmsg_pntr = msg.payload.length;\n    \t}\n    }\n    \n    pData.sf_val = parseInt(msg.datr.replace(\"SF\",\"  \"),10);\n    \n    context.global.data_out = pData;\n    \n    pData.deveui = msg.deveui;\n    return pData;\n}","outputs":1,"noerr":0,"x":196,"y":458,"wires":[["b73ed3d1.f0fd3"]]}]
scriptrdotio

Flow Info

created 1 year, 1 month ago
updated 1 month, 3 weeks ago

Node Types

Core
  • comment (x2)
  • debug (x2)
  • function (x4)
  • http request (x1)
  • inject (x1)
Other
  • lora in (x1)
  • tab (x1)

Tags

  • scriptr.io
  • multitech
  • conduit
  • mDot-Box
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option