@montagny/node-red-contrib-lorawan-bacnet
Version:
Provides custom Node-RED nodes for integrating LoRaWAN devices with BACnet systems
948 lines • 65.7 kB
JSON
[
{
"id": "d5185b34c44ae683",
"type": "tab",
"label": "LoRaBAC",
"disabled": false,
"info": "",
"env": []
},
{
"id": "e5621a7de149225c",
"type": "subflow",
"name": "Rest API Read downlink/ Write uplink",
"info": "",
"category": "",
"in": [
{
"x": 300,
"y": 360,
"wires": [
{
"id": "8823ed2c61e64b0f"
},
{
"id": "580fb662e924a681"
}
]
}
],
"out": [
{
"x": 1540,
"y": 460,
"wires": [
{
"id": "f8a6a26c3a4274bf",
"port": 0
}
]
}
],
"env": [
{
"name": "dataDirection",
"type": "str",
"value": "uplink",
"ui": {
"icon": "font-awesome/fa-location-arrow",
"label": {
"en-US": "Direction"
},
"type": "select",
"opts": {
"opts": [
{
"l": {
"en-US": "Uplink"
},
"v": "uplink"
},
{
"l": {
"en-US": "Downlink"
},
"v": "downlink"
}
]
}
}
}
],
"meta": {},
"color": "#DDAA99",
"status": {
"x": 660,
"y": 580,
"wires": [
{
"id": "c5511cd0867d06e2",
"port": 0
}
]
}
},
{
"id": "b3c9c070e14e7188",
"type": "group",
"z": "e5621a7de149225c",
"name": "Read/Write BACnet Objects",
"style": {
"stroke": "#ff0000",
"fill": "#ffbfbf",
"label": true,
"color": "#000000"
},
"nodes": [
"884f11face673e2c",
"8823ed2c61e64b0f"
],
"x": 394,
"y": 319,
"w": 492,
"h": 82
},
{
"id": "58e5886e40ebacd1",
"type": "group",
"z": "e5621a7de149225c",
"name": "Create missing BACnet Objects",
"style": {
"stroke": "#ff0000",
"fill": "#ffbfbf",
"label": true,
"color": "#000000"
},
"nodes": [
"8e36bdc54932c785",
"4671414315b782ca",
"11c02edca91f42f3",
"0a876338e2be6dd8",
"5e76761149f4280e",
"d04fc338048162ec",
"d7fb76702f1ebdab"
],
"x": 1154,
"y": 219,
"w": 762,
"h": 142
},
{
"id": "cfdfc86845dc9bb9",
"type": "group",
"z": "d5185b34c44ae683",
"name": "Uplink",
"style": {
"label": true,
"color": "#000000",
"fill": "#e3f3d3"
},
"nodes": [
"6004507d410bec08",
"5338fce50ecc57d9",
"cfaebde9dbc1901d",
"2694240cdee88a83"
],
"x": 14,
"y": 219,
"w": 612,
"h": 162
},
{
"id": "90c82936d1f21eda",
"type": "group",
"z": "d5185b34c44ae683",
"name": "Downlink",
"style": {
"fill": "#e3f3d3",
"label": true,
"color": "#000000"
},
"nodes": [
"4fbd348dfae7d9ce",
"8ef7bc0b865b1230",
"a6ff13a9089b4272",
"adb0c33fcb917696"
],
"x": 634,
"y": 219,
"w": 712,
"h": 162
},
{
"id": "c5511cd0867d06e2",
"type": "junction",
"z": "e5621a7de149225c",
"x": 600,
"y": 580,
"wires": [
[]
]
},
{
"id": "f8a6a26c3a4274bf",
"type": "junction",
"z": "e5621a7de149225c",
"x": 1460,
"y": 460,
"wires": [
[
"a1b40b26fe6dd41f"
]
]
},
{
"id": "914dac3cf2194eb6",
"type": "mqtt-broker",
"name": "The Things Network",
"broker": "eu1.cloud.thethings.network",
"port": 1883,
"clientid": "",
"autoConnect": true,
"usetls": false,
"protocolVersion": 4,
"keepalive": 60,
"cleansession": true,
"autoUnsubscribe": true,
"birthTopic": "",
"birthQos": "0",
"birthRetain": "false",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closeQos": "0",
"closeRetain": "false",
"closePayload": "",
"closeMsg": {},
"willTopic": "",
"willQos": "0",
"willRetain": "false",
"willPayload": "",
"willMsg": {},
"userProps": "",
"sessionExpiry": ""
},
{
"id": "74dfa9d48cf1b5c8",
"type": "mqtt-broker",
"name": "mosquitto local (docker)",
"broker": "mosquitto",
"port": 1883,
"clientid": "",
"autoConnect": true,
"usetls": false,
"protocolVersion": 4,
"keepalive": 60,
"cleansession": true,
"autoUnsubscribe": true,
"birthTopic": "",
"birthQos": "0",
"birthRetain": "false",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closeQos": "0",
"closeRetain": "false",
"closePayload": "",
"closeMsg": {},
"willTopic": "",
"willQos": "0",
"willRetain": "false",
"willPayload": "",
"willMsg": {},
"userProps": "",
"sessionExpiry": ""
},
{
"id": "5f754ddd5c5bce24",
"type": "mqtt-broker",
"name": "The Things Stack",
"broker": "stack",
"port": "1883",
"tls": "",
"clientid": "",
"autoConnect": true,
"usetls": false,
"protocolVersion": "4",
"keepalive": "60",
"cleansession": true,
"autoUnsubscribe": true,
"birthTopic": "",
"birthQos": "0",
"birthRetain": "false",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closeQos": "0",
"closeRetain": "false",
"closePayload": "",
"closeMsg": {},
"willTopic": "",
"willQos": "0",
"willRetain": "false",
"willPayload": "",
"willMsg": {},
"userProps": "",
"sessionExpiry": ""
},
{
"id": "2418490b3512393e",
"type": "tls-config",
"name": "",
"cert": "",
"key": "",
"ca": "",
"certname": "",
"keyname": "",
"caname": "",
"servername": "",
"verifyservercert": false,
"alpnprotocol": ""
},
{
"id": "d6dd3de173c23998",
"type": "mqtt-broker",
"name": "Chirpstack-Cloud",
"broker": "chirpstack.univ-lorawan.fr",
"port": "8883",
"tls": "2418490b3512393e",
"clientid": "",
"autoConnect": true,
"usetls": true,
"protocolVersion": "4",
"keepalive": "60",
"cleansession": true,
"autoUnsubscribe": true,
"birthTopic": "",
"birthQos": "0",
"birthRetain": "false",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closeQos": "0",
"closeRetain": "false",
"closePayload": "",
"closeMsg": {},
"willTopic": "",
"willQos": "0",
"willRetain": "false",
"willPayload": "",
"willMsg": {},
"userProps": "",
"sessionExpiry": ""
},
{
"id": "fa7f1bf4647d5456",
"type": "function",
"z": "e5621a7de149225c",
"name": "BACnet Object Exist ?",
"func": "let device = msg.device;\nconst debug = flow.get(\"$parent.g_debug\");\n\nswitch (msg.statusCode) {\n ////////////////////////////////////////////////// \n // Case 200 : \"Success\" > Stops here OR continue to read Downlink Objects. \n // Case 200 : \"Object does not exist\" > Create Objects\n //////////////////////////////////////////////////\n case 200:\n if (msg.payload.includes(\"Unknown Object\")) {\n debug(device, \"creation\", `${device.identity.deviceName} : Some BACnet objects don't exist`);\n return [{ device: device }, null]; // Create Downlink Objects\n }\n else {\n switch (env.get(\"dataDirection\")) {\n case \"uplink\":\n debug(device, \"up\", `${device.identity.deviceName} (RestAPI) : Write Uplink Objects`);\n\n const dataDirection = Object.values(device.bacnet.objects).map(obj => obj.dataDirection);\n\n if (dataDirection.some(direction => { return direction === \"downlink\" })) {\n return [null, { device: device }]; // Continue to read downlink Objects\n }\n else {\n debug(device, \"txTime\", `${device.identity.deviceName} (${device.controller.protocol}) : TX time = ${Date.now() - device.transmitTime} ms`);\n return [null, null]; // Stops here\n }\n break;\n case \"downlink\":\n debug(device, \"down\", `${device.identity.deviceName} (RestAPI) : Read Downlink Objects`);\n return [null, { device: device, payload: JSON.parse(msg.payload) }];\n break;\n default:\n \n }\n \n }\n\n case 400:\n node.error(\"Error : Bad HTTP Request\");\n if (msg.payload.includes(\"write-access-denied\")) {\n node.error(\"Error : Trying to write a Read Only object (analogInput)\");\n }\n return [null, null];\n\n case 401:\n node.error(\"Error : Can't connect to controller : Authorization error\");\n return [null, null];\n\n case 500:\n node.error(\"Error : Server Error 500\");\n return [null, null];\n\n case 404:\n node.error(\"Error : 404\");\n return [null, null];\n\n case \"ETIMEDOUT\":\n node.error(\"Error : Can't connect to controller : TimeOut\");\n return [null, null];\n\n case \"UNABLE_TO_VERIFY_LEAF_SIGNATURE\":\n node.error(\"Error : You forgot to enable the TLS config in your HTTP node\");\n return [null, null];\n\n}\n",
"outputs": 2,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1000,
"y": 360,
"wires": [
[
"8e36bdc54932c785",
"780a20b5b444c3d7"
],
[
"8118aa09800ae8ee"
]
],
"icon": "node-red/switch.svg"
},
{
"id": "884f11face673e2c",
"type": "http request",
"z": "e5621a7de149225c",
"g": "b3c9c070e14e7188",
"name": "HTTP REQUEST",
"method": "use",
"ret": "txt",
"paytoqs": "ignore",
"url": "",
"tls": "2418490b3512393e",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 770,
"y": 360,
"wires": [
[
"fa7f1bf4647d5456"
]
]
},
{
"id": "8823ed2c61e64b0f",
"type": "function",
"z": "e5621a7de149225c",
"g": "b3c9c070e14e7188",
"name": "READ/WRITE Objects",
"func": "\nlet device = msg.device;\nlet dataDirection = env.get('dataDirection')\nlet bacnetObjects = device?.bacnet?.objects;\n\nif(device?.controller?.protocol != \"restAPIBacnet\") return null;\n\nswitch (device?.controller?.model) {\n\n ///////////////////////////////////////////////////////////\n ////// Distech Controls Eclypse APEX\n ////// https://www.postman.com/distech/distech-ecy-v2-public/request/3qk28wy/write-property-multiple\n ///////////////////////////////////////////////////////////\n case \"distechControlsV2\":\n /********* HTTP Request Write Properties\n {\n \"method\": \"POST\",\n \"url\": \"https://@IP/api/rest/v2/services/bacnet/local/objects/write-property-multiple\",\n \"headers\": {Authorization: httpAuthentication,\n ContentType: \"application/json\"},\n \n \"payload\": {\n \"encode\": \"text\",\n \"property-references\": [\n {\n \"type\": \"analogValue\",\n \"instance\": y,\n \"property\": \"presentValue\",\n \"value\" : \"xx\"\n },\n {\n \"type\": \"analogValue\",\n \"instance\": y,\n \"property\": \"presentValue\",\n \"value\" : \"xx\"\n },\n ...\n ]\n },\n \"requestTimeout\" : xxx (ms)\n }\n */\n\n let property_references = [];\n for (let object in bacnetObjects) {\n if (bacnetObjects[object].dataDirection == dataDirection) {\n let temp = {}\n switch (dataDirection) {\n case \"uplink\":\n temp = '{ \"type\": \"' + bacnetObjects[object].objectType + '\", \"instance\": ' + bacnetObjects[object].instanceNum + ', \"property\": \"presentValue\", \"value\": ' + bacnetObjects[object].value + ' }';\n property_references.push(JSON.parse(temp));\n break;\n case \"downlink\":\n temp = '{ \"type\": \"' + bacnetObjects[object].objectType + '\", \"instance\": ' + bacnetObjects[object].instanceNum + ', \"property\": \"presentValue\"}';\n property_references.push(JSON.parse(temp));\n break;\n default:\n \n }\n }\n }\n\n // Return HTTP Request\n switch (dataDirection) {\n case \"uplink\":\n return {\n \"method\": \"POST\",\n \"url\": \"https://\" + device.controller.ipAddress + \"/api/rest/v2/services/bacnet/local/objects/write-property-multiple\",\n \"headers\": {\n Authorization: device.controller.httpAuthentication,\n ContentType: \"application/json\"\n },\n \"payload\": {\n \"encode\": \"text\",\n \"property-references\": property_references\n },\n \"requestTimeout\": flow.get('$parent.g_httpRequestTimeOut'),\n \"device\": device\n }\n break;\n case \"downlink\":\n return {\n \"method\": \"POST\",\n \"url\": \"https://\" + device.controller.ipAddress + \"/api/rest/v2/services/bacnet/local/objects/read-property-multiple\",\n \"headers\": {\n Authorization: device.controller.httpAuthentication,\n ContentType: \"application/json\"\n },\n \"payload\": {\n \"encode\": \"text\",\n \"property-references\": property_references\n },\n \"requestTimeout\": flow.get('$parent.g_httpRequestTimeOut'),\n \"device\": device\n }\n break;\n default:\n return\n break;\n }\n \n\n\n ///////////////////////////////////////////////////////////\n ////// XXXXX Controller\n ////// URL to the API documentation\n ///////////////////////////////////////////////////////////\n case \"anotherController\":\n\n break;\n default:\n \n return null;\n break;\n}\n\n\n\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 520,
"y": 360,
"wires": [
[
"884f11face673e2c"
]
]
},
{
"id": "8e36bdc54932c785",
"type": "function",
"z": "e5621a7de149225c",
"g": "58e5886e40ebacd1",
"name": "CREATE Objects",
"func": "\nlet device = msg.device;\nlet bacnetObjects = device.bacnet.objects;\n\nswitch (device.controller.model) {\n\n ///////////////////////////////////////////////////////////\n ////// Distech Controls Eclypse APEX\n ////// https://www.postman.com/distech/distech-ecy-v2-public/request/57jbx8w/create-objects-multiple\n ///////////////////////////////////////////////////////////\n case \"distechControlsV2\":\n\n /********** Objects creation on the controller\n {\n \"method\": \"POST\",\n \"url\": \"https://\" + flow.get('$parent.g_controllerIP') +\"/api/rest/v2/batch\",\n \"headers\": {Authorization: flow.get('$parent.g_httpAuthentication'),\n ContentType: \"application/json\"}\n \"payload\":{\n \"requests\": [\n {\n \"id\": \"1\",\n \"method\": \"POST\",\n \"url\": \"/api/rest/v2/services/bacnet/local/objects/add\",\n \"body\": {\n \"object-type\": \"AnalogValue\",\n \"instance-number\": 10010,\n \"name\": \"apiAVTest10\"\n }\n },\n {\n \"id\": \"2\",\n \"method\": \"POST\",\n \"url\": \"/api/rest/v2/services/bacnet/local/objects/add\",\n \"body\": {\n \"object-type\": \"BinaryValue\",\n \"instance-number\": 10010,\n \"name\": \"apiBVTest10\"\n }\n },\n ...\n ]\n },\n \"requestTimeout\" : xxx (ms)\n }\n */\n\n\n let requests = [], i = 1;\n\n for (let object in bacnetObjects) {\n let temp = '{ \"id\": \"' + (i++) + '\", \"method\": \"POST\", \"url\": \"/api/rest/v2/services/bacnet/local/objects/add\", \"body\": { \"object-type\": \"' + bacnetObjects[object].objectType + '\", \"instance-number\": ' + bacnetObjects[object].instanceNum + ', \"name\": \"' + bacnetObjects[object].objectName + '\" } }';\n requests.push(JSON.parse(temp));\n }\n\n // Return HTTP Request\n return {\n \"method\": \"POST\",\n \"url\": \"https://\" + device.controller.ipAddress + \"/api/rest/v2/batch\",\n \"headers\": {\n Authorization: device.controller.httpAuthentication,\n ContentType: \"application/json\"\n },\n \"payload\": { \"requests\": requests },\n \"requestTimeout\": flow.get('$parent.g_httpRequestTimeOut'),\n \"device\": device\n }\n\n ///////////////////////////////////////////////////////////\n ////// XXXXX Controller\n ////// URL to the API documentation\n ///////////////////////////////////////////////////////////\n case \"anotherController\":\n\n \n}",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1270,
"y": 260,
"wires": [
[
"4671414315b782ca"
]
]
},
{
"id": "4671414315b782ca",
"type": "http request",
"z": "e5621a7de149225c",
"g": "58e5886e40ebacd1",
"name": "HTTP REQUEST",
"method": "use",
"ret": "txt",
"paytoqs": "ignore",
"url": "",
"tls": "2418490b3512393e",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 1550,
"y": 260,
"wires": [
[
"11c02edca91f42f3",
"5e76761149f4280e"
]
]
},
{
"id": "11c02edca91f42f3",
"type": "function",
"z": "e5621a7de149225c",
"g": "58e5886e40ebacd1",
"name": "READ/WRITE Objects",
"func": "\nlet device = msg.device;\nlet previousValues = flow.get(\"$parent.g_previousValues\");\nlet dataDirection = env.get('dataDirection')\nlet bacnetObjects = device.bacnet.objects;\n\nswitch (device.controller.model) {\n\n ///////////////////////////////////////////////////////////\n ////// Distech Controls Eclypse APEX\n ////// https://www.postman.com/distech/distech-ecy-v2-public/request/3qk28wy/write-property-multiple\n ///////////////////////////////////////////////////////////\n case \"distechControlsV2\":\n /********* HTTP Request Write Properties\n {\n \"method\": \"POST\",\n \"url\": \"https://@IP/api/rest/v2/services/bacnet/local/objects/write-property-multiple\",\n \"headers\": {Authorization: httpAuthentication,\n ContentType: \"application/json\"},\n \n \"payload\": {\n \"encode\": \"text\",\n \"property-references\": [\n {\n \"type\": \"analogValue\",\n \"instance\": y,\n \"property\": \"presentValue\",\n \"value\" : \"xx\"\n },\n {\n \"type\": \"analogValue\",\n \"instance\": y,\n \"property\": \"presentValue\",\n \"value\" : \"xx\"\n },\n ...\n ]\n },\n \"requestTimeout\" : xxx (ms)\n }\n */\n\n let property_references = [];\n for (let object in bacnetObjects) {\n let temp = {}\n switch (bacnetObjects[object].dataDirection) {\n case \"uplink\":\n temp = '{ \"type\": \"' + bacnetObjects[object].objectType + '\", \"instance\": ' + bacnetObjects[object].instanceNum + ', \"property\": \"presentValue\", \"value\": ' + bacnetObjects[object].value + ' }';\n property_references.push(JSON.parse(temp));\n break;\n case \"downlink\":\n // if it exist take the previous value of the downlink BACnet object\n if (previousValues.hasOwnProperty(device.identity.deviceName)){\n temp = '{ \"type\": \"' + bacnetObjects[object].objectType + '\", \"instance\": ' + bacnetObjects[object].instanceNum + ', \"property\": \"presentValue\", \"value\": ' + previousValues[device.identity.deviceName].bacnet.objects[object].value + ' }';\n property_references.push(JSON.parse(temp));\n }else{\n temp = '{ \"type\": \"' + bacnetObjects[object].objectType + '\", \"instance\": ' + bacnetObjects[object].instanceNum + ', \"property\": \"presentValue\", \"value\": ' + bacnetObjects[object].value + ' }';\n property_references.push(JSON.parse(temp));\n }\n\n break;\n default:\n }\n \n }\n\n // Return HTTP Request\n return {\n \"method\": \"POST\",\n \"url\": \"https://\" + device.controller.ipAddress + \"/api/rest/v2/services/bacnet/local/objects/write-property-multiple\",\n \"headers\": {\n Authorization: device.controller.httpAuthentication,\n ContentType: \"application/json\"\n },\n \"payload\": {\n \"encode\": \"text\",\n \"property-references\": property_references\n },\n \"requestTimeout\": global.get('g_httpRequestTimeOut'),\n \"device\": device\n }\n\n ///////////////////////////////////////////////////////////\n ////// XXXXX Controller\n ////// URL to the API documentation\n ///////////////////////////////////////////////////////////\n case \"anotherController\":\n\n\n}\n\n\n\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1280,
"y": 320,
"wires": [
[
"0a876338e2be6dd8"
]
]
},
{
"id": "0a876338e2be6dd8",
"type": "http request",
"z": "e5621a7de149225c",
"g": "58e5886e40ebacd1",
"name": "HTTP REQUEST",
"method": "use",
"ret": "txt",
"paytoqs": "ignore",
"url": "",
"tls": "2418490b3512393e",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 1550,
"y": 320,
"wires": [
[
"d04fc338048162ec"
]
]
},
{
"id": "5e76761149f4280e",
"type": "function",
"z": "e5621a7de149225c",
"g": "58e5886e40ebacd1",
"name": "Creation results",
"func": "let device = msg.device;\nconst debug = flow.get('$parent.g_debug');\n\nswitch (msg.statusCode) {\n ////////////////////////////////////////////////// \n // Case 200 : \"Success\" > Objects have been created\n //////////////////////////////////////////////////\n case 200:\n if (msg.payload.includes(\"\\\"status\\\":200\")) {\n debug(device, \"creation\", `${device.identity.deviceName} (RestAPI) : Some BACnet objects have been created`);\n }\n if (msg.payload.includes(\"Instance already exists\") || msg.payload.includes(\"Object with same name already exists\")) {\n flow.set('g_errorObjectCreation', flow.get('g_errorObjectCreation') + 1);\n node.error(`${device.identity.deviceName} : Some BACnet objects already existed`);\n }\n break;\n\n case 400:\n node.error(\"Error : Bad HTTP Request\");\n break;\n\n case 401:\n node.error(\"Error : Can't connect to controller : Authorization error\");\n break;\n\n case 500:\n node.error(\"Error : Server Error 500\");\n break;\n\n case 404:\n node.error(\"Error : 404\");\n break;\n\n\n case \"ETIMEDOUT\":\n node.error(\"Error : Can't connect to controller : TimeOut\");\n break;\n\n case \"UNABLE_TO_VERIFY_LEAF_SIGNATURE\":\n node.error(\"Error : You forgot to enable the TLS config in your HTTP node\");\n break;\n\n}\n\n\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "// Le code ajouté ici sera exécuté une fois\n// à chaque démarrage du noeud.\nglobal.set('g_errorObjectCreation', 0);",
"finalize": "",
"libs": [],
"x": 1780,
"y": 260,
"wires": [
[]
],
"icon": "node-red/alert.svg"
},
{
"id": "d04fc338048162ec",
"type": "function",
"z": "e5621a7de149225c",
"g": "58e5886e40ebacd1",
"name": "Debug Write",
"func": "let device = msg.device;\nconst debug = flow.get('$parent.g_debug');\n\ndebug(device, \"txTime\", `${device.identity.deviceName} (${device.controller.protocol}) : TX time = ${Date.now() - device.transmitTime} ms`); \n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1770,
"y": 320,
"wires": [
[
"d7fb76702f1ebdab"
]
]
},
{
"id": "0ad4d2e61e8cd612",
"type": "change",
"z": "e5621a7de149225c",
"name": "Read/Write in process",
"rules": [
{
"t": "delete",
"p": "payload",
"pt": "msg"
},
{
"t": "set",
"p": "payload.text",
"pt": "msg",
"to": "$env('dataDirection') & \" Read/Write in process...\"",
"tot": "jsonata"
},
{
"t": "set",
"p": "payload.fill",
"pt": "msg",
"to": "yellow",
"tot": "str"
},
{
"t": "set",
"p": "payload.shape",
"pt": "msg",
"to": "ring",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 460,
"y": 600,
"wires": [
[
"c5511cd0867d06e2"
]
]
},
{
"id": "a3a2696eda710a5e",
"type": "change",
"z": "e5621a7de149225c",
"name": "Read/Write complete",
"rules": [
{
"t": "delete",
"p": "payload",
"pt": "msg"
},
{
"t": "set",
"p": "payload.text",
"pt": "msg",
"to": "$env('dataDirection') & \" Read/Write complete\"",
"tot": "jsonata"
},
{
"t": "set",
"p": "payload.fill",
"pt": "msg",
"to": "green",
"tot": "str"
},
{
"t": "set",
"p": "payload.shape",
"pt": "msg",
"to": "dot",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 460,
"y": 560,
"wires": [
[
"c5511cd0867d06e2"
]
]
},
{
"id": "ed3e8b9be7a13e70",
"type": "change",
"z": "e5621a7de149225c",
"name": "Object creation complete",
"rules": [
{
"t": "delete",
"p": "payload",
"pt": "msg"
},
{
"t": "set",
"p": "payload.text",
"pt": "msg",
"to": "$env('dataDirection') & \" Object creation complete\"",
"tot": "jsonata"
},
{
"t": "set",
"p": "payload.fill",
"pt": "msg",
"to": "green",
"tot": "str"
},
{
"t": "set",
"p": "payload.shape",
"pt": "msg",
"to": "dot",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 470,
"y": 640,
"wires": [
[
"c5511cd0867d06e2"
]
]
},
{
"id": "e3be43afc71d8df9",
"type": "change",
"z": "e5621a7de149225c",
"name": "Object creation in process",
"rules": [
{
"t": "delete",
"p": "payload",
"pt": "msg"
},
{
"t": "set",
"p": "payload.text",
"pt": "msg",
"to": "$env('dataDirection') & \" Object creation in process...\"",
"tot": "jsonata"
},
{
"t": "set",
"p": "payload.fill",
"pt": "msg",
"to": "yellow",
"tot": "str"
},
{
"t": "set",
"p": "payload.shape",
"pt": "msg",
"to": "ring",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 470,
"y": 520,
"wires": [
[
"c5511cd0867d06e2"
]
]
},
{
"id": "d7fb76702f1ebdab",
"type": "link out",
"z": "e5621a7de149225c",
"g": "58e5886e40ebacd1",
"name": "link out Object creation complete",
"mode": "link",
"links": [
"9fdaa35d3e506094"
],
"x": 1875,
"y": 320,
"wires": []
},
{
"id": "9fdaa35d3e506094",
"type": "link in",
"z": "e5621a7de149225c",
"name": "link in Object creation complete",
"links": [
"d7fb76702f1ebdab"
],
"x": 295,
"y": 640,
"wires": [
[
"ed3e8b9be7a13e70"
]
]
},
{
"id": "408508b0ed73706f",
"type": "link in",
"z": "e5621a7de149225c",
"name": "link in Read/Write in progress",
"links": [
"7d4514aea698cd4b"
],
"x": 295,
"y": 600,
"wires": [
[
"0ad4d2e61e8cd612"
]
]
},
{
"id": "452d87a8fe8e5e5b",
"type": "link in",
"z": "e5621a7de149225c",
"name": "link in Read/Write complete",
"links": [
"a1b40b26fe6dd41f"
],
"x": 295,
"y": 560,
"wires": [
[
"a3a2696eda710a5e"
]
]
},
{
"id": "b6703631b9a583d8",
"type": "link in",
"z": "e5621a7de149225c",
"name": "link in object creation in process",
"links": [
"780a20b5b444c3d7"
],
"x": 295,
"y": 520,
"wires": [
[
"e3be43afc71d8df9"
]
]
},
{
"id": "780a20b5b444c3d7",
"type": "link out",
"z": "e5621a7de149225c",
"name": "link out object creation",
"mode": "link",
"links": [
"b6703631b9a583d8"
],
"x": 1015,
"y": 260,
"wires": []
},
{
"id": "7d4514aea698cd4b",
"type": "link out",
"z": "e5621a7de149225c",
"name": "link out Read/Write in progress",
"mode": "link",
"links": [
"408508b0ed73706f"
],
"x": 535,
"y": 420,
"wires": []
},
{
"id": "6c31111be68ac559",
"type": "function",
"z": "e5621a7de149225c",
"name": "Store Downlink object",
"func": "/////////////////////////////////////////////////////////////////////\n///////////////// Store Downlink Objects //////////////\n/////////////////////////////////////////////////////////////////////\n/* This function stores the downlink data from the controller */\n\nlet device = msg.device;\nlet bacnetObjects = device.bacnet.objects;\n\n// For InfluxDB support\ndevice.influxdb.source = \"downlink\";\n\nswitch (device.controller.model) {\n case \"distechControlsV2\":\n let donwlinkObjects = msg.payload;\n\n for (let i = 0; i < donwlinkObjects.results.length; i++) {\n Object.values(bacnetObjects).forEach(obj => {\n if (donwlinkObjects.results[i].type == obj.objectType && donwlinkObjects.results[i].instance == obj.instanceNum) {\n if (obj.objectType == \"analogValue\") obj.value = Number(donwlinkObjects.results[i].value);\n if (obj.objectType == \"binaryValue\") obj.value = donwlinkObjects.results[i].value;\n }\n });\n }\n\n return {\n device: device\n };\n\n\n\n ///////////////////////////////////////////////////////////\n ////// XXXXX Controller\n ////// URL to the API documentation\n ///////////////////////////////////////////////////////////\n case \"anotherController\":\n\n}\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1320,
"y": 420,
"wires": [
[
"f8a6a26c3a4274bf"
]
]
},
{
"id": "a1b40b26fe6dd41f",
"type": "link out",
"z": "e5621a7de149225c",
"name": "link out Read/Write complete",
"mode": "link",
"links": [
"452d87a8fe8e5e5b"
],
"x": 1535,
"y": 500,
"wires": []
},
{
"id": "8118aa09800ae8ee",
"type": "switch",
"z": "e5621a7de149225c",
"name": "",
"property": "dataDirection",
"propertyType": "env",
"rules": [
{
"t": "eq",
"v": "downlink",
"vt": "str"
},
{
"t": "eq",
"v": "uplink",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 1175,
"y": 460,
"wires": [
[
"6c31111be68ac559"
],
[
"f8a6a26c3a4274bf"
]
],
"outputLabels": [
"downlink",
"uplink"
],
"l": false
},
{
"id": "580fb662e924a681",
"type": "switch",
"z": "e5621a7de149225c",
"name": "",
"property": "device.controller.protocol",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "RestAPIBacnet",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 450,
"y": 420,
"wires": [
[
"7d4514aea698cd4b"
]
]
},
{
"id": "adb0c33fcb917696",
"type": "mqtt out",
"z": "d5185b34c44ae683",
"g": "90c82936d1f21eda",
"name": "MQTT Publisher",
"topic": "",
"qos": "",
"retain": "",
"respTopic": "",
"contentType": "",
"userProps": "",
"correl": "",
"expiry": "",
"broker": "d6dd3de173c23998",
"x": 1240,
"y": 300,
"wires": []
},
{
"id": "4fbd348dfae7d9ce",
"type": "function",
"z": "d5185b34c44ae683",
"g": "90c82936d1f21eda",
"name": "prepare downlink",
"func": "///////////////////////////////////////////////////////////\n////// This part is device dependant\n////// The configuration depends on the downlink strategy\n///////////////////////////////////////////////////////////\n\nlet device = msg.device;\n\nvar staticDownlinkObjects = device.lorawan.defaultValuesForDownlink ?? null ;\n\nlet bacnetObjects = device.bacnet.objects;\nconst debug = flow.get(\"g_debug\");\nlet downlinkLowPriorityObject = 0, downlinkHighPriorityObject = 0; \nlet previousValues = flow.get(\"g_previousValues\");\nlet previousBacnetObject = previousValues[device.identity.deviceName].bacnet.objects;\nlet payload={};\n\nfunction downlinkPayloadCreation(downlinkObjectToSend) {\n //Creation of the downlink payload\n for (let obj in bacnetObjects){\n if (bacnetObjects[obj].dataDirection === \"downlink\" && bacnetObjects[obj].downlinkPort == bacnetObjects[downlinkObjectToSend].downlinkPort){\n let temp = \"{ \\\"\" + obj + \"\\\" : \" + JSON.stringify(bacnetObjects[obj].value) + \" }\"; \n payload = { ...payload, ...JSON.parse(temp) }\n }\n }\n // Chek if there are other values to add to the payload\n if (device.lorawan.hasOwnProperty(\"defaultValuesForDownlink\") ){\n node.warn(\"my warning\");\n if (device.lorawan.defaultValuesForDownlink.hasOwnProperty(\"fPort_\" + bacnetObjects[downlinkObjectToSend].downlinkPort)){\n for (let obj in staticDownlinkObjects[\"fPort_\"+ bacnetObjects[downlinkObjectToSend].downlinkPort]){\n let temp = \"{ \\\"\" + obj + \"\\\" : \" + JSON.stringify(staticDownlinkObjects[\"fPort_\"+ bacnetObjects[downlinkObjectToSend].downlinkPort][obj]) + \" }\"; \n payload = { ...payload, ...JSON.parse(temp) }\n \n }\n }\n }\n msg.device.lorawan.downlinkPort = bacnetObjects[downlinkObjectToSend].downlinkPort\n \n}\n\nfor (let object in bacnetObjects) {\n\n if (bacnetObjects[object].dataDirection === \"downlink\") {\n \n switch (bacnetObjects[object].downlinkPortPriority) {\n case \"high\":\n switch (bacnetObjects[object].downlinkStrategy) {\n case \"onChangeOfThisValue\":\n if (bacnetObjects[object].value != previousBacnetObject[object].value) {\n node.status({ fill: \"yellow\", shape: \"dot\", text: \"Downlink high priority COV\" });\n debug(device, \"down\", `${device.identity.deviceName} (${device.controller.protocol}) : Downlink scheduled : Previous value ${previousBacnetObject[object].value} != ${object} ${bacnetObjects[object].value}`);\n //Creation of the downlink payload\n downlinkPayloadCreation(object)\n }\n break;\n case \"onChangeOfThisValueWithinRange\":\n if (bacnetObjects[object].value != previousBacnetObject[object].value && bacnetObjects[object].value <= bacnetObjects[object].range[1] && bacnetObjects[object].value >= bacnetObjects[object].range[0]) {\n node.status({ fill: \"yellow\", shape: \"dot\", text: \"Downlink high priority COVWR\" });\n debug(device, \"down\", `${device.identity.deviceName} (${device.controller.protocol}) : Downlink scheduled : Previous value ${previousBacnetObject[object].value} != ${object} ${bacnetObjects[object].value}`);\n //Creation of the downlink payload\n downlinkPayloadCreation(object)\n }\n break;\n case \"compareToUplinkObjectWithinRange\":\n if (bacnetObjects[object].value != bacnetObjects[bacnetObjects[object].uplinkToCompareWith].value && bacnetObjects[object].value <= bacnetObjects[object].range[1] && bacnetObjects[object].value >= bacnetObjects[object].range[0]) {\n node.status({ fill: \"yellow\", shape: \"dot\", text: \"Downlink high priority CUVWR\" });\n debug(device, \"down\", `${device.identity.deviceName} (${device.controller.protocol}) : Downlink scheduled : ${bacnetObjects[object].uplinkToCompareWith} ${bacnetObjects[bacnetObjects[object].uplinkToCompareWith].value} != ${object} ${bacnetObjects[object].value}`);\n //Creation of the downlink payload\n downlinkPayloadCreation(object)\n }\n break;\n case \"compareToUplinkObject\":\n if (bacnetObjects[object].value != bacnetObjects[bacnetObjects[object].uplinkToCompareWith].value) {\n node.status({ fill: \"yellow\", shape: \"dot\", text: \"Downlink high priority CUV\" });\n debug(device, \"down\", `${device.identity.deviceName} (${device.controller.protocol}) : Downlink scheduled : ${bacnetObjects[object].uplinkToCompareWith} ${bacnetObjects[bacnetObjects[object].uplinkToCompareWith].value} != ${object} ${bacnetObjects[object].value}`);\n //Creation of the downlink payload\n downlinkPayloadCreation(object)\n }\n break;\n default:\n \n }\n \n break;\n case \"low\":\n //In case of low priority downlink the object name is kept till the end of the for loop \n // to be sur that there is not any high priority downlink to send \n switch (bacnetObjects[object].downlinkStrategy) {\n case \"onChangeOfThisValue\":\n if (bacnetObjects[object].value != previousBacnetObject[object].value) {\n downlinkLowPriorityObject = object;\n }\n break;\n case \"onChangeOfThisValueWithinRange\":\n if (bacnetObjects[object].value != previousBacnetObject[object].value && bacnetObjects[object].value <= bacnetObjects[object].range[1] && bacnetObjects[object].value >= bacnetObjects[object].range[0]) {\n downlinkLowPriorityObject = object;\n }\n break;\n case \"compareToUplinkObjectWithinRange\":\n if (bacnetObjects[object].value != bacnetObjects[bacnetObjects[object].uplinkToCompareWith]?.value && bacnetObjects[object].value <= bacnetObjects[object].range[1] && bacnetObjects[object].value >= bacnetObjects[object].range[0]) {\n downlinkLowPriorityObject = object;\n }\n break;\n case \"compareToUplinkObject\":\n if (bacnetObjects[object].value != bacnetObjects[bacnetObjects[object].uplinkToCompareWith].value) {\n downlinkLowPriorityObject = object;\n }\n break;\n default:\n \n }\n break;\n default:\n\n }\n }\n if (Object.keys(payload).length !== 0){\n break;\n }\n}\nif (downlinkLowPriorityObject != 0 && Object.keys(payload).length === 0) {\n node.status({ fill: \"yellow\", shape: \"dot\", text: \"Downlink low priority\" });\n switch (bacnetObjects[downlinkLowPriorityObject].downlinkStrategy) {\n case \"onChangeOfThisValue\":\n debug(device, \"down\", `${device.identity.deviceName} (${device.controller.protocol}) : Downlink scheduled : Previous value ${previousBacnetObject[downlinkLowPriorityObject].value} != ${downlinkLowPriorityObject} ${bacnetObjects[downlinkLowPriorityObject].value}`);\n break;\n case \"onChangeOfThisValueWithinRange\":\n debug(device, \"down\", `${device.identity.deviceName} (${device.controller.protocol}) : Downlink scheduled : Previous value ${previousBacnetObject[downlinkLowPriorityObject].value} != ${downlinkLowPriorityObject} ${bacnetObjects[downlinkLowPriorityObject].value}`);\n break;\n case \"compareToUplinkObjectWithinRange\":\n debug(device, \"down\", `${device.identity.deviceName} (${device.controller.protocol}) : Downlink scheduled : ${bacnetObjects[downlinkLowPriorityObject].uplinkToCompareWith} ${bacnetObjects[bacnetObjects[downlinkLowPriorityObject].uplinkToCompareWith].value} != ${downlinkLowPriorityObject} ${bacnetObjects[downlinkLowPriorityObject].value}`);\n break;\n case \"compareToUplinkObject\":\n debug(device, \"down\", `${device.identity.deviceName} (${device.controller.protocol}) : Downlink scheduled : ${bacnetObjects[downlinkLowPriorityObject].uplinkToCompareWith} ${bacnetObjects[bacnetObjects[downlinkLowPriorityObject].uplinkToCompareWith].value} != ${downlinkLowPriorityObject} ${bacnetObjects[downlinkLowPriorityObject].value}`);\n break;\n default:\n \n }\n //Creation of the dowlink payload\n downlinkPayloadCreation(downlinkLowPriorityObject) \n}else if (Object.keys(payload).length === 0){\n\n node.status({fill: \"green\", shape: \"dot\" ,text: \"No downlink\"});\n return null;\n}\n\n//Update the previous values\nfor (let object in payload) {\n previousBacnetObject[object].value = payload[object] ;\n}\n\n\n//Create the downlink