Mihai Popa
Published © Apache-2.0

Smart home with Mixtile Edge 2 Kit

Using the Mixtile Edge 2 Kit computer with Debian as a control and monitoring center for Smart home project

AdvancedWork in progress8 hours331
Smart home with Mixtile Edge 2 Kit

Things used in this project

Hardware components

Mixtile Edge 2 Kit
×1
Mixtile 2-in-1 Zigbee & Z-Wave mPCIe Interface Module
×1
Espressif ESP32-C6
×2
Texas Instruments CC2530 dev board
×3
Tuya temperature sensor
×1
Sonoff button
×1
Texas Instruments CC Debugger
×1
DFRobot Beetle ESP32 C6 Mini
×1
DFRobot FireBeetle 2 ESP32-C6 IoT
×1
DFRobot Fermion: 2.0" 320x240 IPS TFT LCD Display
×1
Espressif esp32 Devit1
×1
Adafruit LTR390 U
×1
Gravity: I2C BME280 Environmental Sensor
DFRobot Gravity: I2C BME280 Environmental Sensor
×1
Mini PCIe to USB adapter
×1
SX1302 CoreCell LoRa Gateway for 868MHz (EU)
×1
LoRa Gateway antenna
×1
RFM95W 868 MHz EU
×1

Software apps and online services

Debian OS 11 for Mixtile Edge 2 Kit
Node-RED
Node-RED
Mosquitto MQTT broker
Zigbee2Mqtt bridg
Arduino IDE
SQLITE
Python3
minicom app

Story

Read more

Schematics

Schematic of weather station

Code

BLE beacon sensors

