[
    {
        "id": "a73423a2.863a1",
        "type": "tab",
        "label": "Temperature Controller",
        "disabled": false,
        "info": "# Starter-Kit IoT\nThis example flow shall ease the first setup of Weidmüller's [Starter-Kit IoT](https://www.weidmueller.com/int/service/support_for_u_control_starter_kits.jsp). It implements a simple temperature controller.\n\nPlease refer to the accompanying Quick Start Guide to setup your Starter-Kit. If you want to know more about this example application, take a look at the accompanying Application Note. Both are available for download at the link above.\n\n---\n## Inputs\nThe following components of the Starter-Kit are used as an input:\n - the rotary switch selects the operating mode:\n    - 2 o'clock (momentary): reset alarms\n    - 3 o'clock (stationary): inactive\n    - 4 o'clock (stationary): active\n - the rotary encoder is used to adjust the temperature setpoint between 10°C (ccw) and 30°C (cw)\n - the temperature sensor measures the actual temperature\n\n---\n## Outputs\nThe following components of the Starter-Kit are used as an output:\n - red LED: heating\n - yellow LED: idle\n - green LED: cooling\n - white LED\n    - off: controller inactive\n    - static: controller active\n    - flashing: alarm triggered\n\nThese LEDs are also replicated on the u-create Web [Visualisation](../visu).\n\n---\nv1.0.0\n\n© 2021 Weidmüller Interface GmbH & Co. KG, Author: w100141\n\nPublished under the [MIT License](https://spdx.org/licenses/MIT.html)."
    },
    {
        "id": "6041f4d6.1e2c8c",
        "type": "uc-iodataIn",
        "z": "a73423a2.863a1",
        "mode": "wi_single_variable",
        "variable": "I_ITEMPERATURE",
        "name": "Temperature",
        "pollInterval": "1000",
        "pollIntervalBase": "s",
        "x": 110,
        "y": 100,
        "wires": [
            [
                "8535e7d.c3dd018"
            ],
            []
        ]
    },
    {
        "id": "32ba5dff.2f9ac2",
        "type": "uc-iodataIn",
        "z": "a73423a2.863a1",
        "mode": "wi_single_variable",
        "variable": "I_IPOTIVALUE",
        "name": "Potentiometer",
        "pollInterval": "1000",
        "pollIntervalBase": "s",
        "x": 110,
        "y": 220,
        "wires": [
            [
                "48f00ad.e472df4"
            ],
            []
        ]
    },
    {
        "id": "48f00ad.e472df4",
        "type": "range",
        "z": "a73423a2.863a1",
        "minin": "0",
        "maxin": "32767",
        "minout": "10",
        "maxout": "30",
        "action": "scale",
        "round": false,
        "property": "payload.value",
        "name": "scale",
        "x": 290,
        "y": 220,
        "wires": [
            [
                "256584f5.3dfd7c",
                "6f7bcaf8.893e44"
            ]
        ]
    },
    {
        "id": "8535e7d.c3dd018",
        "type": "function",
        "z": "a73423a2.863a1",
        "name": "convert to °C",
        "func": "/* -------------------------------------------------------\n * Author      : w100141\n * Version     : 1.0.0\n * Copyright   : (c) 2021 Weidmüller Interface GmbH & Co. KG\n * Description : Simple type conversion and rescaling\n * -------------------------------------------------------\n */\n\n// parse a String to an Integer and divide by ten, resulting in a float.\n// turns \"234\" into 23.4\n\nmsg.payload.value = parseInt(msg.payload.value) / 10\n\nreturn msg\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 310,
        "y": 100,
        "wires": [
            [
                "256584f5.3dfd7c",
                "570744b8.5f30cc",
                "36cd4c4f.b547a4"
            ]
        ],
        "inputLabels": [
            "Numeric Value as a String"
        ],
        "outputLabels": [
            "Numeric Value divided by 10"
        ],
        "info": "The actual temperature is given as a String in 10th degrees Centigrade,  e.g. 234 equals 23.4°C. Therefore we parse this String to an Integer and divide by ten.\n\n---\nv1.0.0\n\n© 2021 Weidmüller Interface GmbH & Co. KG, Author: w100141\n\nPublished under the [MIT License](https://spdx.org/licenses/MIT.html)."
    },
    {
        "id": "570744b8.5f30cc",
        "type": "function",
        "z": "a73423a2.863a1",
        "name": "Alarms",
        "func": "let ledState;\n\n// Nodes can only have a single input. We use a switch statement to distingush \n// between messages received from the \"Temperature\" node and from the \n// \"Reset Error\" node. The name of the variable read by the iodata-in node is \n// stored in msg.payload.name.\n\nswitch(msg.payload.name){\n    case \"I_ITEMPERATURE\":\n        // If the temperature is above 30°C, the alarm is triggered\n        if (msg.payload.value > 30 || msg.payload.value < 10)\n            flow.set(\"tempAlarm\", true)\n        break;\n\n    case \"I_XROTATIONCCW\":\n        if (msg.payload.value == 1)\n            // If the rotary switch is turned to the CCW position, the alarm is reset\n            flow.set(\"tempAlarm\", false)\n            \n        if (flow.get(\"tempControllerRun\") && !flow.get(\"tempAlarm\")) {\n            // If the controller is running, the white LED is lit\n            ledState = true\n            node.status({})\n        } else if (flow.get(\"tempAlarm\")) {\n            // If the alarm has been triggered, the white LED is flashing\n            // To do this, we invert the LED's state (on/off) each time this \n            // node receives a message from the \"Reset Error\" node (every 500ms).\n            ledState = !context.get(\"ledState\") || 0\n            node.status({fill:\"red\", shape:\"dot\", text: \"Alarm\"})\n        } else {\n            // If the controller is inactive, turn the LED off.\n            ledState = false\n            node.status({})\n        }\n        \n        // Output the LED's state as a message to the \"status indicator\" node. \n        context.set(\"ledState\", ledState)\n        msg = {\"payload\": {\"value\": ledState }}\n        \n        return msg\n}",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 510,
        "y": 40,
        "wires": [
            [
                "17204097.c6cecf"
            ]
        ],
        "inputLabels": [
            "Temperature [numeric, in °C] and State of Reset Button [1, 0]"
        ],
        "outputLabels": [
            "Alarm Indicator State [true, false]"
        ],
        "icon": "font-awesome/fa-exclamation-triangle",
        "info": "Lights or flashes the white LED depending on the state of the temperature controller.\n\n---\nv1.0.0\n\n© 2021 Weidmüller Interface GmbH & Co. KG, Author: w100141\n\nPublished under the [MIT License](https://spdx.org/licenses/MIT.html)."
    },
    {
        "id": "256584f5.3dfd7c",
        "type": "function",
        "z": "a73423a2.863a1",
        "name": "Controller",
        "func": "// The operation mode of the controller is stored in a local variable.\nlet mode = \"idle\"\n\n\n// Nodes can only have a single input. We use a switch statement to distingush \n// between messages received from different nodes. The name of the variable read\n// by the iodata-in node is stored in msg.payload.name.\n//\n// To compare the actual temperature with the set point, we need to store both\n// values. Since each message contains only one of these values and variables in\n// Node-RED are non-persistent, we store them using context. \nswitch(msg.payload.name){\n    case \"deadband\":\n        context.set(\"tempControllerDeadband\", msg.payload.value)\n        break;\n        \n    case \"I_ITEMPERATURE\":\n        context.set(\"tempActualValue\", msg.payload.value);\n        break;\n    \n    case \"I_IPOTIVALUE\":\n        context.set(\"tempSetPoint\", msg.payload.value.toFixed(1))\n        break;\n        \n    case \"I_XROTATIONCW\":\n        flow.set(\"tempControllerRun\", msg.payload.value == 1)\n        break;    \n}\n\n// The temperature controller implemented here is a simple deadband controller\n// for heating and cooling. If the actual temperature lies within the deadband \n// around the temperature set point, the controller is idle.\nif(flow.get(\"tempControllerRun\") && !flow.get(\"tempAlarm\")) {\n    // The controller is active and the alarm has not been triggered.\n    let deadband  = context.get(\"tempControllerDeadband\") || 2\n    let actualValue = context.get(\"tempActualValue\")\n    let setPoint    = context.get(\"tempSetPoint\")\n\n    if (setPoint - actualValue > deadband / 2) {\n        mode = \"heating\"\n        node.status({fill:\"red\", shape:\"dot\", \n                     text: \"heating (\" + actualValue + \"°C, \" + setPoint + \"°C)\"})\n        \n    } else if (actualValue - setPoint > deadband / 2) { \n        mode = \"cooling\"\n        node.status({fill:\"green\", shape:\"dot\", \n                     text: \"cooling (\" + actualValue + \"°C, \" + setPoint + \"°C)\"})\n        \n    } else {\n        mode = \"idle\"\n        node.status({fill:\"yellow\", shape:\"dot\", \n                     text: \"idle (\" + actualValue + \"°C, \" + setPoint + \"°C)\"})\n    }    \n} else {\n    // The controller is inactive or the alarm has been triggered.\n    mode = \"stop\"\n    node.status({fill:\"grey\", shape:\"dot\", text: \"stop\"})\n}\n\n// Multiple outputs are adressed by returning an array with a message for each \n// output.\nmsg = [ {\"payload\": {\"value\": mode == \"heating\"}},\n            {\"payload\": {\"value\": mode == \"idle\"}},\n            {\"payload\": {\"value\": mode == \"cooling\"}}]\n            \nreturn msg;",
        "outputs": 3,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 520,
        "y": 160,
        "wires": [
            [
                "3b78ba92.f90f16"
            ],
            [
                "f29acdd9.1d5ea"
            ],
            [
                "aed454ce.bdba38"
            ]
        ],
        "inputLabels": [
            "Actual Temperature [numeric, in °C], Target Temperature [numeric, in °C], State of Enable Button [1, 0]"
        ],
        "outputLabels": [
            "Heating Indicator State [true, false]",
            "Idle Indicator State [true, false]",
            "Cooling Indicator State [true, false]"
        ],
        "icon": "font-awesome/fa-thermometer-3",
        "info": "The temperature controller implemented here is a simple deadband controller for heating and cooling. If the actual temperature lies within the deadband around the temperature set point, the controller is idle.\n\n---\nv1.0.0\n\n© 2021 Weidmüller Interface GmbH & Co. KG, Author: w100141\n\nPublished under the [MIT License](https://spdx.org/licenses/MIT.html)."
    },
    {
        "id": "3b78ba92.f90f16",
        "type": "uc-iodataOut",
        "z": "a73423a2.863a1",
        "variableId": "f385deb8-c1bc-4fdf-8015-d4b0712efafb",
        "variableName": "O_XRED",
        "name": "heating",
        "x": 720,
        "y": 100,
        "wires": [
            []
        ]
    },
    {
        "id": "f29acdd9.1d5ea",
        "type": "uc-iodataOut",
        "z": "a73423a2.863a1",
        "variableId": "c96852a4-c872-4c40-9d9b-277961bcddcd",
        "variableName": "O_XYELLOW",
        "name": "idle",
        "x": 710,
        "y": 160,
        "wires": [
            []
        ]
    },
    {
        "id": "aed454ce.bdba38",
        "type": "uc-iodataOut",
        "z": "a73423a2.863a1",
        "variableId": "20068f6b-8ca5-43a5-92cb-20b2d0017f79",
        "variableName": "O_XGREEN",
        "name": "cooling",
        "x": 720,
        "y": 220,
        "wires": [
            []
        ]
    },
    {
        "id": "17204097.c6cecf",
        "type": "uc-iodataOut",
        "z": "a73423a2.863a1",
        "variableId": "e2a73cae-dcaf-4d54-b40f-03e240f722d2",
        "variableName": "O_XWHITE",
        "name": "status indicator",
        "x": 740,
        "y": 40,
        "wires": [
            []
        ]
    },
    {
        "id": "6a4719a7.fc5ae8",
        "type": "uc-iodataIn",
        "z": "a73423a2.863a1",
        "mode": "wi_single_variable",
        "variable": "I_XROTATIONCCW",
        "name": "Reset Error",
        "pollInterval": "500",
        "pollIntervalBase": "ms",
        "x": 110,
        "y": 40,
        "wires": [
            [
                "570744b8.5f30cc"
            ],
            []
        ]
    },
    {
        "id": "7ec08407.d106ac",
        "type": "uc-iodataIn",
        "z": "a73423a2.863a1",
        "mode": "wi_single_variable",
        "variable": "I_XROTATIONCW",
        "name": "Enable Controller",
        "pollInterval": "500",
        "pollIntervalBase": "ms",
        "x": 120,
        "y": 160,
        "wires": [
            [
                "256584f5.3dfd7c"
            ],
            []
        ]
    },
    {
        "id": "6f7bcaf8.893e44",
        "type": "uc-iodataOut",
        "z": "a73423a2.863a1",
        "variableId": "f6b73921-3348-465a-a78f-6311c9729708",
        "variableName": "RSETPOINT",
        "name": "to Visu",
        "x": 510,
        "y": 220,
        "wires": [
            []
        ]
    },
    {
        "id": "36cd4c4f.b547a4",
        "type": "uc-iodataOut",
        "z": "a73423a2.863a1",
        "variableId": "875e8821-2847-4754-85ec-25e74a4307ff",
        "variableName": "RTEMPERATURE",
        "name": "to Visu",
        "x": 510,
        "y": 100,
        "wires": [
            []
        ]
    },
    {
        "id": "c083b3bb.da1ff",
        "type": "comment",
        "z": "a73423a2.863a1",
        "name": "License",
        "info": "## MIT License\n\nCopyright (c) 2021 Weidmüller Interface GmbH & Co. KG\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.",
        "x": 90,
        "y": 280,
        "wires": []
    }
]