From f0d883b8c214b875be1a55e4116e39d0e8a5b7f8 Mon Sep 17 00:00:00 2001 From: nearxos Date: Sun, 8 Feb 2026 17:09:30 +0200 Subject: [PATCH] Remove obsolete INFORMATION_NEEDED.md and streamline README.md for clarity - Deleted the INFORMATION_NEEDED.md file as it was no longer necessary for documentation. - Revised README.md to enhance the overview of the home automation system, focusing on how services and CODESYS connectivity work. - Updated project structure in README.md for better organization and clarity, including links to relevant documentation. This update simplifies the documentation and improves the overall user experience for developers and users of the home automation system. --- INFORMATION_NEEDED.md | 127 - README.md | 61 +- codesys/src/NVL/README.md | 8 +- codesys/src/POUs/PLC_App.st | 68 +- docs/README.md | 46 +- docs/codesys/codesys-analysis.md | 189 - docs/codesys/codesys-export-guide.md | 147 - docs/codesys/codesys-xml-analysis.md | 260 - docs/integration/ha-to-nvl-function.js | 31 + docs/integration/nodered-flows-analysis.md | 70 + docs/integration/nodered-flows.json | 14504 ++++++++++++++++ docs/integration/nodered-livingroom-flow.md | 167 + docs/integration/nvl-to-ha-http-call.js | 30 + docs/integration/nvl-to-ha-sync-livingroom.js | 33 + docs/integration/state-to-nvl-send-payload.js | 30 + docs/integration/zigbee-to-nvl-function.js | 32 + docs/redesign/fb_switch-analysis-summary.md | 89 - docs/redesign/fb_switch-documentation.md | 223 - .../fb_switch-redesign-recommendation.md | 451 - docs/redesign/light-naming-configuration.md | 873 - .../redesign-implementation-roadmap.md | 239 - docs/redesign/redesign-summary.md | 101 - docs/redesign/structure-comparison-visual.md | 362 - docs/redesign/structure-redesign-analysis.md | 538 - 24 files changed, 15006 insertions(+), 3673 deletions(-) delete mode 100644 INFORMATION_NEEDED.md delete mode 100644 docs/codesys/codesys-analysis.md delete mode 100644 docs/codesys/codesys-export-guide.md delete mode 100644 docs/codesys/codesys-xml-analysis.md create mode 100644 docs/integration/ha-to-nvl-function.js create mode 100644 docs/integration/nodered-flows-analysis.md create mode 100644 docs/integration/nodered-flows.json create mode 100644 docs/integration/nodered-livingroom-flow.md create mode 100644 docs/integration/nvl-to-ha-http-call.js create mode 100644 docs/integration/nvl-to-ha-sync-livingroom.js create mode 100644 docs/integration/state-to-nvl-send-payload.js create mode 100644 docs/integration/zigbee-to-nvl-function.js delete mode 100644 docs/redesign/fb_switch-analysis-summary.md delete mode 100644 docs/redesign/fb_switch-documentation.md delete mode 100644 docs/redesign/fb_switch-redesign-recommendation.md delete mode 100644 docs/redesign/light-naming-configuration.md delete mode 100644 docs/redesign/redesign-implementation-roadmap.md delete mode 100644 docs/redesign/redesign-summary.md delete mode 100644 docs/redesign/structure-comparison-visual.md delete mode 100644 docs/redesign/structure-redesign-analysis.md diff --git a/INFORMATION_NEEDED.md b/INFORMATION_NEEDED.md deleted file mode 100644 index 0a3076d..0000000 --- a/INFORMATION_NEEDED.md +++ /dev/null @@ -1,127 +0,0 @@ -# Information Needed for Documentation - -This document outlines the information needed to complete the home automation system documentation. Please provide as much detail as possible for each section. - -## 1. Network Configuration - -- [ ] IP addresses for all devices: - - Raspberry Pi (CODESYS) - - Node-RED host - - MQTT broker (LXC container) - - Zigbee2MQTT host - - Home Assistant host -- [ ] Network topology (VLANs, subnets, etc.) -- [ ] Port numbers used by services (MQTT, Node-RED, Home Assistant, etc.) -- [ ] Firewall rules or network restrictions - -## 2. CODESYS Configuration - -- [ ] CODESYS version and license type -- [ ] Network variable names and types (sent to Node-RED) -- [ ] Network variable names and types (received from Node-RED) -- [ ] EtherCAT IO card model and configuration -- [ ] Relay mapping (which relay controls which light/device) -- [ ] Water boiler control logic -- [ ] Project file location or export (if available) - -## 3. Node-RED Configuration - -- [ ] Node-RED version -- [ ] Flow export or description of main flows -- [ ] MQTT topics used for communication -- [ ] CODESYS network variable integration details -- [ ] Custom nodes or functions used -- [ ] Automation logic descriptions - -## 4. MQTT Broker - -- [ ] MQTT broker software (Mosquitto, Eclipse, etc.) and version -- [ ] LXC container specifications -- [ ] Authentication configuration (users, passwords, ACLs) -- [ ] Topic structure and naming conventions -- [ ] Retain policies and QoS levels - -## 5. Zigbee2MQTT - -- [ ] Zigbee2MQTT version -- [ ] Coordinator device model -- [ ] List of Zigbee devices (switches, sensors, etc.) with: - - Device names - - Model numbers - - MQTT topics - - Functions/features -- [ ] Configuration file location or export -- [ ] Network key (if you're comfortable sharing, or note that it's stored securely) - -## 6. Home Assistant - -- [ ] Home Assistant version and installation type (HassOS, Docker, etc.) -- [ ] Integration configurations: - - MQTT integration settings - - Node-RED integration (if used) - - Zigbee integration (if direct) -- [ ] Dashboard screenshots or descriptions -- [ ] Automation rules -- [ ] Custom components or add-ons - -## 7. Hardware Inventory - -- [ ] Raspberry Pi model and specifications -- [ ] Beckhoff EtherCAT IO card model number -- [ ] Relay module details (model, number of channels) -- [ ] Zigbee coordinator hardware -- [ ] Zigbee switch models and quantities -- [ ] Water boiler model and control interface -- [ ] Any other connected hardware - -## 8. Physical Wiring - -- [ ] Relay to device mapping (which relay controls which physical device) -- [ ] Wiring diagrams or descriptions -- [ ] Safety considerations or warnings - -## 9. Automation Logic - -- [ ] How light switches trigger actions -- [ ] Water boiler control logic and schedules -- [ ] Any time-based automations -- [ ] Error handling and fallback behaviors -- [ ] Manual override procedures - -## 10. Access Information (Optional) - -If you'd like me to access services directly to gather information: - -- [ ] SSH access details (if comfortable) -- [ ] Web interface URLs and credentials (if comfortable) -- [ ] API endpoints and authentication -- [ ] File system paths to configuration files - -**Note:** Only provide access information if you're comfortable doing so. I can work with exported configuration files instead. - -## 11. Troubleshooting & Known Issues - -- [ ] Common problems and solutions -- [ ] Known limitations -- [ ] Planned improvements - -## Priority Items - -To get started quickly, the most important information is: -1. Network IP addresses and topology -2. CODESYS network variable names -3. MQTT topic structure -4. Zigbee device list -5. Relay to device mapping - ---- - -**How to Provide Information:** - -1. Fill out this checklist and provide details in a separate document -2. Export configuration files and share them -3. Grant access to services (if comfortable) -4. Provide screenshots of configurations -5. Schedule a walkthrough session - -Let me know your preferred method! diff --git a/README.md b/README.md index 0bbc692..233cc0d 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,35 @@ -# Home Automation System +# Home Automation -## Overview +How home automation works in this house: **services** and **CODESYS** connectivity. -This project documents a comprehensive home automation system built around a Raspberry Pi running CODESYS, integrated with Node-RED, Zigbee2MQTT, MQTT broker, and Home Assistant. +## How it works -## System Architecture +- **CODESYS** (Raspberry Pi) runs the PLC: lights and water boiler via EtherCAT I/O. +- **Node-RED** runs automation logic and talks to CODESYS over **UDP (network variables)**. +- **MQTT** broker (LXC) is the message bus. +- **Zigbee2MQTT** bridges Zigbee switches to MQTT. +- **Home Assistant** is the dashboard and can trigger automations. -- **CODESYS Runtime** (Raspberry Pi) — Industrial control, physical I/O -- **Beckhoff EtherCAT IO** — Relays for lighting and water boiler -- **Node-RED** — Automation logic and integration -- **MQTT Broker** (LXC) — Message broker -- **Zigbee2MQTT** — Zigbee ↔ MQTT bridge -- **Home Assistant** — Dashboard and automation -- **Zigbee Switches** — Wireless light switches +Flow: Zigbee switch or HA → Node-RED → UDP → CODESYS → EtherCAT → relays/lights or boiler. -## Project Structure +## Project layout ``` kkelomatic_home/ -├── codesys/ # CODESYS project and exports -│ ├── Home_Automation (1).project -│ ├── Home_Automation.export -│ ├── Home_Automation.xml -│ └── Home_Automation.projectarchive -├── docs/ # Documentation -│ ├── README.md # Documentation index (start here) -│ ├── codesys/ # CODESYS docs -│ ├── integration/ # Node-RED, MQTT, HA, Zigbee -│ ├── redesign/ # Redesign and implementation -│ └── reference/ # Architecture, hardware, setup -├── scripts/ # Helper scripts -│ └── parse-codesys-xml.py -├── INFORMATION_NEEDED.md -└── README.md +├── codesys/ # CODESYS project and source (PLC logic, NVL) +├── docs/ # Documentation (architecture, services, Codesys) +└── scripts/ # parse-codesys-xml.py ``` ## Documentation -See **[docs/README.md](docs/README.md)** for the full documentation index. +- **Architecture & setup:** [docs/reference/architecture.md](docs/reference/architecture.md), [setup.md](docs/reference/setup.md) +- **Services:** [docs/integration/](docs/integration/) — MQTT, Node-RED, Home Assistant, Zigbee2MQTT +- **CODESYS:** [docs/codesys/codesys.md](docs/codesys/codesys.md), [codesys/src/NVL/README.md](codesys/src/NVL/README.md) (connectivity) -### Quick links -- [Architecture](docs/reference/architecture.md) -- [CODESYS Configuration](docs/codesys/codesys.md) -- [Setup Guide](docs/reference/setup.md) -- [Redesign Summary](docs/redesign/redesign-summary.md) -- [Light Naming Configuration](docs/redesign/light-naming-configuration.md) +## CODESYS connectivity -## Information Needed +- **NVL_Out** (PLC → Node-RED): light states + boiler status, UDP. +- **NVL_In** (Node-RED → PLC): light and boiler commands, UDP. -See [INFORMATION_NEEDED.md](INFORMATION_NEEDED.md) for what’s required to complete the documentation. - -## Maintenance - -- Last Updated: January 27, 2026 -- System Status: Operational +See [codesys/src/NVL/README.md](codesys/src/NVL/README.md) and [nodered-payload.md](codesys/src/NVL/nodered-payload.md) for layout and ports. diff --git a/codesys/src/NVL/README.md b/codesys/src/NVL/README.md index 822ca6d..5f5d391 100644 --- a/codesys/src/NVL/README.md +++ b/codesys/src/NVL/README.md @@ -48,7 +48,7 @@ This folder describes the **network variable** setup used for CODESYS ↔ Node-R **In CODESYS:** Create an **NVL Receiver**, bind it to the **NVL_In** structure. Set protocol to UDP, listen port, task and interval. The PLC will overwrite `NVL_In` with received data each cycle. -**In Node-RED:** Send one UDP packet per update (or cyclic) with the same binary layout as `NVL_In` so CODESYS can unpack it into the receiver struct. +**In Node-RED:** Use a **UDP out** node to the PLC IP and NVL Receiver port. Send one UDP packet per update with the same binary layout as `NVL_In`; see [nodered-payload.md](nodered-payload.md) for building the buffer. --- @@ -76,7 +76,5 @@ This folder describes the **network variable** setup used for CODESYS ↔ Node-R ## 4. Node-RED usage -- **Receive (PLC → Node-RED):** Use a **UDP in** node listening on the port configured in the CODESYS NVL Sender (e.g. 5555). The payload is binary; see [nodered-payload.md](nodered-payload.md) for struct layout and how to parse it to JSON for MQTT/HA. -- **Send (Node-RED → PLC):** Use a **UDP out** node targeting the PLC IP and the NVL Receiver port (e.g. 5556). Build the payload buffer from your command (e.g. from MQTT/HA) using the same layout as [nodered-payload.md](nodered-payload.md). - -See **nodered-payload.md** in this folder for byte layout and example parsing/building. +- **Receive (PLC → Node-RED):** **UDP in** node on the NVL Sender port (e.g. 5555). Payload is binary; see [nodered-payload.md](nodered-payload.md) for layout and parsing. +- **Send (Node-RED → PLC):** **UDP out** node to PLC IP and NVL Receiver port (e.g. 5556). Build the buffer from [nodered-payload.md](nodered-payload.md). diff --git a/codesys/src/POUs/PLC_App.st b/codesys/src/POUs/PLC_App.st index 3d86ba4..fe0d1b1 100644 --- a/codesys/src/POUs/PLC_App.st +++ b/codesys/src/POUs/PLC_App.st @@ -39,12 +39,76 @@ masterBathroom(switches := NVL_In.masterBathroom, relay_status := EtherCAT_Relay EtherCAT_RelayFeedback.masterBathroom := masterBathroom.lights; NVL_Out.l_masterBathroom := masterBathroom.lights; -// Bedroom 1..2, bathroom, guest_wc, kitchen, pantry, livingRoom, diningRoom, -// entrance, hallway, veranda, front, back, side: same pattern (fb_room, RelayFeedback, NVL_Out). +// Bedroom 1 bedroom_1(switches := NVL_In.bedroom_1, relay_status := EtherCAT_RelayFeedback.bedroom_1); EtherCAT_RelayFeedback.bedroom_1 := bedroom_1.lights; NVL_Out.l_bedroom_1 := bedroom_1.lights; +// Bedroom 2 +bedroom_2(switches := NVL_In.bedroom_2, relay_status := EtherCAT_RelayFeedback.bedroom_2); +EtherCAT_RelayFeedback.bedroom_2 := bedroom_2.lights; +NVL_Out.l_bedroom_2 := bedroom_2.lights; + +// Bathroom +bathroom(switches := NVL_In.bathroom, relay_status := EtherCAT_RelayFeedback.bathroom); +EtherCAT_RelayFeedback.bathroom := bathroom.lights; +NVL_Out.l_bathroom := bathroom.lights; + +// Guest WC +guest_wc(switches := NVL_In.guestWc, relay_status := EtherCAT_RelayFeedback.guest_wc); +EtherCAT_RelayFeedback.guest_wc := guest_wc.lights; +NVL_Out.l_guestWc := guest_wc.lights; + +// Kitchen +kitchen(switches := NVL_In.kitchen, relay_status := EtherCAT_RelayFeedback.kitchen); +EtherCAT_RelayFeedback.kitchen := kitchen.lights; +NVL_Out.l_kitchen := kitchen.lights; + +// Pantry +pantry(switches := NVL_In.pantry, relay_status := EtherCAT_RelayFeedback.pantry); +EtherCAT_RelayFeedback.pantry := pantry.lights; +NVL_Out.l_pantry := pantry.lights; + +// Living Room +livingRoom(switches := NVL_In.livingRoom, relay_status := EtherCAT_RelayFeedback.livingRoom); +EtherCAT_RelayFeedback.livingRoom := livingRoom.lights; +NVL_Out.l_livingRoom := livingRoom.lights; + +// Dining Room +diningRoom(switches := NVL_In.dinningRoom, relay_status := EtherCAT_RelayFeedback.diningRoom); +EtherCAT_RelayFeedback.diningRoom := diningRoom.lights; +NVL_Out.l_dinningRoom := diningRoom.lights; + +// Entrance +entrance(switches := NVL_In.entrance, relay_status := EtherCAT_RelayFeedback.entrance); +EtherCAT_RelayFeedback.entrance := entrance.lights; +NVL_Out.l_entrance := entrance.lights; + +// Hallway +hallway(switches := NVL_In.hallway, relay_status := EtherCAT_RelayFeedback.hallway); +EtherCAT_RelayFeedback.hallway := hallway.lights; +NVL_Out.l_hallway := hallway.lights; + +// Veranda +veranda(switches := NVL_In.outVeranda, relay_status := EtherCAT_RelayFeedback.veranda); +EtherCAT_RelayFeedback.veranda := veranda.lights; +NVL_Out.l_outVeranda := veranda.lights; + +// Front +front(switches := NVL_In.outFront, relay_status := EtherCAT_RelayFeedback.front); +EtherCAT_RelayFeedback.front := front.lights; +NVL_Out.l_outFront := front.lights; + +// Back +back(switches := NVL_In.outBack, relay_status := EtherCAT_RelayFeedback.back); +EtherCAT_RelayFeedback.back := back.lights; +NVL_Out.l_outBack := back.lights; + +// Side +side(switches := NVL_In.outSide, relay_status := EtherCAT_RelayFeedback.side); +EtherCAT_RelayFeedback.side := side.lights; +NVL_Out.l_outSide := side.lights; + // ========== SECTION 2: BOILER ========== boiler( diff --git a/docs/README.md b/docs/README.md index 3a097c8..7e6eb25 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,30 +1,24 @@ -# Documentation Index +# Documentation -## Reference (Architecture & Setup) -- [Architecture Overview](reference/architecture.md) -- [Hardware Inventory](reference/hardware.md) -- [Network Topology](reference/network.md) -- [Setup Guide](reference/setup.md) -- [Quick Reference](reference/quick-reference.md) +Basics: **how home automation works**, **services**, and **CODESYS connectivity**. + +## Reference (architecture & setup) + +- [Architecture](reference/architecture.md) — Components and data flow +- [Hardware](reference/hardware.md) — Raspberry Pi, EtherCAT, Zigbee +- [Network](reference/network.md) — Topology +- [Setup](reference/setup.md) — Installation order +- [Quick reference](reference/quick-reference.md) + +## Services + +- [MQTT](integration/mqtt.md) — Broker (LXC) +- [Node-RED](integration/nodered.md) — Automation and CODESYS bridge +- [Home Assistant](integration/homeassistant.md) — Dashboard +- [Zigbee2MQTT](integration/zigbee2mqtt.md) — Zigbee ↔ MQTT ## CODESYS -- [CODESYS Configuration](codesys/codesys.md) -- [CODESYS Export Guide](codesys/codesys-export-guide.md) -- [CODESYS Project Analysis](codesys/codesys-analysis.md) -- [CODESYS XML Analysis](codesys/codesys-xml-analysis.md) -## Integration (Node-RED, MQTT, HA, Zigbee) -- [Node-RED](integration/nodered.md) -- [MQTT Broker](integration/mqtt.md) -- [Home Assistant](integration/homeassistant.md) -- [Zigbee2MQTT](integration/zigbee2mqtt.md) - -## Redesign (Structure & Implementation) -- [Redesign Summary](redesign/redesign-summary.md) — **Start here for redesign** -- [Light Naming Configuration](redesign/light-naming-configuration.md) — Configure lights per room -- [Implementation Roadmap](redesign/redesign-implementation-roadmap.md) -- [Structure Redesign Analysis](redesign/structure-redesign-analysis.md) -- [Structure Comparison (Visual)](redesign/structure-comparison-visual.md) -- [fb_switch Documentation](redesign/fb_switch-documentation.md) -- [fb_switch Redesign Recommendation](redesign/fb_switch-redesign-recommendation.md) -- [fb_switch Analysis Summary](redesign/fb_switch-analysis-summary.md) +- [CODESYS configuration](codesys/codesys.md) — Runtime, EtherCAT, NVL +- [PLC algorithm design](codesys/plc-algorithm-design.md) — Logic and I/O +- **NVL (connectivity):** [codesys/src/NVL/README.md](../codesys/src/NVL/README.md) — UDP, NVL_In / NVL_Out, payload layout diff --git a/docs/codesys/codesys-analysis.md b/docs/codesys/codesys-analysis.md deleted file mode 100644 index 34c6786..0000000 --- a/docs/codesys/codesys-analysis.md +++ /dev/null @@ -1,189 +0,0 @@ -# CODESYS Project Analysis - -## Overview - -This document contains information extracted from the CODESYS project export file (`Home_Automation.export`). - -## Device Information - -- **Device Name**: `codesys-home` -- **CODESYS Version**: 4.15.0.0 -- **Platform**: CODESYS Control for Raspberry Pi MC SL -- **SDK Version**: 3.5.21.0 -- **Order Number**: 2302000009 - -## Hardware Configuration - -### EtherCAT Network - -- **EtherCAT Master**: Version 4.9.0.0 -- **EtherCAT Task**: `EtherCAT_Task` - -### EtherCAT Devices - -1. **EK1100** - EtherCAT Coupler (2A E-Bus) - - Vendor: Beckhoff Automation GmbH & Co. KG - - Order Number: EK1100 - - Function: EtherCAT coupler connecting to E-Bus modules - -2. **EL1809** - 16Ch. Digital Input 24V, 3ms - - Vendor: Beckhoff Automation GmbH & Co. KG - - Order Number: EL1809 - - Function: 16-channel digital input module - - Channels: 16 digital inputs (Channels 1-16) - -3. **Output Module** (Likely EL2004 or similar) - - 16 output channels configured - - Channel addresses: 16#1A00 through 16#1A0F (Channels 1-16) - -## Network Variables - -CODESYS communicates with Node-RED using UDP-based network variables. - -### Variables Sent to Node-RED (NVL_Sender) - -**List Identifier**: 1 -**Protocol**: UDP -**Task**: EtherCAT_Task -**Transmission**: Cyclic -**Interval**: T#50ms -**Minimum Gap**: T#20ms - -**Structure**: `struct_lights` containing light states for each room: - -| Variable Name | Type | Description | -|--------------|------|-------------| -| `l_masterBedroom` | struct_lights | Master bedroom lights | -| `l_masterBathroom` | struct_lights | Master bathroom lights | -| `l_bedroom_1` | struct_lights | Bedroom 1 lights | -| `l_bedroom_2` | struct_lights | Bedroom 2 lights | -| `l_bathroom` | struct_lights | Bathroom lights | -| `l_hallway` | struct_lights | Hallway lights | -| `l_pantry` | struct_lights | Pantry lights | -| `l_kitchen` | struct_lights | Kitchen lights | -| `l_livingRoom` | struct_lights | Living room lights | -| `l_dinningRoom` | struct_lights | Dining room lights | -| `l_entrance` | struct_lights | Entrance lights | -| `l_guestWc` | struct_lights | Guest WC lights | -| `l_outVeranda` | struct_lights | Outdoor veranda lights | -| `l_outFront` | struct_lights | Outdoor front lights | -| `l_outSide` | struct_lights | Outdoor side lights | -| `l_outBack` | struct_lights | Outdoor back lights | - -### Variables Received from Node-RED (NVL_Receiver) - -**List Identifier**: 3 -**Protocol**: UDP -**Task**: EtherCAT_Task -**Transmission**: Cyclic -**Interval**: T#50ms -**Minimum Gap**: T#20ms -**Import File**: `C:\Users\NearchosParaskeva\OneDrive - individual\Codesys_Home_Automation\NVL_Receiver.gvl` - -**Structure**: `struct_switches` containing switch states for each room: - -| Variable Name | Type | Description | -|--------------|------|-------------| -| `masterBedroom` | struct_switches | Master bedroom switches | -| `masterBathroom` | struct_switches | Master bathroom switches | -| `bedroom_1` | struct_switches | Bedroom 1 switches | -| `bedroom_2` | struct_switches | Bedroom 2 switches | -| `bathroom` | struct_switches | Bathroom switches | -| `hallway` | struct_switches | Hallway switches | -| `pantry` | struct_switches | Pantry switches | -| `kitchen` | struct_switches | Kitchen switches | -| `livingRoom` | struct_switches | Living room switches | -| `dinningRoom` | struct_switches | Dining room switches | -| `entrance` | struct_switches | Entrance switches | -| `guestWc` | struct_switches | Guest WC switches | -| `outVeranda` | struct_switches | Outdoor veranda switches | -| `outFront` | struct_switches | Outdoor front switches | -| `outSide` | struct_switches | Outdoor side switches | -| `outBack` | struct_switches | Outdoor back switches | - -## Program Structure - -### Main Program (PLC_PRG) - -- **Type**: Program -- **Language**: Structured Text (ST) -- **Function Blocks Used**: `fb_switch` -- **Instances**: - - `masterBedroom: fb_switch` - -### Function Blocks - -- **fb_toogleButton**: Toggle button function block -- **fb_switch**: Switch control function block - -### Variables Used in Logic - -From the program code, the following variables are referenced: -- `lights.l_1`, `lights.l_2`, `lights.l_3`, `lights.l_4`, `lights.l_5`, `lights.l_6` -- `switches.sw_1`, `switches.sw_2`, `switches.sw_3`, `switches.sw_4`, `switches.sw_5`, `switches.sw_6` -- `switches.all_off` -- `switches.all_on` - -## Room Configuration - -The system controls lighting for 15 rooms/areas: - -1. Master Bedroom -2. Master Bathroom -3. Bedroom 1 -4. Bedroom 2 -5. Bathroom -6. Hallway -7. Pantry -8. Kitchen -9. Living Room -10. Dining Room -11. Entrance -12. Guest WC -13. Outdoor Veranda -14. Outdoor Front -15. Outdoor Side -16. Outdoor Back - -## I/O Mapping - -### Inputs (EL1809) -- 16 digital input channels -- Likely used for local switch inputs or status feedback - -### Outputs (16 channels, likely EL2004) -- 16 digital output channels (16#1A00 - 16#1A0F) -- Used to control relay outputs for lighting - -**Note**: The exact mapping between relay outputs and rooms needs to be determined from the program logic or wiring documentation. - -## Libraries Used - -- **IoStandard** (System) -- **SM3_Basic** (3S - Smart Software Solutions GmbH) -- **SM3_CNC** (3S - Smart Software Solutions GmbH) -- **3SLicense** (CODESYS) -- **SM3_CamBuilder** (CODESYS) -- **SM3_Robotics** (CODESYS) -- **SM3_Robotics_Visu** (CODESYS) -- **SM3_Transformation** (CODESYS) -- **Standard** (System) -- **CmpLog** (System) - -## Task Configuration - -- **EtherCAT Task**: Used for EtherCAT communication and network variable updates -- **Update Rate**: 50ms (cyclic transmission) - -## Notes - -- The project uses structured data types (`struct_lights`, `struct_switches`) for organizing room data -- Network variables are packed and transmitted via UDP -- The system appears to support both indoor and outdoor lighting control -- Water boiler control mentioned in documentation was not found in the export - may be in a separate module or not yet implemented - ---- - -**Analysis Date**: January 27, 2026 -**Source File**: `Home_Automation.export` -**Export Format**: CODESYS XML diff --git a/docs/codesys/codesys-export-guide.md b/docs/codesys/codesys-export-guide.md deleted file mode 100644 index 1d78313..0000000 --- a/docs/codesys/codesys-export-guide.md +++ /dev/null @@ -1,147 +0,0 @@ -# CODESYS Project Export Guide - -## Overview - -CODESYS projects can be exported to readable XML formats that allow for better analysis, documentation, and version control. The binary `.project` file format is proprietary and not human-readable, but CODESYS provides export options that create XML files. - -## Export Formats - -### 1. CODESYS XML Format (*.export) - -**Best for:** Full project export, version control, complete documentation - -- **Format:** Fully compatible CODESYS XML format -- **Content:** Complete project structure in machine-parsable XML -- **Compatibility:** 100% compatible with CODESYS -- **Use Cases:** - - Full project backup - - Version control systems - - Automated analysis and documentation - - Project migration - -**How to Export:** -1. Open your project in CODESYS IDE -2. Go to **Project → Export** -3. Select the objects you want to export (or select all) -4. Choose **CODESYS XML Format (*.export)** as the file type -5. Save the file (e.g., `Home_Automation.export`) - -### 2. PLCopen XML Format (*.xml) - -**Best for:** Interoperability, documentation tools, program structure - -- **Format:** PLCopen XML standard -- **Content:** Subset of CODESYS elements (programs, function blocks, variables) -- **Compatibility:** May not include all CODESYS-specific features -- **Use Cases:** - - Documentation generation - - Integration with other tools - - Program structure analysis - - Cross-platform compatibility - -**How to Export:** -1. Open your project in CODESYS IDE -2. Go to **Project → Export PLCopenXML** -3. Select the objects to export -4. Save the file (e.g., `Home_Automation.xml`) - -## What Information Can Be Extracted - -From exported XML files, we can extract: - -### Project Structure -- Program Organization Units (POUs) -- Function blocks and functions -- Libraries and dependencies -- Project hierarchy - -### Network Variables -- Variable names -- Data types -- Initial values -- Network variable declarations -- Direction (input/output) - -### I/O Configuration -- EtherCAT device configuration -- I/O channel mappings -- Device parameters -- Scan rates - -### Control Logic -- Program code structure -- Variable declarations -- Function block instances -- Logic flow - -### Hardware Configuration -- Device configurations -- Network settings -- Communication parameters - -## Recommended Export Process - -For comprehensive documentation, export both formats: - -1. **Full Export (CODESYS XML):** - ``` - File: Home_Automation.export - - Complete project structure - - All configurations - - Network variables - - I/O mappings - ``` - -2. **Program Export (PLCopen XML):** - ``` - File: Home_Automation.xml - - Program structure - - Variable declarations - - Logic organization - ``` - -## After Export - -Once you have the exported XML files: - -1. **Share the files** - The XML files can be analyzed and documented -2. **Version control** - XML files are text-based and work well with Git -3. **Documentation** - Information can be extracted and added to the docs -4. **Analysis** - Scripts can parse the XML to extract specific information - -## Next Steps - -After exporting: - -1. Place exported files in the project directory -2. Update `.gitignore` if needed (or commit the exports) -3. Use the exported files to: - - Document network variables - - Map I/O configurations - - Understand control logic - - Complete the documentation templates - -## Example Export Workflow - -```bash -# In CODESYS IDE: -1. File → Open → "Home_Automation (1).project" -2. Project → Export - - Select: All objects - - Format: CODESYS XML (*.export) - - Save as: "Home_Automation.export" -3. Project → Export PLCopenXML - - Select: Programs, POUs - - Save as: "Home_Automation.xml" -``` - -## Notes - -- Export files are typically much larger than binary project files -- XML exports are human-readable and can be opened in any text editor -- Some CODESYS-specific features may not be fully represented in PLCopen XML -- Exported files can be re-imported into CODESYS if needed - ---- - -**Status:** Ready for export - follow the steps above to create readable project files diff --git a/docs/codesys/codesys-xml-analysis.md b/docs/codesys/codesys-xml-analysis.md deleted file mode 100644 index 1353abc..0000000 --- a/docs/codesys/codesys-xml-analysis.md +++ /dev/null @@ -1,260 +0,0 @@ -# CODESYS PLCopen XML Analysis - -## Overview - -This document contains detailed information extracted from the PLCopen XML export (`Home_Automation.xml`), which provides a more structured view of the project compared to the CODESYS export format. - -## Project Information - -- **Format**: PLCopen XML (TC6 0200) -- **CODESYS Version**: V3.5 SP21 -- **Export Date**: 2026-01-27T14:48:52 -- **Project Name**: Home_Automation.project - -## Task Configuration - -### MainTask -- **Type**: Cyclic -- **Interval**: t#4ms (4 milliseconds) -- **Priority**: 1 -- **Watchdog**: Disabled -- **Program**: PLC_PRG - -### EtherCAT_Task -- **Type**: Cyclic -- **Interval**: t#4ms (4 milliseconds) -- **Priority**: 1 -- **Watchdog**: Disabled -- **Purpose**: EtherCAT communication and network variable updates - -## Data Structures - -### struct_switches - -```iec -TYPE struct_switches : -STRUCT - all_off: BOOL; // Command to turn all lights off - all_on: BOOL; // Command to turn all lights on - sw_1: BOOL; // Switch 1 state - sw_2: BOOL; // Switch 2 state - sw_3: BOOL; // Switch 3 state - sw_4: BOOL; // Switch 4 state - sw_5: BOOL; // Switch 5 state - sw_6: BOOL; // Switch 6 state -END_STRUCT -END_TYPE -``` - -### struct_lights - -```iec -TYPE struct_lights: -STRUCT - l_1: BOOL; // Light 1 control output - l_2: BOOL; // Light 2 control output - l_3: BOOL; // Light 3 control output - l_4: BOOL; // Light 4 control output - l_5: BOOL; // Light 5 control output - l_6: BOOL; // Light 6 control output -END_STRUCT -END_TYPE -``` - -## Function Block: fb_toogleButton - -### Interface - -**Inputs**: -- `Input: BOOL` - Input signal (switch state from network) - -**Outputs**: -- `Output: BOOL` - Output signal (light control) - -**Internal Variables**: -- `Switch: BOOL` - Internal switch state -- `Light: BOOL` - Internal light state -- `Flag1: BOOL` - Internal flag 1 -- `Flag2: BOOL` - Internal flag 2 -- `RT: R_TRIG` - Rising edge trigger -- `Pulse: TP` - Pulse timer -- `CTU_0: CTU` - Counter up 0 -- `CTD_0: CTD` - Counter down 0 -- `CTU_1: CTU` - Counter up 1 -- `CTU_2: CTU` - Counter up 2 -- `TOF_0: TOF` - Timer off delay 0 - -### Implementation (Ladder Diagram) - -**Network 1: Input Debouncing** -- Input signal → TP (Pulse Timer) with PT = t#100ms -- TP output → TOF (Timer Off Delay) with PT = t#100ms -- TOF output → Switch (internal state) -- **Purpose**: Debounces input signal with 100ms pulse and 100ms off delay - -**Network 2: Flag1 Logic** -- Switch AND (NOT Light) AND (NOT Flag2) → Flag1 -- **Purpose**: Sets Flag1 when switch is on, light is off, and Flag2 is off - -**Network 3: Flag2 Logic** -- Switch AND Light AND (NOT Flag1) → Flag2 -- **Purpose**: Sets Flag2 when switch is on, light is on, and Flag1 is off - -**Network 4: Light Toggle Logic** -- Flag1 AND (NOT Flag2) → Light (toggle) -- **Purpose**: Toggles light state based on flag conditions - -**Network 5: Output Assignment** -- Light → Output -- **Purpose**: Direct output assignment - -### Timing Characteristics - -- **Input Debounce**: 100ms pulse timer (TP) -- **Output Delay**: 100ms off delay (TOF) -- **Total Response Time**: ~200ms (debounce + delay) - -## Function Block: fb_switch - -### Interface - -**Inputs**: -- `switches: struct_switches` - Switch states from Node-RED - -**Outputs**: -- `lights: struct_lights` - Light control outputs to Node-RED - -**Internal Variables**: -- `all_off: fb_toogleButton` - Toggle button for "all off" -- `all_on: fb_toogleButton` - Toggle button for "all on" -- `sw_1: fb_toogleButton` - Toggle button for switch 1 -- `sw_2: fb_toogleButton` - Toggle button for switch 2 -- `sw_3: fb_toogleButton` - Toggle button for switch 3 -- `sw_4: fb_toogleButton` - Toggle button for switch 4 -- `sw_5: fb_toogleButton` - Toggle button for switch 5 -- `sw_6: fb_toogleButton` - Toggle button for switch 6 - -### Implementation (Continuous Function Chart - CFC) - -**Logic Flow for Each Light (l_1 through l_6)**: - -1. **All Off Logic**: - ``` - all_off.Output AND lights.l_X → AND gate → (forces light off) - ``` - -2. **All On Logic**: - ``` - all_on.Output AND (NOT lights.l_X) → AND gate → (forces light on) - ``` - -3. **Individual Switch Logic**: - ``` - switches.sw_X → sw_X.Input → sw_X.Output → lights.l_X - ``` - -4. **Combined Logic (OR)**: - ``` - (all_on logic) OR (sw_X.Output) OR (other sources) → lights.l_X - ``` - -**Execution Order** (example for l_1): -1. Execution Order 1: AND gate for all_on logic -2. Execution Order 2: sw_2 toggle button -3. Execution Order 3: OR gate combining sources -4. Execution Order 4: AND gate for all_off logic -5. Execution Order 5: Output assignment to lights.l_1 -6. Execution Order 30: sw_1 toggle button (individual control) - -**Key Logic Patterns**: - -- **All Off**: Uses AND gate with `all_off.Output` and current light state -- **All On**: Uses AND gate with `all_on.Output` and inverted light state -- **Individual Control**: Each switch has its own toggle button instance -- **Priority**: All_off can override individual controls, all_on combines with individual - -## Program: Lights - -The "Lights" program contains instances of `fb_switch` for all 15 rooms: - -1. `masterBedroom: fb_switch` -2. `masterBathroom: fb_switch` -3. `bedroom_1: fb_switch` -4. `bedroom_2: fb_switch` -5. `bathroom: fb_switch` -6. `guest_wc: fb_switch` -7. `kitchen: fb_switch` -8. `pantry: fb_switch` -9. `livingRoom: fb_switch` -10. `diningRoom: fb_switch` -11. `veranda: fb_switch` -12. `entrance: fb_switch` -13. `front: fb_switch` -14. `back: fb_switch` -15. `side: fb_switch` -16. `hallway: fb_switch` - -## Network Variables - -### NVL_Sender (Sent to Node-RED) - -All 15 room light structures: -- `l_masterBedroom` through `l_outBack` (struct_lights) - -### NVL_Receiver (Received from Node-RED) - -All 15 room switch structures: -- `masterBedroom` through `outBack` (struct_switches) - -## Control Logic Summary - -### Individual Light Control - -1. **Switch Input** → `switches.sw_X` (from Node-RED) -2. **Toggle Processing** → `fb_toogleButton` instance - - 100ms debounce (TP) - - 100ms delay (TOF) - - Toggle logic with flags -3. **Output** → `lights.l_X` (to Node-RED) - -### Global Commands - -**All Off**: -- `switches.all_off` → `all_off` toggle button -- Output ANDed with current light state -- Forces all lights off - -**All On**: -- `switches.all_on` → `all_on` toggle button -- Output ANDed with inverted light state -- Forces all lights on -- Combined with individual switches via OR logic - -### Timing Characteristics - -- **Task Cycle**: 4ms (MainTask and EtherCAT_Task) -- **Input Debounce**: 100ms -- **Output Delay**: 100ms -- **Network Variable Update**: 50ms (from .export analysis) -- **Total Response**: ~200ms (debounce + delay) - -## Implementation Language - -- **fb_toogleButton**: Ladder Diagram (LD) -- **fb_switch**: Continuous Function Chart (CFC) -- **Lights Program**: Continuous Function Chart (CFC) - -## Key Insights - -1. **Debouncing**: All switch inputs are debounced with 100ms pulse timer -2. **Toggle Logic**: Uses flag-based state machine for reliable toggling -3. **Priority System**: All_off has priority over individual controls -4. **Combined Control**: All_on works in conjunction with individual switches -5. **Fast Response**: 4ms task cycle ensures responsive control -6. **Structured Design**: Each room is independent with its own fb_switch instance - ---- - -**Analysis Date**: January 27, 2026 -**Source File**: `Home_Automation.xml` -**Format**: PLCopen XML (TC6 0200) diff --git a/docs/integration/ha-to-nvl-function.js b/docs/integration/ha-to-nvl-function.js new file mode 100644 index 0000000..9f22f2b --- /dev/null +++ b/docs/integration/ha-to-nvl-function.js @@ -0,0 +1,31 @@ +// Writes to state.rooms.cmd_livingroom (NVL variable cmd_livingroom). +const ROOM_NAME = 'cmd_livingroom'; +const entityId = (msg.topic || '').toString(); +const parts = entityId.split('_'); +const last = parts[parts.length - 1] || ''; +const num = parseInt(last, 10); +const lightNum = (!isNaN(num) && num >= 1) ? Math.min(6, num) : 1; + +// Normalize state: HA / trigger-state can send payload as string, object with .state, or data.new_state.state +let rawState = msg.payload; +if (rawState !== null && typeof rawState === 'object' && rawState.state !== undefined) rawState = rawState.state; +if (msg.data && msg.data.new_state && msg.data.new_state.state !== undefined) rawState = msg.data.new_state.state; +const isOn = (rawState === 'on' || rawState === true); + +node.warn('[HA to NVL] topic=' + entityId + ' rawState=' + JSON.stringify(rawState) + ' → lightNum=' + lightNum + ' isOn=' + isOn); + +if (!flow.get('nvlInState')) flow.set('nvlInState', { rooms: {}, boiler: {} }); +const state = flow.get('nvlInState'); +if (!state.rooms[ROOM_NAME]) state.rooms[ROOM_NAME] = {}; +const r = state.rooms[ROOM_NAME]; +r['ha_l' + lightNum + '_on'] = false; +r['ha_l' + lightNum + '_off'] = false; +if (isOn) r['ha_l' + lightNum + '_on'] = true; +else r['ha_l' + lightNum + '_off'] = true; +flow.set('nvlInState', state); + +node.warn('[HA to NVL] set cmd_livingroom ha_l' + lightNum + '_' + (isOn ? 'on' : 'off') + '=true, buildAndSend'); + +// PLC uses R_TRIG (rising edge). If "off" still doesn’t work, add a flow: after this node, 80ms delay then clear ha_l*_off and buildAndSend again (pulse). +msg.payload = { buildAndSend: true }; +return msg; diff --git a/docs/integration/nodered-flows-analysis.md b/docs/integration/nodered-flows-analysis.md new file mode 100644 index 0000000..64d0839 --- /dev/null +++ b/docs/integration/nodered-flows-analysis.md @@ -0,0 +1,70 @@ +# Node-RED flows analysis + +Flows pulled from **root@10.20.30.12** (`/root/.node-red/flows.json`). +Snapshot: Feb 8, 2026. + +--- + +## Tabs (flow sheets) + +| Tab | Purpose (from nodes) | +|----------------|-----------------------------| +| **Flow 1** | CODESYS NVL (UDP), HA buttons, Room Lights subflow, dashboard links | +| Flow 2–7 | (Unnamed / per-room or feature) | +| **Living Room**| Living room–specific logic → **[Living Room flow doc](nodered-livingroom-flow.md)** | +| **Notifications** | Notifications (Telegram, etc.) | +| **Zigbee** | Zigbee2MQTT in/out, MQTT | +| **Scheduler** | cronplus, bigtimer, triggers | + +--- + +## Node counts (summary) + +| Category | Node types / count | +|-----------------|--------------------| +| **UI** | 128 ui_button, 17 ui_tab, 17 ui_group, 1 ui_base | +| **Logic** | 59 function, 12 change, 10 switch, 17 inject, 2 trigger, 2 delay, 2 bigtimer, 3 cronplus | +| **Home Assistant** | 47 ha-entity-config, 29 ha-button, 18 ha-binary-sensor, 15 ha-device-config, 5 api-current-state, 2 api-call-service, 1 server-state-changed, 1 poll-state, 1 trigger-state, 1 ha-sensor | +| **Zigbee** | 28 zigbee2mqtt-in, 9 zigbee2mqtt-out, 1 zigbee2mqtt-server, 1 zigbee2mqtt-get | +| **CODESYS / NVL** | 1 nvl-send, 1 nvl-receive, 1 nvl-datatypes, 1 udp in, 1 udp out | +| **Other** | 1 server (HA WebSocket), 1 mqtt-broker, 2 mqtt in, 9 http request, 2 telegram bot, 1 telegram sender, 1 telegram receiver, 43 debug, 29 link in, 29 link out | +| **Subflows** | Room Lights (57bd1b149526fcee), Room filter (4dc13919bcc9d676), Subflow 1 | + +--- + +## CODESYS connectivity (Flow 1) + +All NVL and UDP nodes are on tab **Flow 1** (`z": "46892ced77481340"`). + +| Node | Role | Config | +|-----------|------|--------| +| **udp out** | Send to PLC | `addr`: **10.20.30.5**, `port`: **1202** | +| **udp in** | Receive from PLC | `port`: **1202** | +| **nvl-send** | Build NVL_In payload | Feeds **udp out**. Uses `node-red-contrib-nvl` with inline definition (struct_switches × 15 rooms + cmd_livingroom struct_room_cmds). | +| **nvl-receive** | Parse NVL_Out | Triggered by **udp in**. Definition: 15 × struct_lights + **light_livingRoom** as struct_room_outs. | + +**Notes** + +- PLC IP is **10.20.30.5**; Node-RED uses the same port **1202** for both sending to and receiving from the PLC (CODESYS NVL Sender and Receiver may use the same port or different; confirm in CODESYS). +- nvl-receive uses **struct_lights** (6 BOOLs) for 15 rooms and **struct_room_outs** (6 outputs + 6 status) only for `light_livingRoom`. Your `codesys/src/NVL` docs use struct_room_outs for all rooms; the flow is a subset. +- Room Lights subflow (**Room Lights**) is used from Flow 1 (and possibly Living Room) to map HA/buttons to room/light commands. + +--- + +## Integrations + +- **Home Assistant**: WebSocket **server** config; many **ha-entity-config** / **ha-device-config**; **ha-button**, **ha-binary-sensor**; **api-current-state**, **api-call-service**. +- **Zigbee2MQTT**: One **zigbee2mqtt-server**; **zigbee2mqtt-in** (28) and **zigbee2mqtt-out** (9) for device events and commands. +- **MQTT**: One **mqtt-broker**; **mqtt in** (2) for other topics. +- **Telegram**: **telegram bot** (2), **telegram sender**, **telegram receiver** for notifications. +- **Dashboard**: **node-red-dashboard** (ui_base, ui_tab, ui_group, ui_button) for a simple UI. + +--- + +## File location + +- **Flows (full export):** [nodered-flows.json](nodered-flows.json) +- **This analysis:** [nodered-flows-analysis.md](nodered-flows-analysis.md) + +To refresh: from the project PC run +`ssh root@10.20.30.12 "cat /root/.node-red/flows.json" > docs/integration/nodered-flows.json` diff --git a/docs/integration/nodered-flows.json b/docs/integration/nodered-flows.json new file mode 100644 index 0000000..129c3ed --- /dev/null +++ b/docs/integration/nodered-flows.json @@ -0,0 +1,14504 @@ +[ + { + "id": "46892ced77481340", + "type": "tab", + "label": "Flow 1", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "480553bb6f5b2b51", + "type": "tab", + "label": "Notifications", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "f9a2fb576f0d360d", + "type": "tab", + "label": "Zigbee ", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "6722d185624fd57a", + "type": "tab", + "label": "Scheduler", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "7eef035843cf87db", + "type": "tab", + "label": "Flow 2", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "31693504fcbbd954", + "type": "tab", + "label": "Flow 3", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "50971c50479dfd7a", + "type": "tab", + "label": "Flow 4", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "a6a28d6b6c0b4f4a", + "type": "tab", + "label": "Flow 5", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "b673e10beab091ff", + "type": "tab", + "label": "Flow 6", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "24e9a56732cfa6f1", + "type": "tab", + "label": "Flow 7", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "7de41d810b04d992", + "type": "tab", + "label": "Living Room", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "57bd1b149526fcee", + "type": "subflow", + "name": "Room Lights", + "info": "", + "category": "", + "in": [ + { + "x": 260, + "y": 220, + "wires": [ + { + "id": "ee7c2a47d6441c26" + } + ] + } + ], + "out": [ + { + "x": 560, + "y": 120, + "wires": [ + { + "id": "ee7c2a47d6441c26", + "port": 0 + } + ] + }, + { + "x": 560, + "y": 160, + "wires": [ + { + "id": "ee7c2a47d6441c26", + "port": 1 + } + ] + }, + { + "x": 560, + "y": 200, + "wires": [ + { + "id": "ee7c2a47d6441c26", + "port": 2 + } + ] + }, + { + "x": 560, + "y": 240, + "wires": [ + { + "id": "ee7c2a47d6441c26", + "port": 3 + } + ] + }, + { + "x": 560, + "y": 280, + "wires": [ + { + "id": "ee7c2a47d6441c26", + "port": 4 + } + ] + }, + { + "x": 560, + "y": 320, + "wires": [ + { + "id": "ee7c2a47d6441c26", + "port": 5 + } + ] + } + ], + "env": [ + { + "name": "room_name", + "type": "str", + "value": "" + } + ], + "meta": {}, + "color": "#DDAA99" + }, + { + "id": "d0de479f6b80ab6c", + "type": "subflow", + "name": "Subflow 1", + "info": "", + "in": [ + { + "x": 60, + "y": 80, + "wires": [ + { + "id": "f9557fd9a8b2eee2" + } + ] + } + ], + "out": [ + { + "x": 340, + "y": 80, + "wires": [ + { + "id": "f9557fd9a8b2eee2", + "port": 0 + } + ] + } + ] + }, + { + "id": "4dc13919bcc9d676", + "type": "subflow", + "name": "Room filter", + "info": "", + "category": "", + "in": [ + { + "x": 60, + "y": 80, + "wires": [ + { + "id": "d2a3760f36aa6f41" + } + ] + } + ], + "out": [ + { + "x": 640, + "y": 100, + "wires": [ + { + "id": "0ce841560c8c4087", + "port": 0 + }, + { + "id": "64b045db5a4541fa", + "port": 0 + }, + { + "id": "4a891a7505b5fd00", + "port": 0 + }, + { + "id": "258a84ec1d54a43a", + "port": 0 + }, + { + "id": "5532273136423869", + "port": 0 + } + ] + } + ], + "env": [ + { + "name": "switch_1", + "type": "str", + "value": ".sw_1" + }, + { + "name": "switch_2", + "type": "str", + "value": ".sw_2" + }, + { + "name": "switch_3", + "type": "str", + "value": ".sw_3" + }, + { + "name": "switch_4", + "type": "str", + "value": ".sw_4" + }, + { + "name": "single", + "type": "str", + "value": ".sw_1" + } + ], + "meta": {}, + "color": "#DDAA99" + }, + { + "id": "664de832a54bc4e0", + "type": "group", + "z": "46892ced77481340", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "039e1be0cfe8e630", + "740b766e9bdc0d2a" + ], + "x": 1934, + "y": 179, + "w": 292, + "h": 82 + }, + { + "id": "f27a48975d61e376", + "type": "group", + "z": "46892ced77481340", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "e37aa132859c1ea8", + "d3f0c80e0188aabf", + "1a534f2c5be60889" + ], + "x": 2244, + "y": 179, + "w": 502, + "h": 152 + }, + { + "id": "9113c5ebf5c089e3", + "type": "group", + "z": "480553bb6f5b2b51", + "name": "Door Sensor Notifications", + "style": { + "fill": "#addb7b", + "fill-opacity": "0.89", + "label": true, + "stroke": "#ffffff", + "stroke-opacity": "0" + }, + "nodes": [ + "0385d24efa18d698", + "9d611c0ff2a0b1ca", + "10448f46514c1406", + "a9d786b1ba52704b", + "e250cc2f901bea9f", + "5e94eb39aec6e82d", + "8b8cce25637907b1", + "3f9797c13c3a11a1", + "7301d71523d87e94", + "4055425d6c42dd13", + "ada6fe8339eab29b", + "8e0d5ec04f029b9a", + "eb25ece689ea1045", + "63cb264d89898c6a", + "bd4b7dd437c1aaaf", + "f6adcaa35a793e58" + ], + "x": 34, + "y": -1, + "w": 1562, + "h": 302 + }, + { + "id": "91b1f4cb0e1ffa84", + "type": "group", + "z": "480553bb6f5b2b51", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "d592161c8e50638a", + "2a218aee475ce267", + "f5ef8cb0a1bc7def" + ], + "x": 2234, + "y": 39, + "w": 412, + "h": 122 + }, + { + "id": "d24bcb9ea3d9711c", + "type": "group", + "z": "46892ced77481340", + "name": "", + "style": { + "fill": "#ffdf7f", + "label": true + }, + "nodes": [ + "925b79f58f75e449", + "b88648a578b60cb8", + "4a9361ba088ba046", + "0271d58953459100", + "4b360c7d54e39e0a", + "62cc8431c77a4797", + "2d6eb3ac6de382be", + "af99b05ba44c6741", + "3c7f8838aa2349b9", + "0b0b27deacee2bfc" + ], + "x": 38, + "y": 653, + "w": 1634, + "h": 684 + }, + { + "id": "7c29230c98070fd5", + "type": "group", + "z": "46892ced77481340", + "name": "", + "style": { + "fill": "#bfdbef", + "label": true, + "color": "#000000" + }, + "nodes": [ + "ff517137dc8eb4e5", + "2b9f0ac7a58d6e99", + "007a0818e00b6e8a", + "42040eb51b6909d2", + "888d856dae38ce27", + "2ba7fb1e189ec017", + "3cc3b21e7b1f7b13", + "f16b851b413f0d32", + "1a348789bcd14214", + "8044f4e5402ca464", + "f83a4ef2662f4f11", + "48b019b444db2a5a", + "639e1c2545c2de96" + ], + "x": 48, + "y": 133, + "w": 1764, + "h": 494 + }, + { + "id": "41609eab5db77f0a", + "type": "group", + "z": "f9a2fb576f0d360d", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "36d1d2c1e10100e7", + "4c584ffba6a17ccb", + "c53543186fd45d8b", + "33eb6e0725214c0b", + "5df52e7f8217a6d9", + "6dd69a9285fc9583", + "3e5fabb6aa93449c", + "aadcf8d1921d77e6" + ], + "x": 94, + "y": 2019, + "w": 1272, + "h": 162 + }, + { + "id": "b6271f4a3284f8d9", + "type": "group", + "z": "f9a2fb576f0d360d", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "46517eb4342b3d18", + "a3d5be29d6effa34", + "d23c32a704af639d", + "68f7d3015de2dbab", + "701c1cd70874e615" + ], + "x": 94, + "y": 1879, + "w": 1272, + "h": 122 + }, + { + "id": "3db6f7ccc63a5f8b", + "type": "group", + "z": "f9a2fb576f0d360d", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "1633e8e6861be2e0", + "9c36d1335b0f8ebb", + "fe5612a41399f1f5", + "7f48ed25c8ec1fce", + "ab17cf6a7d65fcb9" + ], + "x": 94, + "y": 1699, + "w": 1272, + "h": 162 + }, + { + "id": "5270af8a6312b29d", + "type": "group", + "z": "f9a2fb576f0d360d", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "25cf5d71108cbbb4", + "cbcd3d741247b12b", + "07e63813fb04b906", + "4d3d4a82a512ca10", + "0bf96ec77f6d1809" + ], + "x": 94, + "y": 1459, + "w": 1272, + "h": 122 + }, + { + "id": "6809b1c79783ab0c", + "type": "group", + "z": "f9a2fb576f0d360d", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "a330bf2f4d26c536", + "398c06a48e4daa13" + ], + "x": 94, + "y": 1599, + "w": 1272, + "h": 82 + }, + { + "id": "1166ae03751d953e", + "type": "group", + "z": "f9a2fb576f0d360d", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "ef6df11582a22110", + "774c15def7e6cf96" + ], + "x": 94, + "y": 1359, + "w": 1272, + "h": 82 + }, + { + "id": "fb2d97888a83fc98", + "type": "group", + "z": "f9a2fb576f0d360d", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "6f028b285108ff17", + "82c1d5f7ae54b348" + ], + "x": 94, + "y": 1259, + "w": 1272, + "h": 82 + }, + { + "id": "5b22d521e762f0da", + "type": "group", + "z": "f9a2fb576f0d360d", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "6e30880884a2cdb2", + "da7ab159be01b35c" + ], + "x": 94, + "y": 1159, + "w": 1272, + "h": 82 + }, + { + "id": "74596bdef53b62cf", + "type": "group", + "z": "f9a2fb576f0d360d", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "0bd6051323b66072", + "6aac621e2a9acf79" + ], + "x": 94, + "y": 1059, + "w": 1272, + "h": 82 + }, + { + "id": "5d202845936f0275", + "type": "group", + "z": "f9a2fb576f0d360d", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "becbc51cb810ff3d", + "4673e2e3f2c0626f" + ], + "x": 94, + "y": 959, + "w": 1272, + "h": 82 + }, + { + "id": "68db2262f7c7313e", + "type": "group", + "z": "f9a2fb576f0d360d", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "675ba75711ef84e7", + "84272551b322cde2" + ], + "x": 94, + "y": 859, + "w": 1272, + "h": 82 + }, + { + "id": "39f557d8eec2fbae", + "type": "group", + "z": "f9a2fb576f0d360d", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "a6cf1bd4ae01ee56", + "16eaca15570ff092", + "109c390c131b09a8", + "d02a9d4e0468bc67", + "2d6bcc49a2785fd6" + ], + "x": 94, + "y": 379, + "w": 1272, + "h": 122 + }, + { + "id": "795d604329d51272", + "type": "group", + "z": "f9a2fb576f0d360d", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "1aa9e0ff37d1afee", + "b9c07d22cbaae6a2" + ], + "x": 94, + "y": 279, + "w": 1272, + "h": 82 + }, + { + "id": "4b06cbc568091f6f", + "type": "group", + "z": "f9a2fb576f0d360d", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "6654c150377ed2bb", + "83bbabef65831016" + ], + "x": 94, + "y": 179, + "w": 1272, + "h": 82 + }, + { + "id": "b5ffb4a4212da258", + "type": "group", + "z": "f9a2fb576f0d360d", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "ca1a1f7f8b90cfc9", + "b8f3d3242fb39bdd" + ], + "x": 94, + "y": 79, + "w": 1272, + "h": 82 + }, + { + "id": "5c95d99a679a5e00", + "type": "group", + "z": "480553bb6f5b2b51", + "name": "Water Leak Notifications", + "style": { + "label": true, + "fill": "#0070c0", + "fill-opacity": "0.54", + "stroke": "#000000", + "color": "#000000" + }, + "nodes": [ + "8f42e8fdfef40579", + "58ee1ac91f379cb9", + "a1f1e1ea2cf90f61", + "3242605bce7f6f92", + "22740e52f6cc3399", + "53799a37bb6cf7c6", + "8b9761ea2f86b4f1", + "70f797398b0e6783", + "2157cdd444dfec91", + "8b2f06afcf0d773e", + "e31ec1ce332c4c99", + "1164cfc4fd2e5382", + "748c39a404648158", + "a73395db2426b4fc" + ], + "x": 54, + "y": 379, + "w": 1562, + "h": 288 + }, + { + "id": "1c73b8aaec035f06", + "type": "group", + "z": "480553bb6f5b2b51", + "name": "Battery notifications", + "style": { + "fill": "#ffbfbf", + "label": true, + "color": "#000000" + }, + "nodes": [ + "eb721beae24b271b", + "41f1e9feaf1ea6aa", + "c60307d3b68ebe34", + "81d16e7a5307e9cc", + "209d79611a5414cc", + "e6a93b0cbc48af98", + "a9f755806e4e8a75", + "5c2272c244ffc8b0", + "6d60df5c0c6920d2" + ], + "x": 54, + "y": 699, + "w": 1562, + "h": 162 + }, + { + "id": "bd16cebc90c528e9", + "type": "group", + "z": "46892ced77481340", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "b4e49387d80fa951", + "860779317fad91b5", + "2ae3fedce613ecfe", + "1a8d95afd537e151", + "d1c2e1ee1fba88c6" + ], + "x": 1944, + "y": 319, + "w": 482, + "h": 232 + }, + { + "id": "507ca4f1b82ad9c2", + "type": "group", + "z": "46892ced77481340", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "66255b06a0d4a4c9", + "9c99ecea6e4aefd8" + ], + "x": 1954, + "y": 559, + "w": 292, + "h": 82 + }, + { + "id": "cc230a0894b116d3", + "type": "group", + "z": "6722d185624fd57a", + "name": "", + "style": { + "fill": "#7fb7df", + "label": true + }, + "nodes": [ + "05ecf6f492d4a8cf", + "ad81a0918c31a862", + "7049d2e629127b0e", + "e44fa1b081b4aba7", + "532476a808456423", + "9e01966beafd582b", + "301e955ac79a9326", + "54161a78e09f85c3", + "0fdf497d70900d11", + "434ef7e821c63223" + ], + "x": 134, + "y": 939, + "w": 992, + "h": 277 + }, + { + "id": "b73455843d7a5a9f", + "type": "group", + "z": "6722d185624fd57a", + "name": "Watering Schedule", + "style": { + "label": true, + "fill": "#b797cf" + }, + "nodes": [ + "905629455f4dd222", + "33e972f98673a6b6", + "ab7182ad5c37be48", + "d51ae77e82bb86f0", + "b43dc5183a67d128", + "2896d687417bbc1d", + "1199fd928d0abd32", + "86153b2dba01756f" + ], + "x": 54, + "y": 379, + "w": 1432, + "h": 242 + }, + { + "id": "ef555db28dba4c46", + "type": "group", + "z": "f9a2fb576f0d360d", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "0b112f9f314fa0bb", + "a1f5a94baf1fbcd7", + "ae314d3d3457b679", + "0c35b08a3c7d347f", + "f0de9c882920d07c" + ], + "x": 94, + "y": 519, + "w": 1272, + "h": 122 + }, + { + "id": "1756bd5d4ff2c23b", + "type": "group", + "z": "f9a2fb576f0d360d", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "310f749a07eded9d", + "d0febe343ef897ac", + "e1965a78e430acad", + "8cce4a0a7061ac87", + "d5cad6d8ca5fe08c" + ], + "x": 94, + "y": 2199, + "w": 1272, + "h": 162 + }, + { + "id": "860e99939cf4fb5c", + "type": "group", + "z": "6722d185624fd57a", + "name": "Christmas Light", + "style": { + "label": true + }, + "nodes": [ + "d97b1b86e0e0040e", + "3737a8c897de2221", + "ca74d224370a0b5a", + "3a8262144b91d661", + "d20a28e1dea2fcde", + "01df5278eead8a98", + "1460ed0d062f8b73", + "6fdd0a95dd76a2e8", + "bb1639973b4a6a05", + "800cb2f6a21c882f", + "893d9228d166c0d2", + "be86177040153c9f" + ], + "x": 654, + "y": 1679, + "w": 1272, + "h": 382 + }, + { + "id": "ce79bea4667a77e2", + "type": "group", + "z": "480553bb6f5b2b51", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "8656a450e0ae020d", + "7ff8359abb38dca4", + "doorbell_state_check", + "bd7fed2198a7b950", + "78a1c2a40934ef20", + "05f0d77a9b605f22", + "19d0e8d8c5addb1f", + "eff49303c6d84242", + "c74ddf6b84ef7a95", + "d496d62222d1a073", + "48bfe0ad8a973a84", + "88d855e525ab78f0", + "a4752681740af47e", + "7e88e31d094e343d", + "365adc2679063bc8", + "842a49fa21a0587a", + "bedee5f9397c27f1", + "3f9c2db4dfea3095", + "0446448ed8c8da8a", + "d241b1f4991cfc05" + ], + "x": 14, + "y": 1129, + "w": 2362, + "h": 272 + }, + { + "id": "9da6393897881dca", + "type": "group", + "z": "46892ced77481340", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "5351a670ea3bcf15", + "8dd17125d6bb49f7", + "9b4f318fa5f438aa" + ], + "x": 1764, + "y": 839, + "w": 522, + "h": 152 + }, + { + "id": "ff517137dc8eb4e5", + "type": "group", + "z": "46892ced77481340", + "g": "7c29230c98070fd5", + "name": "Office ", + "style": { + "label": true + }, + "nodes": [ + "a91dcb332bae2807", + "11f594c5b9f0fce8" + ], + "x": 354, + "y": 379, + "w": 312, + "h": 102 + }, + { + "id": "2b9f0ac7a58d6e99", + "type": "group", + "z": "46892ced77481340", + "g": "7c29230c98070fd5", + "name": "Shower", + "style": { + "label": true + }, + "nodes": [ + "a2eceb1ef60667cb", + "102d7d54a45e3f18", + "a024d8a903074aa4", + "d680ff6c7ac109cf", + "9dc26f31e8b58f14" + ], + "x": 1034, + "y": 259, + "w": 402, + "h": 222 + }, + { + "id": "007a0818e00b6e8a", + "type": "group", + "z": "46892ced77481340", + "g": "7c29230c98070fd5", + "name": "Master Bedroom", + "style": { + "label": true + }, + "nodes": [ + "67149a39f56c5694", + "d5dacec70d47b2e5", + "28d2293b97a7b72b", + "a6493ad8864f2b68" + ], + "x": 354, + "y": 159, + "w": 312, + "h": 202 + }, + { + "id": "42040eb51b6909d2", + "type": "group", + "z": "46892ced77481340", + "g": "7c29230c98070fd5", + "name": "Kitchen", + "style": { + "label": true, + "fill": "#d1d1d1", + "color": "#000000" + }, + "nodes": [ + "e9aa8b33edf056f6", + "eda94d52e6cf650a", + "188a3b16710467e7", + "2dc64fabc1602ec8", + "a1f44cc63b77a2c3" + ], + "x": 74, + "y": 159, + "w": 262, + "h": 422 + }, + { + "id": "888d856dae38ce27", + "type": "group", + "z": "46892ced77481340", + "g": "7c29230c98070fd5", + "name": "Veranda", + "style": { + "label": true + }, + "nodes": [ + "f714485f125865af", + "2565695beaa14d76", + "9594045be414b84c" + ], + "x": 1434, + "y": 159, + "w": 352, + "h": 142 + }, + { + "id": "2ba7fb1e189ec017", + "type": "group", + "z": "46892ced77481340", + "g": "7c29230c98070fd5", + "name": "Living Room ", + "style": { + "label": true + }, + "nodes": [ + "58525896ff637a27", + "beeb512081d25291" + ], + "x": 694, + "y": 379, + "w": 312, + "h": 82 + }, + { + "id": "3cc3b21e7b1f7b13", + "type": "group", + "z": "46892ced77481340", + "g": "7c29230c98070fd5", + "name": "Dining Room", + "style": { + "label": true + }, + "nodes": [ + "66cafcf3b261e8b5", + "04e183fcfe9e9f6d", + "1f3ba1a73e155da9", + "6190a90971a43aba" + ], + "x": 694, + "y": 159, + "w": 312, + "h": 202 + }, + { + "id": "f16b851b413f0d32", + "type": "group", + "z": "46892ced77481340", + "g": "7c29230c98070fd5", + "name": "Guest WC", + "style": { + "label": true + }, + "nodes": [ + "f1cf19264e9957f3", + "0d7f6d11034f98ee" + ], + "x": 1034, + "y": 499, + "w": 342, + "h": 82 + }, + { + "id": "1a348789bcd14214", + "type": "group", + "z": "46892ced77481340", + "g": "7c29230c98070fd5", + "name": "Back yard", + "style": { + "label": true + }, + "nodes": [ + "518a9fd6f52bfc23", + "839bb9d987729d16" + ], + "x": 1434, + "y": 419, + "w": 352, + "h": 82 + }, + { + "id": "8044f4e5402ca464", + "type": "group", + "z": "46892ced77481340", + "g": "7c29230c98070fd5", + "name": "Master Bathroom", + "style": { + "label": true + }, + "nodes": [ + "0f93e95f3e92150a", + "ed3b84f4d63a8bd5" + ], + "x": 1034, + "y": 159, + "w": 342, + "h": 82 + }, + { + "id": "f83a4ef2662f4f11", + "type": "group", + "z": "46892ced77481340", + "g": "7c29230c98070fd5", + "name": "Front Yard", + "style": { + "label": true + }, + "nodes": [ + "194c35655de1a128", + "45e37c698b0b2e5a" + ], + "x": 1434, + "y": 319, + "w": 352, + "h": 82 + }, + { + "id": "48b019b444db2a5a", + "type": "group", + "z": "46892ced77481340", + "g": "7c29230c98070fd5", + "name": "Drive ways", + "style": { + "label": true + }, + "nodes": [ + "bbda8fd42d8aa120", + "01da9e8ebba7f592" + ], + "x": 1434, + "y": 519, + "w": 352, + "h": 82 + }, + { + "id": "925b79f58f75e449", + "type": "group", + "z": "46892ced77481340", + "g": "d24bcb9ea3d9711c", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "40fd804bb4acf519", + "b9ab51a28b433f94", + "0d1a60ec03adac5c" + ], + "x": 64, + "y": 999, + "w": 522, + "h": 152 + }, + { + "id": "b88648a578b60cb8", + "type": "group", + "z": "46892ced77481340", + "g": "d24bcb9ea3d9711c", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "1feb11ab8be3ef2e", + "01b4eb4b91f9c9e2", + "1ee2b22951639d6b" + ], + "x": 64, + "y": 839, + "w": 522, + "h": 152 + }, + { + "id": "4a9361ba088ba046", + "type": "group", + "z": "46892ced77481340", + "g": "d24bcb9ea3d9711c", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "3c894953f414add5", + "7ec85095958da9f5", + "566e5b2ec261934f", + "89762968e1556a42" + ], + "x": 64, + "y": 679, + "w": 522, + "h": 152 + }, + { + "id": "0271d58953459100", + "type": "group", + "z": "46892ced77481340", + "g": "d24bcb9ea3d9711c", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "af7220d8f5bb9bf2", + "b51586d393da96cd", + "e8db33fc81400531", + "e0739e7f8c82e487", + "1530556181903c3a" + ], + "x": 624, + "y": 1099, + "w": 482, + "h": 202 + }, + { + "id": "4b360c7d54e39e0a", + "type": "group", + "z": "46892ced77481340", + "g": "d24bcb9ea3d9711c", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "53178ccb3f691dad", + "b2641f71595c1d2d", + "ba2859e1aa882eee" + ], + "x": 624, + "y": 919, + "w": 482, + "h": 152 + }, + { + "id": "62cc8431c77a4797", + "type": "group", + "z": "46892ced77481340", + "g": "d24bcb9ea3d9711c", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "b664207011dd548a", + "39d435c6a32d4f09", + "5f7ffd82e5200d9a", + "f0bc3094d4af717e", + "0a4414fc667405b4", + "22c73018f4564256" + ], + "x": 624, + "y": 679, + "w": 482, + "h": 262 + }, + { + "id": "2d6eb3ac6de382be", + "type": "group", + "z": "46892ced77481340", + "g": "d24bcb9ea3d9711c", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "e6e67dc471f2ecb3", + "fc5020edbd894baa", + "29af11e532f234d4" + ], + "x": 1164, + "y": 839, + "w": 482, + "h": 152 + }, + { + "id": "af99b05ba44c6741", + "type": "group", + "z": "46892ced77481340", + "g": "d24bcb9ea3d9711c", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "b7e6812909ef1051", + "37cdfd56758051ef", + "280e6cc7a9de62b5" + ], + "x": 1164, + "y": 679, + "w": 482, + "h": 152 + }, + { + "id": "3c7f8838aa2349b9", + "type": "group", + "z": "46892ced77481340", + "g": "d24bcb9ea3d9711c", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "806720bc7ab2d4e8", + "3b5e1f4f9ccd9c74", + "07bae699fea75512" + ], + "x": 1164, + "y": 999, + "w": 482, + "h": 152 + }, + { + "id": "0b0b27deacee2bfc", + "type": "group", + "z": "46892ced77481340", + "g": "d24bcb9ea3d9711c", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "3ec8968914e324f5", + "2b29c71c180a50df", + "197fc35230069190" + ], + "x": 64, + "y": 1159, + "w": 522, + "h": 152 + }, + { + "id": "639e1c2545c2de96", + "type": "group", + "z": "46892ced77481340", + "g": "7c29230c98070fd5", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "a8ac75d5944a28b9", + "f11fa2bedfc41e41" + ], + "x": 354, + "y": 499, + "w": 292, + "h": 82 + }, + { + "id": "a73395db2426b4fc", + "type": "group", + "z": "480553bb6f5b2b51", + "g": "5c95d99a679a5e00", + "name": "Watering Schedule Notification", + "style": { + "label": true, + "fill": "#c8e7a7", + "color": "#000000" + }, + "nodes": [ + "c57ea029ee569428", + "fe7372c7f33d96f3", + "c1d2a4dc34b63730", + "141f635c6c9d380f", + "18454d6cad2042dc" + ], + "x": 474, + "y": 499, + "w": 622, + "h": 142 + }, + { + "id": "4e20fc347c658518", + "type": "zigbee2mqtt-server", + "name": "", + "host": "10.20.30.6", + "mqtt_port": 1883, + "mqtt_username": "nodered", + "mqtt_password": "sh1pb0x1", + "mqtt_qos": "0", + "tls": "", + "usetls": false, + "base_topic": "zigbee2mqtt" + }, + { + "id": "29eb84a2708d96e0", + "type": "server", + "name": "Home Assistant", + "version": 6, + "addon": false, + "rejectUnauthorizedCerts": true, + "ha_boolean": [ + "y", + "yes", + "true", + "on", + "home", + "open" + ], + "connectionDelay": true, + "cacheJson": true, + "heartbeat": false, + "heartbeatInterval": 30, + "areaSelector": "friendlyName", + "deviceSelector": "friendlyName", + "entitySelector": "friendlyName", + "statusSeparator": ": ", + "statusYear": "hidden", + "statusMonth": "short", + "statusDay": "numeric", + "statusHourCycle": "default", + "statusTimeFormat": "h:m", + "enableGlobalContextStore": false + }, + { + "id": "70fca1d2a3467c93", + "type": "ha-device-config", + "name": "Office", + "hwVersion": "", + "manufacturer": "Node-RED", + "model": "", + "swVersion": "" + }, + { + "id": "7fe55db85a29f18c", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "70fca1d2a3467c93", + "name": "Office Light", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Office Light" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "4f23c8e7043f542b", + "type": "ha-device-config", + "name": "Master Bedroom", + "hwVersion": "", + "manufacturer": "Node-RED", + "model": "", + "swVersion": "" + }, + { + "id": "5e7f6287035ecc94", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "4f23c8e7043f542b", + "name": "Master Bedroom Light", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Master Bedroom Light" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "b2c7564cfe776a2d", + "type": "nvl-datatypes", + "name": "Example types", + "datatypes": "//Structure definition\nTYPE ST_MyStruct:\nSTRUCT\n xBool: BOOL;\n bByte: BYTE;\nEND_STRUCT\nEND_TYPE\n\n//Enumeration definition\nTYPE ENUM_Light: (\n Red,\n Orange,\n Green := 10\n);\nEND_TYPE" + }, + { + "id": "17b3adc633ee3394", + "type": "ha-device-config", + "name": "Shower", + "hwVersion": "", + "manufacturer": "Node-RED", + "model": "", + "swVersion": "" + }, + { + "id": "88f51f9c233f4cc3", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "17b3adc633ee3394", + "name": "Shower Light", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Shower Light" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "3cec5437caacb905", + "type": "ha-device-config", + "name": "Kitchen", + "hwVersion": "", + "manufacturer": "Node-RED", + "model": "", + "swVersion": "" + }, + { + "id": "29592d5555367344", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "3cec5437caacb905", + "name": "Kitchen Island", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Kitchen Island Light" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "cc2640502094371a", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "3cec5437caacb905", + "name": "Kitchen Main Light", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Kitchen Main Light" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "8e22cfb83ec013d1", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "3cec5437caacb905", + "name": "Fridge Light", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Fridge Light" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "085d42240f91eec2", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "3cec5437caacb905", + "name": "Pantry Light", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Pantry Light" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "451e5988101b1aac", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "4f23c8e7043f542b", + "name": "Master Bedroom Door Light", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Master Bedroom Door Light" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "c21001f594a359c9", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "4f23c8e7043f542b", + "name": "Master Bedroom Dresser Light", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Master Bedroom Dresser Light" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "29f5afd91874c0b8", + "type": "ha-device-config", + "name": "Veranda", + "hwVersion": "", + "manufacturer": "Node-RED", + "model": "", + "swVersion": "" + }, + { + "id": "2a94bae266d9423d", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "29f5afd91874c0b8", + "name": "Veranda Light", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Veranda Light" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "d721225e966ecf1b", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "29f5afd91874c0b8", + "name": "Veranda BBQ Light", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Veranda BBQ Light" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "2380526f974b097d", + "type": "ha-device-config", + "name": "Living Room", + "hwVersion": "", + "manufacturer": "Node-RED", + "model": "", + "swVersion": "" + }, + { + "id": "eca0cca02aabd143", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "2380526f974b097d", + "name": "Living Room Main Light", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Living Room Main Light" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "5d56cc73c39843cc", + "type": "ha-device-config", + "name": "Dining Room", + "hwVersion": "", + "manufacturer": "Node-RED", + "model": "", + "swVersion": "" + }, + { + "id": "8c493a05a6971f90", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "5d56cc73c39843cc", + "name": "Dining Room Main Light", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Dining Room Main Light" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "9f0f51223282478b", + "type": "ha-device-config", + "name": "Guest WC", + "hwVersion": "", + "manufacturer": "Node-RED", + "model": "", + "swVersion": "" + }, + { + "id": "85fe098bb4010c10", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "9f0f51223282478b", + "name": "Guest WC Light", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Guest WC Light" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "87957c9f7179fe85", + "type": "ha-device-config", + "name": "Master Bathroom", + "hwVersion": "", + "manufacturer": "Node-RED", + "model": "", + "swVersion": "" + }, + { + "id": "ac7e5f5dfeb9ce54", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "87957c9f7179fe85", + "name": "Master Bathroom Main Light", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Master Bathroom Main Light" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "d46352482122dfe6", + "type": "ha-device-config", + "name": "Front Yard", + "hwVersion": "", + "manufacturer": "Node-RED", + "model": "", + "swVersion": "" + }, + { + "id": "aa9cf2e1dc39616f", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "d46352482122dfe6", + "name": "Front Entrance Light", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Front Entrance Light" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "be89dbc3425f9cc0", + "type": "ha-device-config", + "name": "Back Yard", + "hwVersion": "", + "manufacturer": "Node-RED", + "model": "", + "swVersion": "" + }, + { + "id": "a3fb2a27c9896974", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "be89dbc3425f9cc0", + "name": "Flood Light 1", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Flood Light 1" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "a1743637a1619654", + "type": "ha-device-config", + "name": "Driveways", + "hwVersion": "", + "manufacturer": "Node-RED", + "model": "", + "swVersion": "" + }, + { + "id": "a97f2813611faac8", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "a1743637a1619654", + "name": "Driveway Flood Light", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Driveway Flood Light" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "f744459771e70676", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "70fca1d2a3467c93", + "name": "Office Light", + "version": 6, + "entityType": "binary_sensor", + "haConfig": [ + { + "property": "name", + "value": "Office Light" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "2b453fcb286e51f5", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "4f23c8e7043f542b", + "name": "Master Bedroom Main Light", + "version": 6, + "entityType": "binary_sensor", + "haConfig": [ + { + "property": "name", + "value": "Master Bedroom Main Light" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "edf39a33a28182ca", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "87957c9f7179fe85", + "name": "Master Bathroom Main Light", + "version": 6, + "entityType": "binary_sensor", + "haConfig": [ + { + "property": "name", + "value": "Master Bathroom Main Light" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "16560fc93e9523d4", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "17b3adc633ee3394", + "name": "Shower Main Light", + "version": 6, + "entityType": "binary_sensor", + "haConfig": [ + { + "property": "name", + "value": "Shower Main Light" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "9b2dc1266b7513da", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "17b3adc633ee3394", + "name": "Shower Fan", + "version": 6, + "entityType": "binary_sensor", + "haConfig": [ + { + "property": "name", + "value": "Shower Fan" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "12e315808eced5db", + "type": "ha-device-config", + "name": "Bedroom", + "hwVersion": "", + "manufacturer": "Node-RED", + "model": "", + "swVersion": "" + }, + { + "id": "ac2dba905819562f", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "17b3adc633ee3394", + "name": "Shower Fan Switch", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Shower Fan Switch" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "c494e187e88c3208", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "3cec5437caacb905", + "name": "Pantry Light", + "version": 6, + "entityType": "binary_sensor", + "haConfig": [ + { + "property": "name", + "value": "Pantry Light" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "293fc06a888710c5", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "12e315808eced5db", + "name": "Bedroom Main Light", + "version": 6, + "entityType": "binary_sensor", + "haConfig": [ + { + "property": "name", + "value": "Bedroom Main Light" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "1a22f89094ea7b38", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "29f5afd91874c0b8", + "name": "Veranda BBQ Light", + "version": 6, + "entityType": "binary_sensor", + "haConfig": [ + { + "property": "name", + "value": "Veranda BBQ Light" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "8b4d5408fdcd8c0d", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "12e315808eced5db", + "name": "Bedroom Main Light", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Bedroom Main Light" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "f190a69cf72af140", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "5d56cc73c39843cc", + "name": "Dining Room Main", + "version": 6, + "entityType": "binary_sensor", + "haConfig": [ + { + "property": "name", + "value": "Dining Room Main" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "b43c6846b0baeeec", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "2380526f974b097d", + "name": "Living Room Main", + "version": 6, + "entityType": "binary_sensor", + "haConfig": [ + { + "property": "name", + "value": "Living Room Main" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "be947f3b20336437", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "3cec5437caacb905", + "name": "Kitchen Main", + "version": 6, + "entityType": "binary_sensor", + "haConfig": [ + { + "property": "name", + "value": "Kitchen Main" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "9b99cd4af3bdb9a9", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "3cec5437caacb905", + "name": "Kitchen Island", + "version": 6, + "entityType": "binary_sensor", + "haConfig": [ + { + "property": "name", + "value": "Kitchen Island" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "d75c31dd7a1edcbf", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "3cec5437caacb905", + "name": "Fridge Light", + "version": 6, + "entityType": "binary_sensor", + "haConfig": [ + { + "property": "name", + "value": "Fridge Light" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "3dbb887330025103", + "type": "telegram bot", + "botname": "spito_robot", + "usernames": "nearxos", + "chatids": "4747176681", + "baseapiurl": "", + "testenvironment": false, + "updatemode": "polling", + "addressfamily": "0", + "pollinterval": 300, + "usesocks": false, + "sockshost": "", + "socksprotocol": "socks5", + "socksport": 6667, + "socksusername": "anonymous", + "sockspassword": "", + "bothost": "", + "botpath": "", + "localbothost": "0.0.0.0", + "localbotport": 8443, + "publicbotport": 8443, + "privatekey": "", + "certificate": "", + "useselfsignedcertificate": false, + "sslterminated": false, + "verboselogging": true + }, + { + "id": "fe79cfd2fda112db", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "d46352482122dfe6", + "name": "Front Lights", + "version": 6, + "entityType": "binary_sensor", + "haConfig": [ + { + "property": "name", + "value": "Front Lights" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "311d801ad1f1bc0f", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "d46352482122dfe6", + "name": "Front Lights", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Front Lights" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "a1235a09a8d34dea", + "type": "mqtt-broker", + "name": "", + "broker": "10.20.30.6", + "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": "telegram_bot_config", + "type": "telegram bot", + "botname": "Your Bot Name", + "usernames": "", + "chatids": "", + "baseapiurl": "", + "updatemode": "polling", + "pollinterval": "300", + "usesocks": false, + "sockshost": "", + "socksprotocol": "socks5", + "socksport": "6667", + "socksusername": "", + "sockspassword": "", + "bothost": "", + "botpath": "", + "localbotport": "8443", + "publicbotport": "8443", + "privatekey": "", + "certificate": "", + "useselfsignedcertificate": false, + "sslterminated": false, + "verboselogging": false + }, + { + "id": "0d71a45029c6da27", + "type": "ui_tab", + "name": "Lights", + "icon": "dashboard", + "disabled": false, + "hidden": false + }, + { + "id": "7df2b0bd19b6ef42", + "type": "ui_base", + "theme": { + "name": "theme-light", + "lightTheme": { + "default": "#0094CE", + "baseColor": "#0094CE", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "edited": true, + "reset": false + }, + "darkTheme": { + "default": "#097479", + "baseColor": "#097479", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "edited": false + }, + "customTheme": { + "name": "Untitled Theme 1", + "default": "#4B7930", + "baseColor": "#4B7930", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" + }, + "themeState": { + "base-color": { + "default": "#0094CE", + "value": "#0094CE", + "edited": false + }, + "page-titlebar-backgroundColor": { + "value": "#0094CE", + "edited": false + }, + "page-backgroundColor": { + "value": "#fafafa", + "edited": false + }, + "page-sidebar-backgroundColor": { + "value": "#ffffff", + "edited": false + }, + "group-textColor": { + "value": "#1bbfff", + "edited": false + }, + "group-borderColor": { + "value": "#ffffff", + "edited": false + }, + "group-backgroundColor": { + "value": "#ffffff", + "edited": false + }, + "widget-textColor": { + "value": "#111111", + "edited": false + }, + "widget-backgroundColor": { + "value": "#0094ce", + "edited": false + }, + "widget-borderColor": { + "value": "#ffffff", + "edited": false + }, + "base-font": { + "value": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" + } + }, + "angularTheme": { + "primary": "indigo", + "accents": "blue", + "warn": "red", + "background": "grey", + "palette": "light" + } + }, + "site": { + "name": "Node-RED Dashboard", + "hideToolbar": "false", + "allowSwipe": "false", + "lockMenu": "false", + "allowTempTheme": "true", + "dateFormat": "DD/MM/YYYY", + "sizes": { + "sx": 48, + "sy": 48, + "gx": 6, + "gy": 6, + "cx": 6, + "cy": 6, + "px": 0, + "py": 0 + } + } + }, + { + "id": "f0dee03e196ee8bc", + "type": "ui_group", + "name": "Default", + "tab": "0d71a45029c6da27", + "order": 1, + "disp": true, + "width": 6, + "collapse": false, + "className": "" + }, + { + "id": "tab_masterBedroom", + "type": "ui_tab", + "name": "masterBedroom", + "icon": "dashboard", + "disabled": false, + "hidden": false + }, + { + "id": "group_masterBedroom", + "type": "ui_group", + "name": "masterBedroom", + "tab": "tab_masterBedroom", + "order": 1, + "disp": true, + "width": 6, + "collapse": false, + "className": "" + }, + { + "id": "tab_masterBathroom", + "type": "ui_tab", + "name": "masterBathroom", + "icon": "dashboard", + "disabled": false, + "hidden": false + }, + { + "id": "group_masterBathroom", + "type": "ui_group", + "name": "masterBathroom", + "tab": "tab_masterBathroom", + "order": 1, + "disp": true, + "width": 6, + "collapse": false, + "className": "" + }, + { + "id": "tab_bedroom_1", + "type": "ui_tab", + "name": "bedroom_1", + "icon": "dashboard", + "disabled": false, + "hidden": false + }, + { + "id": "group_bedroom_1", + "type": "ui_group", + "name": "bedroom_1", + "tab": "tab_bedroom_1", + "order": 1, + "disp": true, + "width": 6, + "collapse": false, + "className": "" + }, + { + "id": "tab_bedroom_2", + "type": "ui_tab", + "name": "bedroom_2", + "icon": "dashboard", + "disabled": false, + "hidden": false + }, + { + "id": "group_bedroom_2", + "type": "ui_group", + "name": "bedroom_2", + "tab": "tab_bedroom_2", + "order": 1, + "disp": true, + "width": 6, + "collapse": false, + "className": "" + }, + { + "id": "tab_bathroom", + "type": "ui_tab", + "name": "bathroom", + "icon": "dashboard", + "disabled": false, + "hidden": false + }, + { + "id": "group_bathroom", + "type": "ui_group", + "name": "bathroom", + "tab": "tab_bathroom", + "order": 1, + "disp": true, + "width": 6, + "collapse": false, + "className": "" + }, + { + "id": "tab_hallway", + "type": "ui_tab", + "name": "hallway", + "icon": "dashboard", + "disabled": false, + "hidden": false + }, + { + "id": "group_hallway", + "type": "ui_group", + "name": "hallway", + "tab": "tab_hallway", + "order": 1, + "disp": true, + "width": 6, + "collapse": false, + "className": "" + }, + { + "id": "tab_pantry", + "type": "ui_tab", + "name": "pantry", + "icon": "dashboard", + "disabled": false, + "hidden": false + }, + { + "id": "group_pantry", + "type": "ui_group", + "name": "pantry", + "tab": "tab_pantry", + "order": 1, + "disp": true, + "width": 6, + "collapse": false, + "className": "" + }, + { + "id": "tab_kitchen", + "type": "ui_tab", + "name": "kitchen", + "icon": "dashboard", + "disabled": false, + "hidden": false + }, + { + "id": "group_kitchen", + "type": "ui_group", + "name": "kitchen", + "tab": "tab_kitchen", + "order": 1, + "disp": true, + "width": 6, + "collapse": false, + "className": "" + }, + { + "id": "tab_livingRoom", + "type": "ui_tab", + "name": "livingRoom", + "icon": "dashboard", + "disabled": false, + "hidden": false + }, + { + "id": "group_livingRoom", + "type": "ui_group", + "name": "livingRoom", + "tab": "tab_livingRoom", + "order": 1, + "disp": true, + "width": 6, + "collapse": false, + "className": "" + }, + { + "id": "tab_dinningRoom", + "type": "ui_tab", + "name": "dinningRoom", + "icon": "dashboard", + "disabled": false, + "hidden": false + }, + { + "id": "group_dinningRoom", + "type": "ui_group", + "name": "dinningRoom", + "tab": "tab_dinningRoom", + "order": 1, + "disp": true, + "width": 6, + "collapse": false, + "className": "" + }, + { + "id": "tab_entrance", + "type": "ui_tab", + "name": "entrance", + "icon": "dashboard", + "disabled": false, + "hidden": false + }, + { + "id": "group_entrance", + "type": "ui_group", + "name": "entrance", + "tab": "tab_entrance", + "order": 1, + "disp": true, + "width": 6, + "collapse": false, + "className": "" + }, + { + "id": "tab_guestWc", + "type": "ui_tab", + "name": "guestWc", + "icon": "dashboard", + "disabled": false, + "hidden": false + }, + { + "id": "group_guestWc", + "type": "ui_group", + "name": "guestWc", + "tab": "tab_guestWc", + "order": 1, + "disp": true, + "width": 6, + "collapse": false, + "className": "" + }, + { + "id": "tab_outVeranda", + "type": "ui_tab", + "name": "outVeranda", + "icon": "dashboard", + "disabled": false, + "hidden": false + }, + { + "id": "group_outVeranda", + "type": "ui_group", + "name": "outVeranda", + "tab": "tab_outVeranda", + "order": 1, + "disp": true, + "width": 6, + "collapse": false, + "className": "" + }, + { + "id": "tab_outFront", + "type": "ui_tab", + "name": "outFront", + "icon": "dashboard", + "disabled": false, + "hidden": false + }, + { + "id": "group_outFront", + "type": "ui_group", + "name": "outFront", + "tab": "tab_outFront", + "order": 1, + "disp": true, + "width": 6, + "collapse": false, + "className": "" + }, + { + "id": "tab_outSide", + "type": "ui_tab", + "name": "outSide", + "icon": "dashboard", + "disabled": false, + "hidden": false + }, + { + "id": "group_outSide", + "type": "ui_group", + "name": "outSide", + "tab": "tab_outSide", + "order": 1, + "disp": true, + "width": 6, + "collapse": false, + "className": "" + }, + { + "id": "tab_outBack", + "type": "ui_tab", + "name": "outBack", + "icon": "dashboard", + "disabled": false, + "hidden": false + }, + { + "id": "group_outBack", + "type": "ui_group", + "name": "outBack", + "tab": "tab_outBack", + "order": 1, + "disp": true, + "width": 6, + "collapse": false, + "className": "" + }, + { + "id": "0c9a4c8d912d0a38", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "17b3adc633ee3394", + "name": "Water_boiler", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Water_boiler" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "8166d41aaab8a32a", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "17b3adc633ee3394", + "name": "Water_boiler", + "version": 6, + "entityType": "binary_sensor", + "haConfig": [ + { + "property": "name", + "value": "Water_boiler" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "timer30min_config", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "17b3adc633ee3394", + "name": "Timer_30_Min", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Timer 30 Min" + }, + { + "property": "icon", + "value": "mdi:timer" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "timer1hour_config", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "17b3adc633ee3394", + "name": "Timer_1_Hour", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Timer 1 Hour" + }, + { + "property": "icon", + "value": "mdi:timer" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "timer2hours_config", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "17b3adc633ee3394", + "name": "Timer_2_Hours", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Timer 2 Hours" + }, + { + "property": "icon", + "value": "mdi:timer" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "timerstop_config", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "17b3adc633ee3394", + "name": "Stop_Timer", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Stop Timer" + }, + { + "property": "icon", + "value": "mdi:timer-off" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "timer_remaining_config", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "17b3adc633ee3394", + "name": "Timer_Remaining", + "version": 6, + "entityType": "sensor", + "haConfig": [ + { + "property": "name", + "value": "Water Boiler Timer Remaining" + }, + { + "property": "icon", + "value": "mdi:timer-sand" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + }, + { + "property": "unit_of_measurement", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "c5b6375b9e419298", + "type": "ha-device-config", + "name": "Entrance", + "hwVersion": "", + "manufacturer": "Node-RED", + "model": "", + "swVersion": "" + }, + { + "id": "c1b8d65524acbab3", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "c5b6375b9e419298", + "name": "Entrance Mirror", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Entrance Mirror" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "72e9fc1709b9891c", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "c5b6375b9e419298", + "name": "Entrance Mirror", + "version": 6, + "entityType": "binary_sensor", + "haConfig": [ + { + "property": "name", + "value": "Entrance Mirror" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "121b1dead369ae82", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "c5b6375b9e419298", + "name": "Entrance Light In", + "version": 6, + "entityType": "button", + "haConfig": [ + { + "property": "name", + "value": "Entrance Light In" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "395ea43be3b0e20c", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "c5b6375b9e419298", + "name": "Entrance Light In", + "version": 6, + "entityType": "binary_sensor", + "haConfig": [ + { + "property": "name", + "value": "Entrance Light In" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "3e00db06a8f6a759", + "type": "ha-device-config", + "name": "Living_Room_New", + "hwVersion": "", + "manufacturer": "Node-RED", + "model": "", + "swVersion": "" + }, + { + "id": "6d1793815598b149", + "type": "ha-entity-config", + "server": "29eb84a2708d96e0", + "deviceConfig": "3e00db06a8f6a759", + "name": "Living_room_Lights", + "version": 6, + "entityType": "switch", + "haConfig": [ + { + "property": "name", + "value": "" + }, + { + "property": "icon", + "value": "" + }, + { + "property": "entity_picture", + "value": "" + }, + { + "property": "entity_category", + "value": "" + }, + { + "property": "device_class", + "value": "" + } + ], + "resend": false, + "debugEnabled": false + }, + { + "id": "ee7c2a47d6441c26", + "type": "function", + "z": "57bd1b149526fcee", + "name": "function 1", + "func": "// Get the room name from the environment variable\nconst roomName = env.get(\"room_name\");\n\n// Access Node-RED context for state storage\nlet contextState = context.get(roomName) || {};\n\n// Check if the payload contains the specified room\nif (msg.payload.hasOwnProperty(roomName)) {\n // Get the lights status for the specified room\n let lights = msg.payload[roomName];\n\n // Check if this is the first run (initial state)\n // Context is empty or missing expected light keys\n const expectedLights = ['l_1', 'l_2', 'l_3', 'l_4', 'l_5', 'l_6'];\n const isInitialState = Object.keys(contextState).length === 0 || \n expectedLights.some(light => !(light in contextState));\n\n // Create an output array for the 6 ports\n let outputs = [null, null, null, null, null, null];\n\n // Check each light status and detect changes\n for (let light in lights) {\n let lightStatus = lights[light];\n\n // On initial state, send ALL states to initialize binary sensors\n // Otherwise, only send changed states\n if (isInitialState || contextState[light] !== lightStatus) {\n // Update the context with the new status\n contextState[light] = lightStatus;\n\n // Create a new message preserving original metadata\n let newMsg = {\n payload: lightStatus,\n topic: msg.topic,\n _msgid: msg._msgid\n };\n\n // Assign the message to the correct output port\n switch (light) {\n case 'l_1':\n outputs[0] = newMsg;\n break;\n case 'l_2':\n outputs[1] = newMsg;\n break;\n case 'l_3':\n outputs[2] = newMsg;\n break;\n case 'l_4':\n outputs[3] = newMsg;\n break;\n case 'l_5':\n outputs[4] = newMsg;\n break;\n case 'l_6':\n outputs[5] = newMsg;\n break;\n default:\n // Ignore any unexpected light keys\n break;\n }\n }\n }\n\n // Store the updated state in context\n context.set(roomName, contextState);\n\n // Send states to the respective ports (all on initial, only changes otherwise)\n return outputs;\n\n} else {\n // If the specified room does not exist in the payload, clean up stored context\n context.set(roomName, null); // Remove stored state for the room\n return [null, null, null, null, null, null]; // Return array instead of null\n}\n\n", + "outputs": 6, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 400, + "y": 220, + "wires": [ + [], + [], + [], + [], + [], + [] + ] + }, + { + "id": "f9557fd9a8b2eee2", + "type": "function", + "z": "d0de479f6b80ab6c", + "name": "function 2", + "func": "// Define the masterBedroom and sw_1 variables\nvar masterBedroom = \"bedroom_1\"; // Example variable for masterBedroom\nvar sw_1 = \"sw_1\"; // Example variable for sw_1\n\n// Create a message object to set the value to true\nvar msgTrue = {\n payload: {}\n};\nmsgTrue.payload[masterBedroom] = {};\nmsgTrue.payload[masterBedroom][sw_1] = true;\n\n// Create a message object to set the value to false\nvar msgFalse = {\n payload: {}\n};\nmsgFalse.payload[masterBedroom] = {};\nmsgFalse.payload[masterBedroom][sw_1] = false;\n\n// Send the message to set the value to true\nnode.send(msgTrue);\n\n// Delay for 1 second\nsetTimeout(function() {\n // Send the message to set the value to false after 1 second\n node.send(msgFalse);\n}, 1000); // 1000 milliseconds = 1 second\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 200, + "y": 80, + "wires": [ + [] + ] + }, + { + "id": "d2a3760f36aa6f41", + "type": "switch", + "z": "4dc13919bcc9d676", + "name": "", + "property": "payload", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "1_single", + "vt": "str" + }, + { + "t": "eq", + "v": "2_single", + "vt": "str" + }, + { + "t": "eq", + "v": "3_single", + "vt": "str" + }, + { + "t": "eq", + "v": "4_single", + "vt": "str" + }, + { + "t": "eq", + "v": "single", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 5, + "x": 190, + "y": 80, + "wires": [ + [ + "0ce841560c8c4087" + ], + [ + "64b045db5a4541fa" + ], + [ + "4a891a7505b5fd00" + ], + [ + "258a84ec1d54a43a" + ], + [ + "5532273136423869" + ] + ] + }, + { + "id": "0ce841560c8c4087", + "type": "function", + "z": "4dc13919bcc9d676", + "name": "Switch 1", + "func": "// Retrieve the combined device variable (e.g., \"bedroom_1.sw1\")\nvar switch_1 = env.get(\"switch_1\");\n\n// Split the string into room and switch name parts using the dot as a delimiter\nvar parts = switch_1.split(\".\");\nvar room = parts[0];\nvar switchName = parts[1];\n\n// Create a message object to set the switch to true\nvar msgTrue = {\n payload: {}\n};\nmsgTrue.payload[room] = {};\nmsgTrue.payload[room][switchName] = true;\n\n// Create a message object to set the switch to false\nvar msgFalse = {\n payload: {}\n};\nmsgFalse.payload[room] = {};\nmsgFalse.payload[room][switchName] = false;\n\n// Send the message to set the switch to true\nnode.send(msgTrue);\n\n// Delay for 1 second before setting the switch to false\nsetTimeout(function () {\n node.send(msgFalse);\n}, 1000);\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 380, + "y": 60, + "wires": [ + [] + ] + }, + { + "id": "64b045db5a4541fa", + "type": "function", + "z": "4dc13919bcc9d676", + "name": "Switch 2", + "func": "// Retrieve the combined device variable (e.g., \"bedroom_1.sw1\")\nvar switch_2 = env.get(\"switch_2\");\n\n// Split the string into room and switch name parts using the dot as a delimiter\nvar parts = switch_2.split(\".\");\nvar room = parts[0];\nvar switchName = parts[1];\n\n// Create a message object to set the switch to true\nvar msgTrue = {\n payload: {}\n};\nmsgTrue.payload[room] = {};\nmsgTrue.payload[room][switchName] = true;\n\n// Create a message object to set the switch to false\nvar msgFalse = {\n payload: {}\n};\nmsgFalse.payload[room] = {};\nmsgFalse.payload[room][switchName] = false;\n\n// Send the message to set the switch to true\nnode.send(msgTrue);\n\n// Delay for 1 second before setting the switch to false\nsetTimeout(function () {\n node.send(msgFalse);\n}, 1000);\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 380, + "y": 100, + "wires": [ + [] + ] + }, + { + "id": "4a891a7505b5fd00", + "type": "function", + "z": "4dc13919bcc9d676", + "name": "Switch 3", + "func": "// Retrieve the combined device variable (e.g., \"bedroom_1.sw1\")\nvar switch_3 = env.get(\"switch_3\");\n\n// Split the string into room and switch name parts using the dot as a delimiter\nvar parts = switch_3.split(\".\");\nvar room = parts[0];\nvar switchName = parts[1];\n\n// Create a message object to set the switch to true\nvar msgTrue = {\n payload: {}\n};\nmsgTrue.payload[room] = {};\nmsgTrue.payload[room][switchName] = true;\n\n// Create a message object to set the switch to false\nvar msgFalse = {\n payload: {}\n};\nmsgFalse.payload[room] = {};\nmsgFalse.payload[room][switchName] = false;\n\n// Send the message to set the switch to true\nnode.send(msgTrue);\n\n// Delay for 1 second before setting the switch to false\nsetTimeout(function () {\n node.send(msgFalse);\n}, 1000);\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 380, + "y": 140, + "wires": [ + [] + ] + }, + { + "id": "258a84ec1d54a43a", + "type": "function", + "z": "4dc13919bcc9d676", + "name": "Switch 4", + "func": "// Retrieve the combined device variable (e.g., \"bedroom_1.sw1\")\nvar switch_4 = env.get(\"switch_4\");\n\n// Split the string into room and switch name parts using the dot as a delimiter\nvar parts = switch_4.split(\".\");\nvar room = parts[0];\nvar switchName = parts[1];\n\n// Create a message object to set the switch to true\nvar msgTrue = {\n payload: {}\n};\nmsgTrue.payload[room] = {};\nmsgTrue.payload[room][switchName] = true;\n\n// Create a message object to set the switch to false\nvar msgFalse = {\n payload: {}\n};\nmsgFalse.payload[room] = {};\nmsgFalse.payload[room][switchName] = false;\n\n// Send the message to set the switch to true\nnode.send(msgTrue);\n\n// Delay for 1 second before setting the switch to false\nsetTimeout(function () {\n node.send(msgFalse);\n}, 1000);\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 380, + "y": 180, + "wires": [ + [] + ] + }, + { + "id": "5532273136423869", + "type": "function", + "z": "4dc13919bcc9d676", + "name": "Switch 4", + "func": "// Retrieve the combined device variable (e.g., \"bedroom_1.sw1\")\nvar single = env.get(\"single\");\n\n// Split the string into room and switch name parts using the dot as a delimiter\nvar parts = single.split(\".\");\nvar room = parts[0];\nvar switchName = parts[1];\n\n// Create a message object to set the switch to true\nvar msgTrue = {\n payload: {}\n};\nmsgTrue.payload[room] = {};\nmsgTrue.payload[room][switchName] = true;\n\n// Create a message object to set the switch to false\nvar msgFalse = {\n payload: {}\n};\nmsgFalse.payload[room] = {};\nmsgFalse.payload[room][switchName] = false;\n\n// Send the message to set the switch to true\nnode.send(msgTrue);\n\n// Delay for 1 second before setting the switch to false\nsetTimeout(function () {\n node.send(msgFalse);\n}, 1000);\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 380, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "030bba61776bd099", + "type": "nvl-send", + "z": "46892ced77481340", + "name": "", + "definition": "VAR_GLOBAL\n\tmasterBedroom \t:\tstruct_switches;\n\tmasterBathroom\t:\tstruct_switches;\n\tbedroom_1\t\t:\tstruct_switches;\n\tbedroom_2\t\t:\tstruct_switches;\n\tbathroom\t\t:\tstruct_switches;\n\thallway\t\t\t:\tstruct_switches;\n\tpantry\t\t\t:\tstruct_switches;\n\tkitchen\t\t\t:\tstruct_switches;\n\tlivingRoom\t\t:\tstruct_switches;\n\tdinningRoom\t\t:\tstruct_switches;\n\tentrance\t\t:\tstruct_switches;\n\tguestWc\t\t\t:\tstruct_switches;\n\toutVeranda\t\t:\tstruct_switches;\n\toutFront\t\t:\tstruct_switches;\n\toutSide\t\t\t:\tstruct_switches;\n\toutBack\t\t\t:\tstruct_switches;\n\tcmd_livingroom\t:\tstruct_room_cmds;\nEND_VAR\n\nTYPE struct_switches :\nSTRUCT\n\tall_off:BOOL;\n\tall_on:BOOL;\t\n\tsw_1:BOOL;\n\tsw_2:BOOL;\n\tsw_3:BOOL;\n\tsw_4:BOOL;\n\tsw_5:BOOL;\n\tsw_6:BOOL;\t\nEND_STRUCT\nEND_TYPE\n\nTYPE struct_room_cmds :\nSTRUCT\n // Home Assistant Commands (set/reset)\n ha_l1_on: BOOL;\n ha_l1_off: BOOL;\n ha_l2_on: BOOL;\n ha_l2_off: BOOL;\n ha_l3_on: BOOL;\n ha_l3_off: BOOL;\n ha_l4_on: BOOL;\n ha_l4_off: BOOL;\n ha_l5_on: BOOL;\n ha_l5_off: BOOL;\n ha_l6_on: BOOL;\n ha_l6_off: BOOL;\n \n // Zigbee Switch Inputs (edge detection for toggle)\n zigbee_sw1: BOOL; \n zigbee_sw2: BOOL;\n zigbee_sw3: BOOL;\n zigbee_sw4: BOOL;\n zigbee_sw5: BOOL;\n zigbee_sw6: BOOL;\n \n // Global Commands\n ha_all_on: BOOL;\n ha_all_off: BOOL;\nEND_STRUCT\nEND_TYPE", + "listId": "3", + "globaltypes": "", + "pack": "true", + "port": "", + "x": 360, + "y": 40, + "wires": [ + [ + "040e9ff14df4a326", + "f48d2c10e183fb95" + ] + ] + }, + { + "id": "a18b0ead6d0e88cb", + "type": "nvl-receive", + "z": "46892ced77481340", + "name": "", + "definition": "\nVAR_GLOBAL\n\n\tl_masterBedroom \t:\tstruct_lights;\n\tl_masterBathroom\t:\tstruct_lights;\n\tl_bedroom_1\t\t\t:\tstruct_lights;\n\tl_bedroom_2\t\t\t:\tstruct_lights;\n\tl_bathroom\t\t\t:\tstruct_lights;\n\tl_hallway\t\t\t:\tstruct_lights;\n\tl_pantry\t\t\t:\tstruct_lights;\n\tl_kitchen\t\t\t:\tstruct_lights;\n\tl_livingRoom\t\t:\tstruct_lights;\n\tl_dinningRoom\t\t:\tstruct_lights;\n\tl_entrance\t\t\t:\tstruct_lights;\n\tl_guestWc\t\t\t:\tstruct_lights;\n\tl_outVeranda\t\t:\tstruct_lights;\n\tl_outFront\t\t\t:\tstruct_lights;\n\tl_outSide\t\t\t:\tstruct_lights;\n\tl_outBack\t\t\t:\tstruct_lights;\n\tlight_livingRoom\t\t:\tstruct_room_outs;\nEND_VAR\n\n\nTYPE struct_lights:\nSTRUCT\n l_1 \t\t: BOOL;\n\tl_2 \t\t: BOOL;\n\tl_3 \t\t: BOOL;\n\tl_4 \t\t: BOOL;\n\tl_5 \t\t: BOOL;\n\tl_6 \t\t: BOOL;\t\nEND_STRUCT\nEND_TYPE\n\nTYPE struct_room_outs :\nSTRUCT\n l_1: BOOL; // Light 1 control output\n l_2: BOOL; // Light 2 control output\n l_3: BOOL; // Light 3 control output\n l_4: BOOL; // Light 4 control output\n l_5: BOOL; // Light 5 control output\n l_6: BOOL; // Light 6 control output\n \n // Status feedback (read from actual relay outputs)\n l_1_status: BOOL; // Actual relay 1 state\n l_2_status: BOOL; // Actual relay 2 state\n l_3_status: BOOL; // Actual relay 3 state\n l_4_status: BOOL; // Actual relay 4 state\n l_5_status: BOOL; // Actual relay 5 state\n l_6_status: BOOL; // Actual relay 6 state\nEND_STRUCT\nEND_TYPE", + "listId": "1", + "globaltypes": "", + "pack": "true", + "x": 990, + "y": 40, + "wires": [ + [ + "e059a9fd1da3c3fd", + "9672723b593ef966" + ] + ] + }, + { + "id": "040e9ff14df4a326", + "type": "udp out", + "z": "46892ced77481340", + "name": "", + "addr": "10.20.30.5", + "iface": "", + "port": "1202", + "ipv": "udp4", + "outport": "", + "base64": false, + "multicast": "false", + "x": 580, + "y": 40, + "wires": [] + }, + { + "id": "67f7ec38bbf87e95", + "type": "udp in", + "z": "46892ced77481340", + "name": "", + "iface": "", + "port": "1202", + "ipv": "udp4", + "multicast": "false", + "group": "", + "datatype": "buffer", + "x": 820, + "y": 40, + "wires": [ + [ + "a18b0ead6d0e88cb" + ] + ] + }, + { + "id": "a91dcb332bae2807", + "type": "ha-button", + "z": "46892ced77481340", + "g": "ff517137dc8eb4e5", + "name": "Office", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "7fe55db85a29f18c", + "outputProperties": [ + { + "property": "topic", + "propertyType": "msg", + "value": "bedroom_1", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "sw_1", + "valueType": "str" + } + ], + "x": 430, + "y": 420, + "wires": [ + [ + "11f594c5b9f0fce8" + ] + ] + }, + { + "id": "67149a39f56c5694", + "type": "ha-button", + "z": "46892ced77481340", + "g": "007a0818e00b6e8a", + "name": "Main Light", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "5e7f6287035ecc94", + "outputProperties": [ + { + "property": "topic", + "propertyType": "msg", + "value": "masterBedroom", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "sw_1", + "valueType": "str" + } + ], + "x": 440, + "y": 200, + "wires": [ + [ + "a6493ad8864f2b68" + ] + ] + }, + { + "id": "40fd804bb4acf519", + "type": "subflow:57bd1b149526fcee", + "z": "46892ced77481340", + "g": "925b79f58f75e449", + "name": "Office", + "env": [ + { + "name": "room_name", + "value": "l_bedroom_1", + "type": "str" + } + ], + "x": 230, + "y": 1080, + "wires": [ + [ + "b9ab51a28b433f94" + ], + [], + [], + [], + [], + [] + ] + }, + { + "id": "123bb560d193b3aa", + "type": "function", + "z": "46892ced77481340", + "name": "function 3", + "func": "// Retrieve room and switch from the input message\nvar room = msg.topic; // e.g., \"masterBedroom\"\nvar switchName = msg.payload; // e.g., \"sw_1\"\n\n// Create a message object to set the value to true\nvar msgTrue = {\n payload: {}\n};\nmsgTrue.payload[room] = {};\nmsgTrue.payload[room][switchName] = true;\n\n// Create a message object to set the value to false\nvar msgFalse = {\n payload: {}\n};\nmsgFalse.payload[room] = {};\nmsgFalse.payload[room][switchName] = false;\n\n// Send the message to set the value to true\nnode.send(msgTrue);\n\n// Delay for 1 second\nsetTimeout(function() {\n // Send the message to set the value to false after 1 second\n node.send(msgFalse);\n}, 1000); // 1000 milliseconds = 1 second\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 200, + "y": 40, + "wires": [ + [ + "030bba61776bd099", + "438b13c5cdbb5f1c" + ] + ] + }, + { + "id": "c6162cb59e21491f", + "type": "link in", + "z": "46892ced77481340", + "name": "from zigbee", + "links": [ + "629cbd7ce15bb9e3", + "6fb9cc41c7b14fac" + ], + "x": 245, + "y": 100, + "wires": [ + [ + "030bba61776bd099" + ] + ] + }, + { + "id": "a2eceb1ef60667cb", + "type": "ha-button", + "z": "46892ced77481340", + "g": "2b9f0ac7a58d6e99", + "name": "Shower Light", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "88f51f9c233f4cc3", + "outputProperties": [ + { + "property": "payload", + "propertyType": "msg", + "value": "sw_1", + "valueType": "str" + }, + { + "property": "topic", + "propertyType": "msg", + "value": "bathroom", + "valueType": "str" + } + ], + "x": 1130, + "y": 300, + "wires": [ + [ + "102d7d54a45e3f18" + ] + ] + }, + { + "id": "e9aa8b33edf056f6", + "type": "ha-button", + "z": "46892ced77481340", + "g": "42040eb51b6909d2", + "name": "Kitchen Island", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "29592d5555367344", + "outputProperties": [ + { + "property": "topic", + "propertyType": "msg", + "value": "kitchen", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "sw_2", + "valueType": "str" + } + ], + "x": 170, + "y": 220, + "wires": [ + [ + "a1f44cc63b77a2c3" + ] + ] + }, + { + "id": "eda94d52e6cf650a", + "type": "ha-button", + "z": "46892ced77481340", + "g": "42040eb51b6909d2", + "name": "Main", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "cc2640502094371a", + "outputProperties": [ + { + "property": "topic", + "propertyType": "msg", + "value": "kitchen", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "sw_1", + "valueType": "str" + } + ], + "x": 150, + "y": 280, + "wires": [ + [ + "a1f44cc63b77a2c3" + ] + ] + }, + { + "id": "188a3b16710467e7", + "type": "ha-button", + "z": "46892ced77481340", + "g": "42040eb51b6909d2", + "name": "Fridge", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "8e22cfb83ec013d1", + "outputProperties": [ + { + "property": "topic", + "propertyType": "msg", + "value": "kitchen", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "sw_3", + "valueType": "str" + } + ], + "x": 150, + "y": 340, + "wires": [ + [ + "a1f44cc63b77a2c3" + ] + ] + }, + { + "id": "f714485f125865af", + "type": "ha-button", + "z": "46892ced77481340", + "g": "888d856dae38ce27", + "name": "outVeranda", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "2a94bae266d9423d", + "outputProperties": [ + { + "property": "topic", + "propertyType": "msg", + "value": "outVeranda", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "sw_2", + "valueType": "str" + } + ], + "x": 1530, + "y": 200, + "wires": [ + [ + "9594045be414b84c" + ] + ] + }, + { + "id": "2dc64fabc1602ec8", + "type": "ha-button", + "z": "46892ced77481340", + "g": "42040eb51b6909d2", + "name": "Pantry", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "085d42240f91eec2", + "outputProperties": [ + { + "property": "topic", + "propertyType": "msg", + "value": "pantry", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "sw_1", + "valueType": "str" + } + ], + "x": 210, + "y": 540, + "wires": [ + [ + "a1f44cc63b77a2c3" + ] + ] + }, + { + "id": "58525896ff637a27", + "type": "ha-button", + "z": "46892ced77481340", + "g": "2ba7fb1e189ec017", + "name": "Main", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "eca0cca02aabd143", + "outputProperties": [ + { + "property": "topic", + "propertyType": "msg", + "value": "livingRoom", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "sw_1", + "valueType": "str" + } + ], + "x": 770, + "y": 420, + "wires": [ + [ + "beeb512081d25291" + ] + ] + }, + { + "id": "d5dacec70d47b2e5", + "type": "ha-button", + "z": "46892ced77481340", + "g": "007a0818e00b6e8a", + "name": "Door", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "451e5988101b1aac", + "outputProperties": [ + { + "property": "topic", + "propertyType": "msg", + "value": "masterBedroom", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "sw_2", + "valueType": "str" + } + ], + "x": 430, + "y": 260, + "wires": [ + [ + "a6493ad8864f2b68" + ] + ] + }, + { + "id": "28d2293b97a7b72b", + "type": "ha-button", + "z": "46892ced77481340", + "g": "007a0818e00b6e8a", + "name": "Dresser", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "c21001f594a359c9", + "outputProperties": [ + { + "property": "topic", + "propertyType": "msg", + "value": "", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "", + "valueType": "str" + } + ], + "x": 430, + "y": 320, + "wires": [ + [ + "a6493ad8864f2b68" + ] + ] + }, + { + "id": "2565695beaa14d76", + "type": "ha-button", + "z": "46892ced77481340", + "g": "888d856dae38ce27", + "name": "BBQ", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "d721225e966ecf1b", + "outputProperties": [ + { + "property": "topic", + "propertyType": "msg", + "value": "outSide", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "sw_1", + "valueType": "str" + } + ], + "x": 1510, + "y": 260, + "wires": [ + [ + "9594045be414b84c" + ] + ] + }, + { + "id": "66cafcf3b261e8b5", + "type": "ha-button", + "z": "46892ced77481340", + "g": "3cc3b21e7b1f7b13", + "name": "Main", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "8c493a05a6971f90", + "outputProperties": [ + { + "property": "topic", + "propertyType": "msg", + "value": "dinningRoom", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "sw_1", + "valueType": "str" + } + ], + "x": 770, + "y": 200, + "wires": [ + [ + "04e183fcfe9e9f6d" + ] + ] + }, + { + "id": "0bbcb269da9b75c7", + "type": "link in", + "z": "46892ced77481340", + "name": "From HA Entities", + "links": [ + "11f594c5b9f0fce8", + "102d7d54a45e3f18", + "a6493ad8864f2b68", + "9594045be414b84c", + "beeb512081d25291", + "04e183fcfe9e9f6d", + "0d7f6d11034f98ee", + "839bb9d987729d16", + "ed3b84f4d63a8bd5", + "45e37c698b0b2e5a", + "01da9e8ebba7f592", + "740b766e9bdc0d2a", + "a1f44cc63b77a2c3", + "f11fa2bedfc41e41", + "33eb6e0725214c0b", + "9c99ecea6e4aefd8", + "e44fa1b081b4aba7", + "1d0a46b9d5255560" + ], + "x": 75, + "y": 40, + "wires": [ + [ + "123bb560d193b3aa", + "e4d5c7b8295b4251" + ] + ] + }, + { + "id": "11f594c5b9f0fce8", + "type": "link out", + "z": "46892ced77481340", + "g": "ff517137dc8eb4e5", + "name": "to PLC Function", + "mode": "link", + "links": [ + "0bbcb269da9b75c7" + ], + "x": 625, + "y": 440, + "wires": [] + }, + { + "id": "102d7d54a45e3f18", + "type": "link out", + "z": "46892ced77481340", + "g": "2b9f0ac7a58d6e99", + "name": "link out 2", + "mode": "link", + "links": [ + "0bbcb269da9b75c7" + ], + "x": 1335, + "y": 300, + "wires": [] + }, + { + "id": "a6493ad8864f2b68", + "type": "link out", + "z": "46892ced77481340", + "g": "007a0818e00b6e8a", + "name": "link out 3", + "mode": "link", + "links": [ + "0bbcb269da9b75c7" + ], + "x": 625, + "y": 200, + "wires": [] + }, + { + "id": "a1f44cc63b77a2c3", + "type": "link out", + "z": "46892ced77481340", + "g": "42040eb51b6909d2", + "name": "link out 4", + "mode": "link", + "links": [ + "0bbcb269da9b75c7" + ], + "x": 295, + "y": 200, + "wires": [] + }, + { + "id": "9594045be414b84c", + "type": "link out", + "z": "46892ced77481340", + "g": "888d856dae38ce27", + "name": "link out 5", + "mode": "link", + "links": [ + "0bbcb269da9b75c7" + ], + "x": 1745, + "y": 200, + "wires": [] + }, + { + "id": "beeb512081d25291", + "type": "link out", + "z": "46892ced77481340", + "g": "2ba7fb1e189ec017", + "name": "link out 6", + "mode": "link", + "links": [ + "0bbcb269da9b75c7" + ], + "x": 965, + "y": 420, + "wires": [] + }, + { + "id": "04e183fcfe9e9f6d", + "type": "link out", + "z": "46892ced77481340", + "g": "3cc3b21e7b1f7b13", + "name": "link out 7", + "mode": "link", + "links": [ + "0bbcb269da9b75c7" + ], + "x": 965, + "y": 200, + "wires": [] + }, + { + "id": "f1cf19264e9957f3", + "type": "ha-button", + "z": "46892ced77481340", + "g": "f16b851b413f0d32", + "name": "Guest WC Light", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "85fe098bb4010c10", + "outputProperties": [ + { + "property": "topic", + "propertyType": "msg", + "value": "guestWc", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "sw_1", + "valueType": "str" + } + ], + "x": 1140, + "y": 540, + "wires": [ + [ + "0d7f6d11034f98ee" + ] + ] + }, + { + "id": "0d7f6d11034f98ee", + "type": "link out", + "z": "46892ced77481340", + "g": "f16b851b413f0d32", + "name": "link out 8", + "mode": "link", + "links": [ + "0bbcb269da9b75c7" + ], + "x": 1335, + "y": 540, + "wires": [] + }, + { + "id": "518a9fd6f52bfc23", + "type": "ha-button", + "z": "46892ced77481340", + "g": "1a348789bcd14214", + "name": "Flood Light 1", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "a3fb2a27c9896974", + "outputProperties": [ + { + "property": "topic", + "propertyType": "msg", + "value": "outBack", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "", + "valueType": "str" + } + ], + "x": 1530, + "y": 460, + "wires": [ + [ + "839bb9d987729d16" + ] + ] + }, + { + "id": "839bb9d987729d16", + "type": "link out", + "z": "46892ced77481340", + "g": "1a348789bcd14214", + "name": "link out 9", + "mode": "link", + "links": [ + "0bbcb269da9b75c7" + ], + "x": 1745, + "y": 460, + "wires": [] + }, + { + "id": "0f93e95f3e92150a", + "type": "ha-button", + "z": "46892ced77481340", + "g": "8044f4e5402ca464", + "name": "Main", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "ac7e5f5dfeb9ce54", + "outputProperties": [ + { + "property": "topic", + "propertyType": "msg", + "value": "masterBathroom", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "sw_1", + "valueType": "str" + } + ], + "x": 1110, + "y": 200, + "wires": [ + [ + "ed3b84f4d63a8bd5" + ] + ] + }, + { + "id": "ed3b84f4d63a8bd5", + "type": "link out", + "z": "46892ced77481340", + "g": "8044f4e5402ca464", + "name": "link out 10", + "mode": "link", + "links": [ + "0bbcb269da9b75c7" + ], + "x": 1335, + "y": 200, + "wires": [] + }, + { + "id": "194c35655de1a128", + "type": "ha-button", + "z": "46892ced77481340", + "g": "f83a4ef2662f4f11", + "name": "Entrance Light out", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "aa9cf2e1dc39616f", + "outputProperties": [ + { + "property": "topic", + "propertyType": "msg", + "value": "outFront", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "", + "valueType": "str" + } + ], + "x": 1550, + "y": 360, + "wires": [ + [ + "45e37c698b0b2e5a" + ] + ] + }, + { + "id": "45e37c698b0b2e5a", + "type": "link out", + "z": "46892ced77481340", + "g": "f83a4ef2662f4f11", + "name": "link out 11", + "mode": "link", + "links": [ + "0bbcb269da9b75c7" + ], + "x": 1745, + "y": 360, + "wires": [] + }, + { + "id": "bbda8fd42d8aa120", + "type": "ha-button", + "z": "46892ced77481340", + "g": "48b019b444db2a5a", + "name": "Driveway Flood Light", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "a97f2813611faac8", + "outputProperties": [ + { + "property": "topic", + "propertyType": "msg", + "value": "", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "", + "valueType": "str" + } + ], + "x": 1560, + "y": 560, + "wires": [ + [ + "01da9e8ebba7f592" + ] + ] + }, + { + "id": "01da9e8ebba7f592", + "type": "link out", + "z": "46892ced77481340", + "g": "48b019b444db2a5a", + "name": "link out 12", + "mode": "link", + "links": [ + "0bbcb269da9b75c7" + ], + "x": 1745, + "y": 560, + "wires": [] + }, + { + "id": "039e1be0cfe8e630", + "type": "ha-button", + "z": "46892ced77481340", + "d": true, + "g": "664de832a54bc4e0", + "name": "", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "", + "outputProperties": [ + { + "property": "topic", + "propertyType": "msg", + "value": "", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "", + "valueType": "str" + } + ], + "x": 2010, + "y": 220, + "wires": [ + [ + "740b766e9bdc0d2a" + ] + ] + }, + { + "id": "740b766e9bdc0d2a", + "type": "link out", + "z": "46892ced77481340", + "g": "664de832a54bc4e0", + "name": "link out 13", + "mode": "link", + "links": [ + "0bbcb269da9b75c7" + ], + "x": 2185, + "y": 220, + "wires": [] + }, + { + "id": "b9ab51a28b433f94", + "type": "ha-binary-sensor", + "z": "46892ced77481340", + "g": "925b79f58f75e449", + "name": "Office Light", + "entityConfig": "f744459771e70676", + "version": 0, + "state": "payload", + "stateType": "msg", + "attributes": [], + "inputOverride": "allow", + "outputProperties": [], + "x": 490, + "y": 1040, + "wires": [ + [] + ] + }, + { + "id": "e059a9fd1da3c3fd", + "type": "link out", + "z": "46892ced77481340", + "name": "from PLC states", + "mode": "link", + "links": [ + "0d1a60ec03adac5c", + "01b4eb4b91f9c9e2", + "7ec85095958da9f5", + "b51586d393da96cd", + "b2641f71595c1d2d", + "39d435c6a32d4f09", + "fc5020edbd894baa", + "37cdfd56758051ef", + "d3f0c80e0188aabf", + "3b5e1f4f9ccd9c74", + "2b29c71c180a50df", + "860779317fad91b5", + "8dd17125d6bb49f7" + ], + "x": 1175, + "y": 80, + "wires": [] + }, + { + "id": "0d1a60ec03adac5c", + "type": "link in", + "z": "46892ced77481340", + "g": "925b79f58f75e449", + "name": "link in 1", + "links": [ + "e059a9fd1da3c3fd" + ], + "x": 105, + "y": 1080, + "wires": [ + [ + "40fd804bb4acf519" + ] + ] + }, + { + "id": "1feb11ab8be3ef2e", + "type": "subflow:57bd1b149526fcee", + "z": "46892ced77481340", + "g": "b88648a578b60cb8", + "name": "Living Room", + "env": [ + { + "name": "room_name", + "value": "l_livingRoom", + "type": "str" + } + ], + "x": 250, + "y": 920, + "wires": [ + [ + "1ee2b22951639d6b" + ], + [], + [], + [], + [], + [] + ] + }, + { + "id": "01b4eb4b91f9c9e2", + "type": "link in", + "z": "46892ced77481340", + "g": "b88648a578b60cb8", + "name": "link in 2", + "links": [ + "e059a9fd1da3c3fd" + ], + "x": 105, + "y": 920, + "wires": [ + [ + "1feb11ab8be3ef2e" + ] + ] + }, + { + "id": "3c894953f414add5", + "type": "subflow:57bd1b149526fcee", + "z": "46892ced77481340", + "g": "4a9361ba088ba046", + "name": "Dining Room", + "env": [ + { + "name": "room_name", + "value": "l_dinningRoom", + "type": "str" + } + ], + "x": 250, + "y": 760, + "wires": [ + [ + "566e5b2ec261934f" + ], + [], + [ + "89762968e1556a42" + ], + [], + [], + [] + ] + }, + { + "id": "7ec85095958da9f5", + "type": "link in", + "z": "46892ced77481340", + "g": "4a9361ba088ba046", + "name": "link in 3", + "links": [ + "e059a9fd1da3c3fd" + ], + "x": 105, + "y": 760, + "wires": [ + [ + "3c894953f414add5" + ] + ] + }, + { + "id": "af7220d8f5bb9bf2", + "type": "subflow:57bd1b149526fcee", + "z": "46892ced77481340", + "g": "0271d58953459100", + "name": "Kitchen", + "env": [ + { + "name": "room_name", + "value": "l_kitchen", + "type": "str" + } + ], + "x": 800, + "y": 1180, + "wires": [ + [ + "e8db33fc81400531" + ], + [ + "e0739e7f8c82e487" + ], + [ + "1530556181903c3a" + ], + [], + [], + [] + ] + }, + { + "id": "b51586d393da96cd", + "type": "link in", + "z": "46892ced77481340", + "g": "0271d58953459100", + "name": "link in 4", + "links": [ + "e059a9fd1da3c3fd" + ], + "x": 665, + "y": 1180, + "wires": [ + [ + "af7220d8f5bb9bf2" + ] + ] + }, + { + "id": "53178ccb3f691dad", + "type": "subflow:57bd1b149526fcee", + "z": "46892ced77481340", + "g": "4b360c7d54e39e0a", + "name": "Bedroom", + "env": [ + { + "name": "room_name", + "value": "l_bedroom_2", + "type": "str" + } + ], + "x": 800, + "y": 1000, + "wires": [ + [ + "ba2859e1aa882eee" + ], + [], + [], + [], + [], + [] + ] + }, + { + "id": "b2641f71595c1d2d", + "type": "link in", + "z": "46892ced77481340", + "g": "4b360c7d54e39e0a", + "name": "link in 5", + "links": [ + "e059a9fd1da3c3fd" + ], + "x": 665, + "y": 1000, + "wires": [ + [ + "53178ccb3f691dad" + ] + ] + }, + { + "id": "b664207011dd548a", + "type": "subflow:57bd1b149526fcee", + "z": "46892ced77481340", + "g": "62cc8431c77a4797", + "name": "Shower", + "env": [ + { + "name": "room_name", + "value": "l_bathroom", + "type": "str" + } + ], + "x": 800, + "y": 760, + "wires": [ + [ + "5f7ffd82e5200d9a" + ], + [ + "f0bc3094d4af717e" + ], + [], + [], + [], + [ + "0a4414fc667405b4", + "22c73018f4564256" + ] + ] + }, + { + "id": "39d435c6a32d4f09", + "type": "link in", + "z": "46892ced77481340", + "g": "62cc8431c77a4797", + "name": "link in 6", + "links": [ + "e059a9fd1da3c3fd" + ], + "x": 665, + "y": 760, + "wires": [ + [ + "b664207011dd548a" + ] + ] + }, + { + "id": "1ee2b22951639d6b", + "type": "ha-binary-sensor", + "z": "46892ced77481340", + "g": "b88648a578b60cb8", + "name": "Living Room Main", + "entityConfig": "b43c6846b0baeeec", + "version": 0, + "state": "payload", + "stateType": "msg", + "attributes": [], + "inputOverride": "allow", + "outputProperties": [], + "x": 470, + "y": 880, + "wires": [ + [] + ] + }, + { + "id": "566e5b2ec261934f", + "type": "ha-binary-sensor", + "z": "46892ced77481340", + "g": "4a9361ba088ba046", + "name": "Dining Room Main", + "entityConfig": "f190a69cf72af140", + "version": 0, + "state": "payload", + "stateType": "msg", + "attributes": [], + "inputOverride": "allow", + "outputProperties": [], + "x": 470, + "y": 720, + "wires": [ + [] + ] + }, + { + "id": "5f7ffd82e5200d9a", + "type": "ha-binary-sensor", + "z": "46892ced77481340", + "g": "62cc8431c77a4797", + "name": "Main", + "entityConfig": "16560fc93e9523d4", + "version": 0, + "state": "payload", + "stateType": "msg", + "attributes": [], + "inputOverride": "allow", + "outputProperties": [], + "x": 1030, + "y": 720, + "wires": [ + [] + ] + }, + { + "id": "ba2859e1aa882eee", + "type": "ha-binary-sensor", + "z": "46892ced77481340", + "g": "4b360c7d54e39e0a", + "name": "Main", + "entityConfig": "293fc06a888710c5", + "version": 0, + "state": "payload", + "stateType": "msg", + "attributes": [], + "inputOverride": "allow", + "outputProperties": [], + "x": 1030, + "y": 960, + "wires": [ + [] + ] + }, + { + "id": "e8db33fc81400531", + "type": "ha-binary-sensor", + "z": "46892ced77481340", + "g": "0271d58953459100", + "name": "Kitchen Main", + "entityConfig": "be947f3b20336437", + "version": 0, + "state": "payload", + "stateType": "msg", + "attributes": [], + "inputOverride": "allow", + "outputProperties": [], + "x": 990, + "y": 1140, + "wires": [ + [] + ] + }, + { + "id": "e6e67dc471f2ecb3", + "type": "subflow:57bd1b149526fcee", + "z": "46892ced77481340", + "g": "2d6eb3ac6de382be", + "name": "Master Bathroom", + "env": [ + { + "name": "room_name", + "value": "l_masterBathroom", + "type": "str" + } + ], + "x": 1370, + "y": 920, + "wires": [ + [ + "29af11e532f234d4" + ], + [], + [], + [], + [], + [] + ] + }, + { + "id": "fc5020edbd894baa", + "type": "link in", + "z": "46892ced77481340", + "g": "2d6eb3ac6de382be", + "name": "link in 7", + "links": [ + "e059a9fd1da3c3fd" + ], + "x": 1205, + "y": 920, + "wires": [ + [ + "e6e67dc471f2ecb3" + ] + ] + }, + { + "id": "29af11e532f234d4", + "type": "ha-binary-sensor", + "z": "46892ced77481340", + "g": "2d6eb3ac6de382be", + "name": "Main", + "entityConfig": "edf39a33a28182ca", + "version": 0, + "state": "payload", + "stateType": "msg", + "attributes": [], + "inputOverride": "allow", + "outputProperties": [], + "x": 1570, + "y": 880, + "wires": [ + [] + ] + }, + { + "id": "b7e6812909ef1051", + "type": "subflow:57bd1b149526fcee", + "z": "46892ced77481340", + "g": "af99b05ba44c6741", + "name": "Master Bedroom", + "env": [ + { + "name": "room_name", + "value": "l_masterBedroom", + "type": "str" + } + ], + "x": 1360, + "y": 760, + "wires": [ + [ + "280e6cc7a9de62b5" + ], + [], + [], + [], + [], + [] + ] + }, + { + "id": "37cdfd56758051ef", + "type": "link in", + "z": "46892ced77481340", + "g": "af99b05ba44c6741", + "name": "link in 8", + "links": [ + "e059a9fd1da3c3fd" + ], + "x": 1205, + "y": 760, + "wires": [ + [ + "b7e6812909ef1051" + ] + ] + }, + { + "id": "280e6cc7a9de62b5", + "type": "ha-binary-sensor", + "z": "46892ced77481340", + "g": "af99b05ba44c6741", + "name": "Main", + "entityConfig": "2b453fcb286e51f5", + "version": 0, + "state": "payload", + "stateType": "msg", + "attributes": [], + "inputOverride": "allow", + "outputProperties": [], + "x": 1570, + "y": 720, + "wires": [ + [] + ] + }, + { + "id": "e0739e7f8c82e487", + "type": "ha-binary-sensor", + "z": "46892ced77481340", + "g": "0271d58953459100", + "name": "Kitchen Island", + "entityConfig": "9b99cd4af3bdb9a9", + "version": 0, + "state": "payload", + "stateType": "msg", + "attributes": [], + "inputOverride": "allow", + "outputProperties": [], + "x": 1000, + "y": 1200, + "wires": [ + [] + ] + }, + { + "id": "1530556181903c3a", + "type": "ha-binary-sensor", + "z": "46892ced77481340", + "g": "0271d58953459100", + "name": "Fridge", + "entityConfig": "d75c31dd7a1edcbf", + "version": 0, + "state": "payload", + "stateType": "msg", + "attributes": [], + "inputOverride": "allow", + "outputProperties": [], + "x": 970, + "y": 1260, + "wires": [ + [] + ] + }, + { + "id": "e37aa132859c1ea8", + "type": "subflow:57bd1b149526fcee", + "z": "46892ced77481340", + "d": true, + "g": "f27a48975d61e376", + "name": "Bedroom", + "env": [ + { + "name": "room_name", + "value": "l_bedroom_2", + "type": "str" + } + ], + "x": 2420, + "y": 260, + "wires": [ + [ + "1a534f2c5be60889" + ], + [], + [], + [], + [], + [] + ] + }, + { + "id": "d3f0c80e0188aabf", + "type": "link in", + "z": "46892ced77481340", + "g": "f27a48975d61e376", + "name": "link in 9", + "links": [ + "e059a9fd1da3c3fd" + ], + "x": 2285, + "y": 260, + "wires": [ + [ + "e37aa132859c1ea8" + ] + ] + }, + { + "id": "1a534f2c5be60889", + "type": "ha-binary-sensor", + "z": "46892ced77481340", + "d": true, + "g": "f27a48975d61e376", + "name": "", + "entityConfig": "", + "version": 0, + "state": "payload", + "stateType": "msg", + "attributes": [], + "inputOverride": "allow", + "outputProperties": [], + "x": 2640, + "y": 220, + "wires": [ + [] + ] + }, + { + "id": "f0bc3094d4af717e", + "type": "ha-binary-sensor", + "z": "46892ced77481340", + "g": "62cc8431c77a4797", + "name": "Fan", + "entityConfig": "9b2dc1266b7513da", + "version": 0, + "state": "payload", + "stateType": "msg", + "attributes": [], + "inputOverride": "allow", + "outputProperties": [], + "x": 1030, + "y": 780, + "wires": [ + [] + ] + }, + { + "id": "a024d8a903074aa4", + "type": "ha-button", + "z": "46892ced77481340", + "g": "2b9f0ac7a58d6e99", + "name": "Fan", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "ac2dba905819562f", + "outputProperties": [ + { + "property": "topic", + "propertyType": "msg", + "value": "bathroom", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "sw_2", + "valueType": "str" + } + ], + "x": 1110, + "y": 360, + "wires": [ + [ + "102d7d54a45e3f18" + ] + ] + }, + { + "id": "07bae699fea75512", + "type": "ha-binary-sensor", + "z": "46892ced77481340", + "g": "3c7f8838aa2349b9", + "name": "Pantry", + "entityConfig": "c494e187e88c3208", + "version": 0, + "state": "payload", + "stateType": "msg", + "attributes": [], + "inputOverride": "allow", + "outputProperties": [], + "x": 1570, + "y": 1040, + "wires": [ + [] + ] + }, + { + "id": "806720bc7ab2d4e8", + "type": "subflow:57bd1b149526fcee", + "z": "46892ced77481340", + "g": "3c7f8838aa2349b9", + "name": "Pantry", + "env": [ + { + "name": "room_name", + "value": "l_pantry", + "type": "str" + } + ], + "x": 1330, + "y": 1080, + "wires": [ + [ + "07bae699fea75512" + ], + [], + [], + [], + [], + [] + ] + }, + { + "id": "3b5e1f4f9ccd9c74", + "type": "link in", + "z": "46892ced77481340", + "g": "3c7f8838aa2349b9", + "name": "link in 10", + "links": [ + "e059a9fd1da3c3fd" + ], + "x": 1205, + "y": 1080, + "wires": [ + [ + "806720bc7ab2d4e8" + ] + ] + }, + { + "id": "9672723b593ef966", + "type": "debug", + "z": "46892ced77481340", + "name": "debug 1", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 1340, + "y": 40, + "wires": [] + }, + { + "id": "3ec8968914e324f5", + "type": "subflow:57bd1b149526fcee", + "z": "46892ced77481340", + "g": "0b0b27deacee2bfc", + "name": "Bedroom", + "env": [ + { + "name": "room_name", + "value": "l_outSide", + "type": "str" + } + ], + "x": 240, + "y": 1240, + "wires": [ + [ + "197fc35230069190" + ], + [], + [], + [], + [], + [] + ] + }, + { + "id": "2b29c71c180a50df", + "type": "link in", + "z": "46892ced77481340", + "g": "0b0b27deacee2bfc", + "name": "link in 11", + "links": [ + "e059a9fd1da3c3fd" + ], + "x": 105, + "y": 1240, + "wires": [ + [ + "3ec8968914e324f5" + ] + ] + }, + { + "id": "197fc35230069190", + "type": "ha-binary-sensor", + "z": "46892ced77481340", + "g": "0b0b27deacee2bfc", + "name": "Veranda BBQ Light", + "entityConfig": "1a22f89094ea7b38", + "version": 0, + "state": "payload", + "stateType": "msg", + "attributes": [], + "inputOverride": "allow", + "outputProperties": [], + "x": 470, + "y": 1200, + "wires": [ + [] + ] + }, + { + "id": "a8ac75d5944a28b9", + "type": "ha-button", + "z": "46892ced77481340", + "g": "639e1c2545c2de96", + "name": "Baby Bedroom", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "8b4d5408fdcd8c0d", + "outputProperties": [ + { + "property": "topic", + "propertyType": "msg", + "value": "bedroom_2", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "sw_1", + "valueType": "str" + } + ], + "x": 460, + "y": 540, + "wires": [ + [ + "f11fa2bedfc41e41" + ] + ] + }, + { + "id": "f11fa2bedfc41e41", + "type": "link out", + "z": "46892ced77481340", + "g": "639e1c2545c2de96", + "name": "link out 14", + "mode": "link", + "links": [ + "0bbcb269da9b75c7" + ], + "x": 605, + "y": 540, + "wires": [] + }, + { + "id": "f785dab1a0f37204", + "type": "comment", + "z": "46892ced77481340", + "name": "", + "info": "VAR_GLOBAL\n\tmasterBedroom \t:\tstruct_switches;\n\tmasterBathroom\t:\tstruct_switches;\n\tbedroom_1\t\t:\tstruct_switches;\n\tbedroom_2\t\t:\tstruct_switches;\n\tbathroom\t\t:\tstruct_switches;\n\thallway\t\t\t:\tstruct_switches;\n\tpantry\t\t\t:\tstruct_switches;\n\tkitchen\t\t\t:\tstruct_switches;\n\tlivingRoom\t\t:\tstruct_switches;\n\tdinningRoom\t\t:\tstruct_switches;\n\tentrance\t\t:\tstruct_switches;\n\tguestWc\t\t\t:\tstruct_switches;\n\toutVeranda\t\t:\tstruct_switches;\n\toutFront\t\t:\tstruct_switches;\n\toutSide\t\t\t:\tstruct_switches;\n\toutBack\t\t\t:\tstruct_switches;\nEND_VAR\n\nTYPE struct_switches :\nSTRUCT\n\tall_off:BOOL;\n\tall_on:BOOL;\t\n\tsw_1:BOOL;\n\tsw_2:BOOL;\n\tsw_3:BOOL;\n\tsw_4:BOOL;\n\tsw_5:BOOL;\n\tsw_6:BOOL;\t\nEND_STRUCT\nEND_TYPE", + "x": 1560, + "y": 40, + "wires": [] + }, + { + "id": "b4e49387d80fa951", + "type": "subflow:57bd1b149526fcee", + "z": "46892ced77481340", + "g": "bd16cebc90c528e9", + "name": "Out", + "env": [ + { + "name": "room_name", + "value": "l_outVeranda", + "type": "str" + } + ], + "x": 2110, + "y": 480, + "wires": [ + [ + "2ae3fedce613ecfe", + "d1c2e1ee1fba88c6" + ], + [], + [], + [], + [], + [] + ] + }, + { + "id": "860779317fad91b5", + "type": "link in", + "z": "46892ced77481340", + "g": "bd16cebc90c528e9", + "name": "link in 12", + "links": [ + "e059a9fd1da3c3fd" + ], + "x": 1985, + "y": 480, + "wires": [ + [ + "b4e49387d80fa951", + "1a8d95afd537e151" + ] + ] + }, + { + "id": "2ae3fedce613ecfe", + "type": "ha-binary-sensor", + "z": "46892ced77481340", + "g": "bd16cebc90c528e9", + "name": "Front Lights", + "entityConfig": "fe79cfd2fda112db", + "version": 0, + "state": "payload", + "stateType": "msg", + "attributes": [], + "inputOverride": "allow", + "outputProperties": [], + "x": 2330, + "y": 440, + "wires": [ + [] + ] + }, + { + "id": "66255b06a0d4a4c9", + "type": "ha-button", + "z": "46892ced77481340", + "g": "507ca4f1b82ad9c2", + "name": "Front Lights", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "311d801ad1f1bc0f", + "outputProperties": [ + { + "property": "topic", + "propertyType": "msg", + "value": "outVeranda", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "sw_1", + "valueType": "str" + } + ], + "x": 2050, + "y": 600, + "wires": [ + [ + "9c99ecea6e4aefd8" + ] + ] + }, + { + "id": "9c99ecea6e4aefd8", + "type": "link out", + "z": "46892ced77481340", + "g": "507ca4f1b82ad9c2", + "name": "link out 20", + "mode": "link", + "links": [ + "0bbcb269da9b75c7" + ], + "x": 2205, + "y": 600, + "wires": [] + }, + { + "id": "f48d2c10e183fb95", + "type": "debug", + "z": "46892ced77481340", + "name": "debug 15", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 600, + "y": 80, + "wires": [] + }, + { + "id": "438b13c5cdbb5f1c", + "type": "debug", + "z": "46892ced77481340", + "name": "debug 16", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 380, + "y": 100, + "wires": [] + }, + { + "id": "d680ff6c7ac109cf", + "type": "ha-button", + "z": "46892ced77481340", + "g": "2b9f0ac7a58d6e99", + "name": "Water Boiler", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "0c9a4c8d912d0a38", + "outputProperties": [ + { + "property": "topic", + "propertyType": "msg", + "value": "bathroom", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "sw_6", + "valueType": "str" + } + ], + "x": 1130, + "y": 420, + "wires": [ + [ + "102d7d54a45e3f18", + "9dc26f31e8b58f14" + ] + ] + }, + { + "id": "0a4414fc667405b4", + "type": "ha-binary-sensor", + "z": "46892ced77481340", + "g": "62cc8431c77a4797", + "name": "Water_boiler", + "entityConfig": "8166d41aaab8a32a", + "version": 0, + "state": "payload", + "stateType": "msg", + "attributes": [], + "inputOverride": "allow", + "outputProperties": [], + "x": 1010, + "y": 840, + "wires": [ + [] + ] + }, + { + "id": "9dc26f31e8b58f14", + "type": "debug", + "z": "46892ced77481340", + "g": "2b9f0ac7a58d6e99", + "name": "debug 29", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 1330, + "y": 440, + "wires": [] + }, + { + "id": "1a8d95afd537e151", + "type": "debug", + "z": "46892ced77481340", + "g": "bd16cebc90c528e9", + "name": "debug 31", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 2090, + "y": 360, + "wires": [] + }, + { + "id": "d1c2e1ee1fba88c6", + "type": "debug", + "z": "46892ced77481340", + "g": "bd16cebc90c528e9", + "name": "debug 32", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 2250, + "y": 400, + "wires": [] + }, + { + "id": "e4d5c7b8295b4251", + "type": "debug", + "z": "46892ced77481340", + "name": "debug 27", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 120, + "y": 100, + "wires": [] + }, + { + "id": "22c73018f4564256", + "type": "debug", + "z": "46892ced77481340", + "g": "62cc8431c77a4797", + "name": "debug 34", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 980, + "y": 900, + "wires": [] + }, + { + "id": "1f3ba1a73e155da9", + "type": "ha-button", + "z": "46892ced77481340", + "g": "3cc3b21e7b1f7b13", + "name": "Entrance Mirror", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "c1b8d65524acbab3", + "outputProperties": [ + { + "property": "topic", + "propertyType": "msg", + "value": "dinningRoom", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "sw_3", + "valueType": "str" + } + ], + "x": 800, + "y": 260, + "wires": [ + [ + "04e183fcfe9e9f6d" + ] + ] + }, + { + "id": "89762968e1556a42", + "type": "ha-binary-sensor", + "z": "46892ced77481340", + "g": "4a9361ba088ba046", + "name": "Entrance Miror", + "entityConfig": "72e9fc1709b9891c", + "version": 0, + "state": "payload", + "stateType": "msg", + "attributes": [], + "inputOverride": "allow", + "outputProperties": [], + "x": 460, + "y": 780, + "wires": [ + [] + ] + }, + { + "id": "6190a90971a43aba", + "type": "ha-button", + "z": "46892ced77481340", + "g": "3cc3b21e7b1f7b13", + "name": "Entrance Light in", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "121b1dead369ae82", + "outputProperties": [ + { + "property": "topic", + "propertyType": "msg", + "value": "entrance", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "sw_1", + "valueType": "str" + } + ], + "x": 800, + "y": 320, + "wires": [ + [ + "04e183fcfe9e9f6d" + ] + ] + }, + { + "id": "5351a670ea3bcf15", + "type": "subflow:57bd1b149526fcee", + "z": "46892ced77481340", + "g": "9da6393897881dca", + "name": "Entrance", + "env": [ + { + "name": "room_name", + "value": "l_entrance", + "type": "str" + } + ], + "x": 1940, + "y": 920, + "wires": [ + [ + "9b4f318fa5f438aa" + ], + [], + [], + [], + [], + [] + ] + }, + { + "id": "8dd17125d6bb49f7", + "type": "link in", + "z": "46892ced77481340", + "g": "9da6393897881dca", + "name": "link in 13", + "links": [ + "e059a9fd1da3c3fd" + ], + "x": 1805, + "y": 920, + "wires": [ + [ + "5351a670ea3bcf15" + ] + ] + }, + { + "id": "9b4f318fa5f438aa", + "type": "ha-binary-sensor", + "z": "46892ced77481340", + "g": "9da6393897881dca", + "name": "Entrance Light In", + "entityConfig": "395ea43be3b0e20c", + "version": 0, + "state": "payload", + "stateType": "msg", + "attributes": [], + "inputOverride": "allow", + "outputProperties": [], + "x": 2170, + "y": 880, + "wires": [ + [] + ] + }, + { + "id": "61d0351b6a2d43cc", + "type": "link in", + "z": "46892ced77481340", + "name": "to UDP", + "links": [], + "x": 375, + "y": 20, + "wires": [ + [ + "040e9ff14df4a326" + ] + ] + }, + { + "id": "d592161c8e50638a", + "type": "debug", + "z": "480553bb6f5b2b51", + "g": "91b1f4cb0e1ffa84", + "name": "debug 7", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 2540, + "y": 80, + "wires": [] + }, + { + "id": "2a218aee475ce267", + "type": "debug", + "z": "480553bb6f5b2b51", + "g": "91b1f4cb0e1ffa84", + "name": "debug 10", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 2540, + "y": 120, + "wires": [] + }, + { + "id": "f5ef8cb0a1bc7def", + "type": "telegram receiver", + "z": "480553bb6f5b2b51", + "g": "91b1f4cb0e1ffa84", + "name": "", + "bot": "3dbb887330025103", + "saveDataDir": "", + "filterCommands": false, + "inputs": 0, + "x": 2350, + "y": 80, + "wires": [ + [ + "d592161c8e50638a" + ], + [ + "2a218aee475ce267" + ] + ] + }, + { + "id": "0385d24efa18d698", + "type": "zigbee2mqtt-in", + "z": "480553bb6f5b2b51", + "g": "9113c5ebf5c089e3", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Hallway Door Sensor (TS0203)", + "device_id": "0xa4c138eb9239e0de", + "state": "contact", + "outputAtStartup": true, + "filterChanges": true, + "enableMultiple": false, + "x": 190, + "y": 120, + "wires": [ + [ + "4055425d6c42dd13" + ] + ] + }, + { + "id": "9d611c0ff2a0b1ca", + "type": "zigbee2mqtt-in", + "z": "480553bb6f5b2b51", + "g": "9113c5ebf5c089e3", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Basement Door Sensor (E2013)", + "device_id": "0x048727fffea28deb", + "state": "contact", + "outputAtStartup": true, + "filterChanges": true, + "enableMultiple": false, + "x": 190, + "y": 160, + "wires": [ + [ + "4055425d6c42dd13" + ] + ] + }, + { + "id": "10448f46514c1406", + "type": "zigbee2mqtt-in", + "z": "480553bb6f5b2b51", + "g": "9113c5ebf5c089e3", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Front Door Sensor (TS0203)", + "device_id": "0xa4c138f4b1035c1d", + "state": "contact", + "outputAtStartup": true, + "filterChanges": true, + "enableMultiple": false, + "x": 180, + "y": 200, + "wires": [ + [ + "4055425d6c42dd13" + ] + ] + }, + { + "id": "a9d786b1ba52704b", + "type": "function", + "z": "480553bb6f5b2b51", + "g": "9113c5ebf5c089e3", + "name": "Generate Message with snapshot", + "func": "let contact = msg.payload;\nlet topic = msg.topic || '';\nlet rawName = topic.split('/')[1] || 'Unknown';\nlet cleanName = rawName.replace(/sensor/i, '').trim();\n\n// Mapping between sensor names (or topic parts) and their camera IPs\nconst cameraMap = {\n \"Hallway\": \"10.20.30.33\",\n \"Basement\": \"10.20.30.30\",\n \"Front\": \"10.20.30.30\",\n \"Veranda\": \"10.20.30.32\"\n};\n\n// Find matching camera IP based on topic\nlet cameraIP = Object.keys(cameraMap).find(key => topic.includes(key));\nlet ip = cameraMap[cameraIP] || \"10.20.30.30\"; // fallback to Hallway if not matched\n\n// Construct URL\nmsg.url = `http://${ip}/cgi-bin/api.cgi?cmd=Snap&channel=0&rs=${Math.random().toString(36).substring(7)}&user=admin&password=sh1pb0x1`;\n\n// Final message structure\nmsg.payload = null; // clear previous payload\nmsg.friendly_name = cleanName;\nmsg.door_status = contact === true ? \"CLOSED\" : \"OPENED\";\n\nreturn msg;\n\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 920, + "y": 120, + "wires": [ + [ + "5e94eb39aec6e82d", + "8b8cce25637907b1" + ] + ] + }, + { + "id": "e250cc2f901bea9f", + "type": "function", + "z": "480553bb6f5b2b51", + "g": "9113c5ebf5c089e3", + "name": "Send Message to group", + "func": "// Check that the snapshot came in as a binary buffer\nif (!Buffer.isBuffer(msg.payload)) {\n node.warn(\"❌ Snapshot fetch failed — not a binary image buffer\");\n return null;\n}\n\nmsg.payload = {\n chatId: -4747176681,\n type: 'photo',\n content: msg.payload, // this is the buffer\n caption: `🚪 ${msg.friendly_name} just ${msg.door_status}`\n};\n\nreturn msg;\n\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1410, + "y": 120, + "wires": [ + [ + "7301d71523d87e94" + ] + ] + }, + { + "id": "5e94eb39aec6e82d", + "type": "debug", + "z": "480553bb6f5b2b51", + "g": "9113c5ebf5c089e3", + "name": "debug 6", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "statusVal": "", + "statusType": "auto", + "x": 1160, + "y": 60, + "wires": [] + }, + { + "id": "8b8cce25637907b1", + "type": "http request", + "z": "480553bb6f5b2b51", + "g": "9113c5ebf5c089e3", + "name": "", + "method": "GET", + "ret": "bin", + "paytoqs": "query", + "url": "", + "tls": "", + "persist": false, + "proxy": "", + "insecureHTTPParser": false, + "authType": "", + "senderr": false, + "headers": [], + "x": 1190, + "y": 120, + "wires": [ + [ + "e250cc2f901bea9f" + ] + ] + }, + { + "id": "3f9797c13c3a11a1", + "type": "function", + "z": "480553bb6f5b2b51", + "d": true, + "g": "9113c5ebf5c089e3", + "name": "Door Status Message", + "func": "// Extract contact state and sensor name\nlet contact = msg.payload;\nlet topic = msg.topic || '';\nlet rawName = topic.split('/')[1] || 'Unknown';\nlet cleanName = rawName.replace(/sensor/i, '').trim();\n\n// Create message\nmsg.payload = {\n chatId: -4747176681,\n type: 'message',\n content: `🚪 ${cleanName} just ${contact === true ? \"CLOSED\" : \"OPENED\"}`\n};\n\nreturn msg;\n\n\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 940, + "y": 60, + "wires": [ + [] + ] + }, + { + "id": "c57ea029ee569428", + "type": "zigbee2mqtt-in", + "z": "480553bb6f5b2b51", + "g": "a73395db2426b4fc", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Front Water Valve (SWV)", + "device_id": "0x0ceff6fffe84e4bc", + "state": "state", + "outputAtStartup": false, + "filterChanges": true, + "enableMultiple": false, + "x": 610, + "y": 540, + "wires": [ + [ + "fe7372c7f33d96f3" + ] + ] + }, + { + "id": "fe7372c7f33d96f3", + "type": "function", + "z": "480553bb6f5b2b51", + "g": "a73395db2426b4fc", + "name": "Status Message", + "func": "let state = msg.payload;\nlet status = state === 'ON' ? \"OPENED\" : \"CLOSED\";\n\nmsg.payload = {\n chatId: -4747176681, // your group chat ID\n type: 'message',\n content: `💧 Front Water Valve is now ${status}`\n};\n\nreturn msg;\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 840, + "y": 540, + "wires": [ + [ + "18454d6cad2042dc" + ] + ] + }, + { + "id": "c1d2a4dc34b63730", + "type": "zigbee2mqtt-in", + "z": "480553bb6f5b2b51", + "g": "a73395db2426b4fc", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Back Water Valve (SWV)", + "device_id": "0x7cc6b6fffe38a415", + "state": "state", + "outputAtStartup": false, + "filterChanges": true, + "enableMultiple": false, + "x": 610, + "y": 600, + "wires": [ + [ + "141f635c6c9d380f" + ] + ] + }, + { + "id": "141f635c6c9d380f", + "type": "function", + "z": "480553bb6f5b2b51", + "g": "a73395db2426b4fc", + "name": "Status Message", + "func": "let state = msg.payload;\nlet status = state === 'ON' ? \"OPENED\" : \"CLOSED\";\n\nmsg.payload = {\n chatId: -4747176681, // your group chat ID\n type: 'message',\n content: `💧 Back Water Valve is now ${status}`\n};\n\nreturn msg;\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 840, + "y": 600, + "wires": [ + [ + "18454d6cad2042dc" + ] + ] + }, + { + "id": "77f3053cbdcdb875", + "type": "debug", + "z": "480553bb6f5b2b51", + "name": "debug 9", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 2540, + "y": 260, + "wires": [] + }, + { + "id": "55c50c70b3d0a0bb", + "type": "telegram sender", + "z": "480553bb6f5b2b51", + "name": "", + "bot": "3dbb887330025103", + "haserroroutput": false, + "outputs": 1, + "x": 2330, + "y": 260, + "wires": [ + [ + "77f3053cbdcdb875" + ] + ] + }, + { + "id": "41d29e978759e26e", + "type": "link in", + "z": "480553bb6f5b2b51", + "name": "telegram sender", + "links": [ + "7301d71523d87e94", + "18454d6cad2042dc", + "70f797398b0e6783", + "81d16e7a5307e9cc", + "bd7fed2198a7b950", + "8f15067daf678e66", + "1ba4ee81e40e5c51" + ], + "x": 2145, + "y": 260, + "wires": [ + [ + "55c50c70b3d0a0bb" + ] + ] + }, + { + "id": "7301d71523d87e94", + "type": "link out", + "z": "480553bb6f5b2b51", + "g": "9113c5ebf5c089e3", + "name": "link out 16", + "mode": "link", + "links": [ + "41d29e978759e26e" + ], + "x": 1555, + "y": 120, + "wires": [] + }, + { + "id": "18454d6cad2042dc", + "type": "link out", + "z": "480553bb6f5b2b51", + "g": "a73395db2426b4fc", + "name": "link out 17", + "mode": "link", + "links": [ + "41d29e978759e26e" + ], + "x": 1055, + "y": 540, + "wires": [] + }, + { + "id": "4055425d6c42dd13", + "type": "api-current-state", + "z": "480553bb6f5b2b51", + "g": "9113c5ebf5c089e3", + "name": "", + "server": "29eb84a2708d96e0", + "version": 3, + "outputs": 2, + "halt_if": "home", + "halt_if_type": "str", + "halt_if_compare": "is", + "entity_id": "person.nearchos_paraskeva", + "state_type": "str", + "blockInputOverrides": false, + "outputProperties": [], + "for": "", + "forType": "num", + "forUnits": "seconds", + "override_topic": false, + "state_location": "payload", + "override_payload": "msg", + "entity_location": "data", + "override_data": "msg", + "x": 570, + "y": 120, + "wires": [ + [], + [ + "a9d786b1ba52704b", + "f6adcaa35a793e58" + ] + ] + }, + { + "id": "ada6fe8339eab29b", + "type": "zigbee2mqtt-in", + "z": "480553bb6f5b2b51", + "g": "9113c5ebf5c089e3", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Veranda Door Sensor (E2013)", + "device_id": "0x048727fffea287ba", + "state": "contact", + "outputAtStartup": true, + "filterChanges": true, + "enableMultiple": false, + "x": 180, + "y": 260, + "wires": [ + [ + "4055425d6c42dd13" + ] + ] + }, + { + "id": "8f42e8fdfef40579", + "type": "zigbee2mqtt-in", + "z": "480553bb6f5b2b51", + "g": "5c95d99a679a5e00", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Pantry water leak Sensor (ZG-222ZA)", + "device_id": "0xa4c138aeed18e61c", + "state": "water_leak", + "outputAtStartup": true, + "filterChanges": false, + "enableMultiple": false, + "x": 230, + "y": 620, + "wires": [ + [ + "1164cfc4fd2e5382" + ] + ] + }, + { + "id": "58ee1ac91f379cb9", + "type": "function", + "z": "480553bb6f5b2b51", + "g": "5c95d99a679a5e00", + "name": "Status Message", + "func": "let state = msg.payload;\n\nif (state === true) {\n msg.payload = {\n chatId: -4747176681, // your group chat ID\n type: 'animation',\n content: 'https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExeWoxNjV6Z2Fna3MxMjcxd3RsaWxjMTA3cWhraW9kbGYzbGFtNzUzYiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/lVBtp4SRW6rvDHf1b6/giphy.gif', // Replace with your preferred GIF URL\n caption: '🚨 WATER LEAK DETECTED in Pantry Sink!'\n };\n return msg;\n}\n\nreturn null;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1000, + "y": 460, + "wires": [ + [ + "70f797398b0e6783" + ] + ] + }, + { + "id": "a1f1e1ea2cf90f61", + "type": "inject", + "z": "480553bb6f5b2b51", + "g": "5c95d99a679a5e00", + "name": "Simulate Leak (true)", + "props": [ + { + "p": "payload" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "true", + "payloadType": "bool", + "x": 290, + "y": 420, + "wires": [ + [ + "22740e52f6cc3399" + ] + ] + }, + { + "id": "3242605bce7f6f92", + "type": "inject", + "z": "480553bb6f5b2b51", + "g": "5c95d99a679a5e00", + "name": "Simulate No Leak (false)", + "props": [ + { + "p": "payload" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "false", + "payloadType": "bool", + "x": 270, + "y": 460, + "wires": [ + [ + "22740e52f6cc3399" + ] + ] + }, + { + "id": "22740e52f6cc3399", + "type": "function", + "z": "480553bb6f5b2b51", + "g": "5c95d99a679a5e00", + "name": "Prepare trigger/reset", + "func": "// Pass through leak state\n// Add reset flag when false\n\nif (msg.payload === false) {\n msg.reset = true;\n}\n\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 560, + "y": 420, + "wires": [ + [ + "53799a37bb6cf7c6" + ] + ] + }, + { + "id": "53799a37bb6cf7c6", + "type": "trigger", + "z": "480553bb6f5b2b51", + "g": "5c95d99a679a5e00", + "name": "Repeat every 1 min", + "op1": "true", + "op2": "true", + "op1type": "bool", + "op2type": "bool", + "duration": "1", + "extend": true, + "overrideDelay": false, + "units": "min", + "reset": "reset", + "bytopic": "all", + "topic": "topic", + "outputs": 1, + "x": 780, + "y": 420, + "wires": [ + [ + "8b9761ea2f86b4f1" + ] + ] + }, + { + "id": "8b9761ea2f86b4f1", + "type": "function", + "z": "480553bb6f5b2b51", + "g": "5c95d99a679a5e00", + "name": "Status Message", + "func": "let state = msg.payload;\n\nif (state === true) {\n msg.payload = {\n chatId: -4747176681, // your group chat ID\n type: 'animation',\n content: 'https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExeWoxNjV6Z2Fna3MxMjcxd3RsaWxjMTA3cWhraW9kbGYzbGFtNzUzYiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/lVBtp4SRW6rvDHf1b6/giphy.gif', // Replace with your preferred GIF URL\n caption: '🚨 WATER LEAK DETECTED in Kitchen Sink!'\n };\n return msg;\n}\n\nreturn null;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1000, + "y": 420, + "wires": [ + [ + "70f797398b0e6783" + ] + ] + }, + { + "id": "70f797398b0e6783", + "type": "link out", + "z": "480553bb6f5b2b51", + "g": "5c95d99a679a5e00", + "name": "link out 19", + "mode": "link", + "links": [ + "41d29e978759e26e" + ], + "x": 1575, + "y": 440, + "wires": [] + }, + { + "id": "2157cdd444dfec91", + "type": "zigbee2mqtt-in", + "z": "480553bb6f5b2b51", + "g": "5c95d99a679a5e00", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "kitchen water leak sensor (E2202)", + "device_id": "0xf84477fffeec19e9", + "state": "water_leak", + "outputAtStartup": true, + "filterChanges": false, + "enableMultiple": false, + "x": 240, + "y": 500, + "wires": [ + [ + "22740e52f6cc3399" + ] + ] + }, + { + "id": "8b2f06afcf0d773e", + "type": "inject", + "z": "480553bb6f5b2b51", + "g": "5c95d99a679a5e00", + "name": "Simulate Leak (true)", + "props": [ + { + "p": "payload" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "true", + "payloadType": "bool", + "x": 290, + "y": 540, + "wires": [ + [ + "1164cfc4fd2e5382" + ] + ] + }, + { + "id": "e31ec1ce332c4c99", + "type": "inject", + "z": "480553bb6f5b2b51", + "g": "5c95d99a679a5e00", + "name": "Simulate No Leak (false)", + "props": [ + { + "p": "payload" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "false", + "payloadType": "bool", + "x": 270, + "y": 580, + "wires": [ + [ + "1164cfc4fd2e5382" + ] + ] + }, + { + "id": "1164cfc4fd2e5382", + "type": "function", + "z": "480553bb6f5b2b51", + "g": "5c95d99a679a5e00", + "name": "Prepare trigger/reset", + "func": "// Pass through leak state\n// Add reset flag when false\n\nif (msg.payload === false) {\n msg.reset = true;\n}\n\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 560, + "y": 460, + "wires": [ + [ + "748c39a404648158" + ] + ] + }, + { + "id": "748c39a404648158", + "type": "trigger", + "z": "480553bb6f5b2b51", + "g": "5c95d99a679a5e00", + "name": "Repeat every 1 min", + "op1": "true", + "op2": "true", + "op1type": "bool", + "op2type": "bool", + "duration": "1", + "extend": true, + "overrideDelay": false, + "units": "min", + "reset": "reset", + "bytopic": "all", + "topic": "topic", + "outputs": 1, + "x": 790, + "y": 460, + "wires": [ + [ + "58ee1ac91f379cb9" + ] + ] + }, + { + "id": "eb721beae24b271b", + "type": "zigbee2mqtt-in", + "z": "480553bb6f5b2b51", + "g": "1c73b8aaec035f06", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "27 selected", + "device_id": [ + "0xa4c138ff324b5b7e", + "0xa4c138a5b9771b05", + "0xa4c13873de3faa66", + "0xa4c138cc9b54cec9", + "0xa4c138112a12d55f", + "0xa4c13854734f3a20", + "0xa4c1387dd1c2d40e", + "0xa4c138332cc3da08", + "0xa4c1388b74d83cfa", + "0xa4c138c2ae9ed430", + "0xa4c1380bab38d546", + "0xa4c1383b7c24483a", + "0x8c65a3fffee879a6", + "0xa4c138ceaa5681d5", + "0xa4c138d65f53037c", + "0xa4c1383d7921827a", + "0xa4c138eb9239e0de", + "0xa4c13891821386eb", + "0xa4c138f4b1035c1d", + "0x0ceff6fffe84e4bc", + "0x7cc6b6fffe38a415", + "0x048727fffea28deb", + "0x6cfd22fffe2cc318", + "0x6cfd22fffe66db44", + "0xa4c138aeed18e61c", + "0xf84477fffeec19e9", + "0x048727fffea287ba" + ], + "state": null, + "outputAtStartup": false, + "filterChanges": false, + "enableMultiple": true, + "x": 150, + "y": 820, + "wires": [ + [ + "41f1e9feaf1ea6aa", + "a9f755806e4e8a75" + ] + ] + }, + { + "id": "41f1e9feaf1ea6aa", + "type": "function", + "z": "480553bb6f5b2b51", + "g": "1c73b8aaec035f06", + "name": "function 5", + "func": "let result = [];\nlet devices = msg.payload;\nlet now = Date.now();\nlet dayMs = 24 * 60 * 60 * 1000;\n\n// Use flow context to track last alerts\nlet lastAlerts = flow.get(\"battery_alerts\") || {};\n\nfor (let ieee in devices) {\n let dev = devices[ieee];\n let battery = dev.payload?.battery ?? dev.item?.current_values?.battery;\n let name = dev.item?.friendly_name || dev.topic || ieee;\n\n if (typeof battery === 'number' && battery < 15) {\n let lastAlert = lastAlerts[ieee];\n\n if (!lastAlert || now - lastAlert > dayMs) {\n result.push(`🔋 ${name} is at ${battery}%`);\n lastAlerts[ieee] = now; // update timestamp\n }\n }\n}\n\n// Save updated state\nflow.set(\"battery_alerts\", lastAlerts);\n\nif (result.length > 0) {\n let time = new Date().toLocaleString();\n msg.payload = `Low battery alert(s) [${time}]:\\n` + result.join('\\n');\n return msg;\n}\n\nreturn null;\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 600, + "y": 740, + "wires": [ + [ + "c60307d3b68ebe34", + "5c2272c244ffc8b0" + ] + ] + }, + { + "id": "c60307d3b68ebe34", + "type": "function", + "z": "480553bb6f5b2b51", + "g": "1c73b8aaec035f06", + "name": "function 6", + "func": "let battery = msg.payload;\n\n\nmsg.payload = {\n chatId: -4747176681, // your group chat ID\n type: 'message',\n content: `${battery}`\n};\n\nreturn msg;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1040, + "y": 740, + "wires": [ + [ + "81d16e7a5307e9cc", + "6d60df5c0c6920d2" + ] + ] + }, + { + "id": "81d16e7a5307e9cc", + "type": "link out", + "z": "480553bb6f5b2b51", + "g": "1c73b8aaec035f06", + "name": "link out 18", + "mode": "link", + "links": [ + "41d29e978759e26e" + ], + "x": 1575, + "y": 740, + "wires": [] + }, + { + "id": "209d79611a5414cc", + "type": "function", + "z": "480553bb6f5b2b51", + "d": true, + "g": "1c73b8aaec035f06", + "name": "function 7", + "func": "let result = [];\nlet devices = msg.payload;\n\nfor (let ieee in devices) {\n let dev = devices[ieee];\n\n // Try to get battery from payload or fallback to item.current_values\n let battery = dev.payload?.battery ?? dev.item?.current_values?.battery;\n\n // Get friendly name or fallback to topic or IEEE\n let name = dev.item?.friendly_name || dev.topic || ieee;\n // if (typeof battery === 'number') {\n if (typeof battery === 'number' && battery < 15) {\n result.push(`🔋 ${name} is at ${battery}%`);\n }\n}\n\nif (result.length > 0) {\n msg.payload = `Low battery alert(s):\\n` + result.join('\\n');\n return msg;\n}\n\nreturn null;\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 600, + "y": 780, + "wires": [ + [] + ] + }, + { + "id": "e6a93b0cbc48af98", + "type": "inject", + "z": "480553bb6f5b2b51", + "g": "1c73b8aaec035f06", + "name": "Test Device", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "zigbee2mqtt/Test Device", + "payload": "{\"0xdeadbeef00000001\":{\"item\":{\"friendly_name\":\"Low Battery Sensor\"},\"topic\":\"zigbee2mqtt/Low Battery Sensor\",\"payload\":{\"battery\":9}},\"0xdeadbeef00000002\":{\"item\":{\"friendly_name\":\"Good Battery Sensor\"},\"topic\":\"zigbee2mqtt/Good Battery Sensor\",\"payload\":{\"battery\":87}}}", + "payloadType": "json", + "x": 170, + "y": 740, + "wires": [ + [ + "41f1e9feaf1ea6aa", + "a9f755806e4e8a75" + ] + ] + }, + { + "id": "a9f755806e4e8a75", + "type": "debug", + "z": "480553bb6f5b2b51", + "g": "1c73b8aaec035f06", + "name": "debug 2", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 160, + "y": 780, + "wires": [] + }, + { + "id": "5c2272c244ffc8b0", + "type": "debug", + "z": "480553bb6f5b2b51", + "g": "1c73b8aaec035f06", + "name": "debug 3", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 760, + "y": 760, + "wires": [] + }, + { + "id": "6d60df5c0c6920d2", + "type": "debug", + "z": "480553bb6f5b2b51", + "g": "1c73b8aaec035f06", + "name": "debug 4", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 1200, + "y": 760, + "wires": [] + }, + { + "id": "8e0d5ec04f029b9a", + "type": "zigbee2mqtt-in", + "z": "480553bb6f5b2b51", + "g": "9113c5ebf5c089e3", + "name": "Door Sensors", + "server": "4e20fc347c658518", + "friendly_name": "4 selected", + "device_id": [ + "0xa4c138eb9239e0de", + "0xa4c138f4b1035c1d", + "0x048727fffea28deb", + "0x048727fffea287ba" + ], + "state": null, + "outputAtStartup": false, + "filterChanges": false, + "enableMultiple": true, + "x": 130, + "y": 80, + "wires": [ + [ + "eb25ece689ea1045", + "63cb264d89898c6a" + ] + ] + }, + { + "id": "eb25ece689ea1045", + "type": "rbe", + "z": "480553bb6f5b2b51", + "g": "9113c5ebf5c089e3", + "name": "", + "func": "rbe", + "gap": "", + "start": "", + "inout": "out", + "septopics": true, + "property": "payload", + "topi": "topic", + "x": 350, + "y": 80, + "wires": [ + [ + "bd4b7dd437c1aaaf" + ] + ] + }, + { + "id": "63cb264d89898c6a", + "type": "debug", + "z": "480553bb6f5b2b51", + "g": "9113c5ebf5c089e3", + "name": "debug 5", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 270, + "y": 40, + "wires": [] + }, + { + "id": "bd4b7dd437c1aaaf", + "type": "debug", + "z": "480553bb6f5b2b51", + "g": "9113c5ebf5c089e3", + "name": "debug 8", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 470, + "y": 60, + "wires": [] + }, + { + "id": "f6adcaa35a793e58", + "type": "debug", + "z": "480553bb6f5b2b51", + "g": "9113c5ebf5c089e3", + "name": "debug 11", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 850, + "y": 200, + "wires": [] + }, + { + "id": "8656a450e0ae020d", + "type": "mqtt in", + "z": "480553bb6f5b2b51", + "d": true, + "g": "ce79bea4667a77e2", + "name": "", + "topic": "#", + "qos": "2", + "datatype": "auto-detect", + "broker": "a1235a09a8d34dea", + "nl": false, + "rap": true, + "rh": 0, + "inputs": 0, + "x": 150, + "y": 1200, + "wires": [ + [ + "7ff8359abb38dca4" + ] + ] + }, + { + "id": "7ff8359abb38dca4", + "type": "switch", + "z": "480553bb6f5b2b51", + "d": true, + "g": "ce79bea4667a77e2", + "name": "", + "property": "topic", + "propertyType": "msg", + "rules": [ + { + "t": "cont", + "v": "zigbee2mqtt", + "vt": "str" + }, + { + "t": "eq", + "v": "frigate/doorbell/person", + "vt": "str" + }, + { + "t": "eq", + "v": "frigate/veranda_door/person", + "vt": "str" + }, + { + "t": "eq", + "v": "frigate/basement_driveway/person", + "vt": "str" + }, + { + "t": "cont", + "v": "frigate", + "vt": "str" + }, + { + "t": "else" + } + ], + "checkall": "true", + "repair": false, + "outputs": 6, + "x": 330, + "y": 1200, + "wires": [ + [], + [ + "doorbell_state_check" + ], + [ + "48bfe0ad8a973a84" + ], + [ + "365adc2679063bc8" + ], + [], + [] + ] + }, + { + "id": "doorbell_state_check", + "type": "function", + "z": "480553bb6f5b2b51", + "d": true, + "g": "ce79bea4667a77e2", + "name": "Check State Change", + "func": "// Get current payload value\nvar currentValue = parseInt(msg.payload);\n\n// Get previous state from context (default to 0 if not set)\nvar previousValue = context.get('previousDoorbellState') || 0;\n\n// Store current value for next comparison\ncontext.set('previousDoorbellState', currentValue);\n\n// Only proceed if current value is 1 AND previous value was 0\nif (currentValue === 1 && previousValue === 0) {\n msg.payload = \"🚪 Person detected at doorbell!\";\n return msg;\n}\n\n// Don't send message for other cases\nreturn null;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 700, + "y": 1200, + "wires": [ + [ + "78a1c2a40934ef20" + ] + ] + }, + { + "id": "bd7fed2198a7b950", + "type": "link out", + "z": "480553bb6f5b2b51", + "d": true, + "g": "ce79bea4667a77e2", + "name": "link out 22", + "mode": "link", + "links": [ + "41d29e978759e26e" + ], + "x": 2335, + "y": 1200, + "wires": [] + }, + { + "id": "78a1c2a40934ef20", + "type": "function", + "z": "480553bb6f5b2b51", + "d": true, + "g": "ce79bea4667a77e2", + "name": "Generate Doorbell Camera URL", + "func": "// Hardcoded doorbell camera IP\nconst doorbellCameraIP = \"10.20.30.30\";\n\n// Generate random string for cache busting\nconst randomString = Math.random().toString(36).substring(7);\n\n// Construct camera snapshot URL\nmsg.url = `http://${doorbellCameraIP}/cgi-bin/api.cgi?cmd=Snap&channel=0&rs=${randomString}&user=admin&password=sh1pb0x1`;\n\n// Clear payload for HTTP request\nmsg.payload = null;\n\n// Add camera info to message\nmsg.camera_ip = doorbellCameraIP;\nmsg.camera_location = \"Doorbell Camera\";\n\nnode.status({fill:\"blue\", shape:\"dot\", text:`URL: ${doorbellCameraIP}`});\n\nreturn msg;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 990, + "y": 1200, + "wires": [ + [ + "05f0d77a9b605f22" + ] + ] + }, + { + "id": "05f0d77a9b605f22", + "type": "http request", + "z": "480553bb6f5b2b51", + "d": true, + "g": "ce79bea4667a77e2", + "name": "Fetch Camera Snapshot", + "method": "GET", + "ret": "bin", + "paytoqs": "query", + "url": "", + "tls": "", + "persist": false, + "proxy": "", + "insecureHTTPParser": false, + "authType": "", + "senderr": false, + "headers": [], + "x": 1270, + "y": 1200, + "wires": [ + [ + "19d0e8d8c5addb1f" + ] + ] + }, + { + "id": "19d0e8d8c5addb1f", + "type": "function", + "z": "480553bb6f5b2b51", + "d": true, + "g": "ce79bea4667a77e2", + "name": "Send Doorbell Alert to Telegram", + "func": "// Validate that we received a proper image buffer\nif (!Buffer.isBuffer(msg.payload)) {\n node.warn(\"❌ Doorbell snapshot fetch failed — not a binary image buffer\");\n node.status({fill:\"red\", shape:\"ring\", text:\"Snapshot failed\"});\n return null;\n}\n\n// Check if buffer has reasonable size (basic validation)\nif (msg.payload.length < 1000) {\n node.warn(\"❌ Doorbell snapshot too small — likely corrupted\");\n node.status({fill:\"red\", shape:\"ring\", text:\"Image too small\"});\n return null;\n}\n\n// Format timestamp for display\nconst timestamp = new Date(msg.timestamp).toLocaleString();\n\n// Prepare Telegram message payload\nmsg.payload = {\n chatId: -4747176681, // Telegram group chat ID\n type: 'photo',\n content: msg.payload, // Binary image buffer\n caption: `🔔 DOORBELL ALERT! 🔔\\n\\n📍 Someone is at the ${msg.location}\\n⏰ Time: ${timestamp}\\n📷 Camera: ${msg.camera_location} (${msg.camera_ip})`\n};\n\nnode.status({fill:\"green\", shape:\"dot\", text:\"Alert sent!\"});\n\nreturn msg;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1540, + "y": 1200, + "wires": [ + [ + "bd7fed2198a7b950" + ] + ] + }, + { + "id": "eff49303c6d84242", + "type": "function", + "z": "480553bb6f5b2b51", + "d": true, + "g": "ce79bea4667a77e2", + "name": "Send Doorbell Alert to Telegram", + "func": "// Validate that we received a proper image buffer\nif (!Buffer.isBuffer(msg.payload)) {\n node.warn(\"❌ Doorbell snapshot fetch failed — not a binary image buffer\");\n node.status({fill:\"red\", shape:\"ring\", text:\"Snapshot failed\"});\n return null;\n}\n\n// Check if buffer has reasonable size (basic validation)\nif (msg.payload.length < 1000) {\n node.warn(\"❌ Doorbell snapshot too small — likely corrupted\");\n node.status({fill:\"red\", shape:\"ring\", text:\"Image too small\"});\n return null;\n}\n\n// Format timestamp for display\nconst timestamp = new Date(msg.timestamp).toLocaleString();\n\n// Prepare Telegram message payload\nmsg.payload = {\n chatId: -4747176681, // Telegram group chat ID\n type: 'photo',\n content: msg.payload, // Binary image buffer\n caption: `🔔 Veranda ALERT! 🔔\\n\\n📍 Someone is at the ${msg.location}\\n⏰ Time: ${timestamp}\\n📷 Camera: ${msg.camera_location} (${msg.camera_ip})`\n};\n\nnode.status({fill:\"green\", shape:\"dot\", text:\"Alert sent!\"});\n\nreturn msg;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1550, + "y": 1260, + "wires": [ + [ + "bd7fed2198a7b950" + ] + ] + }, + { + "id": "c74ddf6b84ef7a95", + "type": "http request", + "z": "480553bb6f5b2b51", + "d": true, + "g": "ce79bea4667a77e2", + "name": "Fetch Camera Snapshot", + "method": "GET", + "ret": "bin", + "paytoqs": "query", + "url": "", + "tls": "", + "persist": false, + "proxy": "", + "insecureHTTPParser": false, + "authType": "", + "senderr": false, + "headers": [], + "x": 1270, + "y": 1260, + "wires": [ + [ + "eff49303c6d84242", + "88d855e525ab78f0" + ] + ] + }, + { + "id": "d496d62222d1a073", + "type": "function", + "z": "480553bb6f5b2b51", + "d": true, + "g": "ce79bea4667a77e2", + "name": "Generate Doorbell Camera URL", + "func": "// Hardcoded doorbell camera IP\nconst CameraIP = \"10.20.30.33\";\n\n// Generate random string for cache busting\nconst randomString = Math.random().toString(36).substring(7);\n\n// Construct camera snapshot URL\nmsg.url = `http://${CameraIP}/cgi-bin/api.cgi?cmd=Snap&channel=0&rs=${randomString}&user=admin&password=sh1pb0x1`;\n\n// Clear payload for HTTP request\nmsg.payload = null;\n\n// Add camera info to message\nmsg.camera_ip = CameraIP;\nmsg.camera_location = \"Veranda Door Camera\";\n\nnode.status({fill:\"blue\", shape:\"dot\", text:`URL: ${CameraIP}`});\n\nreturn msg;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 990, + "y": 1260, + "wires": [ + [ + "c74ddf6b84ef7a95" + ] + ] + }, + { + "id": "48bfe0ad8a973a84", + "type": "function", + "z": "480553bb6f5b2b51", + "d": true, + "g": "ce79bea4667a77e2", + "name": "Check State Change", + "func": "// Get current payload value\nvar currentValue = parseInt(msg.payload);\n\n// Get previous state from context (default to 0 if not set)\nvar previousValue = context.get('previousDoorbellState') || 0;\n\n// Store current value for next comparison\ncontext.set('previousDoorbellState', currentValue);\n\n// Only proceed if current value is 1 AND previous value was 0\nif (currentValue === 1 && previousValue === 0) {\n msg.payload = \"🚪 Person detected at Veranda Door!\";\n return msg;\n}\n\n// Don't send message for other cases\nreturn null;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 700, + "y": 1260, + "wires": [ + [ + "d496d62222d1a073" + ] + ] + }, + { + "id": "88d855e525ab78f0", + "type": "function", + "z": "480553bb6f5b2b51", + "d": true, + "g": "ce79bea4667a77e2", + "name": "function 8", + "func": "// Try decoding buffer into string so we can read JSON errors\ntry {\n msg.debug_text = msg.payload.toString();\n node.warn(\"Camera returned text: \" + msg.debug_text);\n} catch (e) {\n node.warn(\"Could not decode payload\");\n}\n\nreturn msg;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1620, + "y": 1300, + "wires": [ + [] + ] + }, + { + "id": "a4752681740af47e", + "type": "inject", + "z": "480553bb6f5b2b51", + "d": true, + "g": "ce79bea4667a77e2", + "name": "Simulate Person Detected", + "props": [ + { + "p": "payload", + "v": "1", + "vt": "num" + }, + { + "p": "timestamp", + "v": "", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 170, + "y": 1300, + "wires": [ + [ + "7e88e31d094e343d" + ] + ] + }, + { + "id": "7e88e31d094e343d", + "type": "function", + "z": "480553bb6f5b2b51", + "d": true, + "g": "ce79bea4667a77e2", + "name": "Add Current Timestamp", + "func": "// Add timestamp\nmsg.timestamp = Date.now();\nreturn msg;", + "outputs": 1, + "x": 170, + "y": 1360, + "wires": [ + [ + "365adc2679063bc8" + ] + ] + }, + { + "id": "365adc2679063bc8", + "type": "function", + "z": "480553bb6f5b2b51", + "d": true, + "g": "ce79bea4667a77e2", + "name": "Check Doorbell State", + "func": "// Get previous state from context\nvar prev = context.get('prev') || 0;\nvar curr = parseInt(msg.payload);\ncontext.set('prev', curr);\n\nif(curr===1 && prev===0){\n msg.payload = \"🚪 Person detected at Basement Driveway!\";\n return msg;\n}\nreturn null;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 700, + "y": 1360, + "wires": [ + [ + "842a49fa21a0587a" + ] + ] + }, + { + "id": "842a49fa21a0587a", + "type": "function", + "z": "480553bb6f5b2b51", + "d": true, + "g": "ce79bea4667a77e2", + "name": "Reolink Login", + "func": "const ip = '10.20.30.34';\nconst username = 'admin';\nconst password = 'sh1pb0x1';\nmsg.url = `http://${ip}/cgi-bin/api.cgi?cmd=Login&token=null`;\nmsg.method = 'POST';\nmsg.headers = { 'Content-Type': 'application/json' };\nmsg.payload = JSON.stringify([\n { cmd: 'Login', param: { User: { userName: username, password: password } } }\n]);\nmsg.camera_ip = ip;\nmsg.camera_location = 'Basement Driveway Camera';\nmsg.nextRandom = Math.random().toString(36).substring(7);\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 950, + "y": 1360, + "wires": [ + [ + "bedee5f9397c27f1" + ] + ] + }, + { + "id": "bedee5f9397c27f1", + "type": "http request", + "z": "480553bb6f5b2b51", + "d": true, + "g": "ce79bea4667a77e2", + "name": "Send Login Request", + "method": "use", + "ret": "obj", + "url": "", + "tls": "", + "persist": false, + "proxy": "", + "x": 1190, + "y": 1360, + "wires": [ + [ + "3f9c2db4dfea3095" + ] + ] + }, + { + "id": "3f9c2db4dfea3095", + "type": "function", + "z": "480553bb6f5b2b51", + "d": true, + "g": "ce79bea4667a77e2", + "name": "Fetch Snapshot", + "func": "// Extract token from login response\nif(!msg.payload || !msg.payload[0] || !msg.payload[0].value){\n node.error('Login failed');\n return null;\n}\nconst token = msg.payload[0].value.Token.name;\nif(!token){ node.error('Token missing'); return null; }\n\n// Build snapshot URL\nmsg.url = `http://${msg.camera_ip}/cgi-bin/api.cgi?cmd=Snap&channel=0&rs=${msg.nextRandom}&token=${token}`;\nmsg.method = 'GET';\nmsg.headers = {};\nmsg.payload = null;\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1430, + "y": 1360, + "wires": [ + [ + "0446448ed8c8da8a" + ] + ] + }, + { + "id": "0446448ed8c8da8a", + "type": "http request", + "z": "480553bb6f5b2b51", + "d": true, + "g": "ce79bea4667a77e2", + "name": "Get Camera Snapshot", + "method": "use", + "ret": "bin", + "url": "", + "tls": "", + "persist": false, + "proxy": "", + "x": 1670, + "y": 1360, + "wires": [ + [ + "d241b1f4991cfc05" + ] + ] + }, + { + "id": "d241b1f4991cfc05", + "type": "function", + "z": "480553bb6f5b2b51", + "d": true, + "g": "ce79bea4667a77e2", + "name": "Send Doorbell Alert to Telegram", + "func": "// Validate that we received a proper image buffer\nif (!Buffer.isBuffer(msg.payload)) {\n node.warn(\"❌ Basement Driveway snapshot fetch failed — not a binary image buffer\");\n node.status({fill:\"red\", shape:\"ring\", text:\"Snapshot failed\"});\n return null;\n}\n\n// Check if buffer has reasonable size (basic validation)\nif (msg.payload.length < 1000) {\n node.warn(\"❌ Basement Driveway snapshot too small — likely corrupted\");\n node.status({fill:\"red\", shape:\"ring\", text:\"Image too small\"});\n return null;\n}\n\n// Format timestamp for display\nconst timestamp = new Date(msg.timestamp).toLocaleString();\n\n// Prepare Telegram message payload\nmsg.payload = {\n chatId: -4747176681, // Telegram group chat ID\n type: 'photo',\n content: msg.payload, // Binary image buffer\n caption: `🔔 Basement Driveway ALERT! 🔔\\n\\n📍 Someone is at the ${msg.location}\\n⏰ Time: ${timestamp}\\n📷 Camera: ${msg.camera_location} (${msg.camera_ip})`\n};\n\nnode.status({fill:\"green\", shape:\"dot\", text:\"Alert sent!\"});\n\nreturn msg;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1950, + "y": 1360, + "wires": [ + [ + "bd7fed2198a7b950" + ] + ] + }, + { + "id": "ca1a1f7f8b90cfc9", + "type": "zigbee2mqtt-in", + "z": "f9a2fb576f0d360d", + "g": "b5ffb4a4212da258", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Office Switch (TS0042)", + "device_id": "0xa4c138a5b9771b05", + "state": "action", + "outputAtStartup": true, + "filterChanges": false, + "enableMultiple": false, + "x": 220, + "y": 120, + "wires": [ + [ + "b8f3d3242fb39bdd" + ] + ] + }, + { + "id": "b8f3d3242fb39bdd", + "type": "subflow:4dc13919bcc9d676", + "z": "f9a2fb576f0d360d", + "g": "b5ffb4a4212da258", + "name": "", + "env": [ + { + "name": "switch_1", + "value": "bedroom_1.sw_1", + "type": "str" + } + ], + "x": 1270, + "y": 120, + "wires": [ + [ + "629cbd7ce15bb9e3" + ] + ] + }, + { + "id": "629cbd7ce15bb9e3", + "type": "link out", + "z": "f9a2fb576f0d360d", + "name": "link out 1", + "mode": "link", + "links": [ + "c6162cb59e21491f" + ], + "x": 1785, + "y": 1340, + "wires": [] + }, + { + "id": "6654c150377ed2bb", + "type": "zigbee2mqtt-in", + "z": "f9a2fb576f0d360d", + "g": "4b06cbc568091f6f", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Hallway Switch (TS0042)", + "device_id": "0xa4c13873de3faa66", + "state": "action", + "outputAtStartup": true, + "filterChanges": false, + "enableMultiple": false, + "x": 230, + "y": 220, + "wires": [ + [ + "83bbabef65831016" + ] + ] + }, + { + "id": "1aa9e0ff37d1afee", + "type": "zigbee2mqtt-in", + "z": "f9a2fb576f0d360d", + "g": "795d604329d51272", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Hallway Door Switch (TS0042)", + "device_id": "0xa4c138112a12d55f", + "state": "action", + "outputAtStartup": true, + "filterChanges": false, + "enableMultiple": false, + "x": 250, + "y": 320, + "wires": [ + [ + "b9c07d22cbaae6a2" + ] + ] + }, + { + "id": "a6cf1bd4ae01ee56", + "type": "zigbee2mqtt-in", + "z": "f9a2fb576f0d360d", + "g": "39f557d8eec2fbae", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Master Bedroom Switch (TS0044)", + "device_id": "0xa4c13854734f3a20", + "state": "action", + "outputAtStartup": true, + "filterChanges": false, + "enableMultiple": false, + "x": 260, + "y": 420, + "wires": [ + [ + "16eaca15570ff092", + "2d6bcc49a2785fd6" + ] + ] + }, + { + "id": "675ba75711ef84e7", + "type": "zigbee2mqtt-in", + "z": "f9a2fb576f0d360d", + "g": "68db2262f7c7313e", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Master bathroom switch (TS0042)", + "device_id": "0xa4c1387dd1c2d40e", + "state": "action", + "outputAtStartup": true, + "filterChanges": false, + "enableMultiple": false, + "x": 260, + "y": 900, + "wires": [ + [ + "84272551b322cde2" + ] + ] + }, + { + "id": "becbc51cb810ff3d", + "type": "zigbee2mqtt-in", + "z": "f9a2fb576f0d360d", + "g": "5d202845936f0275", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Pantry (TS0042)", + "device_id": "0xa4c138c2ae9ed430", + "state": "action", + "outputAtStartup": true, + "filterChanges": false, + "enableMultiple": false, + "x": 200, + "y": 1000, + "wires": [ + [ + "4673e2e3f2c0626f" + ] + ] + }, + { + "id": "0bd6051323b66072", + "type": "zigbee2mqtt-in", + "z": "f9a2fb576f0d360d", + "g": "74596bdef53b62cf", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Kitchen Switch (TS0044)", + "device_id": "0xa4c1380bab38d546", + "state": "action", + "outputAtStartup": true, + "filterChanges": false, + "enableMultiple": false, + "x": 230, + "y": 1100, + "wires": [ + [ + "6aac621e2a9acf79" + ] + ] + }, + { + "id": "6e30880884a2cdb2", + "type": "zigbee2mqtt-in", + "z": "f9a2fb576f0d360d", + "g": "5b22d521e762f0da", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Kitchen Veranda Switch (TS0043)", + "device_id": "0xa4c1383b7c24483a", + "state": "action", + "outputAtStartup": true, + "filterChanges": false, + "enableMultiple": false, + "x": 250, + "y": 1200, + "wires": [ + [ + "da7ab159be01b35c" + ] + ] + }, + { + "id": "83bbabef65831016", + "type": "subflow:4dc13919bcc9d676", + "z": "f9a2fb576f0d360d", + "g": "4b06cbc568091f6f", + "name": "", + "env": [ + { + "name": "switch_1", + "value": "hallway.sw_2", + "type": "str" + }, + { + "name": "switch_2", + "value": "hallway.sw_1", + "type": "str" + } + ], + "x": 1270, + "y": 220, + "wires": [ + [ + "629cbd7ce15bb9e3" + ] + ] + }, + { + "id": "b9c07d22cbaae6a2", + "type": "subflow:4dc13919bcc9d676", + "z": "f9a2fb576f0d360d", + "g": "795d604329d51272", + "name": "", + "env": [ + { + "name": "switch_1", + "value": "outSide.sw_1", + "type": "str" + } + ], + "x": 1270, + "y": 320, + "wires": [ + [ + "629cbd7ce15bb9e3" + ] + ] + }, + { + "id": "16eaca15570ff092", + "type": "subflow:4dc13919bcc9d676", + "z": "f9a2fb576f0d360d", + "g": "39f557d8eec2fbae", + "name": "", + "env": [ + { + "name": "switch_1", + "value": "masterBedroom.sw_1", + "type": "str" + }, + { + "name": "switch_2", + "value": "", + "type": "str" + }, + { + "name": "switch_3", + "value": "masterBathroom.sw_1", + "type": "str" + }, + { + "name": "switch_4", + "value": "masterBedroom.sw_2", + "type": "str" + } + ], + "x": 1270, + "y": 420, + "wires": [ + [ + "629cbd7ce15bb9e3" + ] + ] + }, + { + "id": "84272551b322cde2", + "type": "subflow:4dc13919bcc9d676", + "z": "f9a2fb576f0d360d", + "g": "68db2262f7c7313e", + "name": "", + "env": [ + { + "name": "switch_1", + "value": "masterBathroom.sw_1", + "type": "str" + }, + { + "name": "switch_2", + "value": "masterBathroom.sw_2", + "type": "str" + } + ], + "x": 1270, + "y": 900, + "wires": [ + [ + "629cbd7ce15bb9e3" + ] + ] + }, + { + "id": "4673e2e3f2c0626f", + "type": "subflow:4dc13919bcc9d676", + "z": "f9a2fb576f0d360d", + "g": "5d202845936f0275", + "name": "", + "env": [ + { + "name": "switch_1", + "value": "hallway.sw_1", + "type": "str" + }, + { + "name": "switch_2", + "value": "pantry.sw_1", + "type": "str" + } + ], + "x": 1270, + "y": 1000, + "wires": [ + [ + "629cbd7ce15bb9e3" + ] + ] + }, + { + "id": "6aac621e2a9acf79", + "type": "subflow:4dc13919bcc9d676", + "z": "f9a2fb576f0d360d", + "g": "74596bdef53b62cf", + "name": "", + "env": [ + { + "name": "switch_1", + "value": "kitchen.sw_1", + "type": "str" + }, + { + "name": "switch_2", + "value": "kitchen.sw_2", + "type": "str" + }, + { + "name": "switch_3", + "value": "kitchen.sw_3", + "type": "str" + } + ], + "x": 1270, + "y": 1100, + "wires": [ + [ + "629cbd7ce15bb9e3" + ] + ] + }, + { + "id": "da7ab159be01b35c", + "type": "subflow:4dc13919bcc9d676", + "z": "f9a2fb576f0d360d", + "g": "5b22d521e762f0da", + "name": "", + "env": [ + { + "name": "switch_1", + "value": "livingRoom.sw_1", + "type": "str" + }, + { + "name": "switch_3", + "value": "outVeranda.sw_2", + "type": "str" + } + ], + "x": 1270, + "y": 1200, + "wires": [ + [ + "629cbd7ce15bb9e3" + ] + ] + }, + { + "id": "6f028b285108ff17", + "type": "subflow:4dc13919bcc9d676", + "z": "f9a2fb576f0d360d", + "g": "fb2d97888a83fc98", + "name": "", + "env": [ + { + "name": "switch_1", + "value": "bedroom_2.sw_1", + "type": "str" + } + ], + "x": 1270, + "y": 1300, + "wires": [ + [ + "629cbd7ce15bb9e3" + ] + ] + }, + { + "id": "ef6df11582a22110", + "type": "subflow:4dc13919bcc9d676", + "z": "f9a2fb576f0d360d", + "g": "1166ae03751d953e", + "name": "", + "env": [ + { + "name": "switch_1", + "value": "bathroom.sw_1", + "type": "str" + }, + { + "name": "single", + "value": "bathroom.sw_1", + "type": "str" + } + ], + "x": 1270, + "y": 1400, + "wires": [ + [ + "629cbd7ce15bb9e3" + ] + ] + }, + { + "id": "82c1d5f7ae54b348", + "type": "zigbee2mqtt-in", + "z": "f9a2fb576f0d360d", + "g": "fb2d97888a83fc98", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Bedroom (TS0042)", + "device_id": "0xa4c1388b74d83cfa", + "state": "action", + "outputAtStartup": true, + "filterChanges": false, + "enableMultiple": false, + "x": 210, + "y": 1300, + "wires": [ + [ + "6f028b285108ff17" + ] + ] + }, + { + "id": "774c15def7e6cf96", + "type": "zigbee2mqtt-in", + "z": "f9a2fb576f0d360d", + "g": "1166ae03751d953e", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Shower Switch (TS0041)", + "device_id": "0xa4c138cc9b54cec9", + "state": "action", + "outputAtStartup": true, + "filterChanges": false, + "enableMultiple": false, + "x": 230, + "y": 1400, + "wires": [ + [ + "ef6df11582a22110" + ] + ] + }, + { + "id": "25cf5d71108cbbb4", + "type": "subflow:4dc13919bcc9d676", + "z": "f9a2fb576f0d360d", + "g": "5270af8a6312b29d", + "name": "", + "env": [ + { + "name": "switch_1", + "value": "dinningRoom.sw_1", + "type": "str" + }, + { + "name": "switch_3", + "value": "entrance.sw_1", + "type": "str" + }, + { + "name": "switch_4", + "value": "entrance.sw_2", + "type": "str" + } + ], + "x": 1270, + "y": 1500, + "wires": [ + [ + "629cbd7ce15bb9e3" + ] + ] + }, + { + "id": "a330bf2f4d26c536", + "type": "subflow:4dc13919bcc9d676", + "z": "f9a2fb576f0d360d", + "g": "6809b1c79783ab0c", + "name": "", + "env": [ + { + "name": "single", + "value": "guestWc.sw_1", + "type": "str" + } + ], + "x": 1270, + "y": 1640, + "wires": [ + [ + "629cbd7ce15bb9e3" + ] + ] + }, + { + "id": "cbcd3d741247b12b", + "type": "zigbee2mqtt-in", + "z": "f9a2fb576f0d360d", + "g": "5270af8a6312b29d", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Entrance Switch (TS0044)", + "device_id": "0xa4c138ceaa5681d5", + "state": "action", + "outputAtStartup": true, + "filterChanges": false, + "enableMultiple": false, + "x": 230, + "y": 1500, + "wires": [ + [ + "25cf5d71108cbbb4", + "0bf96ec77f6d1809" + ] + ] + }, + { + "id": "398c06a48e4daa13", + "type": "zigbee2mqtt-in", + "z": "f9a2fb576f0d360d", + "g": "6809b1c79783ab0c", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Guest WC Switch (TS0041)", + "device_id": "0xa4c138d65f53037c", + "state": "action", + "outputAtStartup": true, + "filterChanges": false, + "enableMultiple": false, + "x": 240, + "y": 1640, + "wires": [ + [ + "a330bf2f4d26c536" + ] + ] + }, + { + "id": "1633e8e6861be2e0", + "type": "zigbee2mqtt-in", + "z": "f9a2fb576f0d360d", + "g": "3db6f7ccc63a5f8b", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Basement Switch (TS0043)", + "device_id": "0xa4c13891821386eb", + "state": "action", + "outputAtStartup": true, + "filterChanges": false, + "enableMultiple": false, + "x": 230, + "y": 1740, + "wires": [ + [ + "9c36d1335b0f8ebb", + "7f48ed25c8ec1fce" + ] + ] + }, + { + "id": "9c36d1335b0f8ebb", + "type": "subflow:4dc13919bcc9d676", + "z": "f9a2fb576f0d360d", + "g": "3db6f7ccc63a5f8b", + "name": "", + "env": [ + { + "name": "switch_1", + "value": "dinningRoom.sw_4", + "type": "str" + }, + { + "name": "single", + "value": "guestWc.sw_1", + "type": "str" + } + ], + "x": 1270, + "y": 1740, + "wires": [ + [ + "629cbd7ce15bb9e3" + ] + ] + }, + { + "id": "36d1d2c1e10100e7", + "type": "zigbee2mqtt-in", + "z": "f9a2fb576f0d360d", + "g": "41609eab5db77f0a", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Master Bedroom RODRET (E2201)", + "device_id": "0x6cfd22fffe66db44", + "state": "action", + "outputAtStartup": true, + "filterChanges": false, + "enableMultiple": false, + "x": 260, + "y": 2060, + "wires": [ + [ + "4c584ffba6a17ccb" + ] + ] + }, + { + "id": "4c584ffba6a17ccb", + "type": "switch", + "z": "f9a2fb576f0d360d", + "g": "41609eab5db77f0a", + "name": "", + "property": "payload", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "on", + "vt": "str" + }, + { + "t": "eq", + "v": "off", + "vt": "str" + }, + { + "t": "eq", + "v": "brightness_move_up", + "vt": "str" + }, + { + "t": "eq", + "v": "brightness_move_down", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 4, + "x": 590, + "y": 2120, + "wires": [ + [ + "6dd69a9285fc9583" + ], + [ + "6dd69a9285fc9583" + ], + [ + "c53543186fd45d8b" + ], + [ + "aadcf8d1921d77e6" + ] + ] + }, + { + "id": "c53543186fd45d8b", + "type": "change", + "z": "f9a2fb576f0d360d", + "g": "41609eab5db77f0a", + "name": "", + "rules": [ + { + "t": "set", + "p": "topic", + "pt": "msg", + "to": "masterBedroom", + "tot": "str" + }, + { + "t": "set", + "p": "payload", + "pt": "msg", + "to": "sw_1", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 920, + "y": 2100, + "wires": [ + [ + "33eb6e0725214c0b" + ] + ] + }, + { + "id": "33eb6e0725214c0b", + "type": "link out", + "z": "f9a2fb576f0d360d", + "g": "41609eab5db77f0a", + "name": "link out 15", + "mode": "link", + "links": [ + "0bbcb269da9b75c7" + ], + "x": 1325, + "y": 2100, + "wires": [] + }, + { + "id": "5df52e7f8217a6d9", + "type": "zigbee2mqtt-out", + "z": "f9a2fb576f0d360d", + "g": "41609eab5db77f0a", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Master Bedroom Spots (GL-C-006P)", + "device_id": "0xa4c138883e5d795c", + "command": "brightness", + "commandType": "z2m_cmd", + "payload": "payload", + "payloadType": "msg", + "optionsValue": "", + "optionsType": "nothing", + "x": 1190, + "y": 2060, + "wires": [] + }, + { + "id": "6dd69a9285fc9583", + "type": "function", + "z": "f9a2fb576f0d360d", + "g": "41609eab5db77f0a", + "name": "function 4", + "func": "// Initialize the counter\nlet counter = context.get('counter') || 10;\n\nif (msg.payload === \"on\") {\n msg.payload = counter;\n counter += 10;\n context.set('counter', counter);\n return msg;\n}\n\nif (msg.payload === \"off\") {\n counter -= 10;\n msg.payload = counter;\n context.set('counter', counter);\n return msg;\n}\n\n// Ignore other payloads\nreturn null;\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 840, + "y": 2060, + "wires": [ + [ + "5df52e7f8217a6d9" + ] + ] + }, + { + "id": "3e5fabb6aa93449c", + "type": "zigbee2mqtt-out", + "z": "f9a2fb576f0d360d", + "g": "41609eab5db77f0a", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Master Bedroom Spots (GL-C-006P)", + "device_id": "0xa4c138883e5d795c", + "command": "state", + "commandType": "z2m_cmd", + "payload": "payload", + "payloadType": "msg", + "optionsValue": "", + "optionsType": "nothing", + "x": 1190, + "y": 2140, + "wires": [] + }, + { + "id": "aadcf8d1921d77e6", + "type": "change", + "z": "f9a2fb576f0d360d", + "g": "41609eab5db77f0a", + "name": "", + "rules": [ + { + "t": "set", + "p": "payload", + "pt": "msg", + "to": "off", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 920, + "y": 2140, + "wires": [ + [ + "3e5fabb6aa93449c" + ] + ] + }, + { + "id": "109c390c131b09a8", + "type": "change", + "z": "f9a2fb576f0d360d", + "g": "39f557d8eec2fbae", + "name": "", + "rules": [ + { + "t": "set", + "p": "payload", + "pt": "msg", + "to": "toggle", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 880, + "y": 460, + "wires": [ + [ + "d02a9d4e0468bc67" + ] + ] + }, + { + "id": "d02a9d4e0468bc67", + "type": "zigbee2mqtt-out", + "z": "f9a2fb576f0d360d", + "g": "39f557d8eec2fbae", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Master Bedroom Spots (GL-C-006P)", + "device_id": "0xa4c138883e5d795c", + "command": "state", + "commandType": "z2m_cmd", + "payload": "payload", + "payloadType": "msg", + "optionsValue": "", + "optionsType": "nothing", + "x": 1190, + "y": 460, + "wires": [] + }, + { + "id": "07e63813fb04b906", + "type": "change", + "z": "f9a2fb576f0d360d", + "g": "5270af8a6312b29d", + "name": "", + "rules": [ + { + "t": "set", + "p": "payload", + "pt": "msg", + "to": "toggle", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 820, + "y": 1540, + "wires": [ + [ + "4d3d4a82a512ca10" + ] + ] + }, + { + "id": "4d3d4a82a512ca10", + "type": "zigbee2mqtt-out", + "z": "f9a2fb576f0d360d", + "g": "5270af8a6312b29d", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Dining Room Spots (GL-C-009P)", + "device_id": "0xa4c138df201f25a1", + "command": "state", + "commandType": "z2m_cmd", + "payload": "payload", + "payloadType": "msg", + "optionsValue": "", + "optionsType": "nothing", + "x": 1200, + "y": 1540, + "wires": [] + }, + { + "id": "46517eb4342b3d18", + "type": "zigbee2mqtt-in", + "z": "f9a2fb576f0d360d", + "g": "b6271f4a3284f8d9", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Living Room Door Switch (TS0044)", + "device_id": "0xa4c1383d7921827a", + "state": "action", + "outputAtStartup": true, + "filterChanges": false, + "enableMultiple": false, + "x": 260, + "y": 1920, + "wires": [ + [ + "a3d5be29d6effa34", + "701c1cd70874e615" + ] + ] + }, + { + "id": "a3d5be29d6effa34", + "type": "subflow:4dc13919bcc9d676", + "z": "f9a2fb576f0d360d", + "g": "b6271f4a3284f8d9", + "name": "", + "env": [ + { + "name": "switch_1", + "value": "livingRoom.sw_1", + "type": "str" + }, + { + "name": "switch_2", + "value": "", + "type": "str" + }, + { + "name": "switch_3", + "value": "", + "type": "str" + }, + { + "name": "switch_4", + "value": "", + "type": "str" + }, + { + "name": "single", + "value": "", + "type": "str" + } + ], + "x": 550, + "y": 1920, + "wires": [ + [ + "629cbd7ce15bb9e3" + ] + ] + }, + { + "id": "d23c32a704af639d", + "type": "change", + "z": "f9a2fb576f0d360d", + "g": "b6271f4a3284f8d9", + "name": "", + "rules": [ + { + "t": "set", + "p": "payload", + "pt": "msg", + "to": "toggle", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 720, + "y": 1960, + "wires": [ + [ + "68f7d3015de2dbab" + ] + ] + }, + { + "id": "68f7d3015de2dbab", + "type": "zigbee2mqtt-out", + "z": "f9a2fb576f0d360d", + "g": "b6271f4a3284f8d9", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Living Room Spots (GL-C-006P)", + "device_id": "0xa4c13837f72f43ab", + "command": "state", + "commandType": "z2m_cmd", + "payload": "payload", + "payloadType": "msg", + "optionsValue": "", + "optionsType": "nothing", + "x": 1210, + "y": 1960, + "wires": [] + }, + { + "id": "fe5612a41399f1f5", + "type": "api-call-service", + "z": "f9a2fb576f0d360d", + "g": "3db6f7ccc63a5f8b", + "name": "", + "server": "29eb84a2708d96e0", + "version": 7, + "debugenabled": false, + "action": "switch.toggle", + "floorId": [], + "areaId": [], + "deviceId": [], + "entityId": [ + "switch.basement_lights_switch_1" + ], + "labelId": [], + "data": "", + "dataType": "jsonata", + "mergeContext": "", + "mustacheAltTags": false, + "outputProperties": [], + "queue": "none", + "blockInputOverrides": true, + "domain": "switch", + "service": "toggle", + "x": 1270, + "y": 1780, + "wires": [ + [] + ] + }, + { + "id": "7f48ed25c8ec1fce", + "type": "switch", + "z": "f9a2fb576f0d360d", + "g": "3db6f7ccc63a5f8b", + "name": "", + "property": "payload", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "2_single", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 850, + "y": 1780, + "wires": [ + [ + "fe5612a41399f1f5" + ] + ] + }, + { + "id": "ab17cf6a7d65fcb9", + "type": "inject", + "z": "f9a2fb576f0d360d", + "g": "3db6f7ccc63a5f8b", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 840, + "y": 1820, + "wires": [ + [ + "fe5612a41399f1f5" + ] + ] + }, + { + "id": "0bf96ec77f6d1809", + "type": "switch", + "z": "f9a2fb576f0d360d", + "g": "5270af8a6312b29d", + "name": "", + "property": "payload", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "2_single", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 550, + "y": 1540, + "wires": [ + [ + "07e63813fb04b906" + ] + ] + }, + { + "id": "2d6bcc49a2785fd6", + "type": "switch", + "z": "f9a2fb576f0d360d", + "g": "39f557d8eec2fbae", + "name": "", + "property": "payload", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "2_single", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 630, + "y": 460, + "wires": [ + [ + "109c390c131b09a8" + ] + ] + }, + { + "id": "701c1cd70874e615", + "type": "switch", + "z": "f9a2fb576f0d360d", + "g": "b6271f4a3284f8d9", + "name": "", + "property": "payload", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "2_single", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 530, + "y": 1960, + "wires": [ + [ + "d23c32a704af639d" + ] + ] + }, + { + "id": "0b112f9f314fa0bb", + "type": "zigbee2mqtt-in", + "z": "f9a2fb576f0d360d", + "g": "ef555db28dba4c46", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "0xa4c138332cc3da08 (TS0044)", + "device_id": "0xa4c138332cc3da08", + "state": "action", + "outputAtStartup": true, + "filterChanges": false, + "enableMultiple": false, + "x": 250, + "y": 560, + "wires": [ + [ + "a1f5a94baf1fbcd7", + "f0de9c882920d07c" + ] + ] + }, + { + "id": "a1f5a94baf1fbcd7", + "type": "subflow:4dc13919bcc9d676", + "z": "f9a2fb576f0d360d", + "g": "ef555db28dba4c46", + "name": "", + "env": [ + { + "name": "switch_1", + "value": "masterBedroom.sw_1", + "type": "str" + }, + { + "name": "switch_2", + "value": "", + "type": "str" + }, + { + "name": "switch_3", + "value": "masterBedroom.sw_3", + "type": "str" + }, + { + "name": "switch_4", + "value": "masterBedroom.sw_5", + "type": "str" + } + ], + "x": 1270, + "y": 560, + "wires": [ + [ + "629cbd7ce15bb9e3" + ] + ] + }, + { + "id": "ae314d3d3457b679", + "type": "change", + "z": "f9a2fb576f0d360d", + "g": "ef555db28dba4c46", + "name": "", + "rules": [ + { + "t": "set", + "p": "payload", + "pt": "msg", + "to": "toggle", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 880, + "y": 600, + "wires": [ + [ + "0c35b08a3c7d347f" + ] + ] + }, + { + "id": "0c35b08a3c7d347f", + "type": "zigbee2mqtt-out", + "z": "f9a2fb576f0d360d", + "g": "ef555db28dba4c46", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Master Bedroom Spots (GL-C-006P)", + "device_id": "0xa4c138883e5d795c", + "command": "state", + "commandType": "z2m_cmd", + "payload": "payload", + "payloadType": "msg", + "optionsValue": "", + "optionsType": "nothing", + "x": 1190, + "y": 600, + "wires": [] + }, + { + "id": "f0de9c882920d07c", + "type": "switch", + "z": "f9a2fb576f0d360d", + "g": "ef555db28dba4c46", + "name": "", + "property": "payload", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "2_single", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 630, + "y": 600, + "wires": [ + [ + "ae314d3d3457b679" + ] + ] + }, + { + "id": "310f749a07eded9d", + "type": "zigbee2mqtt-in", + "z": "f9a2fb576f0d360d", + "g": "1756bd5d4ff2c23b", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "basement_switch_2 (TS0043)", + "device_id": "0xa4c138a1968db065", + "state": "action", + "outputAtStartup": true, + "filterChanges": false, + "enableMultiple": false, + "x": 240, + "y": 2240, + "wires": [ + [ + "d0febe343ef897ac", + "8cce4a0a7061ac87" + ] + ] + }, + { + "id": "d0febe343ef897ac", + "type": "subflow:4dc13919bcc9d676", + "z": "f9a2fb576f0d360d", + "g": "1756bd5d4ff2c23b", + "name": "", + "env": [ + { + "name": "switch_1", + "value": "dinningRoom.sw_4", + "type": "str" + }, + { + "name": "single", + "value": "guestWc.sw_1", + "type": "str" + } + ], + "x": 1270, + "y": 2240, + "wires": [ + [ + "629cbd7ce15bb9e3" + ] + ] + }, + { + "id": "e1965a78e430acad", + "type": "api-call-service", + "z": "f9a2fb576f0d360d", + "g": "1756bd5d4ff2c23b", + "name": "", + "server": "29eb84a2708d96e0", + "version": 7, + "debugenabled": false, + "action": "switch.toggle", + "floorId": [], + "areaId": [], + "deviceId": [], + "entityId": [ + "switch.basement_lights_switch_1" + ], + "labelId": [], + "data": "", + "dataType": "jsonata", + "mergeContext": "", + "mustacheAltTags": false, + "outputProperties": [], + "queue": "none", + "blockInputOverrides": true, + "domain": "switch", + "service": "toggle", + "x": 1270, + "y": 2280, + "wires": [ + [] + ] + }, + { + "id": "8cce4a0a7061ac87", + "type": "switch", + "z": "f9a2fb576f0d360d", + "g": "1756bd5d4ff2c23b", + "name": "", + "property": "payload", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "2_single", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 850, + "y": 2280, + "wires": [ + [ + "e1965a78e430acad" + ] + ] + }, + { + "id": "d5cad6d8ca5fe08c", + "type": "inject", + "z": "f9a2fb576f0d360d", + "g": "1756bd5d4ff2c23b", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 840, + "y": 2320, + "wires": [ + [ + "e1965a78e430acad" + ] + ] + }, + { + "id": "05ecf6f492d4a8cf", + "type": "inject", + "z": "6722d185624fd57a", + "g": "cc230a0894b116d3", + "name": "ON", + "props": [ + { + "p": "payload" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "ON", + "payloadType": "str", + "x": 230, + "y": 1020, + "wires": [ + [ + "301e955ac79a9326" + ] + ] + }, + { + "id": "ad81a0918c31a862", + "type": "inject", + "z": "6722d185624fd57a", + "g": "cc230a0894b116d3", + "name": "OFF", + "props": [ + { + "p": "payload" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "OFF", + "payloadType": "str", + "x": 230, + "y": 1140, + "wires": [ + [ + "301e955ac79a9326" + ] + ] + }, + { + "id": "7049d2e629127b0e", + "type": "api-current-state", + "z": "6722d185624fd57a", + "g": "cc230a0894b116d3", + "name": "", + "server": "29eb84a2708d96e0", + "version": 3, + "outputs": 2, + "halt_if": "off", + "halt_if_type": "str", + "halt_if_compare": "is", + "entity_id": "binary_sensor.front_lights", + "state_type": "str", + "blockInputOverrides": true, + "outputProperties": [ + { + "property": "topic", + "propertyType": "msg", + "value": "outVeranda", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "sw_1", + "valueType": "str" + } + ], + "for": "0", + "forType": "num", + "forUnits": "minutes", + "override_topic": false, + "state_location": "payload", + "override_payload": "msg", + "entity_location": "data", + "override_data": "msg", + "x": 840, + "y": 1040, + "wires": [ + [ + "e44fa1b081b4aba7" + ], + [] + ] + }, + { + "id": "e44fa1b081b4aba7", + "type": "link out", + "z": "6722d185624fd57a", + "g": "cc230a0894b116d3", + "name": "link out 21", + "mode": "link", + "links": [ + "0bbcb269da9b75c7" + ], + "x": 1085, + "y": 1060, + "wires": [] + }, + { + "id": "532476a808456423", + "type": "api-current-state", + "z": "6722d185624fd57a", + "g": "cc230a0894b116d3", + "name": "", + "server": "29eb84a2708d96e0", + "version": 3, + "outputs": 2, + "halt_if": "on", + "halt_if_type": "str", + "halt_if_compare": "is", + "entity_id": "binary_sensor.front_lights", + "state_type": "str", + "blockInputOverrides": true, + "outputProperties": [ + { + "property": "topic", + "propertyType": "msg", + "value": "outVeranda", + "valueType": "str" + }, + { + "property": "payload", + "propertyType": "msg", + "value": "sw_1", + "valueType": "str" + } + ], + "for": "0", + "forType": "num", + "forUnits": "minutes", + "override_topic": false, + "state_location": "payload", + "override_payload": "msg", + "entity_location": "data", + "override_data": "msg", + "x": 840, + "y": 1100, + "wires": [ + [ + "e44fa1b081b4aba7" + ], + [] + ] + }, + { + "id": "9e01966beafd582b", + "type": "cronplus", + "z": "6722d185624fd57a", + "d": true, + "g": "cc230a0894b116d3", + "name": "", + "outputField": "payload", + "timeZone": "", + "storeName": "", + "commandResponseMsgOutput": "fanOut", + "defaultLocation": "", + "defaultLocationType": "default", + "outputs": 4, + "options": [ + { + "name": "ON", + "topic": "topic1", + "payloadType": "str", + "payload": "on", + "expressionType": "cron", + "expression": "0 0 20 * * *", + "location": "", + "offset": "0", + "solarType": "all", + "solarEvents": "sunrise,sunset" + }, + { + "name": "OFF", + "topic": "topic2", + "payloadType": "str", + "payload": "off", + "expressionType": "cron", + "expression": "0 0 00 * * *", + "location": "", + "offset": "0", + "solarType": "all", + "solarEvents": "sunrise,sunset" + } + ], + "x": 400, + "y": 1160, + "wires": [ + [], + [], + [], + [] + ] + }, + { + "id": "905629455f4dd222", + "type": "zigbee2mqtt-out", + "z": "6722d185624fd57a", + "g": "b73455843d7a5a9f", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Front Water Valve (SWV)", + "device_id": "0x0ceff6fffe84e4bc", + "command": "state", + "commandType": "z2m_cmd", + "payload": "payload", + "payloadType": "msg", + "optionsValue": "", + "optionsType": "nothing", + "x": 1350, + "y": 460, + "wires": [] + }, + { + "id": "33e972f98673a6b6", + "type": "cronplus", + "z": "6722d185624fd57a", + "g": "b73455843d7a5a9f", + "name": "Olive Trees every 2 days at 19:00", + "outputField": "payload", + "timeZone": "", + "storeName": "file", + "commandResponseMsgOutput": "fanOut", + "defaultLocation": "", + "defaultLocationType": "default", + "outputs": 4, + "options": [ + { + "name": "ON", + "topic": "front", + "payloadType": "default", + "payload": "", + "expressionType": "cron", + "expression": "20 6 */7 * *", + "location": "", + "offset": "0", + "solarType": "all", + "solarEvents": "sunrise,sunset" + }, + { + "name": "OFF", + "topic": "front", + "payloadType": "default", + "payload": "", + "expressionType": "cron", + "expression": "30 6 */7 * *", + "location": "", + "offset": "0", + "solarType": "all", + "solarEvents": "sunrise,sunset" + } + ], + "x": 240, + "y": 440, + "wires": [ + [ + "ab7182ad5c37be48" + ], + [ + "d51ae77e82bb86f0" + ], + [], + [] + ] + }, + { + "id": "ab7182ad5c37be48", + "type": "change", + "z": "6722d185624fd57a", + "g": "b73455843d7a5a9f", + "name": "on", + "rules": [ + { + "t": "set", + "p": "payload", + "pt": "msg", + "to": "on", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 690, + "y": 420, + "wires": [ + [ + "905629455f4dd222" + ] + ] + }, + { + "id": "d51ae77e82bb86f0", + "type": "change", + "z": "6722d185624fd57a", + "g": "b73455843d7a5a9f", + "name": "off", + "rules": [ + { + "t": "set", + "p": "payload", + "pt": "msg", + "to": "off", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 690, + "y": 460, + "wires": [ + [ + "905629455f4dd222" + ] + ] + }, + { + "id": "b43dc5183a67d128", + "type": "zigbee2mqtt-out", + "z": "6722d185624fd57a", + "g": "b73455843d7a5a9f", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Back Water Valve (SWV)", + "device_id": "0x7cc6b6fffe38a415", + "command": "state", + "commandType": "z2m_cmd", + "payload": "payload", + "payloadType": "msg", + "optionsValue": "", + "optionsType": "nothing", + "x": 1350, + "y": 540, + "wires": [] + }, + { + "id": "2896d687417bbc1d", + "type": "cronplus", + "z": "6722d185624fd57a", + "g": "b73455843d7a5a9f", + "name": "Citrus Trees every 2 days at 06:00", + "outputField": "payload", + "timeZone": "", + "storeName": "file", + "commandResponseMsgOutput": "fanOut", + "defaultLocation": "", + "defaultLocationType": "default", + "outputs": 4, + "options": [ + { + "name": "ON", + "topic": "front", + "payloadType": "default", + "payload": "", + "expressionType": "cron", + "expression": "0 6 */2 * *", + "location": "", + "offset": "0", + "solarType": "all", + "solarEvents": "sunrise,sunset" + }, + { + "name": "OFF", + "topic": "front", + "payloadType": "default", + "payload": "", + "expressionType": "cron", + "expression": "15 6 */2 * *", + "location": "", + "offset": "0", + "solarType": "all", + "solarEvents": "sunrise,sunset" + } + ], + "x": 240, + "y": 560, + "wires": [ + [ + "1199fd928d0abd32" + ], + [ + "86153b2dba01756f" + ], + [], + [] + ] + }, + { + "id": "1199fd928d0abd32", + "type": "change", + "z": "6722d185624fd57a", + "g": "b73455843d7a5a9f", + "name": "on", + "rules": [ + { + "t": "set", + "p": "payload", + "pt": "msg", + "to": "on", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 690, + "y": 540, + "wires": [ + [ + "b43dc5183a67d128" + ] + ] + }, + { + "id": "86153b2dba01756f", + "type": "change", + "z": "6722d185624fd57a", + "g": "b73455843d7a5a9f", + "name": "off", + "rules": [ + { + "t": "set", + "p": "payload", + "pt": "msg", + "to": "off", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 690, + "y": 580, + "wires": [ + [ + "b43dc5183a67d128" + ] + ] + }, + { + "id": "301e955ac79a9326", + "type": "bigtimer", + "z": "6722d185624fd57a", + "g": "cc230a0894b116d3", + "outtopic": "", + "outpayload1": "", + "outpayload2": "", + "name": "Big Timer", + "comment": "", + "lat": "34.660694", + "lon": "32.964306", + "starttime": "5004", + "endtime": "60", + "starttime2": "0", + "endtime2": "0", + "startoff": 0, + "endoff": 0, + "startoff2": 0, + "endoff2": 0, + "offs": 0, + "outtext1": "ON", + "outtext2": "OFF", + "timeout": 1440, + "sun": true, + "mon": true, + "tue": true, + "wed": true, + "thu": true, + "fri": true, + "sat": true, + "jan": true, + "feb": true, + "mar": true, + "apr": true, + "may": true, + "jun": true, + "jul": true, + "aug": true, + "sep": true, + "oct": true, + "nov": true, + "dec": true, + "day1": 0, + "month1": 0, + "day2": 0, + "month2": 0, + "day3": 0, + "month3": 0, + "day4": 0, + "month4": 0, + "day5": 0, + "month5": 0, + "day6": 0, + "month6": 0, + "day7": 0, + "month7": 0, + "day8": 0, + "month8": 0, + "day9": 0, + "month9": 0, + "day10": 0, + "month10": 0, + "day11": 0, + "month11": 0, + "day12": 0, + "month12": 0, + "d1": 0, + "w1": 0, + "d2": 0, + "w2": 0, + "d3": 0, + "w3": 0, + "d4": 0, + "w4": 0, + "d5": 0, + "w5": 0, + "d6": 0, + "w6": 0, + "xday1": 0, + "xmonth1": 0, + "xday2": 0, + "xmonth2": 0, + "xday3": 0, + "xmonth3": 0, + "xday4": 0, + "xmonth4": 0, + "xday5": 0, + "xmonth5": 0, + "xday6": 0, + "xmonth6": 0, + "xday7": 0, + "xmonth7": 0, + "xday8": 0, + "xmonth8": 0, + "xday9": 0, + "xmonth9": 0, + "xday10": 0, + "xmonth10": 0, + "xday11": 0, + "xmonth11": 0, + "xday12": 0, + "xmonth12": 0, + "xd1": 0, + "xw1": 0, + "xd2": 0, + "xw2": 0, + "xd3": 0, + "xw3": 0, + "xd4": 0, + "xw4": 0, + "xd5": 0, + "xw5": 0, + "xd6": 0, + "xw6": 0, + "suspend": false, + "random": false, + "randon1": false, + "randoff1": false, + "randon2": false, + "randoff2": false, + "repeat": true, + "atstart": true, + "odd": false, + "even": false, + "x": 400, + "y": 1080, + "wires": [ + [ + "7049d2e629127b0e", + "532476a808456423", + "434ef7e821c63223" + ], + [ + "0fdf497d70900d11" + ], + [ + "54161a78e09f85c3", + "7049d2e629127b0e", + "532476a808456423" + ] + ] + }, + { + "id": "54161a78e09f85c3", + "type": "debug", + "z": "6722d185624fd57a", + "g": "cc230a0894b116d3", + "name": "debug 14", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 560, + "y": 1140, + "wires": [] + }, + { + "id": "0fdf497d70900d11", + "type": "debug", + "z": "6722d185624fd57a", + "g": "cc230a0894b116d3", + "name": "debug 21", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 680, + "y": 980, + "wires": [] + }, + { + "id": "434ef7e821c63223", + "type": "debug", + "z": "6722d185624fd57a", + "g": "cc230a0894b116d3", + "name": "debug 22", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 450, + "y": 980, + "wires": [] + }, + { + "id": "d97b1b86e0e0040e", + "type": "zigbee2mqtt-get", + "z": "6722d185624fd57a", + "g": "860e99939cf4fb5c", + "name": "Get Current Plug State", + "server": "4e20fc347c658518", + "friendly_name": "Living Room Plug (TS011F_plug_3)", + "device_id": "0xa4c138c205230a46", + "state": "state", + "enableMultiple": false, + "x": 1080, + "y": 1860, + "wires": [ + [ + "6fdd0a95dd76a2e8", + "be86177040153c9f" + ] + ] + }, + { + "id": "3737a8c897de2221", + "type": "function", + "z": "6722d185624fd57a", + "g": "860e99939cf4fb5c", + "name": "Compare Desired vs Actual", + "func": "let input = msg.input || null;\n\nif (input === 1) {\n // BigTimer sets desired state\n // Validate state value before storing\n if (msg.payload === \"ON\" || msg.payload === \"OFF\") {\n flow.set(\"desired_state\", msg.payload);\n node.status({fill:\"blue\", shape:\"dot\", text:`Desired: ${msg.payload}`});\n node.warn(`[BigTimer] Desired state set to: ${msg.payload}`);\n } else {\n node.error(`[BigTimer] Invalid state value: ${msg.payload}`);\n node.status({fill:\"red\", shape:\"ring\", text:\"Invalid state\"});\n }\n return null;\n}\n\nif (input === 2) {\n // Zigbee GET input - check and sync state\n let desired = flow.get(\"desired_state\");\n let current = msg.state || \n msg.payload || \n msg.payload?.state || \n msg.data?.state ||\n null;\n\n // Log current status\n node.warn(`[Zigbee Check] Desired: ${desired}, Current: ${current}`);\n\n // Check if states are available\n if (!desired) {\n node.warn(\"[Zigbee Check] No desired state set yet\");\n node.status({fill:\"yellow\", shape:\"ring\", text:\"No desired state\"});\n return null;\n }\n\n if (!current) {\n node.error(\"[Zigbee Check] Current state is undefined\");\n node.status({fill:\"red\", shape:\"ring\", text:\"No current state\"});\n return null;\n }\n\n // Check if sync is needed\n if (desired === \"ON\" && current !== \"ON\") {\n node.warn(`[Zigbee Check] State mismatch - sending ON command`);\n node.status({fill:\"green\", shape:\"dot\", text:\"Syncing → ON\"});\n return { payload: \"ON\" };\n }\n \n if (desired === \"OFF\" && current !== \"OFF\") {\n node.warn(`[Zigbee Check] State mismatch - sending OFF command`);\n node.status({fill:\"green\", shape:\"dot\", text:\"Syncing → OFF\"});\n return { payload: \"OFF\" };\n }\n\n // States match - no action needed\n node.status({fill:\"green\", shape:\"ring\", text:`Synced: ${current}`});\n node.warn(`[Zigbee Check] States match - no action needed`);\n return null;\n}\n\n// Invalid input\nnode.error(`Invalid input value: ${input}`);\nnode.status({fill:\"red\", shape:\"ring\", text:\"Invalid input\"});\nreturn null;\n", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1580, + "y": 1880, + "wires": [ + [ + "ca74d224370a0b5a", + "893d9228d166c0d2" + ] + ] + }, + { + "id": "ca74d224370a0b5a", + "type": "zigbee2mqtt-out", + "z": "6722d185624fd57a", + "g": "860e99939cf4fb5c", + "name": "Set Plug State", + "server": "4e20fc347c658518", + "friendly_name": "Living Room Plug (TS011F_plug_3)", + "device_id": "0xa4c138c205230a46", + "command": "state", + "commandType": "z2m_cmd", + "payload": "payload", + "payloadType": "msg", + "optionsValue": "", + "optionsType": "nothing", + "x": 1820, + "y": 1880, + "wires": [] + }, + { + "id": "3a8262144b91d661", + "type": "inject", + "z": "6722d185624fd57a", + "g": "860e99939cf4fb5c", + "name": "OFF", + "props": [ + { + "p": "payload" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "OFF", + "payloadType": "str", + "x": 750, + "y": 1960, + "wires": [ + [ + "01df5278eead8a98" + ] + ] + }, + { + "id": "d20a28e1dea2fcde", + "type": "inject", + "z": "6722d185624fd57a", + "g": "860e99939cf4fb5c", + "name": "ON", + "props": [ + { + "p": "payload" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "ON", + "payloadType": "str", + "x": 750, + "y": 1840, + "wires": [ + [ + "01df5278eead8a98" + ] + ] + }, + { + "id": "01df5278eead8a98", + "type": "bigtimer", + "z": "6722d185624fd57a", + "g": "860e99939cf4fb5c", + "outtopic": "", + "outpayload1": "", + "outpayload2": "", + "name": "Big Timer", + "comment": "", + "lat": "34.660694", + "lon": "32.964306", + "starttime": "960", + "endtime": "60", + "starttime2": "0", + "endtime2": "0", + "startoff": 0, + "endoff": 0, + "startoff2": 0, + "endoff2": 0, + "offs": 0, + "outtext1": "ON", + "outtext2": "OFF", + "timeout": 1440, + "sun": true, + "mon": true, + "tue": true, + "wed": true, + "thu": true, + "fri": true, + "sat": true, + "jan": true, + "feb": true, + "mar": true, + "apr": true, + "may": true, + "jun": true, + "jul": true, + "aug": true, + "sep": true, + "oct": true, + "nov": true, + "dec": true, + "day1": 0, + "month1": 0, + "day2": 0, + "month2": 0, + "day3": 0, + "month3": 0, + "day4": 0, + "month4": 0, + "day5": 0, + "month5": 0, + "day6": 0, + "month6": 0, + "day7": 0, + "month7": 0, + "day8": 0, + "month8": 0, + "day9": 0, + "month9": 0, + "day10": 0, + "month10": 0, + "day11": 0, + "month11": 0, + "day12": 0, + "month12": 0, + "d1": 0, + "w1": 0, + "d2": 0, + "w2": 0, + "d3": 0, + "w3": 0, + "d4": 0, + "w4": 0, + "d5": 0, + "w5": 0, + "d6": 0, + "w6": 0, + "xday1": 0, + "xmonth1": 0, + "xday2": 0, + "xmonth2": 0, + "xday3": 0, + "xmonth3": 0, + "xday4": 0, + "xmonth4": 0, + "xday5": 0, + "xmonth5": 0, + "xday6": 0, + "xmonth6": 0, + "xday7": 0, + "xmonth7": 0, + "xday8": 0, + "xmonth8": 0, + "xday9": 0, + "xmonth9": 0, + "xday10": 0, + "xmonth10": 0, + "xday11": 0, + "xmonth11": 0, + "xday12": 0, + "xmonth12": 0, + "xd1": 0, + "xw1": 0, + "xd2": 0, + "xw2": 0, + "xd3": 0, + "xw3": 0, + "xd4": 0, + "xw4": 0, + "xd5": 0, + "xw5": 0, + "xd6": 0, + "xw6": 0, + "suspend": false, + "random": false, + "randon1": false, + "randoff1": false, + "randon2": false, + "randoff2": false, + "repeat": true, + "atstart": true, + "odd": false, + "even": false, + "x": 880, + "y": 1900, + "wires": [ + [], + [], + [ + "d97b1b86e0e0040e", + "1460ed0d062f8b73" + ] + ] + }, + { + "id": "1460ed0d062f8b73", + "type": "change", + "z": "6722d185624fd57a", + "g": "860e99939cf4fb5c", + "name": "Tag BigTimer Input", + "rules": [ + { + "t": "set", + "p": "input", + "pt": "msg", + "to": "1", + "tot": "num" + } + ], + "x": 1310, + "y": 1900, + "wires": [ + [ + "3737a8c897de2221", + "bb1639973b4a6a05" + ] + ] + }, + { + "id": "6fdd0a95dd76a2e8", + "type": "change", + "z": "6722d185624fd57a", + "g": "860e99939cf4fb5c", + "name": "Tag Zigbee Input", + "rules": [ + { + "t": "set", + "p": "input", + "pt": "msg", + "to": "2", + "tot": "num" + } + ], + "x": 1310, + "y": 1860, + "wires": [ + [ + "3737a8c897de2221", + "800cb2f6a21c882f" + ] + ] + }, + { + "id": "bb1639973b4a6a05", + "type": "debug", + "z": "6722d185624fd57a", + "g": "860e99939cf4fb5c", + "name": "debug 23", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 1560, + "y": 2020, + "wires": [] + }, + { + "id": "800cb2f6a21c882f", + "type": "debug", + "z": "6722d185624fd57a", + "g": "860e99939cf4fb5c", + "name": "debug 24", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 1540, + "y": 1960, + "wires": [] + }, + { + "id": "893d9228d166c0d2", + "type": "debug", + "z": "6722d185624fd57a", + "g": "860e99939cf4fb5c", + "name": "debug 25", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 1750, + "y": 1980, + "wires": [] + }, + { + "id": "be86177040153c9f", + "type": "debug", + "z": "6722d185624fd57a", + "g": "860e99939cf4fb5c", + "name": "debug 26", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 1230, + "y": 1720, + "wires": [] + }, + { + "id": "6fb9cc41c7b14fac", + "type": "link out", + "z": "31693504fcbbd954", + "name": "link out 23", + "mode": "link", + "links": [ + "c6162cb59e21491f" + ], + "x": 2105, + "y": 880, + "wires": [] + }, + { + "id": "func_node", + "type": "function", + "z": "31693504fcbbd954", + "name": "Build JSON true/false", + "func": "let input = msg.payload;\nlet parts = input.split(\".\");\nif (parts.length !== 2) {\n node.error(\"Invalid input format. Expected room.switch\", msg);\n return null;\n}\n\nlet room = parts[0];\nlet sw = parts[1];\n\nlet onMsg = { payload: { [room]: { [sw]: true } } };\nnode.send(onMsg);\n\nlet offMsg = { payload: { [room]: { [sw]: false } } };\nsetTimeout(() => { node.send(offMsg); }, 1000);\n\nreturn null;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1920, + "y": 880, + "wires": [ + [ + "debug_node", + "6fb9cc41c7b14fac" + ] + ] + }, + { + "id": "debug_node", + "type": "debug", + "z": "31693504fcbbd954", + "name": "Output", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "x": 2110, + "y": 920, + "wires": [] + }, + { + "id": "btn_masterBedroom_all_off", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "masterBedroom all_off", + "group": "group_masterBedroom", + "order": 1, + "width": 0, + "height": 0, + "label": "all_off", + "payload": "masterBedroom.all_off", + "payloadType": "str", + "topic": "", + "x": 140, + "y": 940, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_masterBedroom_all_on", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "masterBedroom all_on", + "group": "group_masterBedroom", + "order": 2, + "width": 0, + "height": 0, + "label": "all_on", + "payload": "masterBedroom.all_on", + "payloadType": "str", + "topic": "", + "x": 140, + "y": 980, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_masterBedroom_sw_1", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "masterBedroom sw_1", + "group": "group_masterBedroom", + "order": 3, + "width": 0, + "height": 0, + "label": "sw_1", + "payload": "masterBedroom.sw_1", + "payloadType": "str", + "topic": "", + "x": 140, + "y": 1020, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_masterBedroom_sw_2", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "masterBedroom sw_2", + "group": "group_masterBedroom", + "order": 4, + "width": 0, + "height": 0, + "label": "sw_2", + "payload": "masterBedroom.sw_2", + "payloadType": "str", + "topic": "", + "x": 140, + "y": 1060, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_masterBedroom_sw_3", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "masterBedroom sw_3", + "group": "group_masterBedroom", + "order": 5, + "width": 0, + "height": 0, + "label": "sw_3", + "payload": "masterBedroom.sw_3", + "payloadType": "str", + "topic": "", + "x": 140, + "y": 1100, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_masterBedroom_sw_4", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "masterBedroom sw_4", + "group": "group_masterBedroom", + "order": 6, + "width": 0, + "height": 0, + "label": "sw_4", + "payload": "masterBedroom.sw_4", + "payloadType": "str", + "topic": "", + "x": 140, + "y": 1140, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_masterBedroom_sw_5", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "masterBedroom sw_5", + "group": "group_masterBedroom", + "order": 7, + "width": 0, + "height": 0, + "label": "sw_5", + "payload": "masterBedroom.sw_5", + "payloadType": "str", + "topic": "", + "x": 140, + "y": 1180, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_masterBedroom_sw_6", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "masterBedroom sw_6", + "group": "group_masterBedroom", + "order": 8, + "width": 0, + "height": 0, + "label": "sw_6", + "payload": "masterBedroom.sw_6", + "payloadType": "str", + "topic": "", + "x": 140, + "y": 1220, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_masterBathroom_all_off", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "masterBathroom all_off", + "group": "group_masterBathroom", + "order": 1, + "width": 0, + "height": 0, + "label": "all_off", + "payload": "masterBathroom.all_off", + "payloadType": "str", + "topic": "", + "x": 390, + "y": 940, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_masterBathroom_all_on", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "masterBathroom all_on", + "group": "group_masterBathroom", + "order": 2, + "width": 0, + "height": 0, + "label": "all_on", + "payload": "masterBathroom.all_on", + "payloadType": "str", + "topic": "", + "x": 390, + "y": 980, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_masterBathroom_sw_1", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "masterBathroom sw_1", + "group": "group_masterBathroom", + "order": 3, + "width": 0, + "height": 0, + "label": "sw_1", + "payload": "masterBathroom.sw_1", + "payloadType": "str", + "topic": "", + "x": 380, + "y": 1020, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_masterBathroom_sw_2", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "masterBathroom sw_2", + "group": "group_masterBathroom", + "order": 4, + "width": 0, + "height": 0, + "label": "sw_2", + "payload": "masterBathroom.sw_2", + "payloadType": "str", + "topic": "", + "x": 380, + "y": 1060, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_masterBathroom_sw_3", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "masterBathroom sw_3", + "group": "group_masterBathroom", + "order": 5, + "width": 0, + "height": 0, + "label": "sw_3", + "payload": "masterBathroom.sw_3", + "payloadType": "str", + "topic": "", + "x": 380, + "y": 1100, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_masterBathroom_sw_4", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "masterBathroom sw_4", + "group": "group_masterBathroom", + "order": 6, + "width": 0, + "height": 0, + "label": "sw_4", + "payload": "masterBathroom.sw_4", + "payloadType": "str", + "topic": "", + "x": 380, + "y": 1140, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_masterBathroom_sw_5", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "masterBathroom sw_5", + "group": "group_masterBathroom", + "order": 7, + "width": 0, + "height": 0, + "label": "sw_5", + "payload": "masterBathroom.sw_5", + "payloadType": "str", + "topic": "", + "x": 380, + "y": 1180, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_masterBathroom_sw_6", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "masterBathroom sw_6", + "group": "group_masterBathroom", + "order": 8, + "width": 0, + "height": 0, + "label": "sw_6", + "payload": "masterBathroom.sw_6", + "payloadType": "str", + "topic": "", + "x": 380, + "y": 1220, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_bedroom_1_all_off", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "bedroom_1 all_off", + "group": "group_bedroom_1", + "order": 1, + "width": 0, + "height": 0, + "label": "all_off", + "payload": "bedroom_1.all_off", + "payloadType": "str", + "topic": "", + "x": 630, + "y": 940, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_bedroom_1_all_on", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "bedroom_1 all_on", + "group": "group_bedroom_1", + "order": 2, + "width": 0, + "height": 0, + "label": "all_on", + "payload": "bedroom_1.all_on", + "payloadType": "str", + "topic": "", + "x": 630, + "y": 980, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_bedroom_1_sw_1", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "bedroom_1 sw_1", + "group": "group_bedroom_1", + "order": 3, + "width": 0, + "height": 0, + "label": "sw_1", + "payload": "bedroom_1.sw_1", + "payloadType": "str", + "topic": "", + "x": 630, + "y": 1020, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_bedroom_1_sw_2", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "bedroom_1 sw_2", + "group": "group_bedroom_1", + "order": 4, + "width": 0, + "height": 0, + "label": "sw_2", + "payload": "bedroom_1.sw_2", + "payloadType": "str", + "topic": "", + "x": 630, + "y": 1060, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_bedroom_1_sw_3", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "bedroom_1 sw_3", + "group": "group_bedroom_1", + "order": 5, + "width": 0, + "height": 0, + "label": "sw_3", + "payload": "bedroom_1.sw_3", + "payloadType": "str", + "topic": "", + "x": 630, + "y": 1100, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_bedroom_1_sw_4", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "bedroom_1 sw_4", + "group": "group_bedroom_1", + "order": 6, + "width": 0, + "height": 0, + "label": "sw_4", + "payload": "bedroom_1.sw_4", + "payloadType": "str", + "topic": "", + "x": 630, + "y": 1140, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_bedroom_1_sw_5", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "bedroom_1 sw_5", + "group": "group_bedroom_1", + "order": 7, + "width": 0, + "height": 0, + "label": "sw_5", + "payload": "bedroom_1.sw_5", + "payloadType": "str", + "topic": "", + "x": 630, + "y": 1180, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_bedroom_1_sw_6", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "bedroom_1 sw_6", + "group": "group_bedroom_1", + "order": 8, + "width": 0, + "height": 0, + "label": "sw_6", + "payload": "bedroom_1.sw_6", + "payloadType": "str", + "topic": "", + "x": 630, + "y": 1220, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_bedroom_2_all_off", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "bedroom_2 all_off", + "group": "group_bedroom_2", + "order": 1, + "width": 0, + "height": 0, + "label": "all_off", + "payload": "bedroom_2.all_off", + "payloadType": "str", + "topic": "", + "x": 1050, + "y": 940, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_bedroom_2_all_on", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "bedroom_2 all_on", + "group": "group_bedroom_2", + "order": 2, + "width": 0, + "height": 0, + "label": "all_on", + "payload": "bedroom_2.all_on", + "payloadType": "str", + "topic": "", + "x": 1050, + "y": 980, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_bedroom_2_sw_1", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "bedroom_2 sw_1", + "group": "group_bedroom_2", + "order": 3, + "width": 0, + "height": 0, + "label": "sw_1", + "payload": "bedroom_2.sw_1", + "payloadType": "str", + "topic": "", + "x": 1050, + "y": 1020, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_bedroom_2_sw_2", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "bedroom_2 sw_2", + "group": "group_bedroom_2", + "order": 4, + "width": 0, + "height": 0, + "label": "sw_2", + "payload": "bedroom_2.sw_2", + "payloadType": "str", + "topic": "", + "x": 1050, + "y": 1060, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_bedroom_2_sw_3", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "bedroom_2 sw_3", + "group": "group_bedroom_2", + "order": 5, + "width": 0, + "height": 0, + "label": "sw_3", + "payload": "bedroom_2.sw_3", + "payloadType": "str", + "topic": "", + "x": 1050, + "y": 1100, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_bedroom_2_sw_4", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "bedroom_2 sw_4", + "group": "group_bedroom_2", + "order": 6, + "width": 0, + "height": 0, + "label": "sw_4", + "payload": "bedroom_2.sw_4", + "payloadType": "str", + "topic": "", + "x": 1050, + "y": 1140, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_bedroom_2_sw_5", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "bedroom_2 sw_5", + "group": "group_bedroom_2", + "order": 7, + "width": 0, + "height": 0, + "label": "sw_5", + "payload": "bedroom_2.sw_5", + "payloadType": "str", + "topic": "", + "x": 1050, + "y": 1180, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_bedroom_2_sw_6", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "bedroom_2 sw_6", + "group": "group_bedroom_2", + "order": 8, + "width": 0, + "height": 0, + "label": "sw_6", + "payload": "bedroom_2.sw_6", + "payloadType": "str", + "topic": "", + "x": 1050, + "y": 1220, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_bathroom_all_off", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "bathroom all_off", + "group": "group_bathroom", + "order": 1, + "width": 0, + "height": 0, + "label": "all_off", + "payload": "bathroom.all_off", + "payloadType": "str", + "topic": "", + "x": 1680, + "y": 940, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_bathroom_all_on", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "bathroom all_on", + "group": "group_bathroom", + "order": 2, + "width": 0, + "height": 0, + "label": "all_on", + "payload": "bathroom.all_on", + "payloadType": "str", + "topic": "", + "x": 1680, + "y": 980, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_bathroom_sw_1", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "bathroom sw_1", + "group": "group_bathroom", + "order": 3, + "width": 0, + "height": 0, + "label": "sw_1", + "payload": "bathroom.sw_1", + "payloadType": "str", + "topic": "", + "x": 1680, + "y": 1020, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_bathroom_sw_2", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "bathroom sw_2", + "group": "group_bathroom", + "order": 4, + "width": 0, + "height": 0, + "label": "sw_2", + "payload": "bathroom.sw_2", + "payloadType": "str", + "topic": "", + "x": 1680, + "y": 1060, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_bathroom_sw_3", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "bathroom sw_3", + "group": "group_bathroom", + "order": 5, + "width": 0, + "height": 0, + "label": "sw_3", + "payload": "bathroom.sw_3", + "payloadType": "str", + "topic": "", + "x": 1680, + "y": 1100, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_bathroom_sw_4", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "bathroom sw_4", + "group": "group_bathroom", + "order": 6, + "width": 0, + "height": 0, + "label": "sw_4", + "payload": "bathroom.sw_4", + "payloadType": "str", + "topic": "", + "x": 1680, + "y": 1140, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_bathroom_sw_5", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "bathroom sw_5", + "group": "group_bathroom", + "order": 7, + "width": 0, + "height": 0, + "label": "sw_5", + "payload": "bathroom.sw_5", + "payloadType": "str", + "topic": "", + "x": 1680, + "y": 1180, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_bathroom_sw_6", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "bathroom sw_6", + "group": "group_bathroom", + "order": 8, + "width": 0, + "height": 0, + "label": "sw_6", + "payload": "bathroom.sw_6", + "payloadType": "str", + "topic": "", + "x": 1680, + "y": 1220, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_hallway_all_off", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "hallway all_off", + "group": "group_hallway", + "order": 1, + "width": 0, + "height": 0, + "label": "all_off", + "payload": "hallway.all_off", + "payloadType": "str", + "topic": "", + "x": 840, + "y": 940, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_hallway_all_on", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "hallway all_on", + "group": "group_hallway", + "order": 2, + "width": 0, + "height": 0, + "label": "all_on", + "payload": "hallway.all_on", + "payloadType": "str", + "topic": "", + "x": 840, + "y": 980, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_hallway_sw_1", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "hallway sw_1", + "group": "group_hallway", + "order": 3, + "width": 0, + "height": 0, + "label": "sw_1", + "payload": "hallway.sw_1", + "payloadType": "str", + "topic": "", + "x": 830, + "y": 1020, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_hallway_sw_2", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "hallway sw_2", + "group": "group_hallway", + "order": 4, + "width": 0, + "height": 0, + "label": "sw_2", + "payload": "hallway.sw_2", + "payloadType": "str", + "topic": "", + "x": 830, + "y": 1060, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_hallway_sw_3", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "hallway sw_3", + "group": "group_hallway", + "order": 5, + "width": 0, + "height": 0, + "label": "sw_3", + "payload": "hallway.sw_3", + "payloadType": "str", + "topic": "", + "x": 830, + "y": 1100, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_hallway_sw_4", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "hallway sw_4", + "group": "group_hallway", + "order": 6, + "width": 0, + "height": 0, + "label": "sw_4", + "payload": "hallway.sw_4", + "payloadType": "str", + "topic": "", + "x": 830, + "y": 1140, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_hallway_sw_5", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "hallway sw_5", + "group": "group_hallway", + "order": 7, + "width": 0, + "height": 0, + "label": "sw_5", + "payload": "hallway.sw_5", + "payloadType": "str", + "topic": "", + "x": 830, + "y": 1180, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_hallway_sw_6", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "hallway sw_6", + "group": "group_hallway", + "order": 8, + "width": 0, + "height": 0, + "label": "sw_6", + "payload": "hallway.sw_6", + "payloadType": "str", + "topic": "", + "x": 830, + "y": 1220, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_pantry_all_off", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "pantry all_off", + "group": "group_pantry", + "order": 1, + "width": 0, + "height": 0, + "label": "all_off", + "payload": "pantry.all_off", + "payloadType": "str", + "topic": "", + "x": 1470, + "y": 940, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_pantry_all_on", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "pantry all_on", + "group": "group_pantry", + "order": 2, + "width": 0, + "height": 0, + "label": "all_on", + "payload": "pantry.all_on", + "payloadType": "str", + "topic": "", + "x": 1470, + "y": 980, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_pantry_sw_1", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "pantry sw_1", + "group": "group_pantry", + "order": 3, + "width": 0, + "height": 0, + "label": "sw_1", + "payload": "pantry.sw_1", + "payloadType": "str", + "topic": "", + "x": 1470, + "y": 1020, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_pantry_sw_2", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "pantry sw_2", + "group": "group_pantry", + "order": 4, + "width": 0, + "height": 0, + "label": "sw_2", + "payload": "pantry.sw_2", + "payloadType": "str", + "topic": "", + "x": 1470, + "y": 1060, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_pantry_sw_3", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "pantry sw_3", + "group": "group_pantry", + "order": 5, + "width": 0, + "height": 0, + "label": "sw_3", + "payload": "pantry.sw_3", + "payloadType": "str", + "topic": "", + "x": 1470, + "y": 1100, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_pantry_sw_4", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "pantry sw_4", + "group": "group_pantry", + "order": 6, + "width": 0, + "height": 0, + "label": "sw_4", + "payload": "pantry.sw_4", + "payloadType": "str", + "topic": "", + "x": 1470, + "y": 1140, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_pantry_sw_5", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "pantry sw_5", + "group": "group_pantry", + "order": 7, + "width": 0, + "height": 0, + "label": "sw_5", + "payload": "pantry.sw_5", + "payloadType": "str", + "topic": "", + "x": 1470, + "y": 1180, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_pantry_sw_6", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "pantry sw_6", + "group": "group_pantry", + "order": 8, + "width": 0, + "height": 0, + "label": "sw_6", + "payload": "pantry.sw_6", + "payloadType": "str", + "topic": "", + "x": 1470, + "y": 1220, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_kitchen_all_off", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "kitchen all_off", + "group": "group_kitchen", + "order": 1, + "width": 0, + "height": 0, + "label": "all_off", + "payload": "kitchen.all_off", + "payloadType": "str", + "topic": "", + "x": 1580, + "y": 580, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_kitchen_all_on", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "kitchen all_on", + "group": "group_kitchen", + "order": 2, + "width": 0, + "height": 0, + "label": "all_on", + "payload": "kitchen.all_on", + "payloadType": "str", + "topic": "", + "x": 1580, + "y": 620, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_kitchen_sw_1", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "kitchen sw_1", + "group": "group_kitchen", + "order": 3, + "width": 0, + "height": 0, + "label": "sw_1", + "payload": "kitchen.sw_1", + "payloadType": "str", + "topic": "", + "x": 1570, + "y": 660, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_kitchen_sw_2", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "kitchen sw_2", + "group": "group_kitchen", + "order": 4, + "width": 0, + "height": 0, + "label": "sw_2", + "payload": "kitchen.sw_2", + "payloadType": "str", + "topic": "", + "x": 1570, + "y": 700, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_kitchen_sw_3", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "kitchen sw_3", + "group": "group_kitchen", + "order": 5, + "width": 0, + "height": 0, + "label": "sw_3", + "payload": "kitchen.sw_3", + "payloadType": "str", + "topic": "", + "x": 1570, + "y": 740, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_kitchen_sw_4", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "kitchen sw_4", + "group": "group_kitchen", + "order": 6, + "width": 0, + "height": 0, + "label": "sw_4", + "payload": "kitchen.sw_4", + "payloadType": "str", + "topic": "", + "x": 1570, + "y": 780, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_kitchen_sw_5", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "kitchen sw_5", + "group": "group_kitchen", + "order": 7, + "width": 0, + "height": 0, + "label": "sw_5", + "payload": "kitchen.sw_5", + "payloadType": "str", + "topic": "", + "x": 1570, + "y": 820, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_kitchen_sw_6", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "kitchen sw_6", + "group": "group_kitchen", + "order": 8, + "width": 0, + "height": 0, + "label": "sw_6", + "payload": "kitchen.sw_6", + "payloadType": "str", + "topic": "", + "x": 1570, + "y": 860, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_livingRoom_all_off", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "livingRoom all_off", + "group": "group_livingRoom", + "order": 1, + "width": 0, + "height": 0, + "label": "all_off", + "payload": "livingRoom.all_off", + "payloadType": "str", + "topic": "", + "x": 1350, + "y": 580, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_livingRoom_all_on", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "livingRoom all_on", + "group": "group_livingRoom", + "order": 2, + "width": 0, + "height": 0, + "label": "all_on", + "payload": "livingRoom.all_on", + "payloadType": "str", + "topic": "", + "x": 1350, + "y": 620, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_livingRoom_sw_1", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "livingRoom sw_1", + "group": "group_livingRoom", + "order": 3, + "width": 0, + "height": 0, + "label": "sw_1", + "payload": "livingRoom.sw_1", + "payloadType": "str", + "topic": "", + "x": 1350, + "y": 660, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_livingRoom_sw_2", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "livingRoom sw_2", + "group": "group_livingRoom", + "order": 4, + "width": 0, + "height": 0, + "label": "sw_2", + "payload": "livingRoom.sw_2", + "payloadType": "str", + "topic": "", + "x": 1350, + "y": 700, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_livingRoom_sw_3", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "livingRoom sw_3", + "group": "group_livingRoom", + "order": 5, + "width": 0, + "height": 0, + "label": "sw_3", + "payload": "livingRoom.sw_3", + "payloadType": "str", + "topic": "", + "x": 1350, + "y": 740, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_livingRoom_sw_4", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "livingRoom sw_4", + "group": "group_livingRoom", + "order": 6, + "width": 0, + "height": 0, + "label": "sw_4", + "payload": "livingRoom.sw_4", + "payloadType": "str", + "topic": "", + "x": 1350, + "y": 780, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_livingRoom_sw_5", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "livingRoom sw_5", + "group": "group_livingRoom", + "order": 7, + "width": 0, + "height": 0, + "label": "sw_5", + "payload": "livingRoom.sw_5", + "payloadType": "str", + "topic": "", + "x": 1350, + "y": 820, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_livingRoom_sw_6", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "livingRoom sw_6", + "group": "group_livingRoom", + "order": 8, + "width": 0, + "height": 0, + "label": "sw_6", + "payload": "livingRoom.sw_6", + "payloadType": "str", + "topic": "", + "x": 1350, + "y": 860, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_dinningRoom_all_off", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "dinningRoom all_off", + "group": "group_dinningRoom", + "order": 1, + "width": 0, + "height": 0, + "label": "all_off", + "payload": "dinningRoom.all_off", + "payloadType": "str", + "topic": "", + "x": 1150, + "y": 580, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_dinningRoom_all_on", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "dinningRoom all_on", + "group": "group_dinningRoom", + "order": 2, + "width": 0, + "height": 0, + "label": "all_on", + "payload": "dinningRoom.all_on", + "payloadType": "str", + "topic": "", + "x": 1140, + "y": 620, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_dinningRoom_sw_1", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "dinningRoom sw_1", + "group": "group_dinningRoom", + "order": 3, + "width": 0, + "height": 0, + "label": "sw_1", + "payload": "dinningRoom.sw_1", + "payloadType": "str", + "topic": "", + "x": 1150, + "y": 660, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_dinningRoom_sw_2", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "dinningRoom sw_2", + "group": "group_dinningRoom", + "order": 4, + "width": 0, + "height": 0, + "label": "sw_2", + "payload": "dinningRoom.sw_2", + "payloadType": "str", + "topic": "", + "x": 1150, + "y": 700, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_dinningRoom_sw_3", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "dinningRoom sw_3", + "group": "group_dinningRoom", + "order": 5, + "width": 0, + "height": 0, + "passthru": false, + "label": "sw_3", + "tooltip": "", + "color": "", + "bgcolor": "", + "className": "", + "icon": "", + "payload": "dinningRoom.sw_3", + "payloadType": "str", + "topic": "", + "topicType": "str", + "x": 1150, + "y": 740, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_dinningRoom_sw_4", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "dinningRoom sw_4", + "group": "group_dinningRoom", + "order": 6, + "width": 0, + "height": 0, + "label": "sw_4", + "payload": "dinningRoom.sw_4", + "payloadType": "str", + "topic": "", + "x": 1150, + "y": 780, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_dinningRoom_sw_5", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "dinningRoom sw_5", + "group": "group_dinningRoom", + "order": 7, + "width": 0, + "height": 0, + "label": "sw_5", + "payload": "dinningRoom.sw_5", + "payloadType": "str", + "topic": "", + "x": 1150, + "y": 820, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_dinningRoom_sw_6", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "dinningRoom sw_6", + "group": "group_dinningRoom", + "order": 8, + "width": 0, + "height": 0, + "label": "sw_6", + "payload": "dinningRoom.sw_6", + "payloadType": "str", + "topic": "", + "x": 1150, + "y": 860, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_entrance_all_off", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "entrance all_off", + "group": "group_entrance", + "order": 1, + "width": 0, + "height": 0, + "label": "all_off", + "payload": "entrance.all_off", + "payloadType": "str", + "topic": "", + "x": 1300, + "y": 940, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_entrance_all_on", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "entrance all_on", + "group": "group_entrance", + "order": 2, + "width": 0, + "height": 0, + "label": "all_on", + "payload": "entrance.all_on", + "payloadType": "str", + "topic": "", + "x": 1300, + "y": 980, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_entrance_sw_1", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "entrance sw_1", + "group": "group_entrance", + "order": 3, + "width": 0, + "height": 0, + "label": "sw_1", + "payload": "entrance.sw_1", + "payloadType": "str", + "topic": "", + "x": 1300, + "y": 1020, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_entrance_sw_2", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "entrance sw_2", + "group": "group_entrance", + "order": 4, + "width": 0, + "height": 0, + "label": "sw_2", + "payload": "entrance.sw_2", + "payloadType": "str", + "topic": "", + "x": 1300, + "y": 1060, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_entrance_sw_3", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "entrance sw_3", + "group": "group_entrance", + "order": 5, + "width": 0, + "height": 0, + "label": "sw_3", + "payload": "entrance.sw_3", + "payloadType": "str", + "topic": "", + "x": 1300, + "y": 1100, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_entrance_sw_4", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "entrance sw_4", + "group": "group_entrance", + "order": 6, + "width": 0, + "height": 0, + "label": "sw_4", + "payload": "entrance.sw_4", + "payloadType": "str", + "topic": "", + "x": 1300, + "y": 1140, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_entrance_sw_5", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "entrance sw_5", + "group": "group_entrance", + "order": 7, + "width": 0, + "height": 0, + "label": "sw_5", + "payload": "entrance.sw_5", + "payloadType": "str", + "topic": "", + "x": 1300, + "y": 1180, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_entrance_sw_6", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "entrance sw_6", + "group": "group_entrance", + "order": 8, + "width": 0, + "height": 0, + "label": "sw_6", + "payload": "entrance.sw_6", + "payloadType": "str", + "topic": "", + "x": 1300, + "y": 1220, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_guestWc_all_off", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "guestWc all_off", + "group": "group_guestWc", + "order": 1, + "width": 0, + "height": 0, + "label": "all_off", + "payload": "guestWc.all_off", + "payloadType": "str", + "topic": "", + "x": 940, + "y": 580, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_guestWc_all_on", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "guestWc all_on", + "group": "group_guestWc", + "order": 2, + "width": 0, + "height": 0, + "label": "all_on", + "payload": "guestWc.all_on", + "payloadType": "str", + "topic": "", + "x": 940, + "y": 620, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_guestWc_sw_1", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "guestWc sw_1", + "group": "group_guestWc", + "order": 3, + "width": 0, + "height": 0, + "label": "sw_1", + "payload": "guestWc.sw_1", + "payloadType": "str", + "topic": "", + "x": 940, + "y": 660, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_guestWc_sw_2", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "guestWc sw_2", + "group": "group_guestWc", + "order": 4, + "width": 0, + "height": 0, + "label": "sw_2", + "payload": "guestWc.sw_2", + "payloadType": "str", + "topic": "", + "x": 940, + "y": 700, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_guestWc_sw_3", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "guestWc sw_3", + "group": "group_guestWc", + "order": 5, + "width": 0, + "height": 0, + "label": "sw_3", + "payload": "guestWc.sw_3", + "payloadType": "str", + "topic": "", + "x": 940, + "y": 740, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_guestWc_sw_4", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "guestWc sw_4", + "group": "group_guestWc", + "order": 6, + "width": 0, + "height": 0, + "label": "sw_4", + "payload": "guestWc.sw_4", + "payloadType": "str", + "topic": "", + "x": 940, + "y": 780, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_guestWc_sw_5", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "guestWc sw_5", + "group": "group_guestWc", + "order": 7, + "width": 0, + "height": 0, + "label": "sw_5", + "payload": "guestWc.sw_5", + "payloadType": "str", + "topic": "", + "x": 940, + "y": 820, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_guestWc_sw_6", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "guestWc sw_6", + "group": "group_guestWc", + "order": 8, + "width": 0, + "height": 0, + "label": "sw_6", + "payload": "guestWc.sw_6", + "payloadType": "str", + "topic": "", + "x": 940, + "y": 860, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outVeranda_all_off", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outVeranda all_off", + "group": "group_outVeranda", + "order": 1, + "width": 0, + "height": 0, + "label": "all_off", + "payload": "outVeranda.all_off", + "payloadType": "str", + "topic": "", + "x": 730, + "y": 580, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outVeranda_all_on", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outVeranda all_on", + "group": "group_outVeranda", + "order": 2, + "width": 0, + "height": 0, + "label": "all_on", + "payload": "outVeranda.all_on", + "payloadType": "str", + "topic": "", + "x": 730, + "y": 620, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outVeranda_sw_1", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outVeranda sw_1", + "group": "group_outVeranda", + "order": 3, + "width": 0, + "height": 0, + "label": "sw_1", + "payload": "outVeranda.sw_1", + "payloadType": "str", + "topic": "", + "x": 730, + "y": 660, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outVeranda_sw_2", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outVeranda sw_2", + "group": "group_outVeranda", + "order": 4, + "width": 0, + "height": 0, + "label": "sw_2", + "payload": "outVeranda.sw_2", + "payloadType": "str", + "topic": "", + "x": 730, + "y": 700, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outVeranda_sw_3", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outVeranda sw_3", + "group": "group_outVeranda", + "order": 5, + "width": 0, + "height": 0, + "label": "sw_3", + "payload": "outVeranda.sw_3", + "payloadType": "str", + "topic": "", + "x": 730, + "y": 740, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outVeranda_sw_4", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outVeranda sw_4", + "group": "group_outVeranda", + "order": 6, + "width": 0, + "height": 0, + "label": "sw_4", + "payload": "outVeranda.sw_4", + "payloadType": "str", + "topic": "", + "x": 730, + "y": 780, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outVeranda_sw_5", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outVeranda sw_5", + "group": "group_outVeranda", + "order": 7, + "width": 0, + "height": 0, + "label": "sw_5", + "payload": "outVeranda.sw_5", + "payloadType": "str", + "topic": "", + "x": 730, + "y": 820, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outVeranda_sw_6", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outVeranda sw_6", + "group": "group_outVeranda", + "order": 8, + "width": 0, + "height": 0, + "label": "sw_6", + "payload": "outVeranda.sw_6", + "payloadType": "str", + "topic": "", + "x": 730, + "y": 860, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outFront_all_off", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outFront all_off", + "group": "group_outFront", + "order": 1, + "width": 0, + "height": 0, + "label": "all_off", + "payload": "outFront.all_off", + "payloadType": "str", + "topic": "", + "x": 520, + "y": 580, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outFront_all_on", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outFront all_on", + "group": "group_outFront", + "order": 2, + "width": 0, + "height": 0, + "label": "all_on", + "payload": "outFront.all_on", + "payloadType": "str", + "topic": "", + "x": 520, + "y": 620, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outFront_sw_1", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outFront sw_1", + "group": "group_outFront", + "order": 3, + "width": 0, + "height": 0, + "label": "sw_1", + "payload": "outFront.sw_1", + "payloadType": "str", + "topic": "", + "x": 520, + "y": 660, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outFront_sw_2", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outFront sw_2", + "group": "group_outFront", + "order": 4, + "width": 0, + "height": 0, + "label": "sw_2", + "payload": "outFront.sw_2", + "payloadType": "str", + "topic": "", + "x": 520, + "y": 700, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outFront_sw_3", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outFront sw_3", + "group": "group_outFront", + "order": 5, + "width": 0, + "height": 0, + "label": "sw_3", + "payload": "outFront.sw_3", + "payloadType": "str", + "topic": "", + "x": 520, + "y": 740, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outFront_sw_4", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outFront sw_4", + "group": "group_outFront", + "order": 6, + "width": 0, + "height": 0, + "label": "sw_4", + "payload": "outFront.sw_4", + "payloadType": "str", + "topic": "", + "x": 520, + "y": 780, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outFront_sw_5", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outFront sw_5", + "group": "group_outFront", + "order": 7, + "width": 0, + "height": 0, + "label": "sw_5", + "payload": "outFront.sw_5", + "payloadType": "str", + "topic": "", + "x": 520, + "y": 820, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outFront_sw_6", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outFront sw_6", + "group": "group_outFront", + "order": 8, + "width": 0, + "height": 0, + "label": "sw_6", + "payload": "outFront.sw_6", + "payloadType": "str", + "topic": "", + "x": 520, + "y": 860, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outSide_all_off", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outSide all_off", + "group": "group_outSide", + "order": 1, + "width": 0, + "height": 0, + "label": "all_off", + "payload": "outSide.all_off", + "payloadType": "str", + "topic": "", + "x": 340, + "y": 580, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outSide_all_on", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outSide all_on", + "group": "group_outSide", + "order": 2, + "width": 0, + "height": 0, + "label": "all_on", + "payload": "outSide.all_on", + "payloadType": "str", + "topic": "", + "x": 340, + "y": 620, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outSide_sw_1", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outSide sw_1", + "group": "group_outSide", + "order": 3, + "width": 0, + "height": 0, + "label": "sw_1", + "payload": "outSide.sw_1", + "payloadType": "str", + "topic": "", + "x": 340, + "y": 660, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outSide_sw_2", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outSide sw_2", + "group": "group_outSide", + "order": 4, + "width": 0, + "height": 0, + "label": "sw_2", + "payload": "outSide.sw_2", + "payloadType": "str", + "topic": "", + "x": 340, + "y": 700, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outSide_sw_3", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outSide sw_3", + "group": "group_outSide", + "order": 5, + "width": 0, + "height": 0, + "label": "sw_3", + "payload": "outSide.sw_3", + "payloadType": "str", + "topic": "", + "x": 340, + "y": 740, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outSide_sw_4", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outSide sw_4", + "group": "group_outSide", + "order": 6, + "width": 0, + "height": 0, + "label": "sw_4", + "payload": "outSide.sw_4", + "payloadType": "str", + "topic": "", + "x": 340, + "y": 780, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outSide_sw_5", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outSide sw_5", + "group": "group_outSide", + "order": 7, + "width": 0, + "height": 0, + "label": "sw_5", + "payload": "outSide.sw_5", + "payloadType": "str", + "topic": "", + "x": 340, + "y": 820, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outSide_sw_6", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outSide sw_6", + "group": "group_outSide", + "order": 8, + "width": 0, + "height": 0, + "label": "sw_6", + "payload": "outSide.sw_6", + "payloadType": "str", + "topic": "", + "x": 340, + "y": 860, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outBack_all_off", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outBack all_off", + "group": "group_outBack", + "order": 1, + "width": 0, + "height": 0, + "label": "all_off", + "payload": "outBack.all_off", + "payloadType": "str", + "topic": "", + "x": 120, + "y": 580, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outBack_all_on", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outBack all_on", + "group": "group_outBack", + "order": 2, + "width": 0, + "height": 0, + "label": "all_on", + "payload": "outBack.all_on", + "payloadType": "str", + "topic": "", + "x": 120, + "y": 620, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outBack_sw_1", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outBack sw_1", + "group": "group_outBack", + "order": 3, + "width": 0, + "height": 0, + "label": "sw_1", + "payload": "outBack.sw_1", + "payloadType": "str", + "topic": "", + "x": 120, + "y": 660, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outBack_sw_2", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outBack sw_2", + "group": "group_outBack", + "order": 4, + "width": 0, + "height": 0, + "label": "sw_2", + "payload": "outBack.sw_2", + "payloadType": "str", + "topic": "", + "x": 120, + "y": 700, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outBack_sw_3", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outBack sw_3", + "group": "group_outBack", + "order": 5, + "width": 0, + "height": 0, + "label": "sw_3", + "payload": "outBack.sw_3", + "payloadType": "str", + "topic": "", + "x": 120, + "y": 740, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outBack_sw_4", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outBack sw_4", + "group": "group_outBack", + "order": 6, + "width": 0, + "height": 0, + "label": "sw_4", + "payload": "outBack.sw_4", + "payloadType": "str", + "topic": "", + "x": 120, + "y": 780, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outBack_sw_5", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outBack sw_5", + "group": "group_outBack", + "order": 7, + "width": 0, + "height": 0, + "label": "sw_5", + "payload": "outBack.sw_5", + "payloadType": "str", + "topic": "", + "x": 120, + "y": 820, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "btn_outBack_sw_6", + "type": "ui_button", + "z": "31693504fcbbd954", + "name": "outBack sw_6", + "group": "group_outBack", + "order": 8, + "width": 0, + "height": 0, + "label": "sw_6", + "payload": "outBack.sw_6", + "payloadType": "str", + "topic": "", + "x": 120, + "y": 860, + "wires": [ + [ + "func_node" + ] + ] + }, + { + "id": "timer30min_btn", + "type": "ha-button", + "z": "50971c50479dfd7a", + "name": "Timer 30 Min", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "timer30min_config", + "outputProperties": [ + { + "property": "payload", + "propertyType": "msg", + "value": "30", + "valueType": "num" + } + ], + "x": 630, + "y": 680, + "wires": [ + [ + "260bf6fdd821f1b9" + ] + ] + }, + { + "id": "timer1hour_btn", + "type": "ha-button", + "z": "50971c50479dfd7a", + "name": "Timer 1 Hour", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "timer1hour_config", + "outputProperties": [ + { + "property": "payload", + "propertyType": "msg", + "value": "60", + "valueType": "num" + } + ], + "x": 790, + "y": 680, + "wires": [ + [ + "260bf6fdd821f1b9" + ] + ] + }, + { + "id": "timer2hours_btn", + "type": "ha-button", + "z": "50971c50479dfd7a", + "name": "Timer 2 Hours", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "timer2hours_config", + "outputProperties": [ + { + "property": "payload", + "propertyType": "msg", + "value": "120", + "valueType": "num" + } + ], + "x": 990, + "y": 680, + "wires": [ + [ + "260bf6fdd821f1b9" + ] + ] + }, + { + "id": "timerstop_btn", + "type": "ha-button", + "z": "50971c50479dfd7a", + "name": "Stop Timer", + "version": 0, + "debugenabled": false, + "outputs": 1, + "entityConfig": "timerstop_config", + "outputProperties": [ + { + "property": "payload", + "propertyType": "msg", + "value": "stop", + "valueType": "str" + } + ], + "x": 980, + "y": 740, + "wires": [ + [ + "a57a98c852762ebf" + ] + ] + }, + { + "id": "water_boiler_timer_handler", + "type": "function", + "z": "50971c50479dfd7a", + "name": "Water Boiler Timer Handler", + "func": "// Helper function to format remaining time\nfunction formatTime(remainingMs) {\n if (remainingMs <= 0) return '0m 0s';\n const totalSeconds = Math.floor(remainingMs / 1000);\n const minutes = Math.floor(totalSeconds / 60);\n const seconds = totalSeconds % 60;\n const hours = Math.floor(minutes / 60);\n const mins = minutes % 60;\n \n let timeStr = '';\n if (hours > 0) timeStr += hours + 'h ';\n if (mins > 0 || hours === 0) timeStr += mins + 'm ';\n timeStr += seconds + 's';\n return timeStr.trim();\n}\n\n// Check if this is a timer command (numeric payload or \"stop\")\nconst payload = msg.payload;\nconst isTimerCommand = typeof payload === 'number' || payload === 'stop';\n\nif (isTimerCommand) {\n // Cancel any existing timer and interval\n const existingTimer = flow.get('water_boiler_timer');\n if (existingTimer) {\n clearTimeout(existingTimer);\n flow.set('water_boiler_timer', null);\n }\n \n const existingInterval = flow.get('water_boiler_timer_interval');\n if (existingInterval) {\n clearInterval(existingInterval);\n flow.set('water_boiler_timer_interval', null);\n }\n \n if (payload === 'stop') {\n // Stop command - turn off water boiler immediately\n const msgFalse = {\n topic: \"bathroom\",\n payload: \"sw_6\",\n value: false\n };\n \n // Send 0 remaining time\n const msgTime = {\n payload: '0m 0s'\n };\n \n node.status({fill:\"yellow\", shape:\"dot\", text:\"Timer stopped\"});\n return [msgFalse, null, msgTime];\n } else {\n // Timer duration in minutes - convert to milliseconds\n const durationMs = payload * 60 * 1000;\n const startTime = Date.now();\n \n // Store start time and duration\n flow.set('water_boiler_timer_start', startTime);\n flow.set('water_boiler_timer_duration', durationMs);\n \n // Turn ON water boiler first\n const msgTrue = {\n topic: \"bathroom\",\n payload: \"sw_6\",\n value: true\n };\n \n // Function to send remaining time\n const sendRemainingTime = function() {\n const elapsed = Date.now() - startTime;\n const remaining = Math.max(0, durationMs - elapsed);\n const timeStr = formatTime(remaining);\n \n const msgTime = {\n payload: timeStr\n };\n node.send([null, null, msgTime]);\n \n // Update status\n node.status({fill:\"blue\", shape:\"dot\", text:`Timer: ${timeStr}`});\n \n // Clear interval if timer expired\n if (remaining <= 0) {\n const intervalId = flow.get('water_boiler_timer_interval');\n if (intervalId) {\n clearInterval(intervalId);\n flow.set('water_boiler_timer_interval', null);\n }\n }\n };\n \n // Send initial remaining time\n sendRemainingTime();\n \n // Set up interval to update remaining time every second (live refresh)\n const intervalId = setInterval(sendRemainingTime, 1000);\n flow.set('water_boiler_timer_interval', intervalId);\n \n // Set timer to turn OFF after duration\n const timerId = setTimeout(function() {\n const msgOff = {\n topic: \"bathroom\",\n payload: \"sw_6\",\n value: false\n };\n \n // Send final time (0m 0s)\n const msgTime = {\n payload: '0m 0s'\n };\n \n node.send([msgOff, null, msgTime]);\n flow.set('water_boiler_timer', null);\n flow.set('water_boiler_timer_start', null);\n flow.set('water_boiler_timer_duration', null);\n \n // Clear interval\n const intervalId = flow.get('water_boiler_timer_interval');\n if (intervalId) {\n clearInterval(intervalId);\n flow.set('water_boiler_timer_interval', null);\n }\n \n node.status({fill:\"green\", shape:\"ring\", text:\"Timer completed\"});\n }, durationMs);\n \n // Store timer ID\n flow.set('water_boiler_timer', timerId);\n \n const minutes = payload;\n const hours = Math.floor(minutes / 60);\n const mins = minutes % 60;\n let timeStr = '';\n if (hours > 0) timeStr += hours + 'h ';\n if (mins > 0 || hours === 0) timeStr += mins + 'm';\n \n // Return ON command on output 1, nothing on output 2, initial time on output 3\n const msgTime = {\n payload: formatTime(durationMs)\n };\n return [msgTrue, null, msgTime];\n }\n} else {\n // Regular button press - pass through to existing function on output 2\n return [null, msg, null];\n}\n\n", + "outputs": 3, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1620, + "y": 680, + "wires": [ + [ + "1d0a46b9d5255560" + ], + [], + [ + "timer_remaining_sensor" + ] + ] + }, + { + "id": "1d0a46b9d5255560", + "type": "link out", + "z": "50971c50479dfd7a", + "name": "link out 24", + "mode": "link", + "links": [ + "0bbcb269da9b75c7" + ], + "x": 1845, + "y": 560, + "wires": [] + }, + { + "id": "timer_remaining_sensor", + "type": "ha-sensor", + "z": "50971c50479dfd7a", + "name": "Timer Remaining", + "entityConfig": "timer_remaining_config", + "version": 1, + "state": "payload", + "stateType": "msg", + "attributes": [], + "x": 1890, + "y": 620, + "wires": [ + [] + ] + }, + { + "id": "a57a98c852762ebf", + "type": "api-current-state", + "z": "50971c50479dfd7a", + "name": "", + "server": "29eb84a2708d96e0", + "version": 3, + "outputs": 2, + "halt_if": "on", + "halt_if_type": "str", + "halt_if_compare": "is", + "entity_id": "binary_sensor.water_boiler", + "state_type": "str", + "blockInputOverrides": false, + "outputProperties": [], + "for": "0", + "forType": "num", + "forUnits": "minutes", + "override_topic": false, + "state_location": "payload", + "override_payload": "msg", + "entity_location": "data", + "override_data": "msg", + "x": 1280, + "y": 740, + "wires": [ + [ + "water_boiler_timer_handler" + ], + [] + ] + }, + { + "id": "260bf6fdd821f1b9", + "type": "api-current-state", + "z": "50971c50479dfd7a", + "name": "", + "server": "29eb84a2708d96e0", + "version": 3, + "outputs": 2, + "halt_if": "off", + "halt_if_type": "str", + "halt_if_compare": "is", + "entity_id": "binary_sensor.water_boiler", + "state_type": "str", + "blockInputOverrides": false, + "outputProperties": [], + "for": "0", + "forType": "num", + "forUnits": "minutes", + "override_topic": false, + "state_location": "payload", + "override_payload": "msg", + "entity_location": "data", + "override_data": "msg", + "x": 1280, + "y": 680, + "wires": [ + [ + "water_boiler_timer_handler" + ], + [] + ] + }, + { + "id": "0ad5d7e5c99c2675", + "type": "poll-state", + "z": "50971c50479dfd7a", + "name": "", + "server": "29eb84a2708d96e0", + "version": 3, + "exposeAsEntityConfig": "", + "updateInterval": "30", + "updateIntervalType": "num", + "updateIntervalUnits": "seconds", + "outputInitially": true, + "outputOnChanged": true, + "entityId": "binary_sensor.water_boiler", + "stateType": "str", + "ifState": "on", + "ifStateType": "str", + "ifStateOperator": "is", + "outputs": 2, + "outputProperties": [ + { + "property": "payload", + "propertyType": "msg", + "value": "", + "valueType": "entityState" + }, + { + "property": "data", + "propertyType": "msg", + "value": "", + "valueType": "entity" + }, + { + "property": "topic", + "propertyType": "msg", + "value": "", + "valueType": "triggerId" + } + ], + "x": 1070, + "y": 600, + "wires": [ + [ + "2472daff4bc82fb2", + "f12fa3448a1b5872" + ], + [] + ] + }, + { + "id": "2472daff4bc82fb2", + "type": "function", + "z": "50971c50479dfd7a", + "name": "Safety Check Function", + "func": "// Safety Check Function\n// This function checks the water boiler state on Node-RED startup ONLY\n// It's connected to the poll-state node which outputs initially when Node-RED starts\n// If the boiler is ON, it turns it off and clears timer state\n// Uses a flag to ensure it only runs once on startup, not on every poll\n\n// Check if safety check has already been performed (only runs once on startup)\nconst safetyCheckDone = flow.get('water_boiler_safety_check_done');\nif (safetyCheckDone === true) {\n // Safety check already performed, ignore this message (from periodic polls)\n // Return nothing to both outputs\n return [null, null];\n}\n\n// Mark safety check as done (so it won't run again until Node-RED restarts)\nflow.set('water_boiler_safety_check_done', true);\n\n// Get the current state from the poll-state node\nconst currentState = msg.payload; // 'on' or 'off'\n\n// Clear any timer state (safety measure on restart)\nflow.set('water_boiler_timer', null);\nflow.set('water_boiler_timer_interval', null);\nflow.set('water_boiler_timer_start', null);\nflow.set('water_boiler_timer_duration', null);\n\n// Only turn off if the boiler is currently ON\nif (currentState === 'on') {\n // Boiler is ON - turn it off for safety\n const msgOff = {\n topic: \"bathroom\",\n payload: \"sw_6\",\n value: false\n };\n \n const msgTime = {\n payload: '0m 0s'\n };\n \n node.status({fill:\"red\", shape:\"ring\", text:\"Safety: Boiler was ON, turned off\"});\n return [msgOff, msgTime];\n} else {\n // Boiler is already off, just reset timer display\n const msgTime = {\n payload: '0m 0s'\n };\n \n node.status({fill:\"green\", shape:\"ring\", text:\"Safety: Boiler already off\"});\n return [null, msgTime];\n}\n\n", + "outputs": 2, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1600, + "y": 620, + "wires": [ + [ + "1d0a46b9d5255560" + ], + [ + "timer_remaining_sensor" + ] + ] + }, + { + "id": "f96bf3ff41b2e8f7", + "type": "server-state-changed", + "z": "50971c50479dfd7a", + "d": true, + "name": "Monitor Boiler (3h Safety)", + "server": "29eb84a2708d96e0", + "version": 6, + "outputs": 1, + "exposeAsEntityConfig": "", + "entities": { + "entity": [ + "binary_sensor.water_boiler" + ], + "substring": [], + "regex": [] + }, + "outputInitially": true, + "stateType": "str", + "ifState": "", + "ifStateType": "str", + "ifStateOperator": "is", + "outputOnlyOnStateChange": true, + "for": "0", + "forType": "num", + "forUnits": "minutes", + "ignorePrevStateNull": true, + "ignorePrevStateUnknown": true, + "ignorePrevStateUnavailable": false, + "ignoreCurrentStateUnknown": false, + "ignoreCurrentStateUnavailable": false, + "outputProperties": [ + { + "property": "payload", + "propertyType": "msg", + "value": "", + "valueType": "entityState" + }, + { + "property": "data", + "propertyType": "msg", + "value": "", + "valueType": "eventData" + }, + { + "property": "topic", + "propertyType": "msg", + "value": "", + "valueType": "triggerId" + } + ], + "x": 1030, + "y": 560, + "wires": [ + [ + "3hour_safety_function_events" + ] + ] + }, + { + "id": "3hour_safety_function_events", + "type": "function", + "z": "50971c50479dfd7a", + "d": true, + "name": "3-Hour Safety Timer", + "func": "// Water Boiler 3-Hour Safety Timer\n// Uses server-state-changed node which provides old_state and new_state in event data\n// Event-driven (real-time) - no polling needed!\n\n// Get state information from server-state-changed node\n// msg.data contains the full event object with old_state and new_state\nconst eventData = msg.data;\nconst newState = eventData.new_state ? eventData.new_state.state : msg.payload;\nconst oldState = eventData.old_state ? eventData.old_state.state : 'unknown';\n\n// Get existing safety timer if any\nconst safetyTimer = flow.get('water_boiler_3hour_timer');\n\n// Check if state changed from off/unknown to on\nif (newState === 'on' && oldState !== 'on') {\n // Boiler just turned ON - start 3-hour safety timer\n \n // Cancel any existing timer (safety measure)\n if (safetyTimer) {\n clearTimeout(safetyTimer);\n flow.set('water_boiler_3hour_timer', null);\n }\n \n // Start 3-hour timer (3 hours = 3 * 60 * 60 * 1000 milliseconds)\n const threeHours = 3 * 60 * 60 * 1000;\n const timerId = setTimeout(function() {\n // 3 hours elapsed - turn off boiler for safety\n const msgOff = {\n topic: \"bathroom\",\n payload: \"sw_6\",\n value: false\n };\n \n // Clear timer state\n flow.set('water_boiler_3hour_timer', null);\n flow.set('water_boiler_3hour_start_time', null);\n \n // Send turn-off command\n node.send(msgOff);\n node.status({fill:\"red\", shape:\"ring\", text:\"Safety: 3h limit reached, turned off\"});\n node.warn(\"Water boiler has been ON for 3 hours - automatically turned off for safety\");\n }, threeHours);\n \n // Store timer ID and start time\n flow.set('water_boiler_3hour_timer', timerId);\n flow.set('water_boiler_3hour_start_time', Date.now());\n \n // Update status\n node.status({fill:\"yellow\", shape:\"dot\", text:\"3h safety timer: 3h remaining\"});\n node.log(\"Water boiler turned ON - 3-hour safety timer started\");\n \n // Don't send any message (just monitoring)\n return null;\n \n} else if (newState === 'off' && oldState === 'on') {\n // Boiler just turned OFF - cancel safety timer\n \n if (safetyTimer) {\n clearTimeout(safetyTimer);\n flow.set('water_boiler_3hour_timer', null);\n \n const startTime = flow.get('water_boiler_3hour_start_time');\n if (startTime) {\n const runtime = Math.floor((Date.now() - startTime) / 1000 / 60); // minutes\n node.log(`Water boiler turned OFF - 3-hour safety timer cancelled (was on for ${runtime} minutes)`);\n }\n \n flow.set('water_boiler_3hour_start_time', null);\n node.status({fill:\"green\", shape:\"ring\", text:\"3h safety timer cancelled\"});\n } else {\n node.status({fill:\"grey\", shape:\"ring\", text:\"Boiler off\"});\n }\n \n // Don't send any message\n return null;\n \n} else {\n // Other state changes (on->on, off->off, initial state, etc.) - ignore\n return null;\n}", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1600, + "y": 560, + "wires": [ + [ + "1d0a46b9d5255560" + ] + ] + }, + { + "id": "f12fa3448a1b5872", + "type": "debug", + "z": "50971c50479dfd7a", + "name": "debug 28", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 1290, + "y": 460, + "wires": [] + }, + { + "id": "inject-person", + "type": "inject", + "z": "a6a28d6b6c0b4f4a", + "name": "Simulate Person Detected", + "props": [ + { + "p": "payload", + "v": "1", + "vt": "num" + }, + { + "p": "timestamp", + "v": "", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 410, + "y": 860, + "wires": [ + [ + "function-add-timestamp" + ] + ] + }, + { + "id": "function-add-timestamp", + "type": "function", + "z": "a6a28d6b6c0b4f4a", + "name": "Add Current Timestamp", + "func": "// Add timestamp\nmsg.timestamp = Date.now();\nreturn msg;", + "outputs": 1, + "x": 670, + "y": 860, + "wires": [ + [ + "function-check-state" + ] + ] + }, + { + "id": "function-check-state", + "type": "function", + "z": "a6a28d6b6c0b4f4a", + "name": "Check Doorbell State", + "func": "// Get previous state from context\nvar prev = context.get('prev') || 0;\nvar curr = parseInt(msg.payload);\ncontext.set('prev', curr);\n\nif(curr===1 && prev===0){\n msg.payload = \"🚪 Person detected at Basement Driveway!\";\n return msg;\n}\nreturn null;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 910, + "y": 860, + "wires": [ + [ + "function-login" + ] + ] + }, + { + "id": "function-login", + "type": "function", + "z": "a6a28d6b6c0b4f4a", + "name": "Reolink Login", + "func": "const ip = '10.20.30.34';\nconst username = 'admin';\nconst password = 'sh1pb0x1';\nmsg.url = `http://${ip}/cgi-bin/api.cgi?cmd=Login&token=null`;\nmsg.method = 'POST';\nmsg.headers = { 'Content-Type': 'application/json' };\nmsg.payload = JSON.stringify([\n { cmd: 'Login', param: { User: { userName: username, password: password } } }\n]);\nmsg.camera_ip = ip;\nmsg.camera_location = 'Basement Driveway Camera';\nmsg.nextRandom = Math.random().toString(36).substring(7);\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1150, + "y": 860, + "wires": [ + [ + "http-login" + ] + ] + }, + { + "id": "http-login", + "type": "http request", + "z": "a6a28d6b6c0b4f4a", + "name": "Send Login Request", + "method": "use", + "ret": "obj", + "url": "", + "tls": "", + "persist": false, + "proxy": "", + "x": 1390, + "y": 860, + "wires": [ + [ + "function-snap" + ] + ] + }, + { + "id": "function-snap", + "type": "function", + "z": "a6a28d6b6c0b4f4a", + "name": "Fetch Snapshot", + "func": "// Extract token from login response\nif(!msg.payload || !msg.payload[0] || !msg.payload[0].value){\n node.error('Login failed');\n return null;\n}\nconst token = msg.payload[0].value.Token.name;\nif(!token){ node.error('Token missing'); return null; }\n\n// Build snapshot URL\nmsg.url = `http://${msg.camera_ip}/cgi-bin/api.cgi?cmd=Snap&channel=0&rs=${msg.nextRandom}&token=${token}`;\nmsg.method = 'GET';\nmsg.headers = {};\nmsg.payload = null;\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1630, + "y": 860, + "wires": [ + [ + "http-snap" + ] + ] + }, + { + "id": "http-snap", + "type": "http request", + "z": "a6a28d6b6c0b4f4a", + "name": "Get Camera Snapshot", + "method": "use", + "ret": "bin", + "url": "", + "tls": "", + "persist": false, + "proxy": "", + "x": 1870, + "y": 860, + "wires": [ + [ + "b95aab8dab0c2fb4" + ] + ] + }, + { + "id": "b95aab8dab0c2fb4", + "type": "function", + "z": "a6a28d6b6c0b4f4a", + "name": "Send Doorbell Alert to Telegram", + "func": "// Validate that we received a proper image buffer\nif (!Buffer.isBuffer(msg.payload)) {\n node.warn(\"❌ Basement Driveway snapshot fetch failed — not a binary image buffer\");\n node.status({fill:\"red\", shape:\"ring\", text:\"Snapshot failed\"});\n return null;\n}\n\n// Check if buffer has reasonable size (basic validation)\nif (msg.payload.length < 1000) {\n node.warn(\"❌ Basement Driveway snapshot too small — likely corrupted\");\n node.status({fill:\"red\", shape:\"ring\", text:\"Image too small\"});\n return null;\n}\n\n// Format timestamp for display\nconst timestamp = new Date(msg.timestamp).toLocaleString();\n\n// Prepare Telegram message payload\nmsg.payload = {\n chatId: -4747176681, // Telegram group chat ID\n type: 'photo',\n content: msg.payload, // Binary image buffer\n caption: `🔔 Basement Driveway ALERT! 🔔\\n\\n📍 Someone is at the ${msg.location}\\n⏰ Time: ${timestamp}\\n📷 Camera: ${msg.camera_location} (${msg.camera_ip})`\n};\n\nnode.status({fill:\"green\", shape:\"dot\", text:\"Alert sent!\"});\n\nreturn msg;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 2150, + "y": 860, + "wires": [ + [ + "8f15067daf678e66" + ] + ] + }, + { + "id": "8f15067daf678e66", + "type": "link out", + "z": "a6a28d6b6c0b4f4a", + "name": "link out 25", + "mode": "link", + "links": [ + "41d29e978759e26e" + ], + "x": 2365, + "y": 860, + "wires": [] + }, + { + "id": "dbea7ee9936e4747", + "type": "mqtt in", + "z": "b673e10beab091ff", + "name": "MQTT Input", + "topic": "#", + "qos": "2", + "datatype": "auto-detect", + "broker": "a1235a09a8d34dea", + "nl": false, + "rap": true, + "rh": 0, + "inputs": 0, + "x": 330, + "y": 1040, + "wires": [ + [ + "b10d3c44bca37652" + ] + ] + }, + { + "id": "b10d3c44bca37652", + "type": "function", + "z": "b673e10beab091ff", + "name": "Camera Config & Topic Parser", + "func": "// Camera configuration - Add/remove cameras here\n// authMethod: 'url' for URL-based auth, 'reolink' for Reolink API login\nconst cameras = [\n {\n name: \"Doorbell Camera\",\n ip: \"10.20.30.30\",\n mqttTopic: \"frigate/doorbell/person\",\n username: \"admin\",\n password: \"sh1pb0x1\",\n authMethod: \"url\"\n },\n {\n name: \"Veranda Door Camera\",\n ip: \"10.20.30.33\",\n mqttTopic: \"frigate/veranda_door/person\",\n username: \"admin\",\n password: \"sh1pb0x1\",\n authMethod: \"url\"\n },\n {\n name: \"Basement Driveway Camera\",\n ip: \"10.20.30.34\",\n mqttTopic: \"frigate/basement_driveway/person\",\n username: \"admin\",\n password: \"sh1pb0x1\",\n authMethod: \"reolink\"\n }\n];\n\n// Find matching camera based on MQTT topic\nconst topic = msg.topic;\nconst camera = cameras.find(cam => topic === cam.mqttTopic);\n\n// Only process if it's a person detection topic for a known camera\nif (!camera) {\n return null; // Not a camera we care about\n}\n\n// Store camera config in message\nmsg.camera_name = camera.name;\nmsg.camera_ip = camera.ip;\nmsg.camera_username = camera.username;\nmsg.camera_password = camera.password;\nmsg.camera_location = camera.name;\nmsg.authMethod = camera.authMethod;\n\n// Add timestamp if not present\nif (!msg.timestamp) {\n msg.timestamp = Date.now();\n}\n\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 670, + "y": 1040, + "wires": [ + [ + "a1e7837147954108", + "50b554a0642ab4e4" + ] + ] + }, + { + "id": "a1e7837147954108", + "type": "function", + "z": "b673e10beab091ff", + "name": "Check State Change", + "func": "// Get current payload value\nvar currentValue = parseInt(msg.payload);\n\n// Use camera name as unique key for state tracking\nvar stateKey = 'previousState_' + msg.camera_name.replace(/\\s+/g, '_');\n\n// Get previous state from context (default to 0 if not set)\nvar previousValue = context.get(stateKey) || 0;\n\n// Store current value for next comparison\ncontext.set(stateKey, currentValue);\n\n// Only proceed if current value is 1 AND previous value was 0\nif (currentValue === 1 && previousValue === 0) {\n node.status({fill:\"blue\", shape:\"dot\", text:`${msg.camera_name}: Person detected!`});\n return msg;\n}\n\n// Don't send message for other cases\nreturn null;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 930, + "y": 1040, + "wires": [ + [ + "90f6a26b7b04cc23", + "ec7ded527bfe8ef2" + ] + ] + }, + { + "id": "90f6a26b7b04cc23", + "type": "switch", + "z": "b673e10beab091ff", + "name": "Route by Auth Method", + "property": "authMethod", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "url", + "vt": "str" + }, + { + "t": "eq", + "v": "reolink", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 2, + "x": 1180, + "y": 1040, + "wires": [ + [ + "d723df03bdda6ca5", + "43983177055e72b1" + ], + [ + "0339e277385e3213", + "6cafa4c162199702" + ] + ] + }, + { + "id": "d723df03bdda6ca5", + "type": "function", + "z": "b673e10beab091ff", + "name": "URL Auth Snapshot", + "func": "// Validate camera info\nif (!msg.camera_ip || !msg.camera_username || !msg.camera_password) {\n node.error(`Missing camera credentials for ${msg.camera_name || 'unknown'}`);\n node.status({fill:\"red\", shape:\"ring\", text:\"Missing credentials\"});\n return null;\n}\n\n// Generate random string for cache busting\nconst randomString = Math.random().toString(36).substring(7);\n\n// URL encode credentials to handle special characters safely\nconst encodedUser = encodeURIComponent(msg.camera_username);\nconst encodedPassword = encodeURIComponent(msg.camera_password);\n\n// Construct camera snapshot URL with credentials\nmsg.url = `http://${msg.camera_ip}/cgi-bin/api.cgi?cmd=Snap&channel=0&rs=${randomString}&user=${encodedUser}&password=${encodedPassword}`;\nmsg.method = 'GET';\nmsg.headers = {};\nmsg.payload = null;\n\nnode.status({fill:\"blue\", shape:\"dot\", text:`Fetching ${msg.camera_name}...`});\n\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1450, + "y": 960, + "wires": [ + [ + "267ea942b61eb093", + "7b685fbbf92cce4f" + ] + ] + }, + { + "id": "0339e277385e3213", + "type": "function", + "z": "b673e10beab091ff", + "name": "Reolink Login", + "func": "// Prepare login request for Reolink API\nconst ip = msg.camera_ip;\nconst username = msg.camera_username;\nconst password = msg.camera_password;\n\n// Build login URL\nmsg.url = `http://${ip}/cgi-bin/api.cgi?cmd=Login&token=null`;\nmsg.method = 'POST';\nmsg.headers = { 'Content-Type': 'application/json' };\nmsg.payload = JSON.stringify([\n { cmd: 'Login', param: { User: { userName: username, password: password } } }\n]);\n\n// Generate random string for cache busting (for snapshot request)\nmsg.nextRandom = Math.random().toString(36).substring(7);\n\n// Preserve camera info\n// msg.camera_ip, msg.camera_name, msg.camera_location already set\n\nnode.status({fill:\"yellow\", shape:\"dot\", text:`Logging in to ${msg.camera_name}...`});\n\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1290, + "y": 1160, + "wires": [ + [ + "36ef8809abd6c068", + "ad8ae18947a58945" + ] + ] + }, + { + "id": "36ef8809abd6c068", + "type": "http request", + "z": "b673e10beab091ff", + "name": "Send Login Request", + "method": "use", + "ret": "obj", + "url": "", + "tls": "", + "persist": false, + "proxy": "", + "senderr": true, + "x": 1450, + "y": 1040, + "wires": [ + [ + "e24c5bb10ec49f08" + ] + ] + }, + { + "id": "e24c5bb10ec49f08", + "type": "function", + "z": "b673e10beab091ff", + "name": "Fetch Snapshot", + "func": "// Check for HTTP errors first\nif (msg.statusCode && msg.statusCode !== 200) {\n node.error(`HTTP ${msg.statusCode} - Login failed for ${msg.camera_name}`);\n node.status({fill:\"red\", shape:\"ring\", text:\"HTTP Error\"});\n return null;\n}\n\n// Extract token from login response\nif(!msg.payload || !msg.payload[0] || !msg.payload[0].value){\n node.error('Login failed for ' + msg.camera_name + ' - Invalid response');\n node.status({fill:\"red\", shape:\"ring\", text:\"Login failed\"});\n return null;\n}\n\nconst token = msg.payload[0].value.Token.name;\nif(!token){ \n node.error('Token missing for ' + msg.camera_name); \n node.status({fill:\"red\", shape:\"ring\", text:\"Token missing\"});\n return null; \n}\n\n// Build snapshot URL\nmsg.url = `http://${msg.camera_ip}/cgi-bin/api.cgi?cmd=Snap&channel=0&rs=${msg.nextRandom}&token=${token}`;\nmsg.method = 'GET';\nmsg.headers = {};\nmsg.payload = null;\n\nnode.status({fill:\"blue\", shape:\"dot\", text:`Fetching snapshot from ${msg.camera_name}...`});\n\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1660, + "y": 1040, + "wires": [ + [ + "267ea942b61eb093" + ] + ] + }, + { + "id": "267ea942b61eb093", + "type": "http request", + "z": "b673e10beab091ff", + "name": "Get Camera Snapshot", + "method": "use", + "ret": "bin", + "url": "", + "tls": "", + "persist": false, + "proxy": "", + "senderr": true, + "x": 1910, + "y": 1040, + "wires": [ + [ + "c52d97f3e3de82d8" + ] + ] + }, + { + "id": "b9939417e38d7ffa", + "type": "function", + "z": "b673e10beab091ff", + "name": "Error Handler", + "func": "// Handle HTTP errors\nconst cameraName = msg.camera_name || 'Unknown Camera';\nconst errorMsg = msg.error || msg.statusCode || msg.message || 'Unknown error';\n\n// Check if it's a timeout\nif (msg.error && (msg.error.message && msg.error.message.includes('timeout') || msg.error.code === 'ETIMEDOUT')) {\n node.error(`⏱️ ${cameraName} request timed out`);\n node.status({fill:\"red\", shape:\"ring\", text:\"Timeout\"});\n} else {\n node.error(`❌ ${cameraName} error: ${errorMsg}`);\n node.status({fill:\"red\", shape:\"ring\", text:\"Error\"});\n}\n\n// Pass error info to debug\nmsg.error_details = {\n camera: cameraName,\n error: errorMsg,\n statusCode: msg.statusCode,\n url: msg.url,\n timestamp: new Date().toISOString()\n};\n\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1700, + "y": 1200, + "wires": [ + [ + "df63eb3ba1bbfb73" + ] + ] + }, + { + "id": "df63eb3ba1bbfb73", + "type": "debug", + "z": "b673e10beab091ff", + "name": "Error Debug", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "error_details", + "statusVal": "", + "statusType": "auto", + "x": 1910, + "y": 1200, + "wires": [] + }, + { + "id": "c52d97f3e3de82d8", + "type": "function", + "z": "b673e10beab091ff", + "name": "Send Alert to Telegram", + "func": "// Validate that we received a proper image buffer\nif (!Buffer.isBuffer(msg.payload)) {\n node.warn(`❌ ${msg.camera_name} snapshot fetch failed — not a binary image buffer`);\n node.status({fill:\"red\", shape:\"ring\", text:\"Snapshot failed\"});\n return null;\n}\n\n// Check if buffer has reasonable size (basic validation)\nif (msg.payload.length < 1000) {\n node.warn(`❌ ${msg.camera_name} snapshot too small — likely corrupted`);\n node.status({fill:\"red\", shape:\"ring\", text:\"Image too small\"});\n return null;\n}\n\n// Format timestamp for display\nconst timestamp = new Date(msg.timestamp).toLocaleString();\n\n// Prepare Telegram message payload\nmsg.payload = {\n chatId: -4747176681, // Telegram group chat ID\n type: 'photo',\n content: msg.payload, // Binary image buffer\n caption: `🔔 ${msg.camera_name.toUpperCase()} ALERT! 🔔\\n\\n📍 Someone detected at ${msg.camera_location}\\n⏰ Time: ${timestamp}\\n📷 Camera: ${msg.camera_name} (${msg.camera_ip})`\n};\n\nnode.status({fill:\"green\", shape:\"dot\", text:\"Alert sent!\"});\n\nreturn msg;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 2190, + "y": 1040, + "wires": [ + [ + "1ba4ee81e40e5c51", + "8df5c69617ed9e92" + ] + ] + }, + { + "id": "1ba4ee81e40e5c51", + "type": "link out", + "z": "b673e10beab091ff", + "name": "link out 22", + "mode": "link", + "links": [ + "41d29e978759e26e" + ], + "x": 2325, + "y": 860, + "wires": [] + }, + { + "id": "8df5c69617ed9e92", + "type": "debug", + "z": "b673e10beab091ff", + "name": "debug 12", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 2330, + "y": 1120, + "wires": [] + }, + { + "id": "50b554a0642ab4e4", + "type": "debug", + "z": "b673e10beab091ff", + "name": "debug 13", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 830, + "y": 1160, + "wires": [] + }, + { + "id": "ad8ae18947a58945", + "type": "debug", + "z": "b673e10beab091ff", + "name": "debug 17", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 1430, + "y": 1260, + "wires": [] + }, + { + "id": "9618c8df3ea2d338", + "type": "inject", + "z": "b673e10beab091ff", + "name": "Test Veranda Door", + "props": [ + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "frigate/veranda_door/person", + "x": 210, + "y": 1180, + "wires": [ + [ + "498fac7ac1d2e87b" + ] + ] + }, + { + "id": "43983177055e72b1", + "type": "debug", + "z": "b673e10beab091ff", + "name": "debug 18", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 1300, + "y": 880, + "wires": [] + }, + { + "id": "7b685fbbf92cce4f", + "type": "debug", + "z": "b673e10beab091ff", + "name": "debug 19", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 1700, + "y": 880, + "wires": [] + }, + { + "id": "6cafa4c162199702", + "type": "debug", + "z": "b673e10beab091ff", + "name": "debug 20", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 1100, + "y": 1240, + "wires": [] + }, + { + "id": "ec7ded527bfe8ef2", + "type": "debug", + "z": "b673e10beab091ff", + "name": "debug 30", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 1080, + "y": 960, + "wires": [] + }, + { + "id": "498fac7ac1d2e87b", + "type": "function", + "z": "b673e10beab091ff", + "name": "function 9", + "func": "// Send payload 0 first, then payload 1 after a short delay\n// This tests the state change detection (0 -> 1)\n\n// Get topic from incoming message (set in inject node)\nconst topic = msg.topic;\n\n// Send payload 0 immediately\nconst msg0 = {\n topic: topic,\n payload: 0,\n timestamp: Date.now()\n};\n\n// Send payload 1 after 500ms delay\nsetTimeout(() => {\n const msg1 = {\n topic: topic,\n payload: 1,\n timestamp: Date.now()\n };\n node.send(msg1);\n}, 500);\n\n// Return the first message (payload 0)\nreturn msg0;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 420, + "y": 1180, + "wires": [ + [ + "b10d3c44bca37652" + ] + ] + }, + { + "id": "85ca22727431531a", + "type": "inject", + "z": "b673e10beab091ff", + "name": "Test Veranda Door", + "props": [ + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "frigate/basement_driveway/person", + "x": 210, + "y": 1220, + "wires": [ + [ + "498fac7ac1d2e87b" + ] + ] + }, + { + "id": "lr-ha-to-nvl", + "type": "function", + "z": "24e9a56732cfa6f1", + "name": "HA to NVL", + "func": "const ROOM_NAME = 'cmd_livingroom';\nconst entityId = (msg.topic || '').toString();\nconst parts = entityId.split('_');\nconst last = parts[parts.length - 1] || '';\nconst num = parseInt(last, 10);\nconst lightNum = (!isNaN(num) && num >= 1) ? Math.min(6, num) : 1;\nconst isOn = (msg.payload === 'on' || msg.payload === true);\nnode.warn('[HA to NVL] topic=' + entityId + ' payload=' + JSON.stringify(msg.payload) + ' (type ' + typeof msg.payload + ') → lightNum=' + lightNum + ' isOn=' + isOn);\nif (!flow.get('nvlInState')) flow.set('nvlInState', { rooms: {}, boiler: {} });\nconst state = flow.get('nvlInState');\nif (!state.rooms[ROOM_NAME]) state.rooms[ROOM_NAME] = {};\nconst r = state.rooms[ROOM_NAME];\nr['ha_l' + lightNum + '_on'] = false;\nr['ha_l' + lightNum + '_off'] = false;\nif (isOn) r['ha_l' + lightNum + '_on'] = true;\nelse r['ha_l' + lightNum + '_off'] = true;\nflow.set('nvlInState', state);\nnode.warn('[HA to NVL] set cmd_livingroom ha_l' + lightNum + '_' + (isOn ? 'on' : 'off') + '=true, buildAndSend');\nmsg.payload = { buildAndSend: true };\nreturn msg;", + "outputs": 1, + "x": 540, + "y": 220, + "wires": [ + [ + "lr-build-nvl", + "59ce11298eda23b6" + ] + ] + }, + { + "id": "lr-zigbee-to-nvl", + "type": "function", + "z": "24e9a56732cfa6f1", + "name": "Zigbee to NVL", + "func": "const ROOM_NAME = 'livingRoom';\nconst actionToSwitch = { single: 1, double: 2, hold: 3, release: 4, triple: 5, quad: 6 };\nconst payload = msg.payload || {};\nconst action = (payload.action || payload.click || '').toLowerCase();\nconst swIndex = actionToSwitch[action];\nif (swIndex == null) return null;\nif (!flow.get('nvlInState')) flow.set('nvlInState', { rooms: {}, boiler: {} });\nconst state = flow.get('nvlInState');\nif (!state.rooms[ROOM_NAME]) state.rooms[ROOM_NAME] = {};\nstate.rooms[ROOM_NAME]['zigbee_sw' + swIndex] = true;\nflow.set('nvlInState', state);\nmsg.payload = { buildAndSend: true };\nmsg.zigbeeClear = { room: ROOM_NAME, key: 'zigbee_sw' + swIndex };\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 540, + "y": 320, + "wires": [ + [ + "lr-build-nvl", + "lr-delay" + ] + ] + }, + { + "id": "lr-delay", + "type": "delay", + "z": "24e9a56732cfa6f1", + "name": "80ms", + "pauseType": "delay", + "timeout": "80", + "timeoutUnits": "milliseconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "seconds", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": true, + "outputs": 1, + "x": 760, + "y": 320, + "wires": [ + [ + "lr-clear-zigbee" + ] + ] + }, + { + "id": "lr-clear-zigbee", + "type": "function", + "z": "24e9a56732cfa6f1", + "name": "Clear zigbee edge", + "func": "if (msg.zigbeeClear) {\n const state = flow.get('nvlInState') || { rooms: {}, boiler: {} };\n const r = state.rooms[msg.zigbeeClear.room];\n if (r) r[msg.zigbeeClear.key] = false;\n flow.set('nvlInState', state);\n}\nmsg.payload = { buildAndSend: true };\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 970, + "y": 320, + "wires": [ + [ + "lr-build-nvl" + ] + ] + }, + { + "id": "lr-build-nvl", + "type": "function", + "z": "24e9a56732cfa6f1", + "name": "Build NVL_In", + "func": "const roomSize = 20;\nconst roomNames = ['masterBedroom','masterBathroom','bedroom_1','bedroom_2','bathroom','guestWc','kitchen','pantry','cmd_livingroom','dinningRoom','entrance','hallway','outVeranda','outFront','outBack','outSide'];\nconst state = flow.get('nvlInState') || { rooms: {}, boiler: {} };\nconst rooms = state.rooms || {};\nconst boiler = state.boiler || {};\nconst buf = Buffer.alloc(328);\nfor (let r = 0; r < 16; r++) {\n const c = rooms[roomNames[r]] || {};\n const base = r * roomSize;\n buf[base+0]=c.ha_l1_on?1:0;buf[base+1]=c.ha_l1_off?1:0;buf[base+2]=c.ha_l2_on?1:0;buf[base+3]=c.ha_l2_off?1:0;buf[base+4]=c.ha_l3_on?1:0;buf[base+5]=c.ha_l3_off?1:0;buf[base+6]=c.ha_l4_on?1:0;buf[base+7]=c.ha_l4_off?1:0;buf[base+8]=c.ha_l5_on?1:0;buf[base+9]=c.ha_l5_off?1:0;buf[base+10]=c.ha_l6_on?1:0;buf[base+11]=c.ha_l6_off?1:0;\n for (let i = 0; i < 6; i++) buf[base+12+i] = c['zigbee_sw'+(i+1)] ? 1 : 0;\n buf[base+18]=c.ha_all_on?1:0;buf[base+19]=c.ha_all_off?1:0;\n}\nconst bo = 320;\nbuf[bo+0]=boiler.ha_on?1:0;buf[bo+1]=boiler.ha_off?1:0;buf[bo+2]=boiler.schedule_on?1:0;buf[bo+3]=boiler.schedule_off?1:0;buf[bo+4]=boiler.emergency_stop?1:0;buf.writeInt16LE(boiler.max_on_time_minutes??480,bo+6);\nmsg.payload = buf;\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 790, + "y": 260, + "wires": [ + [ + "9d20e54acf4079aa" + ] + ] + }, + { + "id": "59ce11298eda23b6", + "type": "debug", + "z": "24e9a56732cfa6f1", + "name": "debug 33", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 730, + "y": 200, + "wires": [] + }, + { + "id": "9d20e54acf4079aa", + "type": "debug", + "z": "24e9a56732cfa6f1", + "name": "debug 35", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 950, + "y": 200, + "wires": [] + }, + { + "id": "5925b9fedc7dc1e1", + "type": "debug", + "z": "24e9a56732cfa6f1", + "name": "debug 38", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 750, + "y": 560, + "wires": [] + }, + { + "id": "ed2071aadf923b50", + "type": "inject", + "z": "24e9a56732cfa6f1", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 140, + "y": 520, + "wires": [ + [] + ] + }, + { + "id": "4045239f675b77d2", + "type": "function", + "z": "7de41d810b04d992", + "name": "HA to NVL", + "func": "const ROOM_NAME = 'cmd_livingroom';\nconst entityId = (msg.topic || '').toString();\nconst parts = entityId.split('_');\nconst last = parts[parts.length - 1] || '';\nconst num = parseInt(last, 10);\nconst lightNum = (!isNaN(num) && num >= 1) ? Math.min(6, num) : 1;\nconst isOn = (msg.payload === 'on' || msg.payload === true);\nnode.warn('[HA to NVL] topic=' + entityId + ' payload=' + JSON.stringify(msg.payload) + ' (type ' + typeof msg.payload + ') → lightNum=' + lightNum + ' isOn=' + isOn);\nif (!flow.get('nvlInState')) flow.set('nvlInState', { rooms: {}, boiler: {} });\nconst state = flow.get('nvlInState');\nif (!state.rooms[ROOM_NAME]) state.rooms[ROOM_NAME] = {};\nconst r = state.rooms[ROOM_NAME];\nr['ha_l' + lightNum + '_on'] = false;\nr['ha_l' + lightNum + '_off'] = false;\nif (isOn) r['ha_l' + lightNum + '_on'] = true;\nelse r['ha_l' + lightNum + '_off'] = true;\nflow.set('nvlInState', state);\nnode.warn('[HA to NVL] set cmd_livingroom ha_l' + lightNum + '_' + (isOn ? 'on' : 'off') + '=true, buildAndSend');\nmsg.payload = { buildAndSend: true };\nreturn msg;", + "outputs": 1, + "x": 1170, + "y": 320, + "wires": [ + [ + "75212a4bc3d085db", + "f891d88454556409" + ] + ] + }, + { + "id": "718ba68ae647874f", + "type": "function", + "z": "7de41d810b04d992", + "name": "Zigbee to NVL", + "func": "const ROOM_NAME = 'livingRoom';\nconst actionToSwitch = { single: 1, double: 2, hold: 3, release: 4, triple: 5, quad: 6 };\nconst payload = msg.payload || {};\nconst action = (payload.action || payload.click || '').toLowerCase();\nconst swIndex = actionToSwitch[action];\nif (swIndex == null) return null;\nif (!flow.get('nvlInState')) flow.set('nvlInState', { rooms: {}, boiler: {} });\nconst state = flow.get('nvlInState');\nif (!state.rooms[ROOM_NAME]) state.rooms[ROOM_NAME] = {};\nstate.rooms[ROOM_NAME]['zigbee_sw' + swIndex] = true;\nflow.set('nvlInState', state);\nmsg.payload = { buildAndSend: true };\nmsg.zigbeeClear = { room: ROOM_NAME, key: 'zigbee_sw' + swIndex };\nreturn msg;", + "outputs": 1, + "x": 1120, + "y": 420, + "wires": [ + [ + "75212a4bc3d085db", + "886c1334e909e2cd", + "c0ba289781c06018" + ] + ] + }, + { + "id": "886c1334e909e2cd", + "type": "delay", + "z": "7de41d810b04d992", + "name": "80ms", + "pauseType": "delay", + "timeout": "80", + "timeoutUnits": "milliseconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "seconds", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": true, + "outputs": 1, + "x": 1340, + "y": 420, + "wires": [ + [ + "44822e048dbaee31" + ] + ] + }, + { + "id": "44822e048dbaee31", + "type": "function", + "z": "7de41d810b04d992", + "name": "Clear zigbee edge", + "func": "if (msg.zigbeeClear) {\n const state = flow.get('nvlInState') || { rooms: {}, boiler: {} };\n const r = state.rooms[msg.zigbeeClear.room];\n if (r) r[msg.zigbeeClear.key] = false;\n flow.set('nvlInState', state);\n}\nmsg.payload = { buildAndSend: true };\nreturn msg;", + "outputs": 1, + "x": 1550, + "y": 420, + "wires": [ + [ + "75212a4bc3d085db", + "b9b1a23de2bb693b" + ] + ] + }, + { + "id": "75212a4bc3d085db", + "type": "function", + "z": "7de41d810b04d992", + "name": "Build NVL_In", + "func": "const roomSize = 20;\nconst roomNames = ['masterBedroom','masterBathroom','bedroom_1','bedroom_2','bathroom','guestWc','kitchen','pantry','cmd_livingroom','dinningRoom','entrance','hallway','outVeranda','outFront','outBack','outSide'];\nconst state = flow.get('nvlInState') || { rooms: {}, boiler: {} };\nconst rooms = state.rooms || {};\nconst boiler = state.boiler || {};\nconst buf = Buffer.alloc(328);\nfor (let r = 0; r < 16; r++) {\n const c = rooms[roomNames[r]] || {};\n const base = r * roomSize;\n buf[base+0]=c.ha_l1_on?1:0;buf[base+1]=c.ha_l1_off?1:0;buf[base+2]=c.ha_l2_on?1:0;buf[base+3]=c.ha_l2_off?1:0;buf[base+4]=c.ha_l3_on?1:0;buf[base+5]=c.ha_l3_off?1:0;buf[base+6]=c.ha_l4_on?1:0;buf[base+7]=c.ha_l4_off?1:0;buf[base+8]=c.ha_l5_on?1:0;buf[base+9]=c.ha_l5_off?1:0;buf[base+10]=c.ha_l6_on?1:0;buf[base+11]=c.ha_l6_off?1:0;\n for (let i = 0; i < 6; i++) buf[base+12+i] = c['zigbee_sw'+(i+1)] ? 1 : 0;\n buf[base+18]=c.ha_all_on?1:0;buf[base+19]=c.ha_all_off?1:0;\n}\nconst bo = 320;\nbuf[bo+0]=boiler.ha_on?1:0;buf[bo+1]=boiler.ha_off?1:0;buf[bo+2]=boiler.schedule_on?1:0;buf[bo+3]=boiler.schedule_off?1:0;buf[bo+4]=boiler.emergency_stop?1:0;buf.writeInt16LE(boiler.max_on_time_minutes??480,bo+6);\nmsg.payload = buf;\nreturn msg;", + "outputs": 1, + "x": 1360, + "y": 360, + "wires": [ + [ + "74c2640db1c8bd3e", + "9e9d60859e34c65e" + ] + ] + }, + { + "id": "17814d6d1beda546", + "type": "zigbee2mqtt-in", + "z": "7de41d810b04d992", + "name": "", + "server": "4e20fc347c658518", + "friendly_name": "Living Room Door Switch (TS0044)", + "device_id": "0xa4c1383d7921827a", + "state": "0", + "outputAtStartup": true, + "filterChanges": false, + "enableMultiple": false, + "x": 820, + "y": 420, + "wires": [ + [ + "718ba68ae647874f" + ] + ] + }, + { + "id": "74c2640db1c8bd3e", + "type": "link out", + "z": "7de41d810b04d992", + "name": "link out 26", + "mode": "link", + "links": [], + "x": 1620, + "y": 360, + "wires": [], + "l": true + }, + { + "id": "95b626ea6980446e", + "type": "trigger-state", + "z": "7de41d810b04d992", + "name": "", + "server": "29eb84a2708d96e0", + "version": 5, + "inputs": 1, + "outputs": 2, + "exposeAsEntityConfig": "", + "entities": { + "entity": [ + "input_boolean.living_room_new" + ], + "substring": [], + "regex": [] + }, + "debugEnabled": true, + "constraints": [], + "customOutputs": [], + "outputInitially": false, + "stateType": "str", + "enableInput": true, + "x": 890, + "y": 320, + "wires": [ + [ + "4045239f675b77d2", + "b72750450ceb1acc" + ], + [] + ] + }, + { + "id": "745066d593433c28", + "type": "inject", + "z": "7de41d810b04d992", + "name": "", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "payload": "", + "payloadType": "date", + "x": 620, + "y": 320, + "wires": [ + [ + "95b626ea6980446e" + ] + ] + }, + { + "id": "b72750450ceb1acc", + "type": "debug", + "z": "7de41d810b04d992", + "name": "debug 36", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 1130, + "y": 260, + "wires": [] + }, + { + "id": "f891d88454556409", + "type": "debug", + "z": "7de41d810b04d992", + "name": "debug 37", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 1380, + "y": 260, + "wires": [] + }, + { + "id": "9e9d60859e34c65e", + "type": "debug", + "z": "7de41d810b04d992", + "name": "debug 39", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 1560, + "y": 300, + "wires": [] + }, + { + "id": "c0ba289781c06018", + "type": "debug", + "z": "7de41d810b04d992", + "name": "debug 40", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 1230, + "y": 500, + "wires": [] + }, + { + "id": "b9b1a23de2bb693b", + "type": "debug", + "z": "7de41d810b04d992", + "name": "debug 41", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 1690, + "y": 460, + "wires": [] + } +] \ No newline at end of file diff --git a/docs/integration/nodered-livingroom-flow.md b/docs/integration/nodered-livingroom-flow.md new file mode 100644 index 0000000..1d831aa --- /dev/null +++ b/docs/integration/nodered-livingroom-flow.md @@ -0,0 +1,167 @@ +# Node-RED: Living Room flow + +This document describes the **Living Room** tab in Node-RED: how HA and Zigbee drive NVL commands and how the 328-byte buffer is built and sent to the PLC. + +--- + +## Flow diagram + +``` +[Inject] ──► [trigger-state] ◄── HA entity: input_boolean.living_room_new + │ + ├──► [HA to NVL] ──┬──► [Build NVL_In] ──┬──► [link out 26] ──► (Flow 1: to UDP → PLC) + │ │ └──► debug 39 + └──► debug 36 │ + │ +[Zigbee2MQTT] ──► [Zigbee to NVL] ──┼──► [Build NVL_In] + Living Room Door Switch │ ▲ + (TS0044) ├──► [80ms delay] ──► [Clear zigbee edge] ──► [Build NVL_In] + │ │ + └──► debug 40 └──► debug 41 +``` + +- **HA path:** `trigger-state` watches `input_boolean.living_room_new` → **HA to NVL** updates `flow.nvlInState.rooms.cmd_livingroom` (ha_lN_on / ha_lN_off) → **Build NVL_In** → link out → Flow 1 → UDP to PLC. +- **Zigbee path:** **zigbee2mqtt-in** (Living Room Door Switch TS0044) → **Zigbee to NVL** sets zigbee_swN in state → **Build NVL_In** (send) → then **80ms delay** → **Clear zigbee edge** (clear zigbee_swN) → **Build NVL_In** again (second packet so PLC sees one pulse). + +--- + +## Nodes (Living Room tab) + +| Node | Type | Role | +|------|------|------| +| **trigger-state** | node-red-contrib-home-assistant-websocket | Listens to `input_boolean.living_room_new`; output 1 → HA to NVL, output 2 → blocked. | +| **Inject** | inject | Manual/startup trigger into trigger-state (optional). | +| **HA to NVL** | function | Maps HA entity state to `cmd_livingroom` in `flow.nvlInState`; sets ha_l1_on/ha_l1_off … ha_l6_on/ha_l6_off from entity ID and payload; then `buildAndSend: true`. | +| **Zigbee to NVL** | function | Maps Zigbee action (single/double/hold/release/triple/quad) to zigbee_sw1…6; updates `flow.nvlInState.rooms.livingRoom`; sets `msg.zigbeeClear` for later clear; then `buildAndSend: true`. | +| **80ms** | delay | 80 ms then passes message to Clear zigbee edge. | +| **Clear zigbee edge** | function | Clears the zigbee_swN flag in `nvlInState` and sets `buildAndSend: true` again. | +| **Build NVL_In** | function | Reads `flow.nvlInState`, builds 328-byte buffer (16 rooms × 20 bytes + boiler 8 bytes), sets `msg.payload = buf`; outputs to link out 26 and debug. | +| **link out 26** | link out | Sends built buffer to Flow 1’s **to UDP** link in → **udp out** (10.20.30.5:1202). | +| **debug 36–41** | debug | Optional debug (37 active). | + +--- + +## Shared state: `flow.nvlInState` + +All rooms and boiler share one object: + +- **Shape:** `{ rooms: { cmd_livingroom: { ha_l1_on, ha_l1_off, … }, … }, boiler: { … } }` +- **Living Room (HA test)** writes to `state.rooms.cmd_livingroom`. **Build NVL_In** reads the full object and serializes all 16 slots + boiler into the 328-byte NVL_In layout. + +Room names order in Build NVL_In (must match PLC NVL): + +`masterBedroom`, `masterBathroom`, `bedroom_1`, `bedroom_2`, `bathroom`, `guestWc`, `kitchen`, `pantry`, **`cmd_livingroom`**, `dinningRoom`, `entrance`, `hallway`, `outVeranda`, `outFront`, `outBack`, `outSide`. + +--- + +## Test variable: `cmd_livingroom` + +The flow uses **`cmd_livingroom`** (the NVL variable you added for testing), not `livingRoom`. HA to NVL writes to `flow.nvlInState.rooms.cmd_livingroom`, and Build NVL_In reads that key for the living-room slot in the 328-byte buffer (same 20-byte layout as struct_room_cmds). + +--- + +## What the trigger-state must output to turn the light ON (PLC) + +The **HA to NVL** node decides ON vs OFF from the message it receives. For the PLC to turn the light **on**: + +| Field | Value | +|----------|--------| +| **payload** | `'on'` (string) **or** `true` (boolean) | +| **topic** | Entity ID (e.g. `input_boolean.living_room_new`). Optional for which light 1–6: if the last part after `_` is a number (e.g. `living_room_2` → 2), that light is used; otherwise light **1** is used. | + +So the **trigger-state** should output to the first (allowed) output: + +- **Turn ON:** `msg.payload === 'on'` or `msg.payload === true` → PLC gets `ha_lN_on = true` in **cmd_livingroom**. +- **Turn OFF:** any other payload (e.g. `'off'`, `false`) → PLC gets `ha_lN_off = true` in **cmd_livingroom**. + +With the current entity `input_boolean.living_room_new` there is no `_1`…`_6`, so it always controls **light 1** in the living room. + +--- + +## HA to NVL (function code) + +- **Room (state key):** `cmd_livingroom` (NVL variable for testing). +- **Entity ID:** from `msg.topic` (e.g. `input_boolean.living_room_new`). Light index 1–6 is parsed from the last `_N` in the entity ID; default 1. +- **ON/OFF:** `msg.payload === 'on'` or `true` → set `ha_lN_on = true`, else `ha_lN_off = true` (the other cleared). +- Then sets `msg.payload = { buildAndSend: true }` and returns. + +### Debug logs (in Node-RED Debug sidebar) + +The function uses `node.warn()` so messages appear in the **Debug** tab (with the warning icon) without needing a separate debug node: + +1. **On input:** `[HA to NVL] topic=... payload=... (type ...) → lightNum=... isOn=...` + Use this to confirm what the trigger-state sent: `topic`, `payload`, its type, and the parsed `lightNum` (1–6) and `isOn` (true/false). + +2. **After state update:** `[HA to NVL] set cmd_livingroom ha_lN_on=true, buildAndSend` (or `ha_lN_off`). + Confirms which command was written and that `buildAndSend` is being passed on. + +If `isOn` is false when you expect ON, check that the trigger-state output is exactly `'on'` (string) or `true` (boolean). If `topic` is wrong or missing, the light number may default to 1. + +**HA to NVL function with debug:** full code in [ha-to-nvl-function.js](ha-to-nvl-function.js) (uses **cmd_livingroom**). Copy that file’s contents into the Node-RED function node. + +--- + +## Zigbee to NVL (function code) + +- **Room:** `livingRoom`. +- **Action map:** `single→1, double→2, hold→3, release→4, triple→5, quad→6` → sets `zigbee_swN`. +- **Edge behaviour:** Passes `msg.zigbeeClear = { room: 'livingRoom', key: 'zigbee_swN' }` so **Clear zigbee edge** (after 80 ms) clears that flag and calls **Build NVL_In** again; PLC sees one pulse. + +--- + +## Build NVL_In (function code) + +- **Buffer:** 328 bytes (Buffer.alloc(328)). +- **Rooms:** 16 rooms × 20 bytes each: bytes 0–11 = ha_l1_on, ha_l1_off, … ha_l6_off; bytes 12–17 = zigbee_sw1…6; bytes 18–19 = ha_all_on, ha_all_off. +- **Boiler:** offset 320: ha_on, ha_off, schedule_on, schedule_off, emergency_stop, (reserved), max_on_time_minutes (INT16 LE). +- **Output:** `msg.payload = buf`; wired to **link out 26** (and debug). + +--- + +## Zigbee device + +- **Friendly name:** Living Room Door Switch (TS0044) +- **Device ID:** 0xa4c1383d7921827a +- **Server:** zigbee2mqtt-server config (id 4e20fc347c658518) + +--- + +## Do you need Build NVL_In? + +**No, if you use the nvl-send node.** The nvl-send node (node-red-contrib-nvl) does the binary packing from a payload object. So you can: + +1. **Remove** the **Build NVL_In** node and the **link out 26** (and the link in “to UDP” path that receives it). +2. **Add** a single function that builds the payload object from `flow.nvlInState` and feed that into **nvl-send** → **udp out**. + +Code for that function (copy into a function node): **[state-to-nvl-send-payload.js](state-to-nvl-send-payload.js)**. It builds the object with the 15 `struct_switches` rooms (default false) and **cmd_livingroom** from `state.rooms.cmd_livingroom`. Wire: **HA to NVL / Zigbee to NVL** → **state-to-nvl-send-payload** → **nvl-send** → **udp out**. + +--- + +## Syncing PLC state back to Home Assistant (Zigbee / any source → HA UI) + +When you change a light with the **Zigbee switch** (or any source that doesn’t go through HA), the PLC updates but HA doesn’t, so entities can stay out of sync. To keep HA in sync for **all** mapped lights: + +1. **After the nvl-receive node** (Flow 1), add a **function** node and paste the code from **[nvl-to-ha-sync-livingroom.js](nvl-to-ha-sync-livingroom.js)**. +2. In that function, **`LIGHT_ENTITY_MAP`** defines which PLC room/light → HA entity to sync. Each entry is `{ room: 'payloadKey', light: 1..6, entityId: 'domain.entity_id' }`. Add one row per light (any room/zone). Example: + - `{ room: 'light_livingRoom', light: 1, entityId: 'input_boolean.living_room_new' }` + - `{ room: 'l_kitchen', light: 1, entityId: 'light.kitchen_ceiling' }` +3. Give the function **2 outputs**. Then either: + **Option A – Action node:** In the palette look under **Home Assistant** for **Action** ([docs](https://zachowj.github.io/node-red-contrib-home-assistant-websocket/node/action.html)). The sync already sends `payload.action` (e.g. `input_boolean.turn_on`) and `payload.target.entity_id` (array). Output 1 → **Action** (turn_on), Output 2 → **Action** (turn_off). You can leave Action config empty and use “Block Input Overrides” off so `msg.payload` sets the action and target. + **Option B – HTTP:** Use **[nvl-to-ha-http-call.js](nvl-to-ha-http-call.js)** + **http request** node; set `flow.set('ha_base_url', 'http://HA_IP:8123')` and `flow.set('ha_token', 'YOUR_TOKEN')`. + +The function only sends a message when a mapped light’s state **changed**, so multiple entities can be updated in one NVL cycle. When you use the Zigbee switch (or the PLC changes state any other way), the matching HA entities are updated. + +--- + +## Connection to Flow 1 (CODESYS) + +- **Option A (with Build NVL_In):** Living Room builds the buffer in a function and sends it via **link out 26** → Flow 1 **link in "to UDP"** → **udp out**. +- **Option B (with nvl-send only):** HA to NVL / Zigbee to NVL → **state-to-nvl-send-payload** → **nvl-send** → **udp out**. No Build NVL_In, no link nodes needed for this path. +- The PLC (CODESYS) receives NVL_In on port 1202 and drives lights/boiler from the packed struct. + +--- + +## File reference + +- **Flows export:** [nodered-flows.json](nodered-flows.json) — Living Room tab id: `7de41d810b04d992` +- **Payload layout (PLC/Node-RED):** [codesys/src/NVL/nodered-payload.md](../../codesys/src/NVL/nodered-payload.md) diff --git a/docs/integration/nvl-to-ha-http-call.js b/docs/integration/nvl-to-ha-http-call.js new file mode 100644 index 0000000..6126654 --- /dev/null +++ b/docs/integration/nvl-to-ha-http-call.js @@ -0,0 +1,30 @@ +// Use when you don't use the Action node: prepares one msg for the "http request" node to call HA. +// Accepts either: (1) Action format: payload.action + payload.target.entity_id, or (2) payload.entity_id + msg.service. +// Set ha_base_url and ha_token in flow or env. + +const HA_BASE_URL = flow.get('ha_base_url') || 'http://homeassistant.local:8123'; +const HA_TOKEN = flow.get('ha_token') || env.get('HA_TOKEN') || ''; + +const p = msg.payload || {}; +let entityId = p.target && p.target.entity_id && p.target.entity_id[0]; +let domain = 'input_boolean'; +let service = 'turn_off'; +if (p.action) { + const parts = p.action.split('.'); + domain = parts[0]; + service = parts[1] || 'turn_off'; +} +if (!entityId) entityId = p.entity_id; +if (!entityId) return null; +if (!p.action) domain = (entityId + '').split('.')[0]; + +const url = HA_BASE_URL.replace(/\/$/, '') + '/api/services/' + domain + '/' + service; + +msg.url = url; +msg.method = 'POST'; +msg.headers = { + 'Authorization': 'Bearer ' + HA_TOKEN, + 'Content-Type': 'application/json' +}; +msg.payload = { entity_id: entityId }; +return msg; diff --git a/docs/integration/nvl-to-ha-sync-livingroom.js b/docs/integration/nvl-to-ha-sync-livingroom.js new file mode 100644 index 0000000..406b1dd --- /dev/null +++ b/docs/integration/nvl-to-ha-sync-livingroom.js @@ -0,0 +1,33 @@ +// Run this AFTER nvl-receive. Syncs all mapped lights from PLC state to HA entities. +// Connect output 1 → Action node (turn_on), output 2 → Action node (turn_off). +// Format matches node-red-contrib-home-assistant-websocket "Action" node: payload.action + payload.target.entity_id (array). +// Only outputs when state changed (per entity). Add more entries to LIGHT_ENTITY_MAP as you add rooms/lights. + +// Map: NVL room key (in payload) + light index 1..6 → HA entity_id +const LIGHT_ENTITY_MAP = [ + { room: 'light_livingRoom', light: 1, entityId: 'input_boolean.living_room_new' }, + // { room: 'light_livingRoom', light: 2, entityId: 'input_boolean.living_room_2' }, + // { room: 'l_kitchen', light: 1, entityId: 'light.kitchen_ceiling' }, +]; + +const payload = msg.payload || {}; +const onMsgs = []; +const offMsgs = []; + +for (const entry of LIGHT_ENTITY_MAP) { + const room = payload[entry.room] || {}; + const isOn = !!(room['l_' + entry.light] || room['l' + entry.light]); + + const flowKey = 'nvlToHa_' + entry.entityId.replace(/\./g, '_'); + const last = flow.get(flowKey); + if (last === isOn) continue; + flow.set(flowKey, isOn); + + const domain = entry.entityId.split('.')[0] || 'input_boolean'; + const action = domain + '.' + (isOn ? 'turn_on' : 'turn_off'); + const out = { payload: { action: action, target: { entity_id: [entry.entityId] } } }; + if (isOn) onMsgs.push(out); else offMsgs.push(out); +} + +if (onMsgs.length === 0 && offMsgs.length === 0) return null; +return [onMsgs, offMsgs]; diff --git a/docs/integration/state-to-nvl-send-payload.js b/docs/integration/state-to-nvl-send-payload.js new file mode 100644 index 0000000..a9c54a3 --- /dev/null +++ b/docs/integration/state-to-nvl-send-payload.js @@ -0,0 +1,30 @@ +// Build the payload for nvl-send from flow.nvlInState. +// Redesign: all rooms use struct_room_cmds. Add more names to roomNames when you add rooms. +// Wire: HA to NVL / Zigbee to NVL → this function → nvl-send → udp out + +const struct_room_cmds_default = { + ha_l1_on: false, ha_l1_off: false, ha_l2_on: false, ha_l2_off: false, + ha_l3_on: false, ha_l3_off: false, ha_l4_on: false, ha_l4_off: false, + ha_l5_on: false, ha_l5_off: false, ha_l6_on: false, ha_l6_off: false, + zigbee_sw1: false, zigbee_sw2: false, zigbee_sw3: false, + zigbee_sw4: false, zigbee_sw5: false, zigbee_sw6: false, + ha_all_on: false, ha_all_off: false +}; + +// NVL variable names per room (struct_room_cmds). Add more when you add rooms. +const roomNames = ['cmd_livingroom']; + +const state = flow.get('nvlInState') || { rooms: {}, boiler: {} }; +const rooms = state.rooms || {}; +const payload = {}; + +for (const name of roomNames) { + const cmd = rooms[name] || {}; + payload[name] = { ...struct_room_cmds_default }; + for (const k of Object.keys(struct_room_cmds_default)) { + if (cmd[k] !== undefined) payload[name][k] = !!cmd[k]; + } +} + +msg.payload = payload; +return msg; diff --git a/docs/integration/zigbee-to-nvl-function.js b/docs/integration/zigbee-to-nvl-function.js new file mode 100644 index 0000000..1818cfa --- /dev/null +++ b/docs/integration/zigbee-to-nvl-function.js @@ -0,0 +1,32 @@ +// Writes to state.rooms.cmd_livingroom (same as HA to NVL). Zigbee action → zigbee_sw1..6. +const ROOM_NAME = 'cmd_livingroom'; +const actionToSwitch = { single: 1, double: 2, hold: 3, release: 4, triple: 5, quad: 6 }; +const payload = msg.payload || {}; +const actionRaw = (payload.action || payload.click || '').toLowerCase(); +// Support "1_single", "2_double" etc. (button_number + action) or plain "single", "double" +const parts = actionRaw.split('_'); +let swIndex = null; +if (parts.length >= 2) { + const buttonNum = parseInt(parts[0], 10); + if (buttonNum >= 1 && buttonNum <= 6) swIndex = buttonNum; +} +if (swIndex == null) swIndex = actionToSwitch[actionRaw]; +if (swIndex == null && parts.length >= 2) swIndex = actionToSwitch[parts.slice(1).join('_')]; +if (swIndex == null) swIndex = actionToSwitch[parts[parts.length - 1]]; + +if (swIndex == null) { + node.warn('[Zigbee to NVL] unknown action: ' + JSON.stringify(actionRaw) + ' payload=' + JSON.stringify(payload)); + return null; +} + +if (!flow.get('nvlInState')) flow.set('nvlInState', { rooms: {}, boiler: {} }); +const state = flow.get('nvlInState'); +if (!state.rooms[ROOM_NAME]) state.rooms[ROOM_NAME] = {}; +state.rooms[ROOM_NAME]['zigbee_sw' + swIndex] = true; +flow.set('nvlInState', state); + +node.warn('[Zigbee to NVL] cmd_livingroom zigbee_sw' + swIndex + '=true (action=' + actionRaw + '), buildAndSend + zigbeeClear'); + +msg.payload = { buildAndSend: true }; +msg.zigbeeClear = { room: ROOM_NAME, key: 'zigbee_sw' + swIndex }; +return msg; diff --git a/docs/redesign/fb_switch-analysis-summary.md b/docs/redesign/fb_switch-analysis-summary.md deleted file mode 100644 index 84d9c14..0000000 --- a/docs/redesign/fb_switch-analysis-summary.md +++ /dev/null @@ -1,89 +0,0 @@ -# fb_switch Analysis Summary - -## Quick Assessment - -**Current Approach**: ⚠️ **Needs Improvement** - -**Main Issue**: Toggle-based control causes state desynchronization with Home Assistant. - -## Key Problems - -### 1. State Desynchronization -- **Problem**: HA sends ON/OFF commands, but CODESYS toggles -- **Result**: HA thinks light is ON, sends ON again → CODESYS toggles it OFF -- **Impact**: Lights don't match HA state - -### 2. No Status Feedback -- **Problem**: Status comes from toggle button, not actual relay -- **Result**: Can't detect relay hardware failures -- **Impact**: HA may show wrong status - -### 3. Mixed Control Sources -- **Problem**: HA and Zigbee both use same toggle mechanism -- **Result**: Conflicting behaviors -- **Impact**: Unpredictable control - -## What You Need - -✅ **Control from Home Assistant** (ON/OFF switches) -✅ **Control from Zigbee switches** (toggle behavior) -✅ **Status feedback** (actual relay state) - -## Recommended Solution - -### Option 1: Full Redesign (Recommended) -- Separate ON/OFF commands for HA -- Edge detection for Zigbee (toggle) -- Read actual relay status from EtherCAT -- Clear priority system - -**Pros**: Clean, maintainable, extensible -**Cons**: Requires more changes - -### Option 2: Simplified Improvement -- Add ON/OFF commands alongside toggle -- Keep most existing structure -- Add status feedback - -**Pros**: Less changes, faster to implement -**Cons**: Less clean, still some complexity - -## Quick Fix (Minimal Changes) - -If you need a quick fix while planning the redesign: - -1. **In Node-RED**: Convert HA ON/OFF to edge pulses - ```javascript - // Only send toggle on state change - if (currentState !== newState) { - sendToggle = true; - } - ``` - -2. **Add status readback**: Read actual EtherCAT output state - ```iec - lights.l_1_status := EtherCAT_Output_Channel_1; - ``` - -3. **Update HA**: Use state topic for status feedback - -## Decision Matrix - -| Approach | Complexity | Time | Maintainability | Recommended For | -|----------|-----------|------|-----------------|-----------------| -| Full Redesign | High | 2-3 days | Excellent | Long-term solution | -| Simplified | Medium | 1 day | Good | Quick improvement | -| Quick Fix | Low | 2-4 hours | Fair | Temporary solution | - -## My Recommendation - -**Start with Quick Fix** → **Then implement Simplified** → **Plan Full Redesign** - -This gives you: -1. Immediate working solution -2. Better solution in short term -3. Best solution for long term - ---- - -**See**: [Full Redesign Recommendation](fb_switch-redesign-recommendation.md) for detailed implementation. diff --git a/docs/redesign/fb_switch-documentation.md b/docs/redesign/fb_switch-documentation.md deleted file mode 100644 index 5f12a15..0000000 --- a/docs/redesign/fb_switch-documentation.md +++ /dev/null @@ -1,223 +0,0 @@ -# fb_switch Function Block Documentation - -## Overview - -The `fb_switch` function block is the core control logic for managing lighting in each room. It processes switch inputs from Node-RED and generates light control outputs. - -## Function Block Structure - -```iec -FUNCTION_BLOCK fb_switch -VAR_INPUT - switches: struct_switches; // Switch states received from Node-RED -END_VAR -VAR_OUTPUT - lights: struct_lights; // Light control outputs sent to Node-RED -END_VAR -VAR - all_off: fb_toogleButton; // Toggle button for "all off" command - all_on: fb_toogleButton; // Toggle button for "all on" command - sw_1: fb_toogleButton; // Toggle button for switch 1 - sw_2: fb_toogleButton; // Toggle button for switch 2 - sw_3: fb_toogleButton; // Toggle button for switch 3 - sw_4: fb_toogleButton; // Toggle button for switch 4 - sw_5: fb_toogleButton; // Toggle button for switch 5 - sw_6: fb_toogleButton; // Toggle button for switch 6 -END_VAR -``` - -## Data Structures - -### struct_switches (Input) - -```iec -TYPE struct_switches : -STRUCT - all_off: BOOL; // Command to turn all lights off - all_on: BOOL; // Command to turn all lights on - sw_1: BOOL; // Switch 1 state - sw_2: BOOL; // Switch 2 state - sw_3: BOOL; // Switch 3 state - sw_4: BOOL; // Switch 4 state - sw_5: BOOL; // Switch 5 state - sw_6: BOOL; // Switch 6 state -END_STRUCT -END_TYPE -``` - -### struct_lights (Output) - -```iec -TYPE struct_lights: -STRUCT - l_1: BOOL; // Light 1 control output - l_2: BOOL; // Light 2 control output - l_3: BOOL; // Light 3 control output - l_4: BOOL; // Light 4 control output - l_5: BOOL; // Light 5 control output - l_6: BOOL; // Light 6 control output -END_STRUCT -END_TYPE -``` - -## Functionality - -### Individual Switch Control - -Each switch (`sw_1` through `sw_6`) is processed by a `fb_toogleButton` instance: -- **Input**: Switch state from `switches.sw_X` (received from Node-RED) -- **Output**: Light control to `lights.l_X` (sent to Node-RED) -- **Behavior**: Toggle functionality - each switch press toggles the corresponding light state - -### Global Commands - -1. **All Off (`switches.all_off`)**: - - When activated, turns off all lights (l_1 through l_6) - - Uses `fb_toogleButton` instance `all_off` - -2. **All On (`switches.all_on`)**: - - When activated, turns on all lights (l_1 through l_6) - - Uses `fb_toogleButton` instance `all_on` - - Logic uses AND gates to combine with individual switch states - -### Logic Flow - -1. **Switch Input Processing**: - - Each `sw_X` input is fed to its corresponding `fb_toogleButton` instance - - The toggle button processes the input and generates an output - -2. **Light Output Generation**: - - Individual switch outputs directly control their corresponding lights - - `all_off` command can override individual controls - - `all_on` command combines with individual switch states using AND logic - -3. **Output Assignment**: - - `sw_1` → `lights.l_1` - - `sw_2` → `lights.l_2` - - `sw_3` → `lights.l_3` - - `sw_4` → `lights.l_4` - - `sw_5` → `lights.l_5` - - `sw_6` → `lights.l_6` - -## fb_toogleButton Function Block - -The `fb_toogleButton` is a toggle button implementation with debouncing and timing features. - -### Structure - -```iec -FUNCTION_BLOCK fb_toogleButton -VAR_INPUT - Input: BOOL; // Input signal (switch state) -END_VAR -VAR_OUTPUT - Output: BOOL; // Output signal (light control) -END_VAR -VAR - Switch: BOOL; // Internal switch state - Light: BOOL; // Internal light state - Flag1: BOOL; // Internal flag 1 - Flag2: BOOL; // Internal flag 2 - RT: R_TRIG; // Rising edge trigger - Pulse: TP; // Pulse timer - CTU_0: CTU; // Counter up 0 - CTD_0: CTD; // Counter down 0 - CTU_1: CTU; // Counter up 1 - CTU_2: CTU; // Counter up 2 - TOF_0: TOF; // Timer off delay 0 -END_VAR -``` - -### Features - -- **Rising Edge Detection**: Uses `R_TRIG` to detect switch press events -- **Pulse Generation**: Uses `TP` (pulse timer) for debouncing or pulse generation -- **Timing Control**: Uses `TOF` (timer off delay) for delayed turn-off -- **Counters**: Uses `CTU` and `CTD` counters for state management -- **Toggle Logic**: Implements toggle behavior - each press changes state - -### Behavior - -1. **Input Processing**: - - Detects rising edge of input signal - - Generates a pulse for processing - - Applies timing delays for debouncing - -2. **State Management**: - - Maintains internal switch and light states - - Toggles output on each valid input pulse - - Uses flags for state tracking - -3. **Output Generation**: - - Output reflects the toggled state - - May include timing delays (TOF) for smooth operation - -## Usage in System - -### Per-Room Instance - -Each room has an instance of `fb_switch`: -- `masterBedroom: fb_switch` -- `masterBathroom: fb_switch` -- `bedroom_1: fb_switch` -- `bedroom_2: fb_switch` -- `bathroom: fb_switch` -- `guest_wc: fb_switch` -- `kitchen: fb_switch` -- `pantry: fb_switch` -- `livingRoom: fb_switch` -- `diningRoom: fb_switch` -- `veranda: fb_switch` -- `entrance: fb_switch` -- `front: fb_switch` -- `back: fb_switch` -- `side: fb_switch` -- `hallway: fb_switch` - -### Network Variable Integration - -- **Input**: Receives `struct_switches` from Node-RED via network variable `NVL_Receiver` -- **Output**: Sends `struct_lights` to Node-RED via network variable `NVL_Sender` - -### Control Flow - -``` -Node-RED (Zigbee Switch) - ↓ -MQTT Message - ↓ -Node-RED Processing - ↓ -Network Variable (struct_switches) - ↓ -fb_switch (CODESYS) - ↓ -fb_toogleButton Processing - ↓ -Light Control Output (struct_lights) - ↓ -Network Variable (NVL_Sender) - ↓ -Node-RED - ↓ -EtherCAT Relay Output - ↓ -Physical Light -``` - -## Notes - -- Each room can control up to 6 individual lights -- Global commands (`all_on`, `all_off`) provide room-level control -- Toggle button implementation includes debouncing to prevent rapid switching -- Timing functions ensure smooth operation and prevent electrical issues -- The system supports both individual light control and group control per room - ---- - -**Documentation Date**: January 27, 2026 -**Sources**: -- `Home_Automation.export` (CODESYS XML) -- `Home_Automation.xml` (PLCopen XML) - -**Additional Details**: See [CODESYS XML Analysis](../codesys/codesys-xml-analysis.md) for timing characteristics and detailed logic flow extracted from the PLCopen XML format. diff --git a/docs/redesign/fb_switch-redesign-recommendation.md b/docs/redesign/fb_switch-redesign-recommendation.md deleted file mode 100644 index fff968d..0000000 --- a/docs/redesign/fb_switch-redesign-recommendation.md +++ /dev/null @@ -1,451 +0,0 @@ -# fb_switch Redesign Recommendation - -## Current Implementation Issues - -### Problem 1: Toggle-Based Control -**Issue**: The current `fb_toogleButton` implementation uses toggle logic, which causes state desynchronization with Home Assistant. - -**Why this is a problem**: -- Home Assistant sends explicit ON/OFF commands -- CODESYS treats every input as a toggle trigger -- If HA thinks light is ON and sends ON again, CODESYS toggles it OFF -- State becomes out of sync between HA and actual relay state - -**Example Scenario**: -1. HA sends `sw_1 = TRUE` (turn ON) -2. CODESYS toggles: OFF → ON ✓ -3. HA sends `sw_1 = TRUE` again (thinking it's already ON) -4. CODESYS toggles: ON → OFF ✗ (wrong!) - -### Problem 2: No Direct Set/Reset Commands -**Issue**: Current structure only has toggle triggers, no explicit ON/OFF commands. - -**Current Structure**: -```iec -struct_switches: - sw_1: BOOL; // Treated as toggle trigger - all_on: BOOL; // Toggle trigger - all_off: BOOL; // Toggle trigger -``` - -**What's needed**: -- Separate ON and OFF commands -- Edge detection for Zigbee switches (toggle behavior) -- Direct set/reset for Home Assistant - -### Problem 3: Status Feedback Limitations -**Issue**: Current `lights.l_X` outputs represent toggle button state, not actual relay status. - -**Problems**: -- No direct feedback from physical relay outputs -- Status may not match actual relay state if relay fails -- No way to detect relay hardware failures - -## Recommended Redesign - -### New Data Structure - -```iec -TYPE struct_switches : -STRUCT - // Home Assistant Commands (set/reset) - ha_l1_on: BOOL; // HA command: Light 1 ON - ha_l1_off: BOOL; // HA command: Light 1 OFF - ha_l2_on: BOOL; - ha_l2_off: BOOL; - ha_l3_on: BOOL; - ha_l3_off: BOOL; - ha_l4_on: BOOL; - ha_l4_off: BOOL; - ha_l5_on: BOOL; - ha_l5_off: BOOL; - ha_l6_on: BOOL; - ha_l6_off: BOOL; - - // Zigbee Switch Inputs (edge detection for toggle) - zigbee_sw1: BOOL; // Zigbee switch 1 (edge detected) - zigbee_sw2: BOOL; - zigbee_sw3: BOOL; - zigbee_sw4: BOOL; - zigbee_sw5: BOOL; - zigbee_sw6: BOOL; - - // Global Commands - ha_all_on: BOOL; // HA: All lights ON - ha_all_off: BOOL; // HA: All lights OFF -END_STRUCT -END_TYPE - -TYPE struct_lights: -STRUCT - l_1: BOOL; // Light 1 control output - l_2: BOOL; // Light 2 control output - l_3: BOOL; // Light 3 control output - l_4: BOOL; // Light 4 control output - l_5: BOOL; // Light 5 control output - l_6: BOOL; // Light 6 control output - - // Status feedback (read from actual relay outputs) - l_1_status: BOOL; // Actual relay 1 state - l_2_status: BOOL; // Actual relay 2 state - l_3_status: BOOL; // Actual relay 3 state - l_4_status: BOOL; // Actual relay 4 state - l_5_status: BOOL; // Actual relay 5 state - l_6_status: BOOL; // Actual relay 6 state -END_STRUCT -END_TYPE -``` - -### New Function Block: fb_lightControl - -```iec -FUNCTION_BLOCK fb_lightControl -VAR_INPUT - // Home Assistant Commands - ha_on: BOOL; // HA command: Turn ON - ha_off: BOOL; // HA command: Turn OFF - - // Zigbee Switch Input - zigbee_sw: BOOL; // Zigbee switch (edge detected) - - // Status Feedback - relay_status: BOOL; // Actual relay state (read from EtherCAT) -END_VAR -VAR_OUTPUT - light_output: BOOL; // Control output to relay - light_status: BOOL; // Status to send back (actual relay state) -END_VAR -VAR - // Edge detection for Zigbee - zigbee_prev: BOOL; - zigbee_edge: BOOL; - - // Command processing - ha_on_prev: BOOL; - ha_off_prev: BOOL; - ha_on_edge: BOOL; - ha_off_edge: BOOL; - - // Internal state - light_state: BOOL; - - // Edge triggers - r_trig_ha_on: R_TRIG; - r_trig_ha_off: R_TRIG; - r_trig_zigbee: R_TRIG; -END_VAR -``` - -### Implementation Logic - -**Priority Order** (highest to lowest): -1. **HA OFF command** - Always turns light OFF -2. **HA ON command** - Always turns light ON -3. **Zigbee toggle** - Toggles current state -4. **Maintain current state** - If no commands - -**Logic Flow**: - -```iec -// Edge detection for commands -r_trig_ha_on(CLK := ha_on); -ha_on_edge := r_trig_ha_on.Q; - -r_trig_ha_off(CLK := ha_off); -ha_off_edge := r_trig_ha_off.Q; - -r_trig_zigbee(CLK := zigbee_sw); -zigbee_edge := r_trig_zigbee.Q; - -// State machine -IF ha_off_edge THEN - light_state := FALSE; -ELSIF ha_on_edge THEN - light_state := TRUE; -ELSIF zigbee_edge THEN - light_state := NOT light_state; -END_IF - -// Output assignment -light_output := light_state; - -// Status feedback (read from actual relay) -light_status := relay_status; -``` - -### New fb_switch Implementation - -```iec -FUNCTION_BLOCK fb_switch -VAR_INPUT - switches: struct_switches; - relay_status: struct_lights; // Read from EtherCAT outputs -END_VAR -VAR_OUTPUT - lights: struct_lights; -END_VAR -VAR - l1: fb_lightControl; - l2: fb_lightControl; - l3: fb_lightControl; - l4: fb_lightControl; - l5: fb_lightControl; - l6: fb_lightControl; -END_VAR - -// Light 1 Control -l1( - ha_on := switches.ha_l1_on, - ha_off := switches.ha_l1_off, - zigbee_sw := switches.zigbee_sw1, - relay_status := relay_status.l_1_status -); -lights.l_1 := l1.light_output; -lights.l_1_status := l1.light_status; - -// Light 2 Control -l2( - ha_on := switches.ha_l2_on, - ha_off := switches.ha_l2_off, - zigbee_sw := switches.zigbee_sw2, - relay_status := relay_status.l_2_status -); -lights.l_2 := l2.light_output; -lights.l_2_status := l2.light_status; - -// ... (repeat for l3-l6) - -// Global Commands -IF switches.ha_all_off THEN - lights.l_1 := FALSE; - lights.l_2 := FALSE; - lights.l_3 := FALSE; - lights.l_4 := FALSE; - lights.l_5 := FALSE; - lights.l_6 := FALSE; -ELSIF switches.ha_all_on THEN - lights.l_1 := TRUE; - lights.l_2 := TRUE; - lights.l_3 := TRUE; - lights.l_4 := TRUE; - lights.l_5 := TRUE; - lights.l_6 := TRUE; -END_IF; -``` - -## Node-RED Integration Changes - -### Current Flow (Toggle-Based) -``` -Zigbee Switch → MQTT → Node-RED → CODESYS (sw_1 = TRUE) → Toggle -Home Assistant → MQTT → Node-RED → CODESYS (sw_1 = TRUE) → Toggle (WRONG!) -``` - -### New Flow (Command-Based) -``` -Zigbee Switch → MQTT → Node-RED → CODESYS (zigbee_sw1 = edge) → Toggle -Home Assistant → MQTT → Node-RED → CODESYS (ha_l1_on = TRUE) → Set ON -Home Assistant → MQTT → Node-RED → CODESYS (ha_l1_off = TRUE) → Set OFF -``` - -### Node-RED Flow Example - -**For Zigbee Switches**: -```javascript -// Detect button press (edge detection) -if (msg.payload.action === "single") { - // Send edge trigger to CODESYS - msg.payload = { - zigbee_sw1: true // Edge detected - }; - return msg; -} -``` - -**For Home Assistant**: -```javascript -// Direct ON/OFF commands -if (msg.payload === "ON") { - msg.payload = { - ha_l1_on: true, - ha_l1_off: false - }; -} else if (msg.payload === "OFF") { - msg.payload = { - ha_l1_on: false, - ha_l1_off: true - }; -} -return msg; -``` - -**Status Feedback to Home Assistant**: -```javascript -// Read status from CODESYS -const status = msg.payload.l_1_status; -// Publish to HA -msg.topic = "homeassistant/light/room1_light1/state"; -msg.payload = status ? "ON" : "OFF"; -return msg; -``` - -## Home Assistant Configuration - -### MQTT Light Entity - -```yaml -light: - - platform: mqtt - name: "Room 1 Light 1" - state_topic: "homeassistant/light/room1_light1/state" - command_topic: "homeassistant/light/room1_light1/set" - state_value_template: "{{ value_json.state }}" - command_on_template: '{"state":"ON"}' - command_off_template: '{"state":"OFF"}' - qos: 1 - retain: true -``` - -### Node-RED Flow for HA Integration - -``` -[HA Command] → [Parse] → [Convert to CODESYS format] → [Network Variable] -[CODESYS Status] → [Network Variable] → [Convert to HA format] → [HA State] -``` - -## Benefits of Redesign - -### 1. State Synchronization -- ✅ HA always knows actual relay state -- ✅ No state desynchronization -- ✅ Reliable ON/OFF commands - -### 2. Multiple Control Sources -- ✅ HA uses set/reset commands -- ✅ Zigbee switches use toggle (edge detection) -- ✅ Both work simultaneously without conflicts - -### 3. Status Feedback -- ✅ Actual relay state read from EtherCAT -- ✅ Hardware failure detection possible -- ✅ Accurate status in Home Assistant - -### 4. Better Debugging -- ✅ Clear separation of commands -- ✅ Can trace which source triggered action -- ✅ Easier troubleshooting - -### 5. Extensibility -- ✅ Easy to add more control sources -- ✅ Can add priority levels -- ✅ Can add scheduling/automation - -## Migration Path - -### Step 1: Update Data Structures -1. Modify `struct_switches` to include HA commands and Zigbee inputs -2. Modify `struct_lights` to include status feedback -3. Keep old structure temporarily for backward compatibility - -### Step 2: Create New Function Blocks -1. Implement `fb_lightControl` with new logic -2. Update `fb_switch` to use new function blocks -3. Test in simulation first - -### Step 3: Update Node-RED Flows -1. Modify flows to send HA commands (ON/OFF) -2. Keep Zigbee flows with edge detection -3. Add status feedback flows - -### Step 4: Update Home Assistant -1. Configure MQTT light entities -2. Test control from HA -3. Verify status updates - -### Step 5: Testing -1. Test HA control (ON/OFF) -2. Test Zigbee switch control (toggle) -3. Test status feedback -4. Test both simultaneously - -## Alternative: Simpler Approach (If Full Redesign Too Complex) - -If the full redesign is too complex, here's a simpler improvement: - -### Simplified Improvement - -**Keep toggle logic but add state tracking**: - -```iec -FUNCTION_BLOCK fb_lightControl_Simple -VAR_INPUT - toggle: BOOL; // Toggle command (edge detected) - set_on: BOOL; // Set ON command (edge detected) - set_off: BOOL; // Set OFF command (edge detected) - relay_status: BOOL; // Actual relay state -END_VAR -VAR_OUTPUT - output: BOOL; - status: BOOL; -END_VAR -VAR - state: BOOL; - r_trig_toggle: R_TRIG; - r_trig_on: R_TRIG; - r_trig_off: R_TRIG; -END_VAR - -// Edge detection -r_trig_toggle(CLK := toggle); -r_trig_on(CLK := set_on); -r_trig_off(CLK := set_off); - -// State machine -IF r_trig_off.Q THEN - state := FALSE; -ELSIF r_trig_on.Q THEN - state := TRUE; -ELSIF r_trig_toggle.Q THEN - state := NOT state; -END_IF; - -output := state; -status := relay_status; // Read from actual relay -``` - -**Data Structure**: -```iec -struct_switches: - sw1_toggle: BOOL; // Zigbee toggle - sw1_on: BOOL; // HA ON command - sw1_off: BOOL; // HA OFF command - // ... repeat for sw2-sw6 -``` - -This simpler approach: -- ✅ Keeps existing structure mostly intact -- ✅ Adds explicit ON/OFF commands for HA -- ✅ Maintains toggle for Zigbee -- ✅ Adds status feedback -- ⚠️ Still requires data structure changes -- ⚠️ Less clean than full redesign - -## Recommendation - -**I recommend the full redesign** because: -1. Better long-term maintainability -2. Clear separation of concerns -3. Easier to extend and debug -4. Proper state management -5. Industry-standard approach - -However, if time is limited, the **simplified approach** is a good compromise that addresses the main issues. - ---- - -**Next Steps**: -1. Review this recommendation -2. Decide on full redesign vs. simplified approach -3. Plan migration strategy -4. Update Node-RED flows accordingly -5. Test thoroughly before deploying diff --git a/docs/redesign/light-naming-configuration.md b/docs/redesign/light-naming-configuration.md deleted file mode 100644 index 9102686..0000000 --- a/docs/redesign/light-naming-configuration.md +++ /dev/null @@ -1,873 +0,0 @@ -# Light Naming Configuration - Hybrid Structure - -## Overview - -This document defines the naming convention and structure for all lights in the home automation system using the Hybrid Structure (Option 4). This will serve as the basis for the redesign. - -**Structure**: Room-organized in CODESYS, flattened for Home Assistant -**Naming Convention**: `{room}_{function}` (e.g., `kitchen_main`, `master_bedroom_bedside_left`) - ---- - -## Room-by-Room Light Configuration - -### 1. Master Bedroom - -**Room ID**: `master_bedroom` -**Display Name**: "Master Bedroom" - -**Lights**: -- `main` - Main ceiling light -- `bedside_left` - Left bedside lamp -- `bedside_right` - Right bedside lamp -- `closet` - Closet light -- `reading` - Reading light (optional) -- `ambient` - Ambient/atmospheric lighting (optional) - -**CODESYS Structure**: -```iec -rooms.master_bedroom: - main: struct_light_control; - bedside_left: struct_light_control; - bedside_right: struct_light_control; - closet: struct_light_control; - reading: struct_light_control; // Optional - ambient: struct_light_control; // Optional -``` - -**Home Assistant Entities**: -- `light.master_bedroom_main` -- `light.master_bedroom_bedside_left` -- `light.master_bedroom_bedside_right` -- `light.master_bedroom_closet` -- `light.master_bedroom_reading` (if used) -- `light.master_bedroom_ambient` (if used) - ---- - -### 2. Master Bathroom - -**Room ID**: `master_bathroom` -**Display Name**: "Master Bathroom" - -**Lights**: -- `main` - Main ceiling light -- `mirror` - Mirror/vanity light -- `shower` - Shower light -- `night_light` - Night light (low brightness) - -**CODESYS Structure**: -```iec -rooms.master_bathroom: - main: struct_light_control; - mirror: struct_light_control; - shower: struct_light_control; - night_light: struct_light_control; -``` - -**Home Assistant Entities**: -- `light.master_bathroom_main` -- `light.master_bathroom_mirror` -- `light.master_bathroom_shower` -- `light.master_bathroom_night_light` - ---- - -### 3. Bedroom 1 - -**Room ID**: `bedroom_1` -**Display Name**: "Bedroom 1" - -**Lights**: -- `main` - Main ceiling light -- `bedside` - Bedside lamp -- `desk` - Desk lamp (if applicable) -- `closet` - Closet light (if applicable) - -**CODESYS Structure**: -```iec -rooms.bedroom_1: - main: struct_light_control; - bedside: struct_light_control; - desk: struct_light_control; // Optional - closet: struct_light_control; // Optional -``` - -**Home Assistant Entities**: -- `light.bedroom_1_main` -- `light.bedroom_1_bedside` -- `light.bedroom_1_desk` (if used) -- `light.bedroom_1_closet` (if used) - ---- - -### 4. Bedroom 2 - -**Room ID**: `bedroom_2` -**Display Name**: "Bedroom 2" - -**Lights**: -- `main` - Main ceiling light -- `bedside` - Bedside lamp -- `desk` - Desk lamp (if applicable) -- `closet` - Closet light (if applicable) - -**CODESYS Structure**: -```iec -rooms.bedroom_2: - main: struct_light_control; - bedside: struct_light_control; - desk: struct_light_control; // Optional - closet: struct_light_control; // Optional -``` - -**Home Assistant Entities**: -- `light.bedroom_2_main` -- `light.bedroom_2_bedside` -- `light.bedroom_2_desk` (if used) -- `light.bedroom_2_closet` (if used) - ---- - -### 5. Bathroom - -**Room ID**: `bathroom` -**Display Name**: "Bathroom" - -**Lights**: -- `main` - Main ceiling light -- `mirror` - Mirror/vanity light -- `night_light` - Night light - -**CODESYS Structure**: -```iec -rooms.bathroom: - main: struct_light_control; - mirror: struct_light_control; - night_light: struct_light_control; -``` - -**Home Assistant Entities**: -- `light.bathroom_main` -- `light.bathroom_mirror` -- `light.bathroom_night_light` - ---- - -### 6. Kitchen - -**Room ID**: `kitchen` -**Display Name**: "Kitchen" - -**Lights**: -- `main` - Main ceiling light -- `under_cabinet` - Under cabinet task lighting -- `island` - Island pendant light -- `pantry` - Pantry light (if separate from pantry room) -- `sink` - Sink area light - -**CODESYS Structure**: -```iec -rooms.kitchen: - main: struct_light_control; - under_cabinet: struct_light_control; - island: struct_light_control; - pantry: struct_light_control; // If in kitchen - sink: struct_light_control; -``` - -**Home Assistant Entities**: -- `light.kitchen_main` -- `light.kitchen_under_cabinet` -- `light.kitchen_island` -- `light.kitchen_pantry` (if used) -- `light.kitchen_sink` - ---- - -### 7. Living Room - -**Room ID**: `living_room` -**Display Name**: "Living Room" - -**Lights**: -- `main` - Main ceiling light -- `reading` - Reading lamp -- `tv` - TV area lighting -- `accent` - Accent lighting -- `fireplace` - Fireplace area lighting (if applicable) - -**CODESYS Structure**: -```iec -rooms.living_room: - main: struct_light_control; - reading: struct_light_control; - tv: struct_light_control; - accent: struct_light_control; - fireplace: struct_light_control; // Optional -``` - -**Home Assistant Entities**: -- `light.living_room_main` -- `light.living_room_reading` -- `light.living_room_tv` -- `light.living_room_accent` -- `light.living_room_fireplace` (if used) - ---- - -### 8. Dining Room - -**Room ID**: `dining_room` -**Display Name**: "Dining Room" - -**Lights**: -- `main` - Main chandelier/ceiling light -- `ambient` - Ambient lighting -- `buffet` - Buffet/sideboard lighting (if applicable) - -**CODESYS Structure**: -```iec -rooms.dining_room: - main: struct_light_control; - ambient: struct_light_control; - buffet: struct_light_control; // Optional -``` - -**Home Assistant Entities**: -- `light.dining_room_main` -- `light.dining_room_ambient` -- `light.dining_room_buffet` (if used) - ---- - -### 9. Hallway - -**Room ID**: `hallway` -**Display Name**: "Hallway" - -**Lights**: -- `main` - Main hallway light -- `secondary` - Secondary hallway light (if long hallway) -- `night_light` - Night light - -**CODESYS Structure**: -```iec -rooms.hallway: - main: struct_light_control; - secondary: struct_light_control; // Optional - night_light: struct_light_control; -``` - -**Home Assistant Entities**: -- `light.hallway_main` -- `light.hallway_secondary` (if used) -- `light.hallway_night_light` - ---- - -### 10. Pantry - -**Room ID**: `pantry` -**Display Name**: "Pantry" - -**Lights**: -- `main` - Main pantry light - -**CODESYS Structure**: -```iec -rooms.pantry: - main: struct_light_control; -``` - -**Home Assistant Entities**: -- `light.pantry_main` - ---- - -### 11. Entrance - -**Room ID**: `entrance` -**Display Name**: "Entrance" - -**Lights**: -- `main` - Main entrance light -- `security` - Security/porch light - -**CODESYS Structure**: -```iec -rooms.entrance: - main: struct_light_control; - security: struct_light_control; -``` - -**Home Assistant Entities**: -- `light.entrance_main` -- `light.entrance_security` - ---- - -### 12. Guest WC - -**Room ID**: `guest_wc` -**Display Name**: "Guest WC" - -**Lights**: -- `main` - Main light -- `night_light` - Night light - -**CODESYS Structure**: -```iec -rooms.guest_wc: - main: struct_light_control; - night_light: struct_light_control; -``` - -**Home Assistant Entities**: -- `light.guest_wc_main` -- `light.guest_wc_night_light` - ---- - -### 13. Outdoor Veranda - -**Room ID**: `outdoor_veranda` -**Display Name**: "Outdoor Veranda" - -**Lights**: -- `main` - Main veranda light -- `ambient` - Ambient/atmospheric lighting -- `security` - Security lighting - -**CODESYS Structure**: -```iec -rooms.outdoor_veranda: - main: struct_light_control; - ambient: struct_light_control; - security: struct_light_control; -``` - -**Home Assistant Entities**: -- `light.outdoor_veranda_main` -- `light.outdoor_veranda_ambient` -- `light.outdoor_veranda_security` - ---- - -### 14. Outdoor Front - -**Room ID**: `outdoor_front` -**Display Name**: "Outdoor Front" - -**Lights**: -- `porch` - Front porch light -- `security` - Security light -- `pathway` - Pathway lighting - -**CODESYS Structure**: -```iec -rooms.outdoor_front: - porch: struct_light_control; - security: struct_light_control; - pathway: struct_light_control; -``` - -**Home Assistant Entities**: -- `light.outdoor_front_porch` -- `light.outdoor_front_security` -- `light.outdoor_front_pathway` - ---- - -### 15. Outdoor Side - -**Room ID**: `outdoor_side` -**Display Name**: "Outdoor Side" - -**Lights**: -- `security` - Side security light -- `pathway` - Side pathway lighting - -**CODESYS Structure**: -```iec -rooms.outdoor_side: - security: struct_light_control; - pathway: struct_light_control; -``` - -**Home Assistant Entities**: -- `light.outdoor_side_security` -- `light.outdoor_side_pathway` - ---- - -### 16. Outdoor Back - -**Room ID**: `outdoor_back` -**Display Name**: "Outdoor Back" - -**Lights**: -- `patio` - Patio/backyard light -- `security` - Back security light -- `garden` - Garden lighting (if applicable) - -**CODESYS Structure**: -```iec -rooms.outdoor_back: - patio: struct_light_control; - security: struct_light_control; - garden: struct_light_control; // Optional -``` - -**Home Assistant Entities**: -- `light.outdoor_back_patio` -- `light.outdoor_back_security` -- `light.outdoor_back_garden` (if used) - ---- - -## Complete CODESYS Structure Definition - -```iec -TYPE struct_light_control : -STRUCT - // Control Commands - ha_on: BOOL; // Home Assistant: Turn ON - ha_off: BOOL; // Home Assistant: Turn OFF - zigbee_toggle: BOOL; // Zigbee Switch: Toggle (edge detected) - - // Status - state: BOOL; // Current light state - relay_status: BOOL; // Actual relay status (read from EtherCAT) -END_STRUCT -END_TYPE - -TYPE struct_room_lights : -STRUCT - main: struct_light_control; - secondary: struct_light_control; - task: struct_light_control; - ambient: struct_light_control; - accent: struct_light_control; - security: struct_light_control; - // Room-specific lights defined per room -END_STRUCT -END_TYPE - -VAR_GLOBAL - rooms: STRUCT - master_bedroom: STRUCT - main: struct_light_control; - bedside_left: struct_light_control; - bedside_right: struct_light_control; - closet: struct_light_control; - reading: struct_light_control; - ambient: struct_light_control; - END_STRUCT; - - master_bathroom: STRUCT - main: struct_light_control; - mirror: struct_light_control; - shower: struct_light_control; - night_light: struct_light_control; - END_STRUCT; - - bedroom_1: STRUCT - main: struct_light_control; - bedside: struct_light_control; - desk: struct_light_control; - closet: struct_light_control; - END_STRUCT; - - bedroom_2: STRUCT - main: struct_light_control; - bedside: struct_light_control; - desk: struct_light_control; - closet: struct_light_control; - END_STRUCT; - - bathroom: STRUCT - main: struct_light_control; - mirror: struct_light_control; - night_light: struct_light_control; - END_STRUCT; - - kitchen: STRUCT - main: struct_light_control; - under_cabinet: struct_light_control; - island: struct_light_control; - pantry: struct_light_control; - sink: struct_light_control; - END_STRUCT; - - living_room: STRUCT - main: struct_light_control; - reading: struct_light_control; - tv: struct_light_control; - accent: struct_light_control; - fireplace: struct_light_control; - END_STRUCT; - - dining_room: STRUCT - main: struct_light_control; - ambient: struct_light_control; - buffet: struct_light_control; - END_STRUCT; - - hallway: STRUCT - main: struct_light_control; - secondary: struct_light_control; - night_light: struct_light_control; - END_STRUCT; - - pantry: STRUCT - main: struct_light_control; - END_STRUCT; - - entrance: STRUCT - main: struct_light_control; - security: struct_light_control; - END_STRUCT; - - guest_wc: STRUCT - main: struct_light_control; - night_light: struct_light_control; - END_STRUCT; - - outdoor_veranda: STRUCT - main: struct_light_control; - ambient: struct_light_control; - security: struct_light_control; - END_STRUCT; - - outdoor_front: STRUCT - porch: struct_light_control; - security: struct_light_control; - pathway: struct_light_control; - END_STRUCT; - - outdoor_side: STRUCT - security: struct_light_control; - pathway: struct_light_control; - END_STRUCT; - - outdoor_back: STRUCT - patio: struct_light_control; - security: struct_light_control; - garden: struct_light_control; - END_STRUCT; - END_STRUCT; -END_VAR -``` - ---- - -## Light Function Types Reference - -Use these standard function names for consistency: - -| Function | Description | Typical Use | -|----------|-------------|-------------| -| `main` | Primary/ceiling light | Main illumination in room | -| `secondary` | Secondary light | Additional main light | -| `bedside_left` | Left bedside lamp | Bedroom bedside | -| `bedside_right` | Right bedside lamp | Bedroom bedside | -| `bedside` | Single bedside lamp | Single bed rooms | -| `closet` | Closet light | Storage areas | -| `desk` | Desk lamp | Study/work areas | -| `reading` | Reading light | Reading areas | -| `mirror` | Mirror/vanity light | Bathrooms | -| `shower` | Shower light | Bathroom shower | -| `night_light` | Night light | Low brightness | -| `under_cabinet` | Under cabinet | Kitchen task lighting | -| `island` | Island pendant | Kitchen island | -| `sink` | Sink area light | Kitchen/bathroom | -| `tv` | TV area lighting | Living room | -| `accent` | Accent lighting | Decorative | -| `ambient` | Ambient lighting | Atmospheric | -| `fireplace` | Fireplace area | Living room | -| `buffet` | Buffet/sideboard | Dining room | -| `porch` | Porch light | Entrance | -| `patio` | Patio light | Backyard | -| `security` | Security light | Outdoor security | -| `pathway` | Pathway lighting | Outdoor paths | -| `garden` | Garden lighting | Outdoor garden | - ---- - -## Water Boiler Control - -### Water Boiler Structure - -```iec -TYPE struct_water_boiler : -STRUCT - // Control Commands - ha_on: BOOL; // Home Assistant: Turn ON - ha_off: BOOL; // Home Assistant: Turn OFF - schedule_on: BOOL; // Scheduled ON (from automation) - schedule_off: BOOL; // Scheduled OFF (from automation) - - // Status - state: BOOL; // Current boiler state (ON/OFF) - relay_status: BOOL; // Actual relay status - temperature: REAL; // Water temperature (if sensor available) - - // Safety - safety_override: BOOL; // Safety system override (read-only) - error_state: BOOL; // Error state flag - last_error: STRING; // Last error message -END_STRUCT -END_TYPE - -VAR_GLOBAL - water_boiler: struct_water_boiler; -END_VAR -``` - -### Water Boiler Function Block - -```iec -FUNCTION_BLOCK fb_waterBoiler -VAR_INPUT - // Control - ha_on: BOOL; - ha_off: BOOL; - schedule_on: BOOL; - schedule_off: BOOL; - - // Sensors - temperature: REAL; // Water temperature sensor - pressure: REAL; // Water pressure (if available) - - // Safety Inputs - emergency_stop: BOOL; // Emergency stop button - flow_sensor: BOOL; // Water flow sensor - overtemp_sensor: BOOL; // Over-temperature sensor - pressure_sensor: BOOL; // Pressure sensor (if available) - - // Relay Output - relay_output: BOOL; // Read actual relay state -END_VAR -VAR_OUTPUT - relay_control: BOOL; // Control output to relay - state: BOOL; // Current state - relay_status: BOOL; // Actual relay status - safety_override: BOOL; // Safety override active - error_state: BOOL; // Error detected - error_code: INT; // Error code - error_message: STRING; // Error description -END_VAR -VAR - // Internal State - internal_state: BOOL; - last_state: BOOL; - - // Safety Timers - max_on_time: TIME := T#8H; // Maximum ON time (8 hours) - min_off_time: TIME := T#30M; // Minimum OFF time (30 minutes) - on_timer: TON; - off_timer: TON; - - // Temperature Limits - max_temp: REAL := 80.0; // Maximum temperature (°C) - min_temp: REAL := 10.0; // Minimum temperature (°C) - - // Safety Flags - temp_ok: BOOL; - pressure_ok: BOOL; - flow_ok: BOOL; - time_ok: BOOL; - - // Edge Detection - r_trig_ha_on: R_TRIG; - r_trig_ha_off: R_TRIG; - r_trig_schedule_on: R_TRIG; - r_trig_schedule_off: R_TRIG; - r_trig_emergency: R_TRIG; -END_VAR -``` - -### Safety Logic Implementation - -```iec -// Edge detection -r_trig_ha_on(CLK := ha_on); -r_trig_ha_off(CLK := ha_off); -r_trig_schedule_on(CLK := schedule_on); -r_trig_schedule_off(CLK := schedule_off); -r_trig_emergency(CLK := emergency_stop); - -// Safety Checks -temp_ok := (temperature >= min_temp) AND (temperature <= max_temp); -pressure_ok := pressure_sensor; // Assuming normally closed safety switch -flow_ok := flow_sensor; // Water flow detected -time_ok := NOT on_timer.Q; // Not exceeded max ON time - -// Safety Override (highest priority) -IF r_trig_emergency.Q OR NOT temp_ok OR NOT pressure_ok THEN - safety_override := TRUE; - internal_state := FALSE; - error_state := TRUE; - error_code := 1; // Safety override - error_message := 'Safety override active'; -ELSIF NOT flow_ok AND internal_state THEN - // No water flow but boiler is on - turn off after delay - off_timer(IN := TRUE, PT := T#5M); // 5 minute delay - IF off_timer.Q THEN - internal_state := FALSE; - error_state := TRUE; - error_code := 2; // No flow - error_message := 'No water flow detected'; - END_IF; -ELSIF NOT time_ok THEN - // Maximum ON time exceeded - internal_state := FALSE; - error_state := TRUE; - error_code := 3; // Time exceeded - error_message := 'Maximum ON time exceeded'; -ELSE - safety_override := FALSE; - error_state := FALSE; - error_code := 0; - error_message := ''; -END_IF; - -// Control Logic (only if safety OK) -IF NOT safety_override THEN - // Priority: Emergency OFF > Manual OFF > Scheduled OFF > Manual ON > Scheduled ON - IF r_trig_ha_off.Q OR r_trig_schedule_off.Q THEN - internal_state := FALSE; - ELSIF r_trig_ha_on.Q OR r_trig_schedule_on.Q THEN - IF temp_ok AND flow_ok AND time_ok THEN - internal_state := TRUE; - on_timer(IN := internal_state, PT := max_on_time); - END_IF; - END_IF; -END_IF; - -// Update timers -on_timer(IN := internal_state AND NOT safety_override, PT := max_on_time); -off_timer(IN := NOT internal_state, PT := min_off_time); - -// Output Assignment -relay_control := internal_state AND NOT safety_override; -state := internal_state; -relay_status := relay_output; // Read from actual relay -``` - -### Safety Measures - -#### 1. Temperature Protection -- **Max Temperature**: 80°C (configurable) -- **Min Temperature**: 10°C (freeze protection) -- **Action**: Auto-shutoff if temperature exceeds limits - -#### 2. Time-Based Protection -- **Max ON Time**: 8 hours (prevents continuous operation) -- **Min OFF Time**: 30 minutes (prevents rapid cycling) -- **Action**: Force OFF after max time, prevent ON during min OFF time - -#### 3. Flow Detection -- **Flow Sensor**: Monitors water flow -- **Action**: Shut off if no flow detected for 5 minutes (dry-run protection) - -#### 4. Emergency Stop -- **Emergency Button**: Physical emergency stop -- **Action**: Immediate shutdown, requires manual reset - -#### 5. Pressure Protection -- **Pressure Sensor**: Monitors system pressure -- **Action**: Shut off if pressure abnormal - -#### 6. State Monitoring -- **Relay Status**: Read actual relay state -- **Action**: Detect relay failures, report status - -#### 7. Error Reporting -- **Error Codes**: - - 0: No error - - 1: Safety override - - 2: No flow - - 3: Time exceeded - - 4: Temperature fault - - 5: Pressure fault -- **Error Messages**: Human-readable descriptions - -### Water Boiler Network Variable - -```iec -// Sent to Node-RED (Status) -VAR_GLOBAL - boiler_status: struct_water_boiler; -END_VAR - -// Received from Node-RED (Commands) -VAR_GLOBAL - boiler_commands: STRUCT - ha_on: BOOL; - ha_off: BOOL; - schedule_on: BOOL; - schedule_off: BOOL; - END_STRUCT; -END_VAR -``` - -### Home Assistant Integration - -**Entity Type**: `switch` or `water_heater` - -```yaml -switch: - - platform: mqtt - name: "Water Boiler" - unique_id: "water_boiler" - state_topic: "homeassistant/water_boiler/state" - command_topic: "homeassistant/water_boiler/set" - state_value_template: "{{ value_json.state }}" - command_on_template: '{"ha_on":true}' - command_off_template: '{"ha_off":true}' - qos: 1 - retain: true - -sensor: - - platform: mqtt - name: "Water Boiler Temperature" - state_topic: "homeassistant/water_boiler/state" - value_template: "{{ value_json.temperature }}" - unit_of_measurement: "°C" - - - platform: mqtt - name: "Water Boiler Error" - state_topic: "homeassistant/water_boiler/state" - value_template: "{{ value_json.error_message }}" -``` - ---- - -## Summary - -### Light Configuration -- **Total Rooms**: 16 (15 indoor + outdoor areas) -- **Total Lights**: ~60-70 lights (varies by room) -- **Structure**: Hybrid (room-organized in CODESYS, flat in HA) -- **Naming**: `{room}_{function}` format - -### Water Boiler -- **Control**: ON/OFF with scheduling -- **Safety**: 7 safety measures implemented -- **Monitoring**: Temperature, flow, pressure, time -- **Error Reporting**: Comprehensive error codes and messages - -### Next Steps -1. Review and modify light names per room based on actual installation -2. Map lights to relay outputs -3. Implement new structure in CODESYS -4. Update Node-RED flows -5. Configure Home Assistant entities -6. Test all safety measures for water boiler - ---- - -**Document Status**: Template - Modify with actual light configuration -**Last Updated**: January 27, 2026 diff --git a/docs/redesign/redesign-implementation-roadmap.md b/docs/redesign/redesign-implementation-roadmap.md deleted file mode 100644 index 6dc8ada..0000000 --- a/docs/redesign/redesign-implementation-roadmap.md +++ /dev/null @@ -1,239 +0,0 @@ -# Redesign Implementation Roadmap - -## Overview - -This document provides a step-by-step roadmap for implementing the redesigned structure based on the light naming configuration. - -## Phase 1: Configuration & Planning - -### Step 1.1: Review Light Naming Configuration -- [ ] Review `light-naming-configuration.md` -- [ ] Modify light names per room based on actual installation -- [ ] Remove unused lights from structure -- [ ] Add any missing lights -- [ ] Document relay-to-light mapping - -### Step 1.2: Map Relays to Lights -- [ ] Create mapping table: Relay # → Room → Light Name -- [ ] Document in `relay-mapping.md` -- [ ] Verify all relays are accounted for - -**Example Mapping**: -``` -Relay 1 → kitchen → main -Relay 2 → kitchen → under_cabinet -Relay 3 → kitchen → island -Relay 4 → master_bedroom → main -Relay 5 → master_bedroom → bedside_left -... -``` - -### Step 1.3: Water Boiler Configuration -- [ ] Review water boiler safety requirements -- [ ] Configure temperature limits -- [ ] Configure time limits (max ON, min OFF) -- [ ] Identify sensor inputs (temperature, flow, pressure) -- [ ] Map boiler relay output - ---- - -## Phase 2: CODESYS Structure Implementation - -### Step 2.1: Create New Data Structures -- [ ] Create `struct_light_control` type -- [ ] Create `struct_water_boiler` type -- [ ] Create room-specific structures -- [ ] Create global `rooms` structure - -### Step 2.2: Create Function Blocks -- [ ] Create `fb_lightControl` function block -- [ ] Create `fb_waterBoiler` function block -- [ ] Test function blocks in simulation - -### Step 2.3: Update Network Variables -- [ ] Update `NVL_Sender` with new structure -- [ ] Update `NVL_Receiver` with new structure -- [ ] Add water boiler network variables -- [ ] Configure network variable properties - -### Step 2.4: Implement Control Logic -- [ ] Replace `fb_switch` with new structure -- [ ] Implement light control per room -- [ ] Implement water boiler control -- [ ] Add relay output mapping -- [ ] Add relay status readback - -### Step 2.5: Testing -- [ ] Test in simulation -- [ ] Test individual light control -- [ ] Test water boiler control -- [ ] Test safety measures -- [ ] Verify network variable transmission - ---- - -## Phase 3: Node-RED Integration - -### Step 3.1: Update Network Variable Nodes -- [ ] Update CODESYS network variable nodes -- [ ] Configure new variable structure -- [ ] Test bidirectional communication - -### Step 3.2: Create Transformation Functions -- [ ] Create function to flatten room structure for HA -- [ ] Create function to convert HA commands to CODESYS format -- [ ] Create function for Zigbee edge detection -- [ ] Test transformations - -### Step 3.3: Update MQTT Flows -- [ ] Update MQTT topics to new naming convention -- [ ] Configure Home Assistant auto-discovery -- [ ] Update Zigbee switch flows -- [ ] Add water boiler MQTT flows - -### Step 3.4: Testing -- [ ] Test HA → CODESYS commands -- [ ] Test Zigbee → CODESYS commands -- [ ] Test CODESYS → HA status updates -- [ ] Test water boiler control -- [ ] Verify MQTT message flow - ---- - -## Phase 4: Home Assistant Configuration - -### Step 4.1: Entity Configuration -- [ ] Create light entities for all lights -- [ ] Configure entity names and icons -- [ ] Set up areas/rooms in HA -- [ ] Create water boiler entity - -### Step 4.2: Dashboard Setup -- [ ] Create lighting dashboard -- [ ] Group lights by room -- [ ] Add water boiler control -- [ ] Add status indicators - -### Step 4.3: Automations -- [ ] Update existing automations -- [ ] Create new automations if needed -- [ ] Test automations - ---- - -## Phase 5: Migration & Deployment - -### Step 5.1: Backup Current System -- [ ] Backup CODESYS project -- [ ] Export Node-RED flows -- [ ] Backup Home Assistant configuration -- [ ] Document current state - -### Step 5.2: Parallel Testing -- [ ] Deploy new structure in test mode -- [ ] Run parallel with old system -- [ ] Compare behavior -- [ ] Fix any issues - -### Step 5.3: Gradual Migration -- [ ] Migrate one room at a time -- [ ] Test each room thoroughly -- [ ] Migrate water boiler -- [ ] Complete migration - -### Step 5.4: Validation -- [ ] Test all lights from HA -- [ ] Test all lights from Zigbee switches -- [ ] Verify status feedback -- [ ] Test water boiler control -- [ ] Test all safety measures -- [ ] Verify no regressions - ---- - -## Implementation Checklist - -### CODESYS -- [ ] New data structures defined -- [ ] Function blocks created and tested -- [ ] Network variables updated -- [ ] Control logic implemented -- [ ] Relay mapping configured -- [ ] Status readback implemented -- [ ] Water boiler safety measures implemented -- [ ] Code tested in simulation - -### Node-RED -- [ ] Network variable nodes updated -- [ ] Transformation functions created -- [ ] MQTT flows updated -- [ ] HA auto-discovery configured -- [ ] Zigbee integration updated -- [ ] Water boiler flows added -- [ ] All flows tested - -### Home Assistant -- [ ] All light entities created -- [ ] Water boiler entity created -- [ ] Areas/rooms configured -- [ ] Dashboard updated -- [ ] Automations updated -- [ ] All entities tested - -### Documentation -- [ ] Light naming configuration finalized -- [ ] Relay mapping documented -- [ ] Network variable structure documented -- [ ] MQTT topics documented -- [ ] Safety measures documented -- [ ] Troubleshooting guide updated - ---- - -## Key Files Reference - -1. **Light Configuration**: `light-naming-configuration.md` -2. **Structure Analysis**: `structure-redesign-analysis.md` -3. **Visual Comparison**: `structure-comparison-visual.md` -4. **Function Block Redesign**: `fb_switch-redesign-recommendation.md` -5. **This Roadmap**: `redesign-implementation-roadmap.md` - ---- - -## Estimated Timeline - -- **Phase 1** (Planning): 1-2 days -- **Phase 2** (CODESYS): 2-3 days -- **Phase 3** (Node-RED): 1-2 days -- **Phase 4** (Home Assistant): 1 day -- **Phase 5** (Migration): 2-3 days - -**Total**: ~7-11 days (depending on complexity and testing) - ---- - -## Risk Mitigation - -### Risks -1. **Network Variable Changes**: May break existing Node-RED flows - - **Mitigation**: Test thoroughly, have rollback plan - -2. **State Synchronization**: During migration, states may be inconsistent - - **Mitigation**: Migrate during low-usage period, verify all states - -3. **Water Boiler Safety**: Critical system, must work correctly - - **Mitigation**: Extensive testing, manual override available - -4. **Home Assistant Entities**: May need to recreate entities - - **Mitigation**: Use unique_id to preserve entity history - -### Rollback Plan -- Keep old CODESYS project as backup -- Keep old Node-RED flows as backup -- Document rollback procedure -- Test rollback procedure - ---- - -**Status**: Planning Phase -**Next Step**: Review and modify `light-naming-configuration.md` with actual light configuration diff --git a/docs/redesign/redesign-summary.md b/docs/redesign/redesign-summary.md deleted file mode 100644 index d0cdd7f..0000000 --- a/docs/redesign/redesign-summary.md +++ /dev/null @@ -1,101 +0,0 @@ -# Redesign Summary - Quick Reference - -## What We're Changing - -### Current Structure → New Structure - -**Before**: -- Room-based with generic `l_1`, `l_2` names -- Fixed 6 lights per room -- Toggle-based control -- No status feedback from relays - -**After**: -- Room-based in CODESYS, flat in Home Assistant -- Descriptive names: `kitchen_main`, `master_bedroom_bedside_left` -- Command-based control (ON/OFF for HA, toggle for Zigbee) -- Actual relay status feedback -- Water boiler with comprehensive safety - ---- - -## Key Documents - -1. **`light-naming-configuration.md`** ⭐ **START HERE** - - Complete light naming for all 16 rooms - - Water boiler control structure - - Safety measures - - **Action**: Review and modify with your actual lights - -2. **`redesign-implementation-roadmap.md`** - - Step-by-step implementation guide - - Phase-by-phase checklist - - Timeline estimates - -3. **`structure-redesign-analysis.md`** - - Detailed analysis of structure options - - Why Option 4 (Hybrid) was chosen - -4. **`fb_switch-redesign-recommendation.md`** - - Function block redesign details - - Control logic improvements - ---- - -## Quick Structure Reference - -### Light Naming Convention -``` -Format: {room}_{function} - -Examples: -- kitchen_main -- master_bedroom_bedside_left -- outdoor_front_security -``` - -### Function Types -- `main` - Primary light -- `bedside_left/right` - Bedside lamps -- `under_cabinet` - Task lighting -- `security` - Security lighting -- `ambient` - Atmospheric lighting -- etc. - -### CODESYS Access -```iec -rooms.kitchen.main.ha_on := TRUE; -rooms.master_bedroom.bedside_left.state; -``` - -### Home Assistant Entity -``` -light.kitchen_main -light.master_bedroom_bedside_left -``` - ---- - -## Water Boiler Safety Features - -1. ✅ **Temperature Protection** (max 80°C, min 10°C) -2. ✅ **Time Limits** (max 8h ON, min 30m OFF) -3. ✅ **Flow Detection** (shut off if no flow) -4. ✅ **Emergency Stop** (immediate shutdown) -5. ✅ **Pressure Protection** (if sensor available) -6. ✅ **State Monitoring** (relay status readback) -7. ✅ **Error Reporting** (comprehensive error codes) - ---- - -## Next Steps - -1. **Review** `light-naming-configuration.md` -2. **Modify** light names per room based on actual installation -3. **Map** relays to lights -4. **Start** Phase 1 of implementation roadmap - ---- - -**Status**: Ready for configuration review -**Priority**: Review and customize light names first diff --git a/docs/redesign/structure-comparison-visual.md b/docs/redesign/structure-comparison-visual.md deleted file mode 100644 index 8074bd5..0000000 --- a/docs/redesign/structure-comparison-visual.md +++ /dev/null @@ -1,362 +0,0 @@ -# Structure Comparison - Visual Guide - -## Current Structure (Room-Centric) - -``` -CODESYS: -┌─────────────────────────────────────┐ -│ masterBedroom: fb_switch │ -│ ├─ l_1 (Light 1) │ -│ ├─ l_2 (Light 2) │ -│ ├─ l_3 (Light 3) │ -│ ├─ l_4 (Light 4) │ -│ ├─ l_5 (Light 5) │ -│ └─ l_6 (Light 6) │ -└─────────────────────────────────────┘ - -Network Variable: -l_masterBedroom.l_1 -l_masterBedroom.l_2 -... - -Home Assistant: -light.master_bedroom_l1 -light.master_bedroom_l2 -light.master_bedroom_l3 -... -``` - -**Problems**: -- ❌ Generic names (l_1, l_2) -- ❌ Fixed 6 lights per room -- ❌ Wasted space (most rooms don't use all 6) -- ❌ Hard to identify which light is which - ---- - -## Option 1: Flat Light-Centric - -``` -CODESYS: -┌─────────────────────────────────────┐ -│ lights: struct_all_lights │ -│ ├─ kitchen_main │ -│ ├─ kitchen_under_cabinet │ -│ ├─ master_bedroom_main │ -│ ├─ master_bedroom_bedside_left │ -│ ├─ master_bedroom_bedside_right │ -│ └─ ... (all lights flat) │ -└─────────────────────────────────────┘ - -Network Variable: -lights.kitchen_main.on -lights.kitchen_main.state -lights.master_bedroom_main.on -... - -Home Assistant: -light.kitchen_main -light.kitchen_under_cabinet -light.master_bedroom_main -light.master_bedroom_bedside_left -``` - -**Benefits**: -- ✅ Clear, descriptive names -- ✅ Easy to find lights -- ✅ No wasted space -- ✅ Perfect for HA organization - ---- - -## Option 4: Hybrid (Recommended) - -``` -CODESYS (Room-organized): -┌─────────────────────────────────────┐ -│ rooms: struct_rooms │ -│ ├─ kitchen │ -│ │ ├─ main │ -│ │ ├─ under_cabinet │ -│ │ └─ island │ -│ ├─ master_bedroom │ -│ │ ├─ main │ -│ │ ├─ bedside_left │ -│ │ └─ bedside_right │ -│ └─ ... │ -└─────────────────────────────────────┘ - -Network Variable: -rooms.kitchen.main.on -rooms.kitchen.main.state -rooms.master_bedroom.main.on -... - -Node-RED (Flattened): -kitchen_main → HA -kitchen_under_cabinet → HA -master_bedroom_main → HA -master_bedroom_bedside_left → HA - -Home Assistant: -light.kitchen_main -light.kitchen_under_cabinet -light.master_bedroom_main -light.master_bedroom_bedside_left -``` - -**Benefits**: -- ✅ Organized in CODESYS (by room) -- ✅ Flat in Home Assistant (by light) -- ✅ Descriptive names -- ✅ Easy to add lights -- ✅ Best of both worlds - ---- - -## Real-World Example Comparison - -### Scenario: Kitchen with 3 lights - -**Current Structure**: -```iec -kitchen: fb_switch - l_1 = ??? (which light?) - l_2 = ??? (which light?) - l_3 = ??? (which light?) - l_4 = unused - l_5 = unused - l_6 = unused -``` - -**Hybrid Structure**: -```iec -rooms.kitchen: - main = Main ceiling light - under_cabinet = Under cabinet lights - island = Island pendant - (no unused slots) -``` - -**Home Assistant**: -``` -Current: - light.kitchen_l1 ← What is this? - light.kitchen_l2 ← What is this? - light.kitchen_l3 ← What is this? - -Hybrid: - light.kitchen_main ← Clear! - light.kitchen_under_cabinet ← Clear! - light.kitchen_island ← Clear! -``` - ---- - -## MQTT Topic Comparison - -### Current -``` -homeassistant/light/master_bedroom_l1/state -homeassistant/light/master_bedroom_l2/state -homeassistant/light/kitchen_l1/state -``` - -**Problems**: Generic, hard to identify - -### Hybrid -``` -homeassistant/light/kitchen_main/state -homeassistant/light/kitchen_under_cabinet/state -homeassistant/light/master_bedroom_main/state -homeassistant/light/master_bedroom_bedside_left/state -``` - -**Benefits**: Self-documenting, clear purpose - ---- - -## Home Assistant Dashboard Comparison - -### Current -``` -Kitchen -├─ Light 1 ← What is this? -├─ Light 2 ← What is this? -└─ Light 3 ← What is this? - -Master Bedroom -├─ Light 1 ← What is this? -├─ Light 2 ← What is this? -└─ Light 3 ← What is this? -``` - -### Hybrid -``` -Kitchen -├─ Main Light -├─ Under Cabinet -└─ Island - -Master Bedroom -├─ Main Light -├─ Bedside Left -└─ Bedside Right -``` - -**Much clearer!** - ---- - -## Network Variable Efficiency - -### Current -- 15 rooms × 6 lights = 90 potential lights -- Most rooms use 2-3 lights -- **Waste**: ~60 unused light slots -- **Network traffic**: Sends all 90 values every 50ms - -### Hybrid -- Only define lights that exist -- Kitchen: 3 lights (not 6) -- Master Bedroom: 3 lights (not 6) -- **Efficiency**: ~50% less network traffic -- **Clarity**: Only relevant data - ---- - -## Scalability Comparison - -### Adding a New Light - -**Current**: -1. Find unused `l_X` slot in room -2. Hope it's available -3. Update documentation -4. Update HA manually - -**Hybrid**: -1. Add `new_light: struct_light_control` to room -2. Done! Auto-discovered in HA - -### Adding a New Room - -**Current**: -1. Add new room structure -2. Add all 6 light slots (even if unused) -3. Update network variables -4. Update HA manually - -**Hybrid**: -1. Add room with only needed lights -2. Done! Auto-discovered in HA - ---- - -## Implementation Example - -### Hybrid Structure in CODESYS - -```iec -TYPE struct_light_control : -STRUCT - on: BOOL; - off: BOOL; - toggle: BOOL; - state: BOOL; - relay_status: BOOL; -END_STRUCT -END_TYPE - -TYPE struct_room_lights : -STRUCT - main: struct_light_control; - secondary: struct_light_control; - task: struct_light_control; - ambient: struct_light_control; - accent: struct_light_control; - security: struct_light_control; -END_STRUCT -END_TYPE - -VAR_GLOBAL - rooms: STRUCT - kitchen: STRUCT - main: struct_light_control; - under_cabinet: struct_light_control; - island: struct_light_control; - END_STRUCT; - - master_bedroom: STRUCT - main: struct_light_control; - bedside_left: struct_light_control; - bedside_right: struct_light_control; - END_STRUCT; - - // ... other rooms - END_STRUCT; -END_VAR -``` - -### Node-RED Transformation - -```javascript -// Transform room-based to flat structure for HA -function transformToHA(msg) { - const rooms = msg.payload.rooms; - const result = {}; - - for (const [roomName, roomLights] of Object.entries(rooms)) { - for (const [lightName, lightControl] of Object.entries(roomLights)) { - const lightId = `${roomName}_${lightName}`; - result[lightId] = { - on: lightControl.on, - off: lightControl.off, - state: lightControl.state, - status: lightControl.relay_status - }; - } - } - - return { payload: result }; -} -``` - -### Home Assistant Auto-Discovery - -```yaml -# Node-RED publishes this automatically -homeassistant/light/kitchen_main/config: - name: "Kitchen Main" - unique_id: "kitchen_main" - state_topic: "homeassistant/light/kitchen_main/state" - command_topic: "homeassistant/light/kitchen_main/set" - state_value_template: "{{ value_json.state }}" - command_on_template: '{"on":true}' - command_off_template: '{"off":true}' -``` - ---- - -## Recommendation Summary - -**Use Hybrid Structure (Option 4)** because: - -1. ✅ **Best Organization**: Room-based in CODESYS, flat in HA -2. ✅ **Clear Naming**: `kitchen_main` vs `l_1` -3. ✅ **Efficiency**: Only define existing lights -4. ✅ **Scalability**: Easy to add lights/rooms -5. ✅ **Maintainability**: Self-documenting structure -6. ✅ **HA Integration**: Perfect for Home Assistant -7. ✅ **Future-Proof**: Easy to extend - -**Key Changes**: -- Replace `l_1`, `l_2` with descriptive names -- Use function-based naming: `main`, `secondary`, `task`, etc. -- Flatten structure in Node-RED for HA -- Only define lights that exist -- Add metadata for better organization - ---- - -**See**: [Full Structure Redesign Analysis](structure-redesign-analysis.md) for complete details. diff --git a/docs/redesign/structure-redesign-analysis.md b/docs/redesign/structure-redesign-analysis.md deleted file mode 100644 index c12c860..0000000 --- a/docs/redesign/structure-redesign-analysis.md +++ /dev/null @@ -1,538 +0,0 @@ -# Room/Light Structure Redesign Analysis - -## Current Structure Analysis - -### Current Organization - -**Room-Centric Approach**: -- 15 rooms, each with 1 `fb_switch` instance -- Each room controls up to 6 lights -- Network variables organized by room name -- Structure: `l_masterBedroom.l_1`, `l_masterBedroom.l_2`, etc. - -**Current Data Flow**: -``` -Room → fb_switch → 6 lights -Network Variable: l_masterBedroom (struct_lights with l_1 to l_6) -``` - -### Current Issues - -1. **Limited Scalability** - - Fixed 6 lights per room - - Adding a 7th light requires structure changes - - Some rooms may only need 1-2 lights (waste) - -2. **Home Assistant Organization** - - HA entities: `light.master_bedroom_l1`, `light.master_bedroom_l2` - - Not intuitive - hard to find lights - - No grouping by function or zone - -3. **Network Variable Efficiency** - - 15 room structures × 6 lights = 90 potential lights - - Most rooms don't use all 6 lights - - Wasted network bandwidth - -4. **Maintenance Complexity** - - Changes require updating room structures - - Hard to add new rooms - - No clear naming convention - -5. **MQTT Topic Structure** - - Topics like: `homeassistant/light/master_bedroom_l1` - - Not standardized - - Hard to discover and manage - -## Alternative Structures - -### Option 1: Flat Light-Centric Structure (Recommended) - -**Concept**: All lights in a single flat structure, organized by unique IDs. - -**Data Structure**: -```iec -TYPE struct_light : -STRUCT - // Control - on: BOOL; // Turn ON command - off: BOOL; // Turn OFF command - toggle: BOOL; // Toggle command (for Zigbee) - - // Status - state: BOOL; // Current state - relay_status: BOOL; // Actual relay status - - // Metadata (for reference, not in network var) - room: STRING; // Room name - name: STRING; // Light name - zone: STRING; // Zone (indoor/outdoor) -END_STRUCT -END_TYPE - -TYPE struct_all_lights : -STRUCT - // Master Bedroom - master_bedroom_main: struct_light; - master_bedroom_bedside: struct_light; - master_bedroom_closet: struct_light; - - // Kitchen - kitchen_ceiling: struct_light; - kitchen_under_cabinet: struct_light; - kitchen_island: struct_light; - - // Living Room - living_room_main: struct_light; - living_room_reading: struct_light; - living_room_tv: struct_light; - - // Outdoor - outdoor_front_porch: struct_light; - outdoor_back_patio: struct_light; - outdoor_side: struct_light; - - // ... all other lights -END_STRUCT -END_TYPE -``` - -**Network Variable**: -```iec -VAR_GLOBAL - lights: struct_all_lights; // All lights in one structure -END_VAR -``` - -**Function Block**: -```iec -FUNCTION_BLOCK fb_lightControl -VAR_INPUT - light: struct_light; - relay_output: BOOL; // Direct relay output -END_VAR -VAR_OUTPUT - relay_control: BOOL; - light_status: struct_light; -END_VAR -// Implementation... -END_FUNCTION_BLOCK -``` - -**Pros**: -- ✅ Each light is independent -- ✅ Easy to add/remove lights -- ✅ Clear naming: `lights.kitchen_ceiling.on` -- ✅ Better for Home Assistant (one entity per light) -- ✅ No wasted space -- ✅ Easy to group in HA by room/zone - -**Cons**: -- ⚠️ Larger structure (but more efficient overall) -- ⚠️ Need to update structure when adding lights - -**Home Assistant Organization**: -``` -light.kitchen_ceiling -light.kitchen_under_cabinet -light.master_bedroom_main -light.master_bedroom_bedside -``` - -**MQTT Topics**: -``` -homeassistant/light/kitchen_ceiling/state -homeassistant/light/kitchen_ceiling/set -homeassistant/light/master_bedroom_main/state -``` - ---- - -### Option 2: Hierarchical Room → Light Structure - -**Concept**: Keep room structure but make it more flexible. - -**Data Structure**: -```iec -TYPE struct_room : -STRUCT - room_id: STRING; // "master_bedroom" - room_name: STRING; // "Master Bedroom" - zone: STRING; // "indoor", "outdoor" - - // Flexible light array (up to 10 lights) - lights: ARRAY[1..10] OF struct_light; - light_count: INT; // Actual number of lights -END_STRUCT -END_TYPE - -TYPE struct_home : -STRUCT - rooms: ARRAY[1..20] OF struct_room; - room_count: INT; -END_STRUCT -END_TYPE -``` - -**Pros**: -- ✅ Maintains room grouping -- ✅ Flexible number of lights per room -- ✅ Can add rooms easily - -**Cons**: -- ⚠️ More complex array handling -- ⚠️ CODESYS arrays less efficient for network vars -- ⚠️ Harder to access specific lights - ---- - -### Option 3: Zone-Based Structure - -**Concept**: Organize by functional zones rather than rooms. - -**Data Structure**: -```iec -TYPE struct_zone : -STRUCT - zone_id: STRING; // "indoor_main", "outdoor_security" - zone_name: STRING; // "Indoor Main", "Outdoor Security" - - // Lights in this zone - lights: ARRAY[1..20] OF struct_light; - light_count: INT; -END_STRUCT -END_TYPE - -TYPE struct_home_zones : -STRUCT - indoor_main: struct_zone; - indoor_bedrooms: struct_zone; - indoor_bathrooms: struct_zone; - outdoor_security: struct_zone; - outdoor_ambient: struct_zone; -END_STRUCT -END_TYPE -``` - -**Zones**: -- **Indoor Main**: Kitchen, Living Room, Dining Room, Hallway -- **Indoor Bedrooms**: All bedrooms -- **Indoor Bathrooms**: All bathrooms -- **Outdoor Security**: Front, Back, Side (security-focused) -- **Outdoor Ambient**: Veranda, Patio (ambient lighting) - -**Pros**: -- ✅ Logical grouping for automation -- ✅ Easy to control all security lights -- ✅ Better for scheduling (indoor vs outdoor) - -**Cons**: -- ⚠️ Less intuitive for room-based control -- ⚠️ Lights may belong to multiple zones conceptually - ---- - -### Option 4: Hybrid: Room + Light ID Structure (Best Balance) - -**Concept**: Combine room organization with individual light control. - -**Data Structure**: -```iec -TYPE struct_light_control : -STRUCT - // Control - on: BOOL; - off: BOOL; - toggle: BOOL; - - // Status - state: BOOL; - relay_status: BOOL; - - // Metadata (for HA, not in network var) - light_id: STRING; // Unique ID: "mb_main", "kit_ceiling" - light_name: STRING; // Display name: "Main Light", "Ceiling Light" - room: STRING; // Room: "master_bedroom", "kitchen" - zone: STRING; // Zone: "indoor", "outdoor" - function: STRING; // Function: "main", "task", "ambient", "security" -END_STRUCT -END_TYPE - -// Organized by room for CODESYS, but flat for HA -TYPE struct_rooms : -STRUCT - // Master Bedroom - master_bedroom: STRUCT - main: struct_light_control; - bedside_left: struct_light_control; - bedside_right: struct_light_control; - closet: struct_light_control; - END_STRUCT; - - // Kitchen - kitchen: STRUCT - ceiling: struct_light_control; - under_cabinet: struct_light_control; - island: struct_light_control; - END_STRUCT; - - // ... other rooms -END_STRUCT -END_TYPE -``` - -**Network Variable**: -```iec -VAR_GLOBAL - rooms: struct_rooms; // Organized by room -END_VAR -``` - -**Access Pattern**: -- CODESYS: `rooms.kitchen.ceiling.on` -- Node-RED: Flatten to `kitchen_ceiling` for HA -- Home Assistant: `light.kitchen_ceiling` - -**Pros**: -- ✅ Best of both worlds -- ✅ Organized in CODESYS (by room) -- ✅ Flat in Home Assistant (by light) -- ✅ Easy to add lights to rooms -- ✅ Clear naming convention - -**Cons**: -- ⚠️ Requires Node-RED transformation -- ⚠️ Slightly more complex - ---- - -## Recommended Structure: Option 4 (Hybrid) - -### Implementation - -**1. CODESYS Structure** (Room-organized): -```iec -TYPE struct_light_control : -STRUCT - on: BOOL; - off: BOOL; - toggle: BOOL; - state: BOOL; - relay_status: BOOL; -END_STRUCT -END_TYPE - -TYPE struct_room_lights : -STRUCT - main: struct_light_control; - secondary: struct_light_control; - task: struct_light_control; - ambient: struct_light_control; - accent: struct_light_control; - security: struct_light_control; -END_STRUCT -END_TYPE - -VAR_GLOBAL - rooms: STRUCT - master_bedroom: struct_room_lights; - master_bathroom: struct_room_lights; - bedroom_1: struct_room_lights; - bedroom_2: struct_room_lights; - bathroom: struct_room_lights; - kitchen: struct_room_lights; - living_room: struct_room_lights; - dining_room: struct_room_lights; - hallway: struct_room_lights; - pantry: struct_room_lights; - entrance: struct_room_lights; - guest_wc: struct_room_lights; - outdoor_veranda: struct_room_lights; - outdoor_front: struct_room_lights; - outdoor_side: struct_room_lights; - outdoor_back: struct_room_lights; - END_STRUCT; -END_VAR -``` - -**2. Node-RED Transformation** (Flatten for HA): -```javascript -// Transform CODESYS structure to flat HA structure -function transformToHA(msg) { - const rooms = msg.payload.rooms; - const haLights = {}; - - // Flatten structure - for (const [roomName, roomLights] of Object.entries(rooms)) { - for (const [lightName, lightControl] of Object.entries(roomLights)) { - const lightId = `${roomName}_${lightName}`; - haLights[lightId] = { - on: lightControl.on, - off: lightControl.off, - state: lightControl.state, - status: lightControl.relay_status, - room: roomName, - light: lightName - }; - } - } - - return { payload: haLights }; -} -``` - -**3. Home Assistant Configuration**: -```yaml -# Auto-discovery via MQTT -light: - - platform: mqtt - name: "Kitchen Main" - unique_id: "kitchen_main" - state_topic: "homeassistant/light/kitchen_main/state" - command_topic: "homeassistant/light/kitchen_main/set" - # ... config -``` - -**4. MQTT Topic Structure**: -``` -homeassistant/light/{room}_{light}/state -homeassistant/light/{room}_{light}/set - -Examples: -homeassistant/light/kitchen_main/state -homeassistant/light/master_bedroom_main/state -homeassistant/light/outdoor_front_security/state -``` - ---- - -## Comparison Matrix - -| Aspect | Current | Option 1 (Flat) | Option 2 (Hierarchical) | Option 3 (Zone) | Option 4 (Hybrid) | -|--------|---------|-----------------|-------------------------|------------------|-------------------| -| **CODESYS Organization** | Room-based | Flat | Array-based | Zone-based | Room-based | -| **HA Organization** | Room-based | Flat | Complex | Zone-based | Flat (transformed) | -| **Scalability** | Poor | Excellent | Good | Good | Excellent | -| **Maintainability** | Medium | Excellent | Medium | Medium | Excellent | -| **Network Efficiency** | Poor | Good | Medium | Medium | Good | -| **Naming Clarity** | Medium | Excellent | Medium | Good | Excellent | -| **Implementation Complexity** | Low | Medium | High | Medium | Medium | -| **Flexibility** | Low | High | High | Medium | High | - ---- - -## Optimization Recommendations - -### 1. Network Variable Optimization - -**Current**: 15 rooms × 6 lights = 90 potential lights -**Optimized**: Only include lights that exist - -**Example**: -```iec -// Only define lights that exist -master_bedroom: STRUCT - main: struct_light_control; - bedside: struct_light_control; - // No closet light if it doesn't exist -END_STRUCT; -``` - -### 2. Function-Based Naming - -Instead of `l_1`, `l_2`, use descriptive names: -- `main` - Main/primary light -- `secondary` - Secondary light -- `task` - Task lighting -- `ambient` - Ambient/atmospheric -- `accent` - Accent lighting -- `security` - Security/outdoor - -### 3. Metadata for Home Assistant - -Add metadata structure (not in network var, but in Node-RED): -```javascript -const lightMetadata = { - "kitchen_main": { - name: "Kitchen Main Light", - room: "Kitchen", - zone: "indoor", - function: "main", - icon: "mdi:ceiling-light" - }, - // ... more lights -}; -``` - -### 4. Grouping in Home Assistant - -Use HA groups and areas: -```yaml -# Automatically group by room -light: - - platform: group - name: "Kitchen Lights" - entities: - - light.kitchen_main - - light.kitchen_under_cabinet - - light.kitchen_island - -# Use areas for room organization -areas: - - name: "Kitchen" - entities: - - light.kitchen_main - - light.kitchen_under_cabinet -``` - ---- - -## Migration Strategy - -### Phase 1: Structure Redesign -1. Define new data structures -2. Create mapping from old to new -3. Update function blocks - -### Phase 2: Node-RED Update -1. Update network variable mapping -2. Add transformation functions -3. Update MQTT topics - -### Phase 3: Home Assistant Migration -1. Create new entities with new structure -2. Migrate automations -3. Update dashboards - -### Phase 4: Testing & Validation -1. Test all control paths -2. Verify status feedback -3. Test HA integration - ---- - -## Final Recommendation - -**Use Option 4 (Hybrid Structure)** because: - -1. ✅ **Best Organization**: Room-based in CODESYS, flat in HA -2. ✅ **Scalability**: Easy to add lights to any room -3. ✅ **Clarity**: Descriptive names (`kitchen_main` vs `l_1`) -4. ✅ **Flexibility**: Can group by room, zone, or function -5. ✅ **Efficiency**: Only define lights that exist -6. ✅ **Maintainability**: Clear structure, easy to understand - -**Key Improvements**: -- Replace `l_1`, `l_2` with `main`, `secondary`, `task`, etc. -- Use descriptive light IDs: `kitchen_main`, `master_bedroom_bedside` -- Flatten structure in Node-RED for Home Assistant -- Add metadata for better HA organization -- Optimize network variables (only existing lights) - ---- - -**Next Steps**: -1. Review this analysis -2. Choose structure option -3. Plan migration -4. Implement new structure -5. Update Node-RED and HA