JSON
File used in Node-red to read and display data from some BLE beacon sensors
[
    {
        "id": "e304fa4e58cbecd1",
        "type": "tab",
        "label": "Flow 2",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "efdbe5c6c654a12d",
        "type": "mqtt in",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "topic": "esp32/altitude",
        "qos": "2",
        "datatype": "auto-detect",
        "broker": "590e3ceaadf4028f",
        "nl": false,
        "rap": true,
        "rh": 0,
        "inputs": 0,
        "x": 110,
        "y": 140,
        "wires": [
            [
                "42a78e3759f2b063"
            ]
        ]
    },
    {
        "id": "f79063db513de7da",
        "type": "mqtt in",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "topic": "esp32/AnalogExternalTempInteger",
        "qos": "2",
        "datatype": "auto-detect",
        "broker": "590e3ceaadf4028f",
        "nl": false,
        "rap": true,
        "rh": 0,
        "inputs": 0,
        "x": 180,
        "y": 340,
        "wires": [
            [
                "ba37a2b88a0ed90c"
            ]
        ]
    },
    {
        "id": "6ce73293a358dbfc",
        "type": "ui_chart",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "group": "797ca08f74dee60b",
        "order": 2,
        "width": 5,
        "height": 4,
        "label": "MCP9700 temp",
        "chartType": "line",
        "legend": "false",
        "xformat": "HH:mm:ss",
        "interpolate": "linear",
        "nodata": "",
        "dot": false,
        "ymin": "",
        "ymax": "",
        "removeOlder": 1,
        "removeOlderPoints": "",
        "removeOlderUnit": "3600",
        "cutout": 0,
        "useOneColor": false,
        "useUTC": false,
        "colors": [
            "#1f77b4",
            "#aec7e8",
            "#ff7f0e",
            "#2ca02c",
            "#98df8a",
            "#d62728",
            "#ff9896",
            "#9467bd",
            "#c5b0d5"
        ],
        "outputs": 1,
        "useDifferentColor": false,
        "className": "",
        "x": 920,
        "y": 340,
        "wires": [
            []
        ]
    },
    {
        "id": "42a78e3759f2b063",
        "type": "ui_gauge",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "group": "35c4ebb396aeece3",
        "order": 3,
        "width": 6,
        "height": 4,
        "gtype": "gage",
        "title": "Altitude",
        "label": "meters",
        "format": "{{value}}",
        "min": 0,
        "max": "500",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "",
        "seg2": "",
        "diff": false,
        "className": "",
        "x": 480,
        "y": 140,
        "wires": []
    },
    {
        "id": "e779a2ce1a81c8be",
        "type": "mqtt in",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "topic": "esp32/analog_batteryVoltage",
        "qos": "2",
        "datatype": "auto-detect",
        "broker": "590e3ceaadf4028f",
        "nl": false,
        "rap": true,
        "rh": 0,
        "inputs": 0,
        "x": 160,
        "y": 600,
        "wires": [
            [
                "e03f1b02372b5906"
            ]
        ]
    },
    {
        "id": "14fbf550ef98e783",
        "type": "mqtt in",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "topic": "esp32/analog_MoistureData",
        "qos": "2",
        "datatype": "auto-detect",
        "broker": "590e3ceaadf4028f",
        "nl": false,
        "rap": true,
        "rh": 0,
        "inputs": 0,
        "x": 160,
        "y": 660,
        "wires": [
            [
                "e89e5e3272c488f4"
            ]
        ]
    },
    {
        "id": "132efbee9efb80d8",
        "type": "mqtt in",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "topic": "esp32/analog_LightData",
        "qos": "2",
        "datatype": "auto-detect",
        "broker": "590e3ceaadf4028f",
        "nl": false,
        "rap": true,
        "rh": 0,
        "inputs": 0,
        "x": 150,
        "y": 720,
        "wires": [
            [
                "e53a0c823775a55e"
            ]
        ]
    },
    {
        "id": "e03f1b02372b5906",
        "type": "ui_gauge",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "group": "797ca08f74dee60b",
        "order": 7,
        "width": 4,
        "height": 4,
        "gtype": "gage",
        "title": "Battery voltage",
        "label": "volts",
        "format": "{{value}}",
        "min": 0,
        "max": "3.5",
        "colors": [
            "#ec0e6e",
            "#b4ee5d",
            "#3acb6a"
        ],
        "seg1": "",
        "seg2": "",
        "diff": false,
        "className": "",
        "x": 500,
        "y": 600,
        "wires": []
    },
    {
        "id": "e89e5e3272c488f4",
        "type": "ui_gauge",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "group": "797ca08f74dee60b",
        "order": 6,
        "width": 4,
        "height": 4,
        "gtype": "gage",
        "title": "Moisture sensor",
        "label": "units",
        "format": "{{value}}",
        "min": 0,
        "max": "2000",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "",
        "seg2": "",
        "diff": false,
        "className": "",
        "x": 500,
        "y": 660,
        "wires": []
    },
    {
        "id": "e53a0c823775a55e",
        "type": "ui_gauge",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "group": "797ca08f74dee60b",
        "order": 5,
        "width": 4,
        "height": 4,
        "gtype": "gage",
        "title": "Light sensor",
        "label": "units",
        "format": "{{value}}",
        "min": "0",
        "max": "2000",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "",
        "seg2": "",
        "diff": false,
        "className": "",
        "x": 490,
        "y": 720,
        "wires": []
    },
    {
        "id": "ad1c8bf84ba5773b",
        "type": "mqtt in",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "topic": "esp32/temperature",
        "qos": "2",
        "datatype": "auto-detect",
        "broker": "590e3ceaadf4028f",
        "nl": false,
        "rap": true,
        "rh": 0,
        "inputs": 0,
        "x": 130,
        "y": 80,
        "wires": [
            [
                "9212e6e74813e158",
                "a062287a56ea6b17"
            ]
        ]
    },
    {
        "id": "9212e6e74813e158",
        "type": "ui_gauge",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "group": "35c4ebb396aeece3",
        "order": 1,
        "width": 6,
        "height": 4,
        "gtype": "gage",
        "title": "Temperature",
        "label": "C",
        "format": "{{value}}",
        "min": 0,
        "max": "60",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "",
        "seg2": "",
        "diff": false,
        "className": "",
        "x": 490,
        "y": 80,
        "wires": []
    },
    {
        "id": "a062287a56ea6b17",
        "type": "ui_chart",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "group": "35c4ebb396aeece3",
        "order": 2,
        "width": 6,
        "height": 4,
        "label": "Temperature",
        "chartType": "line",
        "legend": "false",
        "xformat": "HH:mm:ss",
        "interpolate": "linear",
        "nodata": "",
        "dot": false,
        "ymin": "",
        "ymax": "",
        "removeOlder": 1,
        "removeOlderPoints": "",
        "removeOlderUnit": "3600",
        "cutout": 0,
        "useOneColor": false,
        "useUTC": false,
        "colors": [
            "#1f77b4",
            "#aec7e8",
            "#ff7f0e",
            "#2ca02c",
            "#98df8a",
            "#d62728",
            "#ff9896",
            "#9467bd",
            "#c5b0d5"
        ],
        "outputs": 1,
        "useDifferentColor": false,
        "className": "",
        "x": 490,
        "y": 40,
        "wires": [
            []
        ]
    },
    {
        "id": "843e4a24401f9145",
        "type": "ui_gauge",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "group": "797ca08f74dee60b",
        "order": 1,
        "width": 7,
        "height": 4,
        "gtype": "gage",
        "title": "MCP9700 temp",
        "label": "C",
        "format": "{{value}}",
        "min": 0,
        "max": "50",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "",
        "seg2": "",
        "diff": false,
        "className": "",
        "x": 920,
        "y": 400,
        "wires": []
    },
    {
        "id": "af2bd4c95af86eee",
        "type": "mqtt in",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "topic": "esp32/atmospheric_pressure",
        "qos": "2",
        "datatype": "auto-detect",
        "broker": "590e3ceaadf4028f",
        "nl": false,
        "rap": true,
        "rh": 0,
        "inputs": 0,
        "x": 160,
        "y": 200,
        "wires": [
            [
                "4719b65865ab77db"
            ]
        ]
    },
    {
        "id": "4719b65865ab77db",
        "type": "function",
        "z": "e304fa4e58cbecd1",
        "name": "Convert Pa in mmHg",
        "func": "// Preia valoarea presiunii din mesaj\nvar pressurePa = msg.payload;\n\n// Transform valoarea din Pa n mmHg\nvar pressureMmHg = Math.round(pressurePa / 133.3223684);\n\n// Creeaz un nou mesaj cu valoarea transformat\nmsg.payload = pressureMmHg;\n\n// Returneaz mesajul\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 340,
        "y": 260,
        "wires": [
            [
                "76d89e8d970f05e3"
            ]
        ]
    },
    {
        "id": "76d89e8d970f05e3",
        "type": "ui_gauge",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "group": "35c4ebb396aeece3",
        "order": 4,
        "width": 6,
        "height": 4,
        "gtype": "gage",
        "title": "Atmospheric pressure",
        "label": "mmHg",
        "format": "{{value}}",
        "min": "700",
        "max": "800",
        "colors": [
            "#b30012",
            "#e6e600",
            "#04fb21"
        ],
        "seg1": "740",
        "seg2": "760",
        "diff": false,
        "className": "",
        "x": 520,
        "y": 200,
        "wires": []
    },
    {
        "id": "ba37a2b88a0ed90c",
        "type": "function",
        "z": "e304fa4e58cbecd1",
        "name": "Combine integer and decimal for external temp sensor",
        "func": "// Verific dac flow context are deja datele pentru partea ntreag i partea zecimal\nlet partIntreaga = flow.get('partIntreaga') || null;\nlet partZecimala = flow.get('partZecimala') || null;\n\nif (msg.topic === \"esp32/AnalogExternalTempInteger\") {\n    partIntreaga = msg.payload;\n    flow.set('partIntreaga', partIntreaga);\n} else if (msg.topic === \"esp32/AnalogExternalTempDecimal\") {\n    partZecimala = msg.payload;\n    flow.set('partZecimala', partZecimala);\n}\n\n// Dac ambele pri sunt disponibile, combin-le\nif (partIntreaga !== null && partZecimala !== null) {\n    let numarComplet = parseFloat(partIntreaga + '.' + partZecimala);\n\n    // Reseteaz prile n flow context\n    flow.set('partIntreaga', null);\n    flow.set('partZecimala', null);\n\n    // Trimite numrul complet pentru afiare\n    msg.payload = numarComplet;\n    return msg;\n} else {\n    // Dac nu avem nc ambele pri, nu trimitem nimic\n    return null;\n}\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 580,
        "y": 360,
        "wires": [
            [
                "6ce73293a358dbfc",
                "843e4a24401f9145"
            ]
        ]
    },
    {
        "id": "cb456cd7124c3af4",
        "type": "mqtt in",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "topic": "esp32/AnalogExternalTempDecimal",
        "qos": "2",
        "datatype": "auto-detect",
        "broker": "590e3ceaadf4028f",
        "nl": false,
        "rap": true,
        "rh": 0,
        "inputs": 0,
        "x": 180,
        "y": 400,
        "wires": [
            [
                "ba37a2b88a0ed90c"
            ]
        ]
    },
    {
        "id": "fa96a33041c9425f",
        "type": "mqtt in",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "topic": "esp32/AnalogInternalTempInteger",
        "qos": "2",
        "datatype": "auto-detect",
        "broker": "590e3ceaadf4028f",
        "nl": false,
        "rap": true,
        "rh": 0,
        "inputs": 0,
        "x": 170,
        "y": 460,
        "wires": [
            [
                "7ca2d5244f392948"
            ]
        ]
    },
    {
        "id": "fa243e4ba081f644",
        "type": "mqtt in",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "topic": "esp32/AnalogInternalTempDecimal",
        "qos": "2",
        "datatype": "auto-detect",
        "broker": "590e3ceaadf4028f",
        "nl": false,
        "rap": true,
        "rh": 0,
        "inputs": 0,
        "x": 180,
        "y": 520,
        "wires": [
            [
                "7ca2d5244f392948"
            ]
        ]
    },
    {
        "id": "7ca2d5244f392948",
        "type": "function",
        "z": "e304fa4e58cbecd1",
        "name": "Combine integer and decimal for interna temp sensor",
        "func": "// Verific dac flow context are deja datele pentru partea ntreag i partea zecimal\nlet partIntreaga = flow.get('partIntreaga') || null;\nlet partZecimala = flow.get('partZecimala') || null;\n\nif (msg.topic === \"esp32/AnalogInternalTempInteger\") {\n    partIntreaga = msg.payload;\n    flow.set('partIntreaga', partIntreaga);\n} else if (msg.topic === \"esp32/AnalogInternalTempDecimal\") {\n    partZecimala = msg.payload;\n    flow.set('partZecimala', partZecimala);\n}\n\n// Dac ambele pri sunt disponibile, combin-le\nif (partIntreaga !== null && partZecimala !== null) {\n    let numarComplet = parseFloat(partIntreaga + '.' + partZecimala);\n\n    // Reseteaz prile n flow context\n    flow.set('partIntreaga', null);\n    flow.set('partZecimala', null);\n\n    // Trimite numrul complet pentru afiare\n    msg.payload = numarComplet;\n    return msg;\n} else {\n    // Dac nu avem nc ambele pri, nu trimitem nimic\n    return null;\n}",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 580,
        "y": 480,
        "wires": [
            [
                "4ff3829940597873",
                "e781571db5355b78"
            ]
        ]
    },
    {
        "id": "e781571db5355b78",
        "type": "ui_gauge",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "group": "797ca08f74dee60b",
        "order": 3,
        "width": 7,
        "height": 4,
        "gtype": "gage",
        "title": "Internal beacon temp",
        "label": "C",
        "format": "{{value}}",
        "min": 0,
        "max": "50",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "",
        "seg2": "",
        "diff": false,
        "className": "",
        "x": 940,
        "y": 520,
        "wires": []
    },
    {
        "id": "4ff3829940597873",
        "type": "ui_chart",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "group": "797ca08f74dee60b",
        "order": 4,
        "width": 5,
        "height": 4,
        "label": "Internal beacon temp",
        "chartType": "line",
        "legend": "false",
        "xformat": "HH:mm:ss",
        "interpolate": "linear",
        "nodata": "",
        "dot": false,
        "ymin": "",
        "ymax": "",
        "removeOlder": 1,
        "removeOlderPoints": "",
        "removeOlderUnit": "3600",
        "cutout": 0,
        "useOneColor": false,
        "useUTC": false,
        "colors": [
            "#1f77b4",
            "#aec7e8",
            "#ff7f0e",
            "#2ca02c",
            "#98df8a",
            "#d62728",
            "#ff9896",
            "#9467bd",
            "#c5b0d5"
        ],
        "outputs": 1,
        "useDifferentColor": false,
        "className": "",
        "x": 940,
        "y": 460,
        "wires": [
            []
        ]
    },
    {
        "id": "5843d70aaa5ff3b0",
        "type": "mqtt in",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "topic": "esp32/sht40_temperature",
        "qos": "2",
        "datatype": "auto-detect",
        "broker": "590e3ceaadf4028f",
        "nl": false,
        "rap": true,
        "rh": 0,
        "inputs": 0,
        "x": 150,
        "y": 820,
        "wires": [
            [
                "8dbf113aac34ead1",
                "27b45588fe35f85a",
                "0cb4895a9905657a"
            ]
        ]
    },
    {
        "id": "14bbc828829b30e9",
        "type": "mqtt in",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "topic": "esp32/sht40_humidity",
        "qos": "2",
        "datatype": "auto-detect",
        "broker": "590e3ceaadf4028f",
        "nl": false,
        "rap": true,
        "rh": 0,
        "inputs": 0,
        "x": 140,
        "y": 960,
        "wires": [
            [
                "d11c433c9e47e93a",
                "0cb4895a9905657a"
            ]
        ]
    },
    {
        "id": "0cb4895a9905657a",
        "type": "function",
        "z": "e304fa4e58cbecd1",
        "name": "Calculate ITU and DEW",
        "func": "// Stocarea valorilor primite n contextul de flux\nif (msg.topic === \"esp32/sht40_temperature\") {\n    flow.set(\"temperature\", parseFloat(msg.payload));\n} else if (msg.topic === \"esp32/sht40_humidity\") {\n    flow.set(\"humidity\", parseFloat(msg.payload));\n}\n\n// Obinerea valorilor din context\nvar temperature = flow.get(\"temperature\");\nvar humidity = flow.get(\"humidity\");\n\n// Calcularea punctului de rou i a factorului de confort\nif (temperature !== undefined && humidity !== undefined) {\n    // Calcularea punctului de rou\n    var dewPoint = temperature - ((100 - humidity) / 5);\n\n    // Calcularea factorului de confort (indicele de cldur)\n    var heatIndex = temperature - ((0.55 - 0.0055 * humidity) * (temperature - 14.5));\n\n    // Trimiterea rezultatelor n dou mesaje separate\n    var msg1 = { topic: \"dewPoint\", payload: dewPoint.toFixed(2) };\n    var msg2 = { topic: \"heatIndex\", payload: heatIndex.toFixed(2) };\n\n    return [msg1, msg2];\n} else {\n    return null; // Dac nu avem ambele valori, nu trimitem nimic\n}\n",
        "outputs": 2,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 430,
        "y": 900,
        "wires": [
            [
                "20273cb17d411f08"
            ],
            [
                "7afb545880a6dae1"
            ]
        ]
    },
    {
        "id": "27b45588fe35f85a",
        "type": "ui_gauge",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "group": "8d899e0618059e63",
        "order": 1,
        "width": 7,
        "height": 4,
        "gtype": "gage",
        "title": "Temperature",
        "label": "C",
        "format": "{{value}}",
        "min": 0,
        "max": "50",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "",
        "seg2": "",
        "diff": false,
        "className": "",
        "x": 490,
        "y": 840,
        "wires": []
    },
    {
        "id": "8dbf113aac34ead1",
        "type": "ui_chart",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "group": "8d899e0618059e63",
        "order": 2,
        "width": 5,
        "height": 4,
        "label": "Temperature",
        "chartType": "line",
        "legend": "false",
        "xformat": "HH:mm:ss",
        "interpolate": "linear",
        "nodata": "",
        "dot": false,
        "ymin": "",
        "ymax": "",
        "removeOlder": 1,
        "removeOlderPoints": "",
        "removeOlderUnit": "3600",
        "cutout": 0,
        "useOneColor": false,
        "useUTC": false,
        "colors": [
            "#1f77b4",
            "#aec7e8",
            "#ff7f0e",
            "#2ca02c",
            "#98df8a",
            "#d62728",
            "#ff9896",
            "#9467bd",
            "#c5b0d5"
        ],
        "outputs": 1,
        "useDifferentColor": false,
        "className": "",
        "x": 490,
        "y": 780,
        "wires": [
            []
        ]
    },
    {
        "id": "d11c433c9e47e93a",
        "type": "ui_gauge",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "group": "8d899e0618059e63",
        "order": 3,
        "width": 12,
        "height": 4,
        "gtype": "gage",
        "title": "Humidity",
        "label": "%RH",
        "format": "{{value}}",
        "min": 0,
        "max": "100",
        "colors": [
            "#dbf070",
            "#0dfd69",
            "#fd0808"
        ],
        "seg1": "50",
        "seg2": "80",
        "diff": false,
        "className": "",
        "x": 480,
        "y": 960,
        "wires": []
    },
    {
        "id": "20273cb17d411f08",
        "type": "ui_gauge",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "group": "8d899e0618059e63",
        "order": 4,
        "width": 6,
        "height": 4,
        "gtype": "gage",
        "title": "DEW point",
        "label": "units",
        "format": "{{value}}",
        "min": "-10",
        "max": "40",
        "colors": [
            "#d0f80d",
            "#0af539",
            "#fb8e8e"
        ],
        "seg1": "",
        "seg2": "",
        "diff": false,
        "className": "",
        "x": 690,
        "y": 860,
        "wires": []
    },
    {
        "id": "7afb545880a6dae1",
        "type": "ui_gauge",
        "z": "e304fa4e58cbecd1",
        "name": "",
        "group": "8d899e0618059e63",
        "order": 5,
        "width": 6,
        "height": 4,
        "gtype": "gage",
        "title": "Confort factor ITU",
        "label": "",
        "format": "{{value}} C",
        "min": 0,
        "max": "100",
        "colors": [
            "#b4ea1f",
            "#00e663",
            "#f92424"
        ],
        "seg1": "40",
        "seg2": "80",
        "diff": false,
        "className": "",
        "x": 710,
        "y": 920,
        "wires": []
    },
    {
        "id": "590e3ceaadf4028f",
        "type": "mqtt-broker",
        "name": "",
        "broker": "localhost",
        "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": "797ca08f74dee60b",
        "type": "ui_group",
        "name": "Analog sensors: MCP9700, Light and Moisture",
        "tab": "996774836996b55a",
        "order": 1,
        "disp": true,
        "width": "12",
        "collapse": false,
        "className": ""
    },
    {
        "id": "35c4ebb396aeece3",
        "type": "ui_group",
        "name": "Digital sensor: ICP10111",
        "tab": "996774836996b55a",
        "order": 1,
        "disp": true,
        "width": "12",
        "collapse": false,
        "className": ""
    },
    {
        "id": "8d899e0618059e63",
        "type": "ui_group",
        "name": "Digital sensor: SHT40",
        "tab": "996774836996b55a",
        "order": 3,
        "disp": true,
        "width": 12,
        "collapse": false,
        "className": ""
    },
    {
        "id": "996774836996b55a",
        "type": "ui_tab",
        "name": "DFRobot",
        "icon": "dashboard",
        "disabled": false,
        "hidden": false
    }
]

Arduino Zigbee bulb example for ESP32

C/C++
This is the example from ESP32 library used with Arduino IDE, created to act as a Zigbee lamp End Device
// Copyright 2023 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @brief This example demonstrates simple Zigbee light bulb.
 *
 * The example demonstrates how to use ESP Zigbee stack to create a end device light bulb.
 * The light bulb is a Zigbee end device, which is controlled by a Zigbee coordinator.
 *
 * Proper Zigbee mode must be selected in Tools->Zigbee mode
 * and also the correct partition scheme must be selected in Tools->Partition Scheme.
 *
 * Please check the README.md for instructions and more detailed description.
 */

#ifndef ZIGBEE_MODE_ED
#error "Zigbee end device mode is not selected in Tools->Zigbee mode"
#endif

#include "esp_zigbee_core.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "ha/esp_zigbee_ha_standard.h"

#define LED_PIN RGB_BUILTIN

/* Default End Device config */
#define ESP_ZB_ZED_CONFIG()                                                                 \
  {                                                                                         \
    .esp_zb_role = ESP_ZB_DEVICE_TYPE_ED, .install_code_policy = INSTALLCODE_POLICY_ENABLE, \
    .nwk_cfg = {                                                                            \
      .zed_cfg =                                                                            \
        {                                                                                   \
          .ed_timeout = ED_AGING_TIMEOUT,                                                   \
          .keep_alive = ED_KEEP_ALIVE,                                                      \
        },                                                                                  \
    },                                                                                      \
  }

#define ESP_ZB_DEFAULT_RADIO_CONFIG() \
  { .radio_mode = ZB_RADIO_MODE_NATIVE, }

#define ESP_ZB_DEFAULT_HOST_CONFIG() \
  { .host_connection_mode = ZB_HOST_CONNECTION_MODE_NONE, }

/* Zigbee configuration */
#define INSTALLCODE_POLICY_ENABLE   false /* enable the install code policy for security */
#define ED_AGING_TIMEOUT            ESP_ZB_ED_AGING_TIMEOUT_64MIN
#define ED_KEEP_ALIVE               3000                                 /* 3000 millisecond */
#define HA_ESP_LIGHT_ENDPOINT       10                                   /* esp light bulb device endpoint, used to process light controlling commands */
#define ESP_ZB_PRIMARY_CHANNEL_MASK ESP_ZB_TRANSCEIVER_ALL_CHANNELS_MASK /* Zigbee primary channel mask use in the example */

/********************* Zigbee functions **************************/
static void bdb_start_top_level_commissioning_cb(uint8_t mode_mask) {
  ESP_ERROR_CHECK(esp_zb_bdb_start_top_level_commissioning(mode_mask));
}

void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) {
  uint32_t *p_sg_p = signal_struct->p_app_signal;
  esp_err_t err_status = signal_struct->esp_err_status;
  esp_zb_app_signal_type_t sig_type = (esp_zb_app_signal_type_t)*p_sg_p;
  switch (sig_type) {
    case ESP_ZB_ZDO_SIGNAL_SKIP_STARTUP:
      log_i("Zigbee stack initialized");
      esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_INITIALIZATION);
      break;
    case ESP_ZB_BDB_SIGNAL_DEVICE_FIRST_START:
    case ESP_ZB_BDB_SIGNAL_DEVICE_REBOOT:
      if (err_status == ESP_OK) {
        log_i("Device started up in %s factory-reset mode", esp_zb_bdb_is_factory_new() ? "" : "non");
        if (esp_zb_bdb_is_factory_new()) {
          log_i("Start network formation");
          esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING);
        } else {
          log_i("Device rebooted");
        }
      } else {
        /* commissioning failed */
        log_w("Failed to initialize Zigbee stack (status: %s)", esp_err_to_name(err_status));
      }
      break;
    case ESP_ZB_BDB_SIGNAL_STEERING:
      if (err_status == ESP_OK) {
        esp_zb_ieee_addr_t extended_pan_id;
        esp_zb_get_extended_pan_id(extended_pan_id);
        log_i(
          "Joined network successfully (Extended PAN ID: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x, PAN ID: 0x%04hx, Channel:%d, Short Address: 0x%04hx)",
          extended_pan_id[7], extended_pan_id[6], extended_pan_id[5], extended_pan_id[4], extended_pan_id[3], extended_pan_id[2], extended_pan_id[1],
          extended_pan_id[0], esp_zb_get_pan_id(), esp_zb_get_current_channel(), esp_zb_get_short_address()
        );
      } else {
        log_i("Network steering was not successful (status: %s)", esp_err_to_name(err_status));
        esp_zb_scheduler_alarm((esp_zb_callback_t)bdb_start_top_level_commissioning_cb, ESP_ZB_BDB_MODE_NETWORK_STEERING, 1000);
      }
      break;
    default: log_i("ZDO signal: %s (0x%x), status: %s", esp_zb_zdo_signal_to_string(sig_type), sig_type, esp_err_to_name(err_status)); break;
  }
}

static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, const void *message) {
  esp_err_t ret = ESP_OK;
  switch (callback_id) {
    case ESP_ZB_CORE_SET_ATTR_VALUE_CB_ID: ret = zb_attribute_handler((esp_zb_zcl_set_attr_value_message_t *)message); break;
    default:                               log_w("Receive Zigbee action(0x%x) callback", callback_id); break;
  }
  return ret;
}

static void esp_zb_task(void *pvParameters) {
  esp_zb_cfg_t zb_nwk_cfg = ESP_ZB_ZED_CONFIG();
  esp_zb_init(&zb_nwk_cfg);
  esp_zb_on_off_light_cfg_t light_cfg = ESP_ZB_DEFAULT_ON_OFF_LIGHT_CONFIG();
  esp_zb_ep_list_t *esp_zb_on_off_light_ep = esp_zb_on_off_light_ep_create(HA_ESP_LIGHT_ENDPOINT, &light_cfg);
  esp_zb_device_register(esp_zb_on_off_light_ep);
  esp_zb_core_action_handler_register(zb_action_handler);
  esp_zb_set_primary_network_channel_set(ESP_ZB_PRIMARY_CHANNEL_MASK);

  //Erase NVRAM before creating connection to new Coordinator
  esp_zb_nvram_erase_at_start(true);  //Comment out this line to erase NVRAM data if you are connecting to new Coordinator

  ESP_ERROR_CHECK(esp_zb_start(false));
  esp_zb_main_loop_iteration();
}

/* Handle the light attribute */

static esp_err_t zb_attribute_handler(const esp_zb_zcl_set_attr_value_message_t *message) {
  esp_err_t ret = ESP_OK;
  bool light_state = 0;

  if (!message) {
    log_e("Empty message");
  }
  if (message->info.status != ESP_ZB_ZCL_STATUS_SUCCESS) {
    log_e("Received message: error status(%d)", message->info.status);
  }

  log_i(
    "Received message: endpoint(%d), cluster(0x%x), attribute(0x%x), data size(%d)", message->info.dst_endpoint, message->info.cluster, message->attribute.id,
    message->attribute.data.size
  );
  if (message->info.dst_endpoint == HA_ESP_LIGHT_ENDPOINT) {
    if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_ON_OFF) {
      if (message->attribute.id == ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_BOOL) {
        light_state = message->attribute.data.value ? *(bool *)message->attribute.data.value : light_state;
        log_i("Light sets to %s", light_state ? "On" : "Off");
        rgbLedWrite(LED_PIN, 255 * light_state, 255 * light_state, 255 * light_state);  // Toggle light
      }
    }
  }
  return ret;
}

/********************* Arduino functions **************************/
void setup() {
  // Init Zigbee
  esp_zb_platform_config_t config = {
    .radio_config = ESP_ZB_DEFAULT_RADIO_CONFIG(),
    .host_config = ESP_ZB_DEFAULT_HOST_CONFIG(),
  };
  ESP_ERROR_CHECK(esp_zb_platform_config(&config));

  // Init RMT and leave light OFF
  rgbLedWrite(LED_PIN, 0, 0, 0);

  // Start Zigbee task
  xTaskCreate(esp_zb_task, "Zigbee_main", 4096, NULL, 5, NULL);
}

void loop() {
  //empty, zigbee running in task
}

Arduino Zigbee temperature sensor example for ESP32

C/C++
This is the example from ESP32 library used with Arduino IDE, created to act as a Zigbee temperature sensor End Device
// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @brief This example demonstrates simple Zigbee temperature sensor.
 *
 * The example demonstrates how to use ESP Zigbee stack to create a end device temperature sensor.
 * The temperature sensor is a Zigbee end device, which is controlled by a Zigbee coordinator.
 *
 * Proper Zigbee mode must be selected in Tools->Zigbee mode
 * and also the correct partition scheme must be selected in Tools->Partition Scheme.
 *
 * Please check the README.md for instructions and more detailed description.
 */

