Skip to content

Instantly share code, notes, and snippets.

@scriptrdotio
Last active May 29, 2019 06:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save scriptrdotio/6485e386d062b2c42b92da9696e152e8 to your computer and use it in GitHub Desktop.
Save scriptrdotio/6485e386d062b2c42b92da9696e152e8 to your computer and use it in GitHub Desktop.
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"]]}]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment