diff --git a/restotracker-scd18/CHANGELOG.md b/restotracker-scd18/CHANGELOG.md new file mode 100644 index 00000000..dda6ac59 --- /dev/null +++ b/restotracker-scd18/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## 1.0.0 - 2026-01-15 + +- First version of plugin \ No newline at end of file diff --git a/restotracker-scd18/LICENSE.md b/restotracker-scd18/LICENSE.md new file mode 100644 index 00000000..bffeef34 --- /dev/null +++ b/restotracker-scd18/LICENSE.md @@ -0,0 +1,7 @@ +Copyright 2026 Thinger.io + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/restotracker-scd18/README.md b/restotracker-scd18/README.md new file mode 100644 index 00000000..c0806d92 --- /dev/null +++ b/restotracker-scd18/README.md @@ -0,0 +1,52 @@ +Based on the device data provided, here is the README.md: + +# SCD18 - Tracker + +The SCD18 is an ultra-low power consumption LoRaWAN tracker designed for both indoor and outdoor asset tracking applications. It features multi-mode positioning capabilities including BLE, WiFi, and GPS technologies, enabling accurate location tracking across diverse environments while maintaining exceptional battery efficiency. + +## Thinger.io Integration + +## Requirements + +A LoRaWAN server is required to communicate the SCD18 Tracker with Thinger.io. Some options are: + +- [The Things Stack](https://www.thethingsindustries.com/stack/) +- [LORIOT](https://loriot.io/) +- [ChirpStack](https://www.chirpstack.io/) + +Alongside, the corresponding plugin for the selected LoRaWAN server needs to be installed in your Thinger.io instance. + +## Get Started + +### Installation + +Look for the plugin in the [Thinger.io Plugin Store](https://marketplace.thinger.io/) and install it in your Thinger.io instance. Once the plugin is installed a new Product will be created for this device. + +### Configuration + +The Product is already preconfigured. Check that the auto provision prefix matches the one selected in your LoRaWAN server plugin in Thinger.io, or change it to your preference. + +### Usage + +Start sending uplinks for autoprovisioning devices and buckets. + +This product also provides a predefined dashboard and downlinks. + +## Key Features + +- **Multi-mode Positioning**: GPS for outdoor tracking, BLE and WiFi for indoor positioning +- **Ultra-low Power Consumption**: Extended battery life for long-term deployments +- **LoRaWAN Connectivity**: Long-range wireless communication with low power requirements +- **Indoor & Outdoor Tracking**: Seamless transition between positioning modes + +## Typical Applications + +- Asset tracking and management +- Fleet and logistics monitoring +- Personnel tracking +- Supply chain visibility +- Equipment location monitoring + +## Additional Resources + +- [Thinger.io Documentation](https://docs.thinger.io) \ No newline at end of file diff --git a/restotracker-scd18/assets/restotracker-scd18.png b/restotracker-scd18/assets/restotracker-scd18.png new file mode 100644 index 00000000..1d47dad3 Binary files /dev/null and b/restotracker-scd18/assets/restotracker-scd18.png differ diff --git a/restotracker-scd18/plugin.json b/restotracker-scd18/plugin.json new file mode 100644 index 00000000..d4e3ca07 --- /dev/null +++ b/restotracker-scd18/plugin.json @@ -0,0 +1,472 @@ +{ + "name": "restotracker_scd18", + "version": "1.0.0", + "description": "Ultra-low power consumption, indoor&outdoor (BLE&WIFI&GPS) LoRaWAN Tracker - SCD18 model", + "author": "Thinger.io", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/thinger-io/plugins.git", + "directory": "restotracker-scd18" + }, + "metadata": { + "name": "RestoTracker SCD18", + "description": "Ultra-low power consumption, indoor&outdoor (BLE&WIFI&GPS) LoRaWAN Tracker - SCD18 model", + "image": "assets/scd18.jpeg", + "category": "devices", + "vendor": "restotracker" + }, + "resources": { + "products": [ + { + "description": "Ultra-low power consumption, indoor&outdoor (BLE&WIFI&GPS) LoRaWAN Tracker - SCD18 model", + "enabled": true, + "name": "RestoTracker SCD18", + "product": "restotracker_scd18", + "profile": { + "api": { + "downlink": { + "enabled": true, + "handle_connectivity": false, + "request": { + "data": { + "path": "/downlink", + "payload": "{\n \"data\" : \"{{payload.data=\"\"}}\",\n \"port\" : {{payload.port=85}},\n \"priority\": {{payload.priority=3}},\n \"confirmed\" : {{payload.confirmed=false}},\n \"uplink\" : {{property.uplink}} \n}", + "payload_function": "", + "payload_type": "", + "plugin": "{{property.uplink.source}}", + "target": "plugin_endpoint" + } + } + }, + "uplink": { + "device_id_resolver": "getId", + "enabled": true, + "handle_connectivity": true, + "request": { + "data": { + "payload": "{{payload}}", + "payload_function": "", + "payload_type": "source_payload", + "resource_stream": "uplink", + "target": "resource_stream" + } + } + } + }, + "autoprovisions": { + "restotracker_scd18_autoprovision": { + "config": { + "mode": "pattern", + "pattern": "scd18-.*" + }, + "enabled": true + } + }, + "buckets": { + "restotracker_scd18_location_data": { + "backend": "mongodb", + "data": { + "payload": "{{payload}}", + "payload_function": "decodeThingerUplink", + "payload_type": "source_payload", + "resource": "uplink", + "source": "resource", + "update": "events" + }, + "enabled": true, + "retention": { + "period": 6, + "unit": "months" + }, + "tags": [] + } + }, + "code": { + "code": "function decodeThingerUplink(thingerData) {\n // 0. If data has already been decoded, we will return it\n if (thingerData.decodedPayload) return thingerData.decodedPayload;\n \n // 1. Extract and Validate Input\n // We need 'payload' (hex string) and 'fPort' (integer)\n const hexPayload = thingerData.payload || \"\";\n const port = thingerData.fPort || 1;\n\n // 2. Convert Hex String to Byte Array\n const bytes = [];\n for (let i = 0; i < hexPayload.length; i += 2) {\n bytes.push(parseInt(hexPayload.substr(i, 2), 16));\n }\n\n // 3. Dynamic Function Detection and Execution\n \n // CASE A: (The Things Stack v3)\n if (typeof decodeUplink === 'function') {\n try {\n const input = {\n bytes: bytes,\n fPort: port\n };\n var result = decodeUplink(input);\n \n if (result.data) return result.data;\n\n return result; \n } catch (e) {\n console.error(\"Error inside decodeUplink:\", e);\n throw e;\n }\n }\n\n // CASE B: Legacy TTN (v2)\n else if (typeof Decoder === 'function') {\n try {\n return Decoder(bytes, port);\n } catch (e) {\n console.error(\"Error inside Decoder:\", e);\n throw e;\n }\n }\n\n // CASE C: No decoder found\n else {\n throw new Error(\"No compatible TTN decoder function (decodeUplink or Decoder) found in scope.\");\n }\n}\n\n\n// TTN decoder\nvar packet_type = [\"heart\",\"fix_success\",\"fix_false\",\"sys_close_info\",\"shake_info\",\"idle_info\",\"demolish_alarm\",\"event\",\"battery_consume\",\"config\",\"store_data\",\"limit_gps_data\"];\nvar dev_mode = [\"standby mode\",\"period mode\",\"timing mode\",\"motion mode\"];\nvar dev_fix_type = [\"work_mode_fix\",\"down_request_fix\"];\nfunction substringBytes(bytes, start, len)\n{\n\tvar char = [];\n\tfor(var i = 0; i < len; i++)\n\t{\n\t\tchar.push(\"0x\"+ bytes[start+i].toString(16) < 0X10 ? (\"0\"+bytes[start+i].toString(16)) : bytes[start+i].toString(16) );\n\t}\n\treturn char.join(\"\");\n}\nfunction BytestoInt(bytes,start) {\n var value = ((bytes[start] << 24) | (bytes[start+1] << 16) | (bytes[start+2] << 8) | (bytes[start+3]));\n return value;\n}\nfunction Decoder(bytes, port)\n{\n\tvar dev_info = {};\n\tdev_info.pack_type = packet_type[port-1];\n\t//common frame head\n\tif(port<=10)\n\t{\n\t\tdev_info.work_mode = dev_mode[bytes[0]&0x03];\n\t\tdev_info.low_power_state = (bytes[0]>>2)&0x01;\n\t\tdev_info.demolish_state = (bytes[0]>>3)&0x01;\n\t\tdev_info.idle_state = (bytes[0]>>4)&0x01;\n\t\tdev_info.motion_state = (bytes[0]>>5)&0x01;\n\t\tif(port==2 || port ==3)\n\t\t{\n\t\t\tdev_info.fix_type = dev_fix_type[(bytes[0]>>6)&0x01];\n\t\t}\n\t\t\n\t\tif(bytes[1]>0x80)\n\t\t{\n\t\t\tdev_info.ic_temperature = bytes[1] - 0x100 + \"°C\";\n\t\t}\n\t\telse\n\t\t{\n\t\t\tdev_info.ic_temperature = bytes[1] + \"°C\";\n\t\t}\n\n\t\tdev_info.lorawan_downlink_count = bytes[2]&0x0f;\n\t\tdev_info.battery_voltage = (22+((bytes[2]>>4)&0x0f))/10 + \"V\";\n\t}\n\tif(port == 1)\n\t{\n\t\tvar restart_reason = [\"power_restart\",\"ble_cmd_restart\",\"lorawan_cmd_restart\",\"switch_off_mode_restart\"];\n\t\tdev_info.last_restart_reason = restart_reason[bytes[3]];\n\n\t\t\n\t\tver_major = (bytes[4]>>6)&0x03;\n\t\tver_mijor = (bytes[4]>>4)&0x03;\n\t\tver_patch = bytes[4]&0x0f;\n\t\tdev_info.firmware_ver = \"V\" + ver_major+\".\"+ver_mijor+\".\"+ver_patch;\n\n\t\tdev_info.motion_count = BytestoInt(bytes,5);\n\t}\n\telse if(port == 2)\n\t{\n\t\tvar fix_tech = [\"wifi\",\"ble\",\"gps\"];\n\t\tvar parse_len = 3; // common head is 3 byte\n\t\tvar datas = [];\n\t\ttech = bytes[parse_len++];\n\t\tdev_info.fix_tech = fix_tech[tech];\n\n\t\tyear = bytes[parse_len]*256 + bytes[parse_len+1];\n\t\tparse_len += 2;\n\t\tmon = bytes[parse_len++];\n\t\tdays = bytes[parse_len++];\n\t\thour = bytes[parse_len++];\n\t\tminute = bytes[parse_len++];\n\t\tsec = bytes[parse_len++];\n\t\ttimezone = bytes[parse_len++];\n\n\t\tif(timezone>0x80)\n\t\t{\n\t\t\tdev_info.utc_time = year + \"-\" + mon + \"-\" + days + \" \" + hour + \":\" + minute + \":\" + sec + \" TZ:\" + (timezone-0x100);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tdev_info.utc_time = year + \"-\" + mon + \"-\" + days + \" \" + hour + \":\" + minute + \":\" + sec + \" TZ:\" + timezone;\n\t\t}\n\t\tdatalen = bytes[parse_len++];\n\n\t\tif(tech==0 || tech ==1)\n\t\t{\n\t\t\tfor(var i=0 ; i<(datalen/7) ; i++)\n\t\t\t{\n\t\t\t var data = {};\n\t\t\t\tdata.mac = substringBytes(bytes, parse_len, 6);\n\t\t\t\tparse_len += 6;\n\t\t\t\tdata.rssi = bytes[parse_len++]-256 +\"dBm\";\n\t\t\t\tdatas.push(data);\n\t\t\t}\n\t\t\tdev_info.mac_data = datas;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tlat =BytestoInt(bytes,parse_len);\n\t\t\tparse_len += 4;\n\t\t\tlon =BytestoInt(bytes,parse_len);\n\t\t\tparse_len += 4;\n\n\t\t\tif(lat>0x80000000)\n\t\t\t\tlat = lat-0x100000000;\n\t\t\tif(lon>0x80000000)\n\t\t\t\tlon = lon-0x100000000;\n\n\t\t\tdev_info.lat = lat/10000000;\n\t\t\tdev_info.lon = lon/10000000;\n\t\t\tdev_info.pdop = bytes[parse_len] /10;\n\t\t}\n\t}\n\telse if(port == 3)\n\t{\n\n\t\tvar fix_false_reason = [\"wifi_fix_time_timeout\",\"wifi_fix_tech_timeout\",\"wifi_module_nofind\",\"ble_fix_time_timeout\",\"ble_fix_tech_timeout\",\"ble_adv\",\"gps_no_budget\",\"gps_coarse_acc_timeout\",\"gps_fine_acc_timeout\",\"gps_fix_timeout\",\"gps_assistnow_timeout\",\"gps_cold_start_timeout\",\"down_request_fix_interrupt\",\"motion_start_fix_false_by_motion_end\",\"motion_end_fix_false_by_motion_start\"];\n\t\tvar parse_len = 3; \n\t\tvar datas = [];\n\t\treason = bytes[parse_len++];\n\t\tdev_info.fix_false_reason = fix_false_reason[reason];\n\t\tdatalen = bytes[parse_len++];\n\t\tif(reason<=5) //wifi and ble reason\n\t\t{\n\t\t if(datalen)\n\t\t {\n \t\t\tfor(var i=0 ; i<(datalen/7) ; i++)\n \t\t\t{\n \t\t\t var data = {};\n \t\t\t\tdata.mac = substringBytes(bytes, parse_len, 6);\n \t\t\t\tparse_len += 6;\n \t\t\t\tdata.rssi = bytes[parse_len++]-256 +\"dBm\";\n \t\t\t\tdatas.push(data);\n \t\t\t}\n \t\t\tdev_info.mac_data = datas;\n\t\t }\n\t\t}\n\t\telse if(reason<=11) //gps reason\n\t\t{\t\n\t\t\tpdop = bytes[parse_len++];\n\t\t\tif(pdop!=0xff)\n\t\t\t\tdev_info.pdop = pdop/10\n\t\t\telse\n\t\t\t\tdev_info.pdop = \"unknow\";\n\t\t\tdev_info.gps_satellite_cn = bytes[parse_len] +\"-\" + bytes[parse_len+1] +\"-\" + bytes[parse_len+2] +\"-\" + bytes[parse_len+3] ;\n\t\t}\n\t}\n\telse if(port == 4)\n\t{\n\t\tvar sys_close_reason = [\"ble_cmd_close\",\"lorawan_cmd_close\",\"reed_switch_close\"];\n\t\tdev_info.sys_close_reason = sys_close_reason[bytes[3]];\n\t}\n\telse if(port == 5)\n\t{\n\t\tdev_info.shake_num = bytes[3]*256+ bytes[4];\n\t}\n\telse if(port == 6)\n\t{\n\t\tdev_info.idle_time = bytes[3]*256+ bytes[4];\n\t}\n\telse if(port == 7)\n\t{\n\t\tvar parse_len = 3; // common head is 3 byte\n\t\tyear = bytes[parse_len]*256 + bytes[parse_len+1];\n\t\tparse_len += 2;\n\t\tmon = bytes[parse_len++];\n\t\tdays = bytes[parse_len++];\n\t\thour = bytes[parse_len++];\n\t\tminute = bytes[parse_len++];\n\t\tsec = bytes[parse_len++];\n\t\ttimezone = bytes[parse_len++];\n\n\t\tif(timezone>0x80)\n\t\t{\n\t\t\tdev_info.alarm_time = year + \"-\" + mon + \"-\" + days + \" \" + hour + \":\" + minute + \":\" + sec + \" TZ:\" + (timezone-0x100);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tdev_info.alarm_time = year + \"-\" + mon + \"-\" + days + \" \" + hour + \":\" + minute + \":\" + sec + \" TZ:\" + timezone;\n\t\t}\n\t}\n\telse if(port == 8)\n\t{\n\t\tvar event = [\"motion start\",\"moving fix start\",\"motion end\",\"lorawan downlink trigger uplink\"];\n\t\tdev_info.event_info = event[bytes[3]];\n\t}\n\telse if(port == 9)\n\t{\n\t\tvar parse_len = 3;\n\t\tdev_info.gps_work_time = BytestoInt(bytes,parse_len);\n\t\tparse_len += 4;\n\t\tdev_info.wifi_work_time = BytestoInt(bytes,parse_len);\n\t\tparse_len += 4;\n\t\tdev_info.ble_scan_work_time = BytestoInt(bytes,parse_len);\n\t\tparse_len += 4;\n\t\tdev_info.ble_adv_work_time = BytestoInt(bytes,parse_len);\n\t\tparse_len += 4;\n\t\tdev_info.lorawan_work_time = BytestoInt(bytes,parse_len);\n\t\tparse_len += 4;\n\t}\n\telse if(port == 10)\n\t{\n\t\t//\n\t}\n\telse if(port == 11)\n\t{\n\t\t//\n\t}\n\telse if(port == 12)\n\t{\n\n\t\tdev_info.work_mode = dev_mode[bytes[0]&0x03];\n\t\tdev_info.low_power_state = bytes[0]&0x04;\n\t\tdev_info.demolish_state = bytes[0]&0x08;\n\t\tdev_info.idle_state = bytes[0]&0x10;\n\t\tdev_info.motion_state = bytes[0]&0x20;\n\t\tdev_info.fix_type = dev_fix_type[(bytes[0]>>6)&0x01];\n\n\t\tdev_info.lorawan_downlink_count = bytes[1]&0x0f;\n\t\tdev_info.battery_voltage = (22+((bytes[2]>>4)&0x0f))/10 + \"V\";\n\n\t\tvar parse_len = 2;\n\t\tlat =BytestoInt(bytes,parse_len);\n\t\tparse_len += 4;\n\t\tlon =BytestoInt(bytes,parse_len);\n\t\tparse_len += 4;\n\n\t\tif(lat>0x80000000)\n\t\t\tlat = lat-0x100000000;\n\t\tif(lon>0x80000000)\n\t\t\tlon = lon-0x100000000;\n\n\t\tdev_info.lat = lat/10000000 ;\n\t\tdev_info.lon = lon/10000000;\n\t\tdev_info.pdop = bytes[parse_len]/10;\n\t}\n\treturn dev_info;\n} \n", + "environment": "javascript", + "storage": "", + "version": "1.0" + }, + "properties": { + "restotracker_scd18_battery": { + "data": { + "payload": "{{payload}}", + "payload_function": "decodeThingerUplink", + "payload_type": "source_payload", + "resource": "uplink", + "source": "resource", + "update": "events" + }, + "default": { + "voltage": 0 + }, + "enabled": true + }, + "restotracker_scd18_last_location": { + "data": { + "payload": "{{payload}}", + "payload_function": "decodeThingerUplink", + "payload_type": "source_payload", + "resource": "uplink", + "source": "resource", + "update": "events" + }, + "default": { + "altitude": 0, + "latitude": 0, + "longitude": 0, + "mode": "unknown" + }, + "enabled": true + }, + "uplink": { + "data": { + "payload": "{{payload}}", + "payload_function": "", + "payload_type": "source_payload", + "resource": "uplink", + "source": "resource", + "update": "events" + }, + "default": { + "source": "value" + }, + "enabled": true + } + } + }, + "_resources": { + "properties": [ + { + "property": "dashboard", + "value": { + "tabs": [ + { + "name": "Tracker Overview", + "widgets": [ + { + "layout": { + "col": 0, + "row": 0, + "sizeX": 6, + "sizeY": 12 + }, + "panel": { + "color": "#1976D2", + "currentColor": "#1976D2", + "showOffline": { + "type": "last_sample" + }, + "title": "Device Location Map" + }, + "properties": { + "center": [ + 0, + 0 + ], + "mode": "geojson", + "zoom": 15 + }, + "sources": [ + { + "bucket": { + "backend": "mongodb", + "id": "restotracker_scd18_location_data", + "mapping": "location", + "tags": { + "device": [], + "group": [] + } + }, + "color": "#2E93fA", + "name": "Current Position", + "source": "bucket", + "timespan": { + "mode": "latest" + } + } + ], + "type": "map" + }, + { + "layout": { + "col": 6, + "row": 0, + "sizeX": 3, + "sizeY": 6 + }, + "panel": { + "color": "#ffffff", + "currentColor": "#ffffff", + "showOffline": { + "type": "none" + }, + "title": "Battery Status" + }, + "properties": { + "color": "#4CAF50", + "max": 4.2, + "min": 2.8, + "unit": "V" + }, + "sources": [ + { + "bucket": { + "backend": "mongodb", + "id": "restotracker_scd18_location_data", + "mapping": "battery", + "tags": { + "device": [], + "group": [] + } + }, + "color": "#4CAF50", + "name": "Battery", + "source": "bucket", + "timespan": { + "mode": "latest" + } + } + ], + "type": "donutchart" + }, + { + "layout": { + "col": 9, + "row": 0, + "sizeX": 3, + "sizeY": 6 + }, + "panel": { + "color": "#ffffff", + "currentColor": "#ffffff", + "showOffline": { + "type": "none" + }, + "title": "Positioning Mode" + }, + "properties": { + "source": "code", + "template": "
\r\n
\r\n \r\n \r\n \r\n \r\n
\r\n
\r\n {{ value.mode || 'Unknown' }}\r\n
\r\n
" + }, + "sources": [ + { + "device_property": { + "device": "{{device}}", + "property": "restotracker_scd18_last_location" + }, + "name": "Mode", + "source": "device_property", + "timespan": { + "mode": "latest" + } + } + ], + "type": "text" + }, + { + "layout": { + "col": 6, + "row": 6, + "sizeX": 6, + "sizeY": 12 + }, + "panel": { + "color": "#ffffff", + "currentColor": "#ffffff", + "showOffline": { + "type": "none" + }, + "title": "Location History" + }, + "properties": { + "source": "code", + "template": "
\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
TimestampLatitudeLongitudeAltitudeModeBattery
{{ entry.ts | date:'medium' }}{{ entry.latitude || entry.lat || '—' }}{{ entry.longitude || entry.lon || '—' }}{{ entry.altitude || entry.alt || '—' }} m\r\n GPS\r\n BLE\r\n WIFI\r\n \r\n {{ entry.battery || entry.bat || '—' }} V
\r\n
" + }, + "sources": [ + { + "aggregation": {}, + "bucket": { + "backend": "mongodb", + "id": "restotracker_scd18_location_data", + "mapping": "ts", + "tags": { + "device": [], + "group": [] + } + }, + "color": "#1abc9c", + "name": "timestamp", + "source": "bucket", + "timespan": { + "magnitude": "day", + "mode": "relative", + "period": "latest", + "value": 7 + } + }, + { + "aggregation": {}, + "bucket": { + "backend": "mongodb", + "id": "restotracker_scd18_location_data", + "mapping": "latitude", + "tags": { + "device": [], + "group": [] + } + }, + "color": "#2E93fA", + "name": "latitude", + "source": "bucket", + "timespan": { + "magnitude": "day", + "mode": "relative", + "period": "latest", + "value": 7 + } + }, + { + "aggregation": {}, + "bucket": { + "backend": "mongodb", + "id": "restotracker_scd18_location_data", + "mapping": "longitude", + "tags": { + "device": [], + "group": [] + } + }, + "color": "#66DA26", + "name": "longitude", + "source": "bucket", + "timespan": { + "magnitude": "day", + "mode": "relative", + "period": "latest", + "value": 7 + } + }, + { + "aggregation": {}, + "bucket": { + "backend": "mongodb", + "id": "restotracker_scd18_location_data", + "mapping": "altitude", + "tags": { + "device": [], + "group": [] + } + }, + "color": "#ff8800", + "name": "altitude", + "source": "bucket", + "timespan": { + "magnitude": "day", + "mode": "relative", + "period": "latest", + "value": 7 + } + }, + { + "aggregation": {}, + "bucket": { + "backend": "mongodb", + "id": "restotracker_scd18_location_data", + "mapping": "mode", + "tags": { + "device": [], + "group": [] + } + }, + "color": "#9b59b6", + "name": "mode", + "source": "bucket", + "timespan": { + "magnitude": "day", + "mode": "relative", + "period": "latest", + "value": 7 + } + }, + { + "aggregation": {}, + "bucket": { + "backend": "mongodb", + "id": "restotracker_scd18_location_data", + "mapping": "battery", + "tags": { + "device": [], + "group": [] + } + }, + "color": "#4CAF50", + "name": "battery", + "source": "bucket", + "timespan": { + "magnitude": "day", + "mode": "relative", + "period": "latest", + "value": 7 + } + } + ], + "type": "html_time" + }, + { + "layout": { + "col": 0, + "row": 12, + "sizeX": 6, + "sizeY": 6 + }, + "panel": { + "color": "#ffffff", + "currentColor": "#ffffff", + "showOffline": { + "type": "none" + }, + "title": "Battery Voltage History" + }, + "properties": { + "axis": true, + "fill": false, + "legend": true, + "multiple_axes": false + }, + "sources": [ + { + "bucket": { + "backend": "mongodb", + "id": "restotracker_scd18_location_data", + "mapping": "battery", + "tags": { + "device": [], + "group": [] + } + }, + "color": "#4CAF50", + "name": "Battery Voltage", + "source": "bucket", + "timespan": { + "magnitude": "day", + "mode": "relative", + "period": "latest", + "value": 7 + } + } + ], + "type": "chart" + } + ] + } + ] + } + } + ] + } + } + ] + } +} \ No newline at end of file