#ifndef ZIGBEE_MODE_ED
#error "Zigbee end device mode is not selected in Tools->Zigbee mode"
#endif

#include "esp_zigbee_core.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "ha/esp_zigbee_ha_standard.h"

/* Switch configuration */
#define GPIO_INPUT_IO_TOGGLE_SWITCH GPIO_NUM_9
#define PAIR_SIZE(TYPE_STR_PAIR)    (sizeof(TYPE_STR_PAIR) / sizeof(TYPE_STR_PAIR[0]))

typedef enum {
  SWITCH_ON_CONTROL,
  SWITCH_OFF_CONTROL,
  SWITCH_ONOFF_TOGGLE_CONTROL,
  SWITCH_LEVEL_UP_CONTROL,
  SWITCH_LEVEL_DOWN_CONTROL,
  SWITCH_LEVEL_CYCLE_CONTROL,
  SWITCH_COLOR_CONTROL,
} switch_func_t;

typedef struct {
  uint8_t pin;
  switch_func_t func;
} switch_func_pair_t;

typedef enum {
  SWITCH_IDLE,
  SWITCH_PRESS_ARMED,
  SWITCH_PRESS_DETECTED,
  SWITCH_PRESSED,
  SWITCH_RELEASE_DETECTED,
} switch_state_t;

static switch_func_pair_t button_func_pair[] = {{GPIO_INPUT_IO_TOGGLE_SWITCH, SWITCH_ONOFF_TOGGLE_CONTROL}};

/* Default End Device config */
#define ESP_ZB_ZED_CONFIG()                                                                 \
  {                                                                                         \
    .esp_zb_role = ESP_ZB_DEVICE_TYPE_ED, .install_code_policy = INSTALLCODE_POLICY_ENABLE, \
    .nwk_cfg = {                                                                            \
      .zed_cfg =                                                                            \
        {                                                                                   \
          .ed_timeout = ED_AGING_TIMEOUT,                                                   \
          .keep_alive = ED_KEEP_ALIVE,                                                      \
        },                                                                                  \
    },                                                                                      \
  }

#define ESP_ZB_DEFAULT_RADIO_CONFIG() \
  { .radio_mode = ZB_RADIO_MODE_NATIVE, }

#define ESP_ZB_DEFAULT_HOST_CONFIG() \
  { .host_connection_mode = ZB_HOST_CONNECTION_MODE_NONE, }

/* Zigbee configuration */
#define INSTALLCODE_POLICY_ENABLE   false /* enable the install code policy for security */
#define ED_AGING_TIMEOUT            ESP_ZB_ED_AGING_TIMEOUT_64MIN
#define ED_KEEP_ALIVE               3000                                 /* 3000 millisecond */
#define HA_ESP_SENSOR_ENDPOINT      10                                   /* esp temperature sensor device endpoint, used for temperature measurement */
#define ESP_ZB_PRIMARY_CHANNEL_MASK ESP_ZB_TRANSCEIVER_ALL_CHANNELS_MASK /* Zigbee primary channel mask use in the example */

/* Temperature sensor configuration */
#define ESP_TEMP_SENSOR_UPDATE_INTERVAL (1)  /* Local sensor update interval (second) */
#define ESP_TEMP_SENSOR_MIN_VALUE       (10) /* Local sensor min measured value (degree Celsius) */
#define ESP_TEMP_SENSOR_MAX_VALUE       (50) /* Local sensor max measured value (degree Celsius) */

/* Attribute values in ZCL string format
 * The string should be started with the length of its own.
 */
#define MANUFACTURER_NAME \
  "\x0B"                  \
  "ESPRESSIF"
#define MODEL_IDENTIFIER "\x09" CONFIG_IDF_TARGET

/********************* Zigbee functions **************************/
static int16_t zb_temperature_to_s16(float temp) {
  return (int16_t)(temp * 100);
}

static void esp_zb_buttons_handler(switch_func_pair_t *button_func_pair) {
  if (button_func_pair->func == SWITCH_ONOFF_TOGGLE_CONTROL) {
    /* Send report attributes command */
    esp_zb_zcl_report_attr_cmd_t report_attr_cmd;
    report_attr_cmd.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT;
    report_attr_cmd.attributeID = ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_VALUE_ID;
    report_attr_cmd.cluster_role = ESP_ZB_ZCL_CLUSTER_SERVER_ROLE;
    report_attr_cmd.clusterID = ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT;
    report_attr_cmd.zcl_basic_cmd.src_endpoint = HA_ESP_SENSOR_ENDPOINT;

    esp_zb_lock_acquire(portMAX_DELAY);
    esp_zb_zcl_report_attr_cmd_req(&report_attr_cmd);
    esp_zb_lock_release();
    log_i("Send 'report attributes' command");
  }
}

static void esp_app_temp_sensor_handler(float temperature) {
  int16_t measured_value = zb_temperature_to_s16(temperature);
  Serial.println("Updating temperature sensor value...");
  Serial.println(measured_value);
  /* Update temperature sensor measured value */
  esp_zb_lock_acquire(portMAX_DELAY);
  esp_zb_zcl_set_attribute_val(
    HA_ESP_SENSOR_ENDPOINT, ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE, ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_VALUE_ID, &measured_value,
    false
  );
  esp_zb_lock_release();
}

static void bdb_start_top_level_commissioning_cb(uint8_t mode_mask) {
  ESP_ERROR_CHECK(esp_zb_bdb_start_top_level_commissioning(mode_mask));
}

void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) {
  uint32_t *p_sg_p = signal_struct->p_app_signal;
  esp_err_t err_status = signal_struct->esp_err_status;
  esp_zb_app_signal_type_t sig_type = (esp_zb_app_signal_type_t)*p_sg_p;
  switch (sig_type) {
    case ESP_ZB_ZDO_SIGNAL_SKIP_STARTUP:
      log_i("Zigbee stack initialized");
      esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_INITIALIZATION);
      break;
    case ESP_ZB_BDB_SIGNAL_DEVICE_FIRST_START:
    case ESP_ZB_BDB_SIGNAL_DEVICE_REBOOT:
      if (err_status == ESP_OK) {
        log_i("Start network steering");
        log_i("Device started up in %s factory-reset mode", esp_zb_bdb_is_factory_new() ? "" : "non");
        // Start Temperature sensor reading task
        xTaskCreate(temp_sensor_value_update, "temp_sensor_update", 2048, NULL, 10, NULL);
        if (esp_zb_bdb_is_factory_new()) {
          log_i("Start network steering");
          esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING);
        } else {
          log_i("Device rebooted");
        }
      } else {
        /* commissioning failed */
        log_w("Failed to initialize Zigbee stack (status: %s)", esp_err_to_name(err_status));
      }
      break;
    case ESP_ZB_BDB_SIGNAL_STEERING:
      if (err_status == ESP_OK) {
        esp_zb_ieee_addr_t extended_pan_id;
        esp_zb_get_extended_pan_id(extended_pan_id);
        log_i(
          "Joined network successfully (Extended PAN ID: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x, PAN ID: 0x%04hx, Channel:%d, Short Address: 0x%04hx)",
          extended_pan_id[7], extended_pan_id[6], extended_pan_id[5], extended_pan_id[4], extended_pan_id[3], extended_pan_id[2], extended_pan_id[1],
          extended_pan_id[0], esp_zb_get_pan_id(), esp_zb_get_current_channel(), esp_zb_get_short_address()
        );
      } else {
        log_i("Network steering was not successful (status: %s)", esp_err_to_name(err_status));
        esp_zb_scheduler_alarm((esp_zb_callback_t)bdb_start_top_level_commissioning_cb, ESP_ZB_BDB_MODE_NETWORK_STEERING, 1000);
      }
      break;
    default: log_i("ZDO signal: %s (0x%x), status: %s", esp_zb_zdo_signal_to_string(sig_type), sig_type, esp_err_to_name(err_status)); break;
  }
}

static esp_zb_cluster_list_t *custom_temperature_sensor_clusters_create(esp_zb_temperature_sensor_cfg_t *temperature_sensor) {
  esp_zb_cluster_list_t *cluster_list = esp_zb_zcl_cluster_list_create();
  esp_zb_attribute_list_t *basic_cluster = esp_zb_basic_cluster_create(&(temperature_sensor->basic_cfg));
  ESP_ERROR_CHECK(esp_zb_basic_cluster_add_attr(basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_MANUFACTURER_NAME_ID, (void *)MANUFACTURER_NAME));
  ESP_ERROR_CHECK(esp_zb_basic_cluster_add_attr(basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_MODEL_IDENTIFIER_ID, (void *)MODEL_IDENTIFIER));
  ESP_ERROR_CHECK(esp_zb_cluster_list_add_basic_cluster(cluster_list, basic_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE));
  ESP_ERROR_CHECK(
    esp_zb_cluster_list_add_identify_cluster(cluster_list, esp_zb_identify_cluster_create(&(temperature_sensor->identify_cfg)), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE)
  );
  ESP_ERROR_CHECK(
    esp_zb_cluster_list_add_identify_cluster(cluster_list, esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_IDENTIFY), ESP_ZB_ZCL_CLUSTER_CLIENT_ROLE)
  );
  ESP_ERROR_CHECK(esp_zb_cluster_list_add_temperature_meas_cluster(
    cluster_list, esp_zb_temperature_meas_cluster_create(&(temperature_sensor->temp_meas_cfg)), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE
  ));
  return cluster_list;
}

static esp_zb_ep_list_t *custom_temperature_sensor_ep_create(uint8_t endpoint_id, esp_zb_temperature_sensor_cfg_t *temperature_sensor) {
  esp_zb_ep_list_t *ep_list = esp_zb_ep_list_create();
  esp_zb_endpoint_config_t endpoint_config = {
    .endpoint = endpoint_id, .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, .app_device_id = ESP_ZB_HA_TEMPERATURE_SENSOR_DEVICE_ID, .app_device_version = 0
  };
  esp_zb_ep_list_add_ep(ep_list, custom_temperature_sensor_clusters_create(temperature_sensor), endpoint_config);
  return ep_list;
}

