[
  {
    "id": "92dfb72c929b6236",
    "type": "tab",
    "label": "Monitor Boiler Pressure and Verify Reset Command",
    "disabled": false,
    "info": "",
    "env": []
  },
  {
    "id": "3b157ae6280725e2",
    "type": "inject",
    "z": "92dfb72c929b6236",
    "name": "Poll Normalized Data",
    "props": [],
    "repeat": "5",
    "crontab": "",
    "once": false,
    "onceDelay": 0.1,
    "topic": "",
    "x": 160,
    "y": 160,
    "wires": [
      [
        "1f0c4ed29b0ba9b8"
      ]
    ]
  },
  {
    "id": "1f0c4ed29b0ba9b8",
    "type": "dequeue",
    "z": "92dfb72c929b6236",
    "name": "Dequeue Normalized Data",
    "connection": "$system-iot-domain",
    "mode": "transactional",
    "inputs": 1,
    "queueName": "<domain-short-id>__IOT.NORMALIZED_DATA",
    "subscriber": "<queue-subscriber>",
    "wait": "0",
    "waitForever": false,
    "batchSize": "2",
    "deqMode": "remove",
    "payloadType": "json",
    "adtTypeName": "",
    "retryEnabled": true,
    "retryDelayMs": 5000,
    "maxRetries": 0,
    "x": 360,
    "y": 100,
    "wires": [
      [
        "322f20c96aafa365",
        "7b3ca2537b863508"
      ]
    ]
  },
  {
    "id": "322f20c96aafa365",
    "type": "function",
    "z": "92dfb72c929b6236",
    "name": "Detect High Boiler Pressure",
    "func": "const PRESSURE_LIMIT = 100;\n\n// Include only Boiler digital twins in this map.\n// HVAC pressure records are ignored because their OCIDs are not mapped here.\nconst boilerExternalKeyMap = {\n    \"<boiler-01-digital-twin-ocid>\": \"fr-guide-boiler-01\",\n    \"<boiler-02-digital-twin-ocid>\": \"fr-guide-boiler-02\"\n};\n\nconst event = msg.payload || {};\nconst twinOcid = event.digitalTwinInstanceId;\nconst externalKey = boilerExternalKeyMap[twinOcid];\n\nif (event.contentPath !== \"pressure\" || !externalKey) {\n    return null;\n}\n\nconst pressure = Number(event.value);\n\nif (!Number.isFinite(pressure) || pressure <= PRESSURE_LIMIT) {\n    return null;\n}\n\n// Fields required by IoT Send Command node\nmsg.digitalTwinOcid = twinOcid;\nmsg.requestEndpoint = `boilers/${externalKey}/command/reset`;\nmsg.responseEndpoint = `boilers/${externalKey}/command/response`;\n\n// Preserved through command-status polling for the final notification.\nmsg.title = `Boiler High Pressure Alert - ${externalKey}`;\nmsg.notificationpayload = {\n    alert: \"Boiler pressure is above the allowed limit\",\n    action: \"Reset boiler command sent\",\n    digitalTwinOcid: twinOcid,\n    externalKey: externalKey,\n    pressure: pressure,\n    threshold: PRESSURE_LIMIT,\n    timeObserved: event.timeObserved\n};\nmsg.payload = {\n    reset: true\n};\n\nreturn msg;",
    "outputs": 1,
    "timeout": 0,
    "noerr": 0,
    "initialize": "",
    "finalize": "",
    "libs": [],
    "x": 630,
    "y": 100,
    "wires": [
      [
        "16ba3587841e3b0c"
      ]
    ]
  },
  {
    "id": "16ba3587841e3b0c",
    "type": "iot-send-command",
    "z": "92dfb72c929b6236",
    "name": "Send Boiler Reset Command",
    "ociConfig": "7ecb7bdb024812c5",
    "digitalTwinOcid": "",
    "requestEndpoint": "",
    "responseEndpoint": "",
    "requestDuration": "PT1M",
    "responseDuration": "PT1M",
    "waitForResponse": true,
    "x": 900,
    "y": 100,
    "wires": [
      [
        "5fa849a7a64122e7",
        "a71417c702fb9843"
      ]
    ]
  },
  {
    "id": "5fa849a7a64122e7",
    "type": "function",
    "z": "92dfb72c929b6236",
    "name": "Initialize Command Status Polling",
    "func": "// Allow the one-minute response window plus ten seconds for the final DB update.\nmsg.pollDeadline = Date.now() + 70000;\nmsg.pollCount = 0;\nreturn msg;",
    "outputs": 1,
    "timeout": 0,
    "noerr": 0,
    "initialize": "",
    "finalize": "",
    "libs": [],
    "x": 1200,
    "y": 100,
    "wires": [
      [
        "17493283a3882f15"
      ]
    ]
  },
  {
    "id": "bf9113ee96f633b0",
    "type": "oci-notification",
    "z": "92dfb72c929b6236",
    "name": "Publish Boiler Alert Notification",
    "ociConfig": "7ecb7bdb024812c5",
    "topicOcid": "<notification-topic-ocid>",
    "msgTitle": "",
    "msgBody": "",
    "x": 410,
    "y": 380,
    "wires": [
      [
        "4e84cd8e60e73922"
      ]
    ]
  },
  {
    "id": "4e84cd8e60e73922",
    "type": "debug",
    "z": "92dfb72c929b6236",
    "name": "Notification Result",
    "active": true,
    "tosidebar": true,
    "console": false,
    "tostatus": false,
    "complete": "true",
    "targetType": "full",
    "statusVal": "",
    "statusType": "auto",
    "x": 770,
    "y": 380,
    "wires": []
  },
  {
    "id": "a861eee4d5178acd",
    "type": "catch",
    "z": "92dfb72c929b6236",
    "name": "Handle Flow Errors",
    "scope": null,
    "uncaught": false,
    "x": 180,
    "y": 500,
    "wires": [
      [
        "26ef3eda13614d3d"
      ]
    ]
  },
  {
    "id": "26ef3eda13614d3d",
    "type": "debug",
    "z": "92dfb72c929b6236",
    "name": "Flow Error Output",
    "active": true,
    "tosidebar": true,
    "console": false,
    "tostatus": false,
    "complete": "true",
    "targetType": "full",
    "statusVal": "",
    "statusType": "auto",
    "x": 430,
    "y": 500,
    "wires": []
  },
  {
    "id": "17493283a3882f15",
    "type": "delay",
    "z": "92dfb72c929b6236",
    "name": "Wait Before Status Check",
    "pauseType": "delay",
    "timeout": "5",
    "timeoutUnits": "seconds",
    "rate": "1",
    "nbRateUnits": "1",
    "rateUnits": "second",
    "randomFirst": "1",
    "randomLast": "5",
    "randomUnits": "seconds",
    "drop": false,
    "allowrate": false,
    "outputs": 1,
    "x": 230,
    "y": 260,
    "wires": [
      [
        "d0d261ae14d52ece"
      ]
    ]
  },
  {
    "id": "d0d261ae14d52ece",
    "type": "sql",
    "z": "92dfb72c929b6236",
    "name": "Query Command Status",
    "connection": "$system-iot-domain",
    "sqlSource": "editor",
    "sqlcmd": "SELECT\n    ID,\n    DIGITAL_TWIN_INSTANCE_ID,\n    REQUEST_ENDPOINT,\n    RESPONSE_ENDPOINT,\n    DELIVERY_STATUS,\n\n    UPPER(\n        JSON_VALUE(\n            RESPONSE_DATA FORMAT JSON,\n            '$.reset'\n            RETURNING VARCHAR2(30)\n            NULL ON ERROR\n        )\n    ) AS RESET_RESULT,\n\n    TIME_CREATED,\n    TIME_UPDATED,\n    TIME_FINISHED\nFROM <domain-short-id>__IOT.RAW_COMMAND_DATA\nWHERE ID = :recordId",
    "maxrows": "1",
    "bindsSource": "editor",
    "binds": "",
    "bindsMappings": "[{\"bindName\":\"recordId\",\"sourceType\":\"msg\",\"value\":\"rawCommandDataRecordId\"}]",
    "x": 470,
    "y": 260,
    "wires": [
      [
        "2b7b124f845382c6"
      ]
    ]
  },
  {
    "id": "2b7b124f845382c6",
    "type": "function",
    "z": "92dfb72c929b6236",
    "name": "Check Command Status",
    "func": "const rows = Array.isArray(msg.payload) ? msg.payload : [];\nconst row = rows[0];\nmsg.pollCount = (msg.pollCount || 0) + 1;\n\nif (!row) {\n    if (Date.now() < msg.pollDeadline) {\n        return [null, msg];\n    }\n\n    msg.payload = [{\n        DELIVERY_STATUS: \"POLL_TIMEOUT\",\n        RESET_RESULT: null,\n        TIME_FINISHED: null\n    }];\n    return [msg, null];\n}\n\nconst status = String(row.DELIVERY_STATUS || \"\").toUpperCase();\nconst finalStatuses = [\n    \"COMPLETED\",\n    \"REJECTED\",\n    \"REFUSED\",\n    \"EXPIRED\",\n    \"BAD_RESPONSE\",\n    \"NOT_RESPONDED\"\n];\n\nif (finalStatuses.includes(status)) {\n    return [msg, null];\n}\n\nif (Date.now() >= msg.pollDeadline) {\n    msg.payload = [{\n        ...row,\n        DELIVERY_STATUS: \"POLL_TIMEOUT\"\n    }];\n    return [msg, null];\n}\n\nreturn [null, msg];",
    "outputs": 2,
    "timeout": 0,
    "noerr": 0,
    "initialize": "",
    "finalize": "",
    "libs": [],
    "x": 730,
    "y": 260,
    "wires": [
      [
        "c82213a0cd4946de",
        "26fbc364157828ca"
      ],
      [
        "17493283a3882f15"
      ]
    ]
  },
  {
    "id": "c82213a0cd4946de",
    "type": "function",
    "z": "92dfb72c929b6236",
    "name": "Format Boiler Command Result Notification",
    "func": "const row = msg.payload[0] || {};\nconst alert = msg.notificationpayload || {};\nconst commandStatus = String(row.DELIVERY_STATUS || \"UNKNOWN\").toUpperCase();\nconst resetResult = String(row.RESET_RESULT || \"NO_RESPONSE\").toUpperCase();\nconst resetSucceeded = commandStatus === \"COMPLETED\" && resetResult === \"SUCCESS\";\n\nlet result;\nif (resetSucceeded) {\n    msg.title = `Boiler High Pressure Alert - Reset Successful - ${alert.externalKey}`;\n    result = \"The device confirmed that the boiler reset completed successfully.\";\n} else if (commandStatus === \"POLL_TIMEOUT\") {\n    msg.title = `Boiler High Pressure Alert - Reset Status Timed Out - ${alert.externalKey}`;\n    result = \"No final command status was available before the polling deadline.\";\n} else {\n    msg.title = `Boiler High Pressure Alert - Reset Failed - ${alert.externalKey}`;\n    result = \"The device did not confirm a successful boiler reset.\";\n}\n\nmsg.payload = JSON.stringify({\n    ...alert,\n    result: result,\n    commandStatus: commandStatus,\n    resetResult: resetResult,\n    timeFinished: row.TIME_FINISHED || null\n}, null, 2);\n\nreturn msg;",
    "outputs": 1,
    "timeout": 0,
    "noerr": 0,
    "initialize": "",
    "finalize": "",
    "libs": [],
    "x": 1150,
    "y": 260,
    "wires": [
      [
        "bf9113ee96f633b0"
      ]
    ]
  },
  {
    "id": "7b3ca2537b863508",
    "type": "debug",
    "z": "92dfb72c929b6236",
    "name": "Normalized Data Output",
    "active": true,
    "tosidebar": true,
    "console": false,
    "tostatus": false,
    "complete": "payload",
    "targetType": "msg",
    "statusVal": "",
    "statusType": "auto",
    "x": 560,
    "y": 40,
    "wires": []
  },
  {
    "id": "a71417c702fb9843",
    "type": "debug",
    "z": "92dfb72c929b6236",
    "name": "Command output",
    "active": true,
    "tosidebar": true,
    "console": false,
    "tostatus": false,
    "complete": "payload",
    "targetType": "msg",
    "statusVal": "",
    "statusType": "auto",
    "x": 1150,
    "y": 40,
    "wires": []
  },
  {
    "id": "26fbc364157828ca",
    "type": "debug",
    "z": "92dfb72c929b6236",
    "name": "Command status output",
    "active": true,
    "tosidebar": true,
    "console": false,
    "tostatus": false,
    "complete": "payload",
    "targetType": "msg",
    "statusVal": "",
    "statusType": "auto",
    "x": 990,
    "y": 200,
    "wires": []
  },
  {
    "id": "$system-iot-domain",
    "type": "db-connection",
    "name": "IoT Domain",
    "usePool": false,
    "authType": "resourcePrincipal",
    "driverMode": "thick",
    "tnsString": "${IOT_DOMAIN_DB_CONNECTION_STRING}",
    "walletLocation": "",
    "externalAuth": true,
    "scope": "${IOT_DB_TOKEN_SCOPE}",
    "configFileLocation": "",
    "profile": "",
    "fingerprint": "",
    "privateKeyLocation": "",
    "regionId": "",
    "tenancyOCID": "",
    "userOCID": "",
    "poolMin": "",
    "poolMax": "",
    "poolIncrement": "",
    "queueTimeout": "",
    "proxyUser": "${IOT_FLOW_RUNTIME_PROXY_USER}",
    "nlsLanguage": "",
    "nlsTerritory": "",
    "timeZone": "",
    "nlsNumeric": "",
    "nlsDateFmt": "",
    "nlsTsFmt": "",
    "nlsTsTzFmt": "",
    "sessionInitSql": ""
  },
  {
    "id": "7ecb7bdb024812c5",
    "type": "oci-config",
    "name": "",
    "authType": "resourcePrincipal",
    "region": "<region>",
    "compartmentOcid": "",
    "configFilePath": "~/.oci/config",
    "profile": "DEFAULT",
    "tenancyOcid": "",
    "userOcid": "",
    "fingerprint": "",
    "privateKeyPath": ""
  },
  {
    "id": "571c79c89efe0b6e",
    "type": "global-config",
    "env": [],
    "modules": {
      "db-nodes": "0.5.0",
      "oci-nodes": "0.5.0"
    }
  }
]
