diff --git a/dingtek-dc500/plugin.json b/dingtek-dc500/plugin.json index aae573509..391961ce4 100644 --- a/dingtek-dc500/plugin.json +++ b/dingtek-dc500/plugin.json @@ -1,134 +1,3 @@ -{ - "name": "dingtek_dc500", - "version": "1.0.0", - "description": "The CNDingtek dc500 is a pir people counter, which calculate the people number.", - "author": "Thinger.io", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/thinger-io/plugins.git", - "directory": "dingtek-dc500" - }, - "metadata": { - "name": "Dingtek DC500", - "description": "The CNDingtek dc500 is a pir people counter, which calculate the people number.", "image": "assets/dc500.png", "category": "devices", "vendor": "dingtek" - }, - "resources": { - "products": [ - { - "description": "The CNDingtek dc500 is a pir people counter, which calculate the people number.", - "enabled": true, - "name": "Dingtek DC500", - "product": "dingtek_dc500", - "profile": { - "api": { - "downlink": { - "enabled": true, - "handle_connectivity": false, - "request": { - "data": { - "path": "/downlink", - "payload": "{\n \"data\" : \"{{payload.data=\"\"}}\",\n \"port\" : {{payload.port=3}},\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": { - "device_autoprovisioning": { - "config": { - "mode": "pattern", - "pattern": "dc500_.*" - }, - "enabled": true - } - }, - "buckets": { - "dingtek_dc500_data": { - "backend": "mongodb", - "data": { - "payload": "{{payload}}", - "payload_function": "", - "payload_type": "source_payload", - "resource_stream": "uplink_decoded", - "source": "resource_stream" - }, - "enabled": true, - "retention": { - "period": 3, - "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\n//IEEE754 hex to float convert\nfunction hex2float(num) {\n var sign = num & 0x80000000 ? -1 : 1;\n var exponent = ((num >> 23) & 0xff) - 127;\n var mantissa = 1 + (num & 0x7fffff) / 0x7fffff;\n return sign * mantissa * Math.pow(2, exponent);\n}\n\nfunction decodeUplink(input) {\n if (input.fPort != 3) {\n return {\n errors: ['unknown FPort'],\n };\n }\n switch (input.bytes.length) {\n case 21:\n return {\n // Decoded data\n data: {\n peopleCounter: ((input.bytes[5] << 8) + input.bytes[6]),\n alarmCounter: Boolean(input.bytes[7] & 0xF0),\n alarmBattery: Boolean(input.bytes[7] & 0x0F), \n volt: ((input.bytes[9] << 8) + input.bytes[10]) / 100,\n frameCounter: (input.bytes[17] << 8) + input.bytes[18],\n },\n };\n case 12:\n return {\n // Decoded data\n data: {\n firmware: input.bytes[5] + \".\" + input.bytes[6],\n uploadInterval: input.bytes[7],\n batteryThreshold: input.bytes[10],\n peopleCounterThreshold: (input.bytes[8] << 8) + input.bytes[9],\n },\n };\n default:\n return {\n errors: ['wrong length'],\n };\n }\n}\n\n// Encode downlink function.\n//\n// Input is an object with the following fields:\n// - data = Object representing the payload that must be encoded.\n// - variables = Object containing the configured device variables.\n//\n// Output must be an object with the following fields:\n// - bytes = Byte array containing the downlink payload.\nfunction encodeDownlink(input) {\n if (input.data.uploadInterval != null && !isNaN(input.data.uploadInterval)) {\n var uploadInterval = input.data.uploadInterval;\n var uploadInterval_high = uploadInterval.toString(16).padStart(2, '0').toUpperCase()[0].charCodeAt(0);\n var uploadInterval_low = uploadInterval.toString(16).padStart(2, '0').toUpperCase()[1].charCodeAt(0);\n if (uploadInterval > 168 || uploadInterval < 1) {\n return {\n errors: ['upload interval range 1-168 hours.'],\n };\n } else {\n return {\n // LoRaWAN FPort used for the downlink message\n fPort: 3,\n // Encoded bytes\n bytes: [0x38, 0x30, 0x30, 0x32, 0x39, 0x39, 0x39, 0x39, 0x30, 0x31, uploadInterval_high, uploadInterval_low, 0x38, 0x31],\n };\n }\n }\n if (input.data.peopleCounterThreshold != null && !isNaN(input.data.peopleCounterThreshold)) {\n var peopleCounterThreshold = input.data.peopleCounterThreshold;\n var peopleCounter_1st = peopleCounterThreshold.toString(16).padStart(4, '0').toUpperCase()[0].charCodeAt(0);\n var peopleCounter_2nd = peopleCounterThreshold.toString(16).padStart(4, '0').toUpperCase()[1].charCodeAt(0);\n var peopleCounter_3rd = peopleCounterThreshold.toString(16).padStart(4, '0').toUpperCase()[2].charCodeAt(0);\n var peopleCounter_4th = peopleCounterThreshold.toString(16).padStart(4, '0').toUpperCase()[3].charCodeAt(0);\n if (peopleCounterThreshold > 65535 || peopleCounterThreshold < 1) {\n return {\n errors: ['people counter range 1-65535.'],\n };\n } else {\n return {\n // LoRaWAN FPort used for the downlink message\n fPort: 3,\n // Encoded bytes\n bytes: [0x38, 0x30, 0x30, 0x32, 0x39, 0x39, 0x39, 0x39, 0x30, 0x32, peopleCounter_1st, peopleCounter_2nd, peopleCounter_3rd, peopleCounter_4th, 0x38, 0x31],\n };\n }\n }\n\n if (input.data.batteryThreshold != null && !isNaN(input.data.batteryThreshold)) {\n var batteryThreshold = input.data.batteryThreshold;\n var batteryThreshold_high = batteryThreshold.toString(16).padStart(2, '0').toUpperCase()[0].charCodeAt(0);\n var batteryThreshold_low = batteryThreshold.toString(16).padStart(2, '0').toUpperCase()[1].charCodeAt(0);\n if (batteryThreshold > 99 || batteryThreshold < 5) {\n return {\n errors: ['battery alarm threshold range 5-99.'],\n };\n } else {\n return {\n // LoRaWAN FPort used for the downlink message\n fPort: 3,\n // Encoded bytes\n bytes: [0x38, 0x30, 0x30, 0x32, 0x39, 0x39, 0x39, 0x39, 0x30, 0x35, batteryThreshold_high, batteryThreshold_low, 0x38, 0x31],\n };\n }\n }\n if (input.data.zeroPeopleCounter != null && !isNaN(input.data.zeroPeopleCounter)) {\n var zeroPeopleCounter = input.data.zeroPeopleCounter;\n if (zeroPeopleCounter != 1) {\n return {\n errors: ['zero people counter: 1.'],\n };\n } else {\n return {\n // LoRaWAN FPort used for the downlink message\n fPort: 3,\n // Encoded bytes\n bytes: [0x38, 0x30, 0x30, 0x32, 0x39, 0x39, 0x39, 0x39, 0x30, 0x39, 0x30, 0x46, 0x38, 0x31],\n };\n }\n } \n return {\n errors: ['invalid downlink parameter.'],\n };\n}\n\nfunction decodeDownlink(input) {\n var input_length = input.bytes.length;\n if (input.fPort != 3) {\n return {\n errors: ['invalid FPort.'],\n };\n }\n\n if (\n input_length < 12 ||\n input.bytes[0] != 0x38 ||\n input.bytes[1] != 0x30 ||\n input.bytes[2] != 0x30 ||\n input.bytes[3] != 0x32 ||\n input.bytes[4] != 0x39 ||\n input.bytes[5] != 0x39 ||\n input.bytes[6] != 0x39 ||\n input.bytes[7] != 0x39 ||\n input.bytes[input_length - 2] != 0x38 ||\n input.bytes[input_length - 1] != 0x31\n ) {\n return {\n errors: ['invalid format.'],\n };\n }\n var option = parseInt(String.fromCharCode(input.bytes[8]) + String.fromCharCode(input.bytes[9]), 16);\n if (input_length == 16)\n var value = parseInt(String.fromCharCode(input.bytes[10]) + String.fromCharCode(input.bytes[11]) + String.fromCharCode(input.bytes[12]) + String.fromCharCode(input.bytes[13]), 16);\n else\n var value = parseInt(String.fromCharCode(input.bytes[10]) + String.fromCharCode(input.bytes[11]), 16);\n switch (option) {\n case 1:\n return {\n data: {\n uploadInterval: value,\n },\n };\n case 2:\n return {\n data: {\n peopleCounterThreshold: value,\n },\n };\n case 5:\n return {\n data: {\n batteryThreshold: value,\n },\n };\n\n case 9:\n switch (value) { \n case 0x0F:\n return {\n data: {\n zeroPeopleCounter: 1,\n },\n };\n default:\n return {\n errors: ['invalid parameter value.'],\n };\n }\n default:\n return {\n errors: ['invalid parameter key.'],\n };\n }\n}", - "environment": "javascript", - "storage": "", - "version": "1.0" - }, - "flows": { - "dingtek_dc500_decoder": { - "data": { - "payload": "{{payload}}", - "payload_function": "decodeThingerUplink", - "payload_type": "source_payload", - "resource": "uplink", - "source": "resource", - "update": "events" - }, - "enabled": true, - "sink": { - "payload": "{{payload}}", - "payload_function": "", - "payload_type": "source_payload", - "resource_stream": "uplink_decoded", - "target": "resource_stream" - }, - "split_data": false - } - }, - "properties": { - "uplink": { - "data": { - "payload": "{{payload}}", - "payload_function": "", - "payload_type": "source_payload", - "resource": "uplink", - "source": "resource", - "update": "events" - }, - "default": { - "source": "value" - }, - "enabled": true - } - } - }, - "_resources": { - "properties": [] - } - } - ] - } -} \ No newline at end of file