static void esp_zb_task(void *pvParameters) {
  esp_zb_cfg_t zb_nwk_cfg = ESP_ZB_ZED_CONFIG();
  esp_zb_init(&zb_nwk_cfg);
  /* Create customized temperature sensor endpoint */
  esp_zb_temperature_sensor_cfg_t sensor_cfg = ESP_ZB_DEFAULT_TEMPERATURE_SENSOR_CONFIG();
  /* Set (Min|Max)MeasuredValure */
  sensor_cfg.temp_meas_cfg.min_value = zb_temperature_to_s16(ESP_TEMP_SENSOR_MIN_VALUE);
  sensor_cfg.temp_meas_cfg.max_value = zb_temperature_to_s16(ESP_TEMP_SENSOR_MAX_VALUE);
  esp_zb_ep_list_t *esp_zb_sensor_ep = custom_temperature_sensor_ep_create(HA_ESP_SENSOR_ENDPOINT, &sensor_cfg);
  /* Register the device */
  esp_zb_device_register(esp_zb_sensor_ep);

  /* Config the reporting info  */
  esp_zb_zcl_reporting_info_t reporting_info = {
    .direction = ESP_ZB_ZCL_CMD_DIRECTION_TO_SRV,
    .ep = HA_ESP_SENSOR_ENDPOINT,
    .cluster_id = ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT,
    .cluster_role = ESP_ZB_ZCL_CLUSTER_SERVER_ROLE,
    .attr_id = ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_VALUE_ID,
    .u =
      {
        .send_info =
          {
            .min_interval = 1,
            .max_interval = 0,
            .delta =
              {
                .u16 = 100,
              },
            .def_min_interval = 1,
            .def_max_interval = 0,
          },
      },
    .dst =
      {
        .profile_id = ESP_ZB_AF_HA_PROFILE_ID,
      },
    .manuf_code = ESP_ZB_ZCL_ATTR_NON_MANUFACTURER_SPECIFIC,
  };
  esp_zb_zcl_update_reporting_info(&reporting_info);
  esp_zb_set_primary_network_channel_set(ESP_ZB_PRIMARY_CHANNEL_MASK);

  //Erase NVRAM before creating connection to new Coordinator
  //esp_zb_nvram_erase_at_start(true); //Comment out this line to erase NVRAM data if you are connecting to new Coordinator

  ESP_ERROR_CHECK(esp_zb_start(false));
  esp_zb_main_loop_iteration();
}

/********************* GPIO functions **************************/
static QueueHandle_t gpio_evt_queue = NULL;

static void IRAM_ATTR gpio_isr_handler(void *arg) {
  xQueueSendFromISR(gpio_evt_queue, (switch_func_pair_t *)arg, NULL);
}

static void switch_gpios_intr_enabled(bool enabled) {
  for (int i = 0; i < PAIR_SIZE(button_func_pair); ++i) {
    if (enabled) {
      enableInterrupt((button_func_pair[i]).pin);
    } else {
      disableInterrupt((button_func_pair[i]).pin);
    }
  }
}

/************************ Temp sensor *****************************/
static void temp_sensor_value_update(void *arg) {
  for (;;) {
    float tsens_value = temperatureRead();
    esp_app_temp_sensor_handler(tsens_value);
    delay(1000);
  }
}

/********************* Arduino functions **************************/
void setup() {
  Serial.begin(115200);
  // Init Zigbee
  esp_zb_platform_config_t config = {
    .radio_config = ESP_ZB_DEFAULT_RADIO_CONFIG(),
    .host_config = ESP_ZB_DEFAULT_HOST_CONFIG(),
  };
  ESP_ERROR_CHECK(esp_zb_platform_config(&config));

  // Init button switch
  for (int i = 0; i < PAIR_SIZE(button_func_pair); i++) {
    pinMode(button_func_pair[i].pin, INPUT_PULLUP);
    /* create a queue to handle gpio event from isr */
    gpio_evt_queue = xQueueCreate(10, sizeof(switch_func_pair_t));
    if (gpio_evt_queue == 0) {
      log_e("Queue was not created and must not be used");
      while (1);
    }
    attachInterruptArg(button_func_pair[i].pin, gpio_isr_handler, (void *)(button_func_pair + i), FALLING);
  }

  // Start Zigbee task
  xTaskCreate(esp_zb_task, "Zigbee_main", 4096, NULL, 5, NULL);
}

void loop() {
  // Handle button switch in loop()
  uint8_t pin = 0;
  switch_func_pair_t button_func_pair;
  static switch_state_t switch_state = SWITCH_IDLE;
  bool evt_flag = false;
  float temperature;

  /* check if there is any queue received, if yes read out the button_func_pair */
  if (xQueueReceive(gpio_evt_queue, &button_func_pair, portMAX_DELAY)) {
    pin = button_func_pair.pin;
    switch_gpios_intr_enabled(false);
    evt_flag = true;
  }
  while (evt_flag) {
    bool value = digitalRead(pin);
    switch (switch_state) {
      case SWITCH_IDLE:           switch_state = (value == LOW) ? SWITCH_PRESS_DETECTED : SWITCH_IDLE; break;
      case SWITCH_PRESS_DETECTED: switch_state = (value == LOW) ? SWITCH_PRESS_DETECTED : SWITCH_RELEASE_DETECTED; break;
      case SWITCH_RELEASE_DETECTED:
        switch_state = SWITCH_IDLE;
        /* callback to button_handler */
        (*esp_zb_buttons_handler)(&button_func_pair);
        break;
      default: break;
    }
    if (switch_state == SWITCH_IDLE) {
      switch_gpios_intr_enabled(true);
      evt_flag = false;
      break;
    }
    delay(10);
  }
}

CC2530 Switch

Plain text
Files used to configure CC2530 zigbee board to act as a switch
No preview (download only).

CC2530 Lamp

Plain text
Files used to configure CC2530 zigbee board to act as a lamp
No preview (download only).

CC2530 DHT11 sensor

Plain text
Files used to configure CC2530 zigbee board to act as a DHT11 sensor
No preview (download only).

Node-red Weather station flow

