[
    {
        "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 payload\nlet downlinkJson = {};\n// Modify the downlink object according to the lorawanPayloadName.\n\nfor (let object in payload) {\n    // Don't do any changes if lorawanPayloadName is the same as the BACnet Object name.\n    if (!Object.keys(device.bacnet.objects).some(element => element == device.bacnet.objects[object].lorawanPayloadName)) {\n        payload[device.bacnet.objects[object].lorawanPayloadName] = payload[object];\n        delete payload[object];\n    }\n}\n\n\n//////////////////////////////////////////////////////////////////////////\n// The Things Stack Network Server \n/////////////////////////////////////////////////////////////////////////\nif (device.lorawan.networkServer == \"tts\") {\n    downlinkJson = {\n        \"topic\": device.mqtt.topicDownlink + (device.lorawan.flushDownlinkQueue ? \"/replace\" : \"/push\"),\n        \"payload\": {\n            \"downlinks\": [\n                {\n                    \"f_port\": device.lorawan.downlinkPort,\n                    \"decoded_payload\": payload,\n                    \"priority\": \"NORMAL\"\n                },\n            ]\n        }\n    }\n    debug(device, \"txTime\", `${device.identity.deviceName} (${device.controller.protocol}) : TX time = ${Date.now() - device.transmitTime} ms`);\n    return downlinkJson;\n}\n/*\n//////////////////////////////////////////////////////////////////////////\n// helium Network Server \n/////////////////////////////////////////////////////////////////////////\nif (device.lorawan.networkServer == \"helium\") {\n    downlinkJson = {\n        \"topic\": device.mqtt.topicDownlink,\n        \"payload\": {\n            \"payload_raw\": \"SGVsbG8sIHdvcmxkIQ==\",\n            \"port\": device.lorawan.downlinkPort,\n            \"confirmed\": false\n        }\n    }\n    debug(device, \"txTime\", `${device.identity.deviceName} (${device.controller.protocol}) : TX time = ${Date.now() - device.transmitTime} ms`);\n    return downlinkJson;\n}\n*/\n//////////////////////////////////////////////////////////////////////////\n// Chipstack Network Server \n/////////////////////////////////////////////////////////////////////////\n// 1. Flush the downlink Queue\n\nif (device.lorawan.networkServer == \"chirpstack\") {\n    if (device.lorawan.flushDownlinkQueue == true) {\n        node.warn(device);\n        debug(device, \"downlink\", device.identity.deviceName + \" flush downlink queue\");\n        //We include flow value from libraries\n        var grpc = grpcJs;\n        var device_grpc = chirpstack_device_grpc;\n        var device_pb = chirpstack_device_pb;\n\n        // This must point to the ChirpStack API interface.\n        const server = device.lorawan.chirpstack.serverAddress + \":\" + device.lorawan.chirpstack.grpcPort;\n        // The DevEUI for which we want to enqueue the downlink.\n        const devEui = device.identity.devEUI;\n        \n        // The API token (can be obtained through the ChirpStack web-interface).\n        const apiToken = device.lorawan.chirpstack.grpcApiKey;\n\n        // Create the client for the DeviceService.\n        const deviceService = new device_grpc.DeviceServiceClient(\n            server,\n            grpc.credentials.createInsecure(),\n        );\n\n        // Create the Metadata object.\n        const metadata = new grpc.Metadata();\n        metadata.set(\"authorization\", \"Bearer \" + apiToken);\n\n        //Flush downlink queue request\n        const flushReq = new device_pb.FlushDeviceQueueRequest();\n        flushReq.setDevEui(devEui);\n\n        //Send the request\n        deviceService.flushQueue(flushReq, metadata, (err, resp) => {\n            if (err !== null) {\n                node.error(`Can't flush ChirpStack downlink queue :  ${err}`);\n            }\n        });\n    }\n\n    // 2. Prepare downlink JSON\n    downlinkJson = {\n        \"topic\": device.mqtt.topicDownlink,\n        \"payload\": {\n            \"devEui\": device.identity.devEUI,\n            \"confirmed\": false,\n            \"fPort\": device.lorawan.downlinkPort,\n            \"object\": payload\n        }\n    }\n    debug(device, \"txTime\", `${device.identity.deviceName} (${device.controller.protocol}) : TX time = ${Date.now() - device.transmitTime} ms`); \n    return downlinkJson;\n}\n\n\n//////////////////////////////////////////////////////////////////////////\n// Actility Network Server \n/////////////////////////////////////////////////////////////////////////\nif (device.lorawan.networkServer == \"actility\") {\n    downlinkJson = {\n        \"topic\": device.mqtt.topicDownlink,\n        \"payload\": {\n            \"DevEUI_downlink\": {\n                \"DevEUI\": device.identity.devEUI,\n                \"FPort\": device.lorawan.downlinkPort,\n                \"payload\": payload,\n                \"FlushDownlinkQueue\": String(+device.lorawan.flushDownlinkQueue),\n                \"DriverCfg\": {\n                    \"app\": {\n                        \"pId\": device.lorawan.actility.driver.pId,\n                        \"mId\": device.lorawan.actility.driver.mId,\n                        \"ver\": device.lorawan.actility.driver.ver\n                    }\n                }\n            }\n        }\n    }\n    debug(device, \"txTime\", `${device.identity.deviceName} (${device.controller.protocol}) : TX time = ${Date.now() - device.transmitTime} ms`);\n    return downlinkJson;\n}\n\n\n\n\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [
            {
                "var": "grpcJs",
                "module": "@grpc/grpc-js"
            },
            {
                "var": "chirpstack_device_pb",
                "module": "@chirpstack/chirpstack-api/api/device_pb"
            },
            {
                "var": "chirpstack_device_grpc",
                "module": "@chirpstack/chirpstack-api/api/device_grpc_pb"
            }
        ],
        "x": 1010,
        "y": 300,
        "wires": [
            [
                "1109c524e8f3621b",
                "adb0c33fcb917696"
            ]
        ],
        "icon": "node-red/cog.svg"
    },
    {
        "id": "1109c524e8f3621b",
        "type": "debug",
        "z": "d5185b34c44ae683",
        "name": "Debug Downlink message",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 1236,
        "y": 400,
        "wires": []
    },
    {
        "id": "6004507d410bec08",
        "type": "function",
        "z": "d5185b34c44ae683",
        "g": "cfdfc86845dc9bb9",
        "name": "WRITE uplink objects",
        "func": "let client = new nodeBacnet();\nlet device = msg.device;\nlet debug = flow.get(\"g_debug\");\nlet temp;\nlet cnt = 0;\nmsg.payload = [];\n\n// If the device controller protocol is not \"bacnet\" there is no need to be here \nif(device?.controller?.protocol != \"bacnet\") return null;\n\nlet bacnetObject = device.bacnet.objects;\nreturn new Promise((resolve, reject) => {\n  for (let object in bacnetObject) {\n    if (bacnetObject[object].dataDirection === \"uplink\") {\n      cnt++;\n      const param1 = { \n        type: bacnetObject[object].objectType, \n        instance: bacnetObject[object].instanceNum\n        }; \n      const param3 = [{ \n        type: ((bacnetObject[object].objectType == 2) ? 4 : 0), \n        value: bacnetObject[object].value \n        }];\n      //  Read of the uplink bacnet objects\n      client.writeProperty(device.controller.ipAddress, param1, 85, param3, (err, value) => {\n        if(err){\n            node.status({fill:\"red\",shape:\"dot\",text:\"BACnet \"+ err});\n            node.error(\"Error writing bacnet objects : \" + err, {\n              errorType: \"nativeBACnet\",\n              error: err,\n\n            });\n            reject(err);\n          } else {\n            \n            msg.payload.push(value);\n            debug(device, \"up\",`${device.identity.deviceName} (${device.controller.protocol}) : Write Uplink Objects`)\n            node.status({fill:\"green\",shape:\"dot\",text:\"Native BACnet\"});\n\n            if (msg.payload.length === cnt){\n              resolve(msg);\n            }\n          }\n      });\n\n    }\n  }\n}).finally((msg) => {\nreturn msg\n}).catch((err) => {\nnode.error(err);\n});",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [
            {
                "var": "nodeBacnet",
                "module": "node-bacnet"
            }
        ],
        "x": 500,
        "y": 340,
        "wires": [
            [
                "8ef7bc0b865b1230"
            ]
        ]
    },
    {
        "id": "8ef7bc0b865b1230",
        "type": "function",
        "z": "d5185b34c44ae683",
        "g": "90c82936d1f21eda",
        "name": "READ downlink objects",
        "func": "let client = new nodeBacnet();\nlet device = msg.device;\nlet bacnetObject = device.bacnet.objects;\nlet debug = flow.get(\"g_debug\");\nlet requestArray = [];\n\nif (device.controller.protocol != \"bacnet\") return null;\n\n// Build the request array\nfor (let object in bacnetObject) {\n  if (bacnetObject[object].dataDirection === \"downlink\") {\n    let temp = JSON.parse('{\"objectId\": { \"type\":' + bacnetObject[object].objectType + ', \"instance\":' + bacnetObject[object].instanceNum + '},\"properties\": [ {\"id\": 85} ] }');\n    requestArray.push(temp);\n  }\n}\n\n// Use a Promise to manage the asynchronous function\nreturn new Promise((resolve, reject) => {\n  client.readPropertyMultiple(device.controller.ipAddress, requestArray, (err, value) => {\n    if (err) {\n      node.error(err);\n      node.status({ fill: \"red\", shape: \"dot\", text: \"BACnet \" + err });\n      reject(err); // reject the promise in case of error\n    } else if (value) {\n      msg.payload = value;\n      debug(device, \"down\", `${device.identity.deviceName} (${device.controller.protocol}) : Read downlink Objects`);\n      node.status({ fill: \"green\", shape: \"dot\", text: \"Native BACnet\" });\n      resolve(msg); // resolve the promise with the node message\n    }\n  });\n}).then((msg) => {\n  // once the promise has been resolved\n  let values = msg.payload.values || [];\n  // store the values in the device objects value property\n  for (let i = 0; i < values.length; i++) {\n    Object.values(bacnetObject).forEach(obj => {\n      if (values[i].objectId.type == obj.objectType && values[i].objectId.instance == obj.instanceNum) {\n        obj.value = values[i].values[0].value[0].value;\n      }\n    });\n  }\n  return {\n    \"device\":device\n  };\n}).catch((err) => {\n  node.status({ fill: \"red\", shape: \"dot\", text: err });\n  node.error(\"Error reading bacnet objects\", {\n    errorType: \"nativeBACnet\",\n    error: err,\n\n  });\n  return null;\n});",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [
            {
                "var": "nodeBacnet",
                "module": "node-bacnet"
            }
        ],
        "x": 770,
        "y": 340,
        "wires": [
            [
                "4fbd348dfae7d9ce"
            ]
        ]
    },
    {
        "id": "4f48d4661bbf9c4a",
        "type": "debug",
        "z": "d5185b34c44ae683",
        "name": "debug MQTT uplink",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 323,
        "y": 400,
        "wires": []
    },
    {
        "id": "cfaebde9dbc1901d",
        "type": "mqtt in",
        "z": "d5185b34c44ae683",
        "g": "cfdfc86845dc9bb9",
        "name": "MQTT Subscriber",
        "topic": "application/+/device/+/event/up",
        "qos": "0",
        "datatype": "auto-detect",
        "broker": "d6dd3de173c23998",
        "nl": false,
        "rap": true,
        "rh": 0,
        "inputs": 0,
        "x": 120,
        "y": 300,
        "wires": [
            [
                "4f48d4661bbf9c4a",
                "2694240cdee88a83"
            ]
        ],
        "info": "\r\n# MQTT Subscriber node\r\n\r\n - How to configure this node\r\n    -\r\n    The configuration off this node depend of which LNS or MQTT broker you use.\r\n\r\n    - For The Things Network\r\n        - \r\n        With **TTN LNS** you have to use the following topic :\r\n            ***`v3/{application_id}@ttn/devices/+/up`*** \\\r\n        Replace `{application_id}` by your application id in TTN.\\\r\n         And don't forget the `@ttn`\r\n    - For The Things Stack\r\n        - \r\n        With **TTS LNS** you have to use the following topic :\r\n            ***`v3/{application_id}/devices/+/up`*** \\\r\n        Replace `{application_id}` by your application id in TTS.\r\n\r\n    - For Chirpstack\r\n        - \r\n        With **Chirpstack LNS** you have to use the following topic :\r\n            ***`application/{application_id}/device/+/event/up`*** \\\r\n        Replace `{application_id}` by your application id (it's the number, not the name !).\r\n    - For Actility\r\n        - \r\n        if you use **Actility LNS** see the [documentation](https://docs.thingpark.com/thingpark-x/latest/Connector/BROKER_MQTT/) for using actillity broker\r\n\r\n        - For actility-USMB Broker : ***`univ-smb/devices/+/uplink`***\r\n        - For HiveMQ Broker : ***`mqtt/things/+/uplink`***\r\n        \r\n    - For other LNS or MQTT broker\r\n        - \r\n        If you use any other LNS or MQTT broker please refer to their documentation to configure this MQTT node."
    },
    {
        "id": "a6ff13a9089b4272",
        "type": "subflow:e5621a7de149225c",
        "z": "d5185b34c44ae683",
        "g": "90c82936d1f21eda",
        "name": "Rest API Read downlink",
        "env": [
            {
                "name": "dataDirection",
                "value": "downlink",
                "type": "str"
            }
        ],
        "x": 770,
        "y": 260,
        "wires": [
            [
                "4fbd348dfae7d9ce"
            ]
        ]
    },
    {
        "id": "5338fce50ecc57d9",
        "type": "subflow:e5621a7de149225c",
        "z": "d5185b34c44ae683",
        "g": "cfdfc86845dc9bb9",
        "name": "Rest API Write uplink",
        "x": 500,
        "y": 260,
        "wires": [
            [
                "a6ff13a9089b4272"
            ]
        ]
    },
    {
        "id": "2694240cdee88a83",
        "type": "LoRaBAC",
        "z": "d5185b34c44ae683",
        "g": "cfdfc86845dc9bb9",
        "name": "LoRaBAC",
        "globalConfig": {
            "ipAddress": "",
            "networkServer": "tts",
            "grpcApiKey": "",
            "serverAddress": "",
            "grpcPort": "8080",
            "protocol": "bacnet",
            "model": "distechControlsV2",
            "bacnetLogin": "",
            "bacnetPassword": ""
        },
        "deviceCount": 1,
        "arrayDeviceList": [],
        "deviceList": {},
        "x": 300,
        "y": 300,
        "wires": [
            [
                "5338fce50ecc57d9",
                "6004507d410bec08"
            ]
        ]
    }
]