JSON
[
    {
        "id": "8f346d23b9083f5f",
        "type": "tab",
        "label": "Home weather station",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "f793cb5a22b2c412",
        "type": "mqtt in",
        "z": "8f346d23b9083f5f",
        "name": "",
        "topic": "weather/temperature",
        "qos": "2",
        "datatype": "auto-detect",
        "broker": "590e3ceaadf4028f",
        "nl": false,
        "rap": true,
        "rh": 0,
        "inputs": 0,
        "x": 120,
        "y": 40,
        "wires": [
            [
                "ad934ecf6dd47990",
                "cf829514225e8bbc"
            ]
        ]
    },
    {
        "id": "20439d64a005f3d0",
        "type": "mqtt in",
        "z": "8f346d23b9083f5f",
        "name": "",
        "topic": "weather/humidity",
        "qos": "2",
        "datatype": "auto-detect",
        "broker": "590e3ceaadf4028f",
        "nl": false,
        "rap": true,
        "rh": 0,
        "inputs": 0,
        "x": 110,
        "y": 100,
        "wires": [
            [
                "d40ed107db227dd9",
                "cf829514225e8bbc"
            ]
        ]
    },
    {
        "id": "b4e6ca09ad5d74ab",
        "type": "mqtt in",
        "z": "8f346d23b9083f5f",
        "name": "",
        "topic": "weather/pressureAtm",
        "qos": "2",
        "datatype": "auto-detect",
        "broker": "590e3ceaadf4028f",
        "nl": false,
        "rap": true,
        "rh": 0,
        "inputs": 0,
        "x": 130,
        "y": 160,
        "wires": [
            [
                "fa02362174dc218e",
                "cf829514225e8bbc"
            ]
        ]
    },
    {
        "id": "731f2a695b041e2b",
        "type": "mqtt in",
        "z": "8f346d23b9083f5f",
        "name": "",
        "topic": "weather/light",
        "qos": "2",
        "datatype": "auto-detect",
        "broker": "590e3ceaadf4028f",
        "nl": false,
        "rap": true,
        "rh": 0,
        "inputs": 0,
        "x": 100,
        "y": 220,
        "wires": [
            [
                "e27b9ec101236c0f",
                "cf829514225e8bbc"
            ]
        ]
    },
    {
        "id": "2330129293ec3b06",
        "type": "mqtt in",
        "z": "8f346d23b9083f5f",
        "name": "",
        "topic": "weather/UVsensor",
        "qos": "2",
        "datatype": "auto-detect",
        "broker": "590e3ceaadf4028f",
        "nl": false,
        "rap": true,
        "rh": 0,
        "inputs": 0,
        "x": 110,
        "y": 280,
        "wires": [
            [
                "41ccfcccf2d00712",
                "cf829514225e8bbc"
            ]
        ]
    },
    {
        "id": "ad934ecf6dd47990",
        "type": "ui_gauge",
        "z": "8f346d23b9083f5f",
        "name": "",
        "group": "ee73cebac126456b",
        "order": 1,
        "width": 4,
        "height": 3,
        "gtype": "gage",
        "title": "Temperature",
        "label": "C",
        "format": "{{value}}",
        "min": 0,
        "max": "55",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "",
        "seg2": "",
        "diff": false,
        "className": "",
        "x": 680,
        "y": 40,
        "wires": []
    },
    {
        "id": "d40ed107db227dd9",
        "type": "ui_gauge",
        "z": "8f346d23b9083f5f",
        "name": "",
        "group": "ee73cebac126456b",
        "order": 2,
        "width": 4,
        "height": 3,
        "gtype": "gage",
        "title": "Humidity",
        "label": "%Rh",
        "format": "{{value}}",
        "min": 0,
        "max": "100",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "",
        "seg2": "",
        "diff": false,
        "className": "",
        "x": 660,
        "y": 100,
        "wires": []
    },
    {
        "id": "fa02362174dc218e",
        "type": "ui_gauge",
        "z": "8f346d23b9083f5f",
        "name": "",
        "group": "ee73cebac126456b",
        "order": 3,
        "width": 4,
        "height": 3,
        "gtype": "gage",
        "title": "Barometer",
        "label": "mmHg",
        "format": "{{value}}",
        "min": "600",
        "max": "900",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "",
        "seg2": "",
        "diff": false,
        "className": "",
        "x": 670,
        "y": 160,
        "wires": []
    },
    {
        "id": "e27b9ec101236c0f",
        "type": "ui_gauge",
        "z": "8f346d23b9083f5f",
        "name": "",
        "group": "ee73cebac126456b",
        "order": 5,
        "width": 4,
        "height": 3,
        "gtype": "gage",
        "title": "Light",
        "label": "lux",
        "format": "{{value}}",
        "min": 0,
        "max": "70000",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "",
        "seg2": "",
        "diff": false,
        "className": "",
        "x": 650,
        "y": 220,
        "wires": []
    },
    {
        "id": "41ccfcccf2d00712",
        "type": "ui_gauge",
        "z": "8f346d23b9083f5f",
        "name": "",
        "group": "ee73cebac126456b",
        "order": 6,
        "width": 4,
        "height": 3,
        "gtype": "gage",
        "title": "UV index",
        "label": "unit",
        "format": "{{value}}",
        "min": 0,
        "max": 10,
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "2",
        "seg2": "6",
        "diff": false,
        "className": "",
        "x": 660,
        "y": 280,
        "wires": []
    },
    {
        "id": "46ee647317f0a280",
        "type": "sqlite",
        "z": "8f346d23b9083f5f",
        "mydb": "bf3a4b3fd2b79471",
        "sqlquery": "prepared",
        "sql": "INSERT INTO readings (temperature, humidity, pressure, light, uv_level, timestamp) VALUES (?, ?, ?, ?, ?, DATETIME('now', 'localtime'));",
        "name": "Record temp in database",
        "x": 640,
        "y": 480,
        "wires": [
            []
        ]
    },
    {
        "id": "cf829514225e8bbc",
        "type": "join",
        "z": "8f346d23b9083f5f",
        "name": "Join messages",
        "mode": "custom",
        "build": "object",
        "property": "payload",
        "propertyType": "msg",
        "key": "topic",
        "joiner": "\\n",
        "joinerType": "str",
        "useparts": true,
        "accumulate": true,
        "timeout": "",
        "count": "5",
        "reduceRight": false,
        "reduceExp": "",
        "reduceInit": "",
        "reduceInitType": "",
        "reduceFixup": "",
        "x": 380,
        "y": 340,
        "wires": [
            [
                "f0f0b30d2aec0354"
            ]
        ]
    },
    {
        "id": "a810f3bab3e012c0",
        "type": "function",
        "z": "8f346d23b9083f5f",
        "name": "Prepare data for SQLite",
        "func": "var temperature = msg.payload[\"weather/temperature\"];\nvar humidity    = msg.payload[\"weather/humidity\"];\nvar pressure    = msg.payload[\"weather/pressureAtm\"];\nvar light       = msg.payload[\"weather/light\"];\nvar uv_level    = msg.payload[\"weather/UVsensor\"]\n\nif (temperature === undefined || humidity === undefined || pressure === undefined || light === undefined || uv_level === undefined) {\n    return { payload: \"Temp or humi or press or light or UV undefined\"}\n}\n\nmsg.params = [temperature, humidity, pressure, light, uv_level];\nreturn msg;\n\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 480,
        "y": 420,
        "wires": [
            [
                "46ee647317f0a280"
            ]
        ]
    },
    {
        "id": "f0f0b30d2aec0354",
        "type": "delay",
        "z": "8f346d23b9083f5f",
        "name": "Record data at 1 minute rate",
        "pauseType": "queue",
        "timeout": "1",
        "timeoutUnits": "minutes",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "minute",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": true,
        "allowrate": false,
        "outputs": 1,
        "x": 190,
        "y": 420,
        "wires": [
            [
                "a810f3bab3e012c0"
            ]
        ]
    },
    {
        "id": "d1f3ea031cac8dc2",
        "type": "http in",
        "z": "8f346d23b9083f5f",
        "name": "Receive prediction from ML",
        "url": "/recommandation",
        "method": "post",
        "upload": false,
        "swaggerDoc": "",
        "x": 150,
        "y": 560,
        "wires": [
            [
                "a913f42f902c2635",
                "4939aa6fa2d4fa2f",
                "69e50fe042678e5e"
            ]
        ]
    },
    {
        "id": "6a962f9d46966644",
        "type": "http response",
        "z": "8f346d23b9083f5f",
        "name": "Sent response to ML",
        "statusCode": "",
        "headers": {},
        "x": 680,
        "y": 560,
        "wires": []
    },
    {
        "id": "a913f42f902c2635",
        "type": "function",
        "z": "8f346d23b9083f5f",
        "name": "Prepare response",
        "func": "//prepare an message for http response to python:\n//this is used in python script predict_from_readings.py\n//to confirm sending and receiving the recommendation from ML to Node-Red\nmsg.payload = 200\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 430,
        "y": 560,
        "wires": [
            [
                "6a962f9d46966644"
            ]
        ]
    },
    {
        "id": "084ae5e964d466a2",
        "type": "exec",
        "z": "8f346d23b9083f5f",
        "command": "python3 /userdata/For_ML/predict_from_readings.py",
        "addpay": "",
        "append": "",
        "useSpawn": "false",
        "timer": "",
        "winHide": false,
        "oldrc": false,
        "name": "",
        "x": 480,
        "y": 760,
        "wires": [
            [
                "2e843a175d6f8369"
            ],
            [],
            []
        ]
    },
    {
        "id": "2e843a175d6f8369",
        "type": "ui_text",
        "z": "8f346d23b9083f5f",
        "group": "ee73cebac126456b",
        "order": 16,
        "width": 12,
        "height": 2,
        "name": "",
        "label": "ML output:",
        "format": "{{msg.payload}}",
        "layout": "col-center",
        "className": "",
        "style": true,
        "font": "Arial,Arial,Helvetica,sans-serif",
        "fontSize": "10",
        "color": "#000000",
        "x": 690,
        "y": 840,
        "wires": []
    },
    {
        "id": "4939aa6fa2d4fa2f",
        "type": "ui_text",
        "z": "8f346d23b9083f5f",
        "d": true,
        "group": "ee73cebac126456b",
        "order": 19,
        "width": 8,
        "height": 1,
        "name": "",
        "label": "Recommendation: ",
        "format": "{{msg.payload}}",
        "layout": "row-spread",
        "className": "",
        "style": true,
        "font": "Arial,Arial,Helvetica,sans-serif",
        "fontSize": "12",
        "color": "#000000",
        "x": 440,
        "y": 600,
        "wires": []
    },
    {
        "id": "b020b34a5aa16e93",
        "type": "inject",
        "z": "8f346d23b9083f5f",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "300",
        "crontab": "",
        "once": true,
        "onceDelay": "60",
        "topic": "",
        "payload": "",
        "payloadType": "num",
        "x": 100,
        "y": 760,
        "wires": [
            [
                "084ae5e964d466a2"
            ]
        ]
    },
    {
        "id": "69e50fe042678e5e",
        "type": "function",
        "z": "8f346d23b9083f5f",
        "name": "Decode prediction",
        "func": "//this function is used to decode prediction and display the recomandations \n\n//map the recommendation value to a descriptive text:\nlet recommendationMap = {\n    1: \"It is very hot; drink water and avoid direct sun exposure!\",\n    2: \"Cold and wet weather. Dress well and stay dry.\",\n    3: \"Increased humidity. Rapid fogging of windows.\",\n    4: \"High risk of ice formation. Use appropiate footwear and clothes.\",\n    5: \"Very dry air. Drink water often and use a himidifier.\",\n    6: \"High UV level. Use sunscreen. Use sun glases!\",\n    7: \"High UV level. Use cover for hand and face!\",\n    8: \"Unstable weather. Prepare for possible thunderstorms.\",\n    9: \"Very high humidity. Be sure to stay hydrated.\",\n    10: \"A perfect day for outdoor activities.\",\n    11: \"It is dark; take a flashlight with you.\",\n    12: \"There are some clouds; you don't need a cap.\",\n    13: \"Moderate UV level; definitely use something to cover.\",\n    14: \"It is a day without precipitation.\",\n    15: \"Thermal comfort index exceeded; seek coolness and drink water!\",\n    16: \"Normal thermal comfort index.\",\n    17: \"Thermal comfort index alert!\",\n    18: \"A normal day. You can do various activities.\",\n}\n\n//obtain the recommendation value:\nlet recommendationValue = msg.payload.Recommandation;\n\n//set output text message :\nmsg.payload = recommendationMap[recommendationValue] || \"Unkown recommendation\";\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 440,
        "y": 640,
        "wires": [
            [
                "dadce65b1dbf6546"
            ]
        ]
    },
    {
        "id": "dadce65b1dbf6546",
        "type": "ui_text",
        "z": "8f346d23b9083f5f",
        "group": "ee73cebac126456b",
        "order": 14,
        "width": 12,
        "height": 1,
        "name": "",
        "label": "Recommendation: ",
        "format": "{{msg.payload}}",
        "layout": "row-spread",
        "className": "",
        "style": false,
        "font": "",
        "fontSize": 16,
        "color": "#000000",
        "x": 680,
        "y": 640,
        "wires": []
    },
    {
        "id": "c04736d2e536788b",
        "type": "ui_button",
        "z": "8f346d23b9083f5f",
        "name": "",
        "group": "ee73cebac126456b",
        "order": 12,
        "width": 5,
        "height": 1,
        "passthru": false,
        "label": "Manual prediction request",
        "tooltip": "",
        "color": "",
        "bgcolor": "",
        "className": "",
        "icon": "",
        "payload": "0",
        "payloadType": "num",
        "topic": "payload",
        "topicType": "msg",
        "x": 140,
        "y": 840,
        "wires": [
            [
                "084ae5e964d466a2"
            ]
        ]
    },
    {
        "id": "7b02c174e7f99067",
        "type": "ui_spacer",
        "z": "8f346d23b9083f5f",
        "name": "spacer",
        "group": "ee73cebac126456b",
        "order": 4,
        "width": 2,
        "height": 1
    },
    {
        "id": "1be4ef1de5fb0ed3",
        "type": "ui_spacer",
        "z": "8f346d23b9083f5f",
        "name": "spacer",
        "group": "ee73cebac126456b",
        "order": 7,
        "width": 2,
        "height": 1
    },
    {
        "id": "869a1f7c23f8be08",
        "type": "ui_spacer",
        "z": "8f346d23b9083f5f",
        "name": "spacer",
        "group": "ee73cebac126456b",
        "order": 8,
        "width": 2,
        "height": 1
    },
    {
        "id": "e32a9a4242bcf1c1",
        "type": "ui_spacer",
        "z": "8f346d23b9083f5f",
        "name": "spacer",
        "group": "ee73cebac126456b",
        "order": 9,
        "width": 2,
        "height": 1
    },
    {
        "id": "814ff8297a43d0e5",
        "type": "ui_spacer",
        "z": "8f346d23b9083f5f",
        "name": "spacer",
        "group": "ee73cebac126456b",
        "order": 10,
        "width": 2,
        "height": 1
    },
    {
        "id": "f8d6d089dbf83be8",
        "type": "ui_spacer",
        "z": "8f346d23b9083f5f",
        "name": "spacer",
        "group": "ee73cebac126456b",
        "order": 11,
        "width": 2,
        "height": 1
    },
    {
        "id": "1118a6587e592804",
        "type": "ui_spacer",
        "z": "8f346d23b9083f5f",
        "name": "spacer",
        "group": "ee73cebac126456b",
        "order": 13,
        "width": 7,
        "height": 1
    },
    {
        "id": "fc739e5bb766e0a7",
        "type": "ui_spacer",
        "z": "8f346d23b9083f5f",
        "name": "spacer",
        "group": "ee73cebac126456b",
        "order": 15,
        "width": 12,
        "height": 1
    },
    {
        "id": "ad522223d87cc1c9",
        "type": "ui_spacer",
        "z": "8f346d23b9083f5f",
        "name": "spacer",
        "group": "ee73cebac126456b",
        "order": 17,
        "width": 12,
        "height": 1
    },
    {
        "id": "1f973d94dc6debd7",
        "type": "ui_spacer",
        "z": "8f346d23b9083f5f",
        "name": "spacer",
        "group": "ee73cebac126456b",
        "order": 18,
        "width": 12,
        "height": 1
    },
    {
        "id": "c50ea89c61474921",
        "type": "ui_spacer",
        "z": "8f346d23b9083f5f",
        "name": "spacer",
        "group": "ee73cebac126456b",
        "order": 20,
        "width": 4,
        "height": 1
    },
    {
        "id": "590e3ceaadf4028f",
        "type": "mqtt-broker",
        "name": "",
        "broker": "localhost",
        "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": "ee73cebac126456b",
        "type": "ui_group",
        "name": "Home weather station",
        "tab": "c86e9cad5ecdb135",
        "order": 1,
        "disp": true,
        "width": 12,
        "collapse": false,
        "className": ""
    },
    {
        "id": "bf3a4b3fd2b79471",
        "type": "sqlitedb",
        "db": "/home/mpopa/userdata/sqlite/sensor_data_1.db",
        "mode": "RWC"
    },
    {
        "id": "c86e9cad5ecdb135",
        "type": "ui_tab",
        "name": "Weather station",
        "icon": "dashboard",
        "order": 1,
        "disabled": false,
        "hidden": false
    }
]

create_table_recommeandation

Python
Python script used to create table used for recommemndation data to train the model
import sqlite3

#connect to database:
conn = sqlite3.connect('/userdata/sqlite/sensor_data_1.db')

#create tabel
conn.execute('''
CREATE TABLE recommandations (
	id INTEGER PRIMARY KEY AUTOINCREMENT,
	temperature REAL,
	humidity REAL,
	pressure REAL,
	light REAL,
	uv_level REAL,
	recommandation TEXT
);
''')



#save modifications and close the connection
conn.commit()
conn.close()

print("Tabel 'recommandations' was created with success!")

insert_training_data

Python
Python script used to insert data in the remmandation table to be sued to train the model
import sqlite3
import random

#connect to database:
conn = sqlite3.connect('/userdata/sqlite/sensor_data_1.db')
cursor = conn.cursor()

#create funtion used to geberate simulation data:
def generate_recommandations(num_entries):
	for i in range(num_entries):
		temperature = round(random.uniform(01.0, 40.0), 1)  #temperature between 1C and 40C
		humidity = round(random.uniform(20.0, 100.0), 1)     #humidity between 20% and 100%
		pressure = round(random.uniform(730, 790), 1)        #atmospheric pressure in mmHg
		uv_level = round(random.uniform(0, 11), 1)           #UV level between 0 and 11
		light = round(random.uniform(0, 1500), 1)            #light sensor - not used yet
		
		#generate recomandations based on temperature, humidity, pressure and uv_level
		if temperature > 35:
			recomandation = "Very hot. Drink water and avoid direct sun exposure!"
		elif temperature < 15 and humidity > 80:
			recommandation = "Cold and wet weather. Dress well and stay dry."
#		elif temperature > 28 and pressure < 750:
#			recommandation = "Possible precipitation. Go out with care."
		elif round((temperature - (100 - humidity)/5), 0) > temperature/2:
			recommandation = "Increased himidity. Rapid fogging of windows."
		elif temperature < 5 and humidity >= 50 and round((temperature - (100 - humidity)/5), 0) < 5:
			recommandation = "High risk of ice formation. Use appropiate footwear and clothes."	
		elif humidity < 30 and temperature > 30:
			recommandation = "Very dry air. Drink water often and use a himidifier."
		elif uv_level >= 8:
			recommandation = "High UV level. Use sunscreen!"
		elif uv_level >= 6 and pressure > 770 and temperature < 15:
			recommandation = "High UV level. Use cover for hand and face!"			
		elif pressure < 740:
			recommandation = "Unstable weather. Prepare for possible thunderstorms." 
		elif humidity > 90:
			recommandation = "Very high humidity. Be sure to stay hydrated."
		elif temperature >=20 and temperature <=30 and humidity <60:
			recommandation = "A perfect day for outdoor activities."
		elif light < 100:
			recommandation = "It is dark; take a flashlight with you."
		elif pressure >= 750 and pressure < 765:
			recommandation = "There are some clouds; you don't need a cap."
		elif uv_level >= 3 and uv_level < 8:
			recommandation = "Moderate UV level; definitely use something to cover"
		elif pressure > 770:
			recommandation = "It is a day without precipitation."
		elif round(float((temperature*1.8 + 32) - (0.55 - 0.0055*humidity) * ((temperature * 1.8 + 32)-58)), 0) >=80:
			recommandation = "Thermal comfort index exceeded; seek coolness and drink water!"
		elif round(float((temperature*1.8 + 32) - (0.55 - 0.0055*humidity) * ((temperature * 1.8 + 32)-58)), 0) < 65:
			recommandation = "Normal thermal comfort index"	
		elif round(float((temperature*1.8 + 32) - (0.55 - 0.0055*humidity) * ((temperature * 1.8 + 32)-58)), 0) >=65 and round(float((temperature*1.8 + 32) - (0.55 - 0.0055*humidity) * ((temperature * 1.8 + 32)-58)), 0) < 80:
			recommandation = "Thermal comfort index alert!"					
		else:
			recommandation = "A normal day. You can do various activities."  
	    
		#insert data in "recommandation" table:
		cursor.execute('''
			INSERT INTO recommandations (temperature, humidity, pressure, uv_level, light, recommandation)
			VALUES (?, ?, ?, ?, ?, ?)
		''', (temperature, humidity, pressure, uv_level, light, recommandation))
	
	#save modifications:
	conn.commit()
    
#generate 5000 records:
num_entries = 5000
generate_recommandations(num_entries)

#close connection with database:
conn.close()

print(f"{num_entries} data was gnerated and inserted in recommandations table.")
    
	    
	    
	    

train_mode.

Python
Python script used to train the model
#import sys
#sys.path.append('/userdata/For_ML')

#1. import all necessaryes libraries:
import sqlite3
import pandas as pd
import numpy as np
import joblib

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score
from sklearn.model_selection import cross_val_score

#2. connect to database and load data:
conn = sqlite3.connect('/userdata/sqlite/sensor_data_1.db') 
query = "SELECT temperature, humidity, pressure, uv_level, light, recommandation FROM recommandations"
data = pd.read_sql_query(query, conn)

#3. prepare data for training:
#transform column 'recommendation' in numeric value (factorize the text)
data['recommandation_encoded'] = pd.factorize(data['recommandation'])[0]

#select inut data (x) and target (y)
x = data[['temperature','humidity','pressure','uv_level','light']]
y = data['recommandation_encoded']

#split data set in dataset for training and dataset for test (80% training, 20% test)
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=42)

#scale the data using StandardScaler:
scaler = StandardScaler()
x_train_scaled = scaler.fit_transform(x_train)
x_test_scaled = scaler.transform(x_test)

#4. create and train model logistic regression:
model = LogisticRegression(max_iter=2000)
model.fit(x_train, y_train)

#5. evaluate the model:
y_pred = model.predict(x_test)
accuracy = accuracy_score(y_test, y_pred)

print(f'Model accuracy: {accuracy * 100:.2f}%')

scores = cross_val_score(model, x_train, y_train, cv=5)
print("Cross-validation scores: ", scores)
print("Average cross-validation score: ", scores.mean())

#6. save the model:
joblib.dump(model, 'model.pkl')
print("The model was saved with success!")

#close connection with database:
conn.close()

predict_from_readings

Python
Python script used to predict the clothing based on real data from reading table
import sqlite3
import pandas as pd
import joblib
import requests
import sys
from statistics import mode
from datetime import datetime, timedelta

#load model:
#model = joblib.load('model.pkl')
model = joblib.load("/userdata/For_ML/model.pkl")

#connect to sqlite database:
conn = sqlite3.connect('/userdata/sqlite/sensor_data_1.db')

#calculate interval for 2 minutes, from database:
end_time = datetime.now()
start_time = end_time - timedelta(minutes=2)

#format timestamps:
start_time_str = start_time.strftime('%Y-%m-%d %H:%M')
end_time_str = end_time.strftime('%Y-%m-%d %H:%M')

#query data from the last 1 minutes:
query = """
SELECT temperature, humidity, pressure, uv_level, light
FROM readings
WHERE timestamp BETWEEN ? AND ?
"""

#params = (start_time.strftime('%Y-%m-%d %H:%M:%S'), end_time.strftime('%Y-%m-%d %H:%M:%S')
params = (start_time_str, end_time_str)

#read data in dataFrame:
df = pd.read_sql_query(query, conn, params=params)

#check if data is abailable:
if df.empty:
	print("No data is avalable in last 2 minutes.")
	#print(start_time_str)
	#print(end_time_str)
else:
	print(df)
	#prepare data for prediction:
	input_data = df[['temperature', 'humidity', 'pressure', 'uv_level','light']]
	
	#prediction:
	predictions = model.predict(input_data)
	
	#display recommendations and send them to Node-red:
	for index, recomandation in enumerate(predictions):
		print(f"Recommendation for data from index {index}: {recomandation}")
	
	recomandation = int(recomandation)
	#final_recommandation = mode(predictions) #use statistic "'majirity of votes" to see what is predominant remmendation from readings
	
	#create payload to be sent to Node-red:
	payload = {'Recommandation': recomandation}
	#payload = {'Recommandation': int(final_recommandation)}	 #send the predominant recommendation to Node-Red
	#Node-Red endpoint URL:
	url = 'http://localhost:1880/recommandation'
	
	#send data to Node-red:
	try:
		response = requests.post(url, json=payload, timeout=5)
		response.raise_for_status()
		if response.status_code ==200:
			print(f"Recomendation was sent with success to Node-Red: {payload}")
		else:
			print(f"Error to send recommendation to Node-Red: {response.status_code}")
	except Exception as e:
		print(f"There was an error sending the recommendation: {e}")
		
#close connection with database:
conn.close()

#end script:
sys.exit()

BLE client

C/C++
Code used for BLE client and file with icons
No preview (download only).

BLE server

C/C++
BLE server used as outside unit
/*
  Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp
  Ported to Arduino ESP32 by Evandro Copercini
  updated by chegewara and MoThunderz
*/
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

#include <Wire.h>

#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

//#include <BH1750.h>

#include <LTR390.h>


//********************used for BLE communication:
BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic_bme = NULL;
BLECharacteristic* pCharacteristic_light = NULL;

BLEDescriptor *pDescr_bme;
BLEDescriptor *pDescr_light;

BLE2902 *pBLE2902_bme;
BLE2902 *pBLE2902_light;

// See the following for generating UUIDs: https://www.uuidgenerator.net/

//BLE server name
#define bleServerName "Environmental station"

#define SERVICE_UUID               "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID_bme    "beb5483e-36e1-4688-b7f5-ea07361b26a8"
#define CHARACTERISTIC_UUID_light  "f944b8d6-8558-44ca-8efa-b6d1a0e47480"

bool deviceConnected    = false;
bool oldDeviceConnected = false;
uint32_t value = 0;

//******************** used for read BME280:
#define temperatureCelsius //Default Temperature is in Celsius

Adafruit_BME280 bme;       // I2C
float temp;
float humi;
float pres;

//******************** used for read BH1750:
//BH1750 lightMeter(0x23); 
//float lux;

//******************** used for read LTR390:
#define I2C_ADDRESS 0x53
LTR390 ltr390(I2C_ADDRESS);
float lux;
float uv;

//******************** used to initialize the onboard led:
int led = 15;


class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
    };

    void onDisconnect(BLEServer* pServer) {
      deviceConnected = false;
    }
};

void initBME280()
{
  if (!bme.begin(0x76)) 
  {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }
}

//void initBH1750()
//{
//    Full mode list of reading:
//
//      BH1750_CONTINUOUS_LOW_RES_MODE
//      BH1750_CONTINUOUS_HIGH_RES_MODE (default)
//      BH1750_CONTINUOUS_HIGH_RES_MODE_2

//      BH1750_ONE_TIME_LOW_RES_MODE
//      BH1750_ONE_TIME_HIGH_RES_MODE
//      BH1750_ONE_TIME_HIGH_RES_MODE_2
  
//  if (lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE)) 
//  {
//    Serial.println(F("BH1750 Advanced begin"));
//  } 
//  else 
//  {
//    Serial.println(F("Error initialising BH1750"));
//  }
//}

void initLTR390()
{
    Serial.println("Adafruit LTR-390 test");

  if ( ! ltr390.init() ) {
    Serial.println("Couldn't find LTR sensor!");
    while (1) delay(10);
  }
  Serial.println("Found LTR sensor!");
  
  ltr390.setMode(LTR390_MODE_ALS);               //set LTR390 mode to AVS: Ambient Light Sensor 
  ltr390.setGain(LTR390_GAIN_3);                 //set LTR390 gain to 3; see example from library
  ltr390.setResolution(LTR390_RESOLUTION_18BIT); //set LTR390 resolution to 16bit;    
}

void setup() {
  // Start serial communication 
  Serial.begin(115200);
  Serial.print("Initializare port serial...");

  // Init BME280 environmental sensor
  initBME280();

  // Init BH1750 light sensor:
//  initBH1750();

  // Init LTR390:
  initLTR390();
  
  // Init onboard led:
  pinMode(led, OUTPUT);
  
  // Create the BLE Device
  BLEDevice::init("ESP32");
//  BLEDevice::init(bleServerName);
  
  // Create the BLE Server
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());

  // Create the BLE Service
  BLEService *pService = pServer->createService(SERVICE_UUID);
  // Create a BLE Characteristic
  pCharacteristic_bme = pService->createCharacteristic(
                      CHARACTERISTIC_UUID_bme,
                      BLECharacteristic::PROPERTY_READ  |
                      BLECharacteristic::PROPERTY_WRITE |
                      BLECharacteristic::PROPERTY_NOTIFY
                    );                   

  pCharacteristic_light = pService->createCharacteristic(
                      CHARACTERISTIC_UUID_light,
                      BLECharacteristic::PROPERTY_READ  |
                      BLECharacteristic::PROPERTY_WRITE |                      
                      BLECharacteristic::PROPERTY_NOTIFY
                    );  
 
  // Create a BLE Descriptor
  
  pDescr_bme = new BLEDescriptor((uint16_t)0x2901);
  pDescr_bme->setValue("Read data from BME280");
  pCharacteristic_bme->addDescriptor(pDescr_bme);

  pDescr_light = new BLEDescriptor((uint16_t)0x2901);
//  pDescr_light->setValue("Read data from BH1750");
  pDescr_light->setValue("Read data from LTR390");
  pCharacteristic_light->addDescriptor(pDescr_light);

  
  pBLE2902_bme = new BLE2902();
  pBLE2902_bme->setNotifications(true);
  pCharacteristic_bme->addDescriptor(pBLE2902_bme);

  pBLE2902_light = new BLE2902();
  pBLE2902_light->setNotifications(true);
  pCharacteristic_light->addDescriptor(pBLE2902_light);  
  
  // Start the service
  pService->start();

  // Start advertising
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(false);
  pAdvertising->setMinPreferred(0x0);  // set value to 0x00 to not advertise this parameter
  BLEDevice::startAdvertising();
  Serial.println("Waiting a client connection to notify...");
}


void loop() 
{
  // notify changed value
  if (deviceConnected) 
  {
//**********Turn on the onboard led:
    digitalWrite(led, HIGH);

//**********Read BME280 data:   
    temp = bme.readTemperature();            // Read temperature as Celsius (the default)
    humi = bme.readHumidity();               // Read humidity
    pres = bme.readPressure() / 100.0F;      // Read atmosfperic pressure in hPa

      Serial.println("**********BME280 data:");
    
      Serial.print("Temperature Celsius: "); // print temperature in serial console
      Serial.print(temp);
      Serial.println(" C");
      
      Serial.print("Humidity: ");            // print temperature in serial console
      Serial.print(humi);
      Serial.println(" %");

      Serial.print("Pressure: ");            // print atmospheric pressure in serial console
      Serial.print(pres);
      Serial.println(" hPa");           

      Serial.println("");

//**********Read BH1750 data: 
//    if (lightMeter.measurementReady()) 
//    {
//      lux = lightMeter.readLightLevel();     // read light value in lux
//    }
//    static char lightTemp[6];                // Convert data from sensor in strings:
//    dtostrf(lux, 6, 2, lightTemp);           
//    Serial.print("Light: ");                 // print light value in serial console  
//    Serial.print(lux);
//    Serial.println(" lx");
  
//    Serial.println("");

//**********Read LTR390 data:
//    lux = ltr390.getLux();
//    uv  = ltr390.getUVI();
    
  if (ltr390.newDataAvailable()) 
  {

    Serial.println("**********LTR390 data:");
/*    
    //read Ambient light lux:
    ltr390.setMode(LTR390_MODE_ALS);                 //set sensor in ALS mode = Ambient Light Sensor
    ltr390.setGain(LTR390_GAIN_3);                   //Recommended for Lux - x3
    ltr390.setResolution(LTR390_RESOLUTION_18BIT);   //Recommended for Lux - 18-bit    
      if (ltr390.getMode() == LTR390_MODE_ALS) 
        {  
          Serial.print("Ambient Light Lux: ");
          lux = ltr390.getLux(); 
          Serial.println(lux);
        }
    delay(10);
    //read UV sensor:
    ltr390.setMode(LTR390_MODE_UVS);                 //set sensor in UV mode
    ltr390.setGain(LTR390_GAIN_18);                  //Recommended for UVI - x18
    ltr390.setResolution(LTR390_RESOLUTION_20BIT);   //Recommended for UVI - 20-bit
      if (ltr390.getMode() == LTR390_MODE_UVS) 
        {  
          Serial.print("UV Index: ");
          uv  = ltr390.getUVI();  
          Serial.println(uv);
        }   
*/

      if (ltr390.getMode() == LTR390_MODE_ALS) 
      {
         Serial.print("Ambient Light Lux: ");
         lux = ltr390.getLux(); 
//         Serial.println(ltr390.getLux());
         Serial.println(lux);
         ltr390.setGain(LTR390_GAIN_18);                  //Recommended for UVI - x18
         ltr390.setResolution(LTR390_RESOLUTION_20BIT);   //Recommended for UVI - 20-bit
         ltr390.setMode(LTR390_MODE_UVS);             
      } 
      else if (ltr390.getMode() == LTR390_MODE_UVS) 
      {
         Serial.print("UV Index: ");
         uv  = ltr390.getUVI(); 
//         Serial.println(ltr390.getUVI());
         Serial.println(uv);
         ltr390.setGain(LTR390_GAIN_3);                   //Recommended for Lux - x3
         ltr390.setResolution(LTR390_RESOLUTION_18BIT);   //Recommended for Lux - 18-bit
         ltr390.setMode(LTR390_MODE_ALS);
      }

  }

    Serial.println("");    
    
//**********Create strings used to transmit data over BLE:
    static char temperatureCTemp[6];         
    static char humidityTemp[6];          
    static char pressureAtmTemp[8]; 

    static char lightTemp[8];
    static char UVTemp[4];
    
    dtostrf(temp, 6, 2, temperatureCTemp);   // Convert data from sensor in string
    dtostrf(humi, 6, 2, humidityTemp);       // Convert data from sensor in string
    dtostrf(pres, 6, 2, pressureAtmTemp);    // Convert data from sensor in string

    dtostrf(lux, 8, 2, lightTemp);
    dtostrf(uv,  2, 2, UVTemp);
    
//**********Create combined strings:
    char bmeCombinedString[6 * 3 + 2 + 1]; // Space for 3 strings + 2 x "," + null end character 
    char lightCombinedString[(8+2) + 1 + 1];       // Space for 2 x strings + 1 x "," + null end character
            
//**********Concatenate of strings:

    strcpy(bmeCombinedString, temperatureCTemp);
    strcat(bmeCombinedString, ",");    
    strcat(bmeCombinedString, humidityTemp);
    strcat(bmeCombinedString, ",");     
    dtostrf(pres, 6, 2, pressureAtmTemp);    // Convert data from sensor in string
    strcat(bmeCombinedString, pressureAtmTemp);

    dtostrf(lux, 8, 2, lightTemp);
    strcpy(lightCombinedString, lightTemp);
    strcat(lightCombinedString, ",");
    strcat(lightCombinedString, UVTemp);  

    pCharacteristic_bme->setValue(bmeCombinedString);
//    pCharacteristic_temp->notify();

//    pCharacteristic_light->setValue(lightTemp);
    pCharacteristic_light->setValue(lightCombinedString);
//    pCharacteristic_humi->notify();
   
    delay(10000);
    }
    // disconnecting
    if (!deviceConnected && oldDeviceConnected) 
    {
        digitalWrite(led, LOW);
        delay(500);                   // give the bluetooth stack the chance to get things ready
        pServer->startAdvertising();  // restart advertising
        Serial.println("start advertising");
        oldDeviceConnected = deviceConnected;
    }
    // connecting
    if (deviceConnected && !oldDeviceConnected) 
    {
        // do stuff here on connecting
        oldDeviceConnected = deviceConnected;
    }
}

Credits

Mihai Popa
9 projects • 7 followers
I am working as test engineer for navigation systems in auto industry. I am passionate about science, IoT, ML, MCU programming, SF movies.
Contact

Comments

Please log in or sign up to comment.