commit d98c44bfa317be713f0355034b64d751b018738c Author: nearxos Date: Tue May 26 21:21:56 2026 +0300 Initial commit: CODESYS tree, integration docs, Node-RED scripts. PLC application sources (NVL, rooms, boiler), HA/Node-RED integration guide, and NVL patch utilities. Co-authored-by: Cursor diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac5bff3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +*.swp +*.swo +*~ +.env +.env.* +*.pem +credentials.json diff --git a/codesys-tree/Device/Plc Logic/Application/0.Structures/struct_boiler_cmd.st b/codesys-tree/Device/Plc Logic/Application/0.Structures/struct_boiler_cmd.st new file mode 100644 index 0000000..1b0bbd6 --- /dev/null +++ b/codesys-tree/Device/Plc Logic/Application/0.Structures/struct_boiler_cmd.st @@ -0,0 +1,18 @@ +(* === DECLARATION === *) +TYPE struct_boiler_cmd : +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) + + // Safety Input + emergency_stop: BOOL; // Emergency stop (physical button or HA) + + // Configuration (can be set via Node-RED) + max_on_time_minutes: INT; // Maximum ON time in minutes (default: 480 = 8 hours) +END_STRUCT +END_TYPE + +(* === IMPLEMENTATION === *) diff --git a/codesys-tree/Device/Plc Logic/Application/0.Structures/struct_boiler_status.st b/codesys-tree/Device/Plc Logic/Application/0.Structures/struct_boiler_status.st new file mode 100644 index 0000000..8483b98 --- /dev/null +++ b/codesys-tree/Device/Plc Logic/Application/0.Structures/struct_boiler_status.st @@ -0,0 +1,22 @@ +(* === DECLARATION === *) +TYPE struct_boiler_status : +STRUCT + // State + state: BOOL; // Current boiler state (ON/OFF) + relay_output: BOOL; // Actual relay output state + + // Runtime Info + on_time_minutes: INT; // Current ON time in minutes + remaining_minutes: INT; // Remaining time before auto-shutoff + + // Safety Status + emergency_active: BOOL; // Emergency stop is active + time_limit_reached: BOOL; // Max time limit was reached + + // Error Handling + error_state: BOOL; // Error detected + error_code: INT; // Error code (0=none, 1=emergency, 2=time limit) +END_STRUCT +END_TYPE + +(* === IMPLEMENTATION === *) diff --git a/codesys-tree/Device/Plc Logic/Application/0.Structures/struct_lights.st b/codesys-tree/Device/Plc Logic/Application/0.Structures/struct_lights.st new file mode 100644 index 0000000..5580eb5 --- /dev/null +++ b/codesys-tree/Device/Plc Logic/Application/0.Structures/struct_lights.st @@ -0,0 +1,13 @@ +(* === DECLARATION === *) +TYPE struct_lights: +STRUCT + l_1 : BOOL; + l_2 : BOOL; + l_3 : BOOL; + l_4 : BOOL; + l_5 : BOOL; + l_6 : BOOL; +END_STRUCT +END_TYPE + +(* === IMPLEMENTATION === *) diff --git a/codesys-tree/Device/Plc Logic/Application/0.Structures/struct_room_cmds.st b/codesys-tree/Device/Plc Logic/Application/0.Structures/struct_room_cmds.st new file mode 100644 index 0000000..be58a1b --- /dev/null +++ b/codesys-tree/Device/Plc Logic/Application/0.Structures/struct_room_cmds.st @@ -0,0 +1,32 @@ +(* === DECLARATION === *) +TYPE struct_room_cmds : +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 + +(* === IMPLEMENTATION === *) diff --git a/codesys-tree/Device/Plc Logic/Application/0.Structures/struct_room_outs.st b/codesys-tree/Device/Plc Logic/Application/0.Structures/struct_room_outs.st new file mode 100644 index 0000000..7688be8 --- /dev/null +++ b/codesys-tree/Device/Plc Logic/Application/0.Structures/struct_room_outs.st @@ -0,0 +1,21 @@ +(* === DECLARATION === *) +TYPE struct_room_outs : +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 + +(* === IMPLEMENTATION === *) diff --git a/codesys-tree/Device/Plc Logic/Application/0.Structures/struct_switches.st b/codesys-tree/Device/Plc Logic/Application/0.Structures/struct_switches.st new file mode 100644 index 0000000..bd7888d --- /dev/null +++ b/codesys-tree/Device/Plc Logic/Application/0.Structures/struct_switches.st @@ -0,0 +1,15 @@ +(* === DECLARATION === *) +TYPE struct_switches : +STRUCT + all_off:BOOL; + all_on:BOOL; + sw_1:BOOL; + sw_2:BOOL; + sw_3:BOOL; + sw_4:BOOL; + sw_5:BOOL; + sw_6:BOOL; +END_STRUCT +END_TYPE + +(* === IMPLEMENTATION === *) diff --git a/codesys-tree/Device/Plc Logic/Application/0.Structures/struct_zones.st b/codesys-tree/Device/Plc Logic/Application/0.Structures/struct_zones.st new file mode 100644 index 0000000..bd94505 --- /dev/null +++ b/codesys-tree/Device/Plc Logic/Application/0.Structures/struct_zones.st @@ -0,0 +1,23 @@ +(* === DECLARATION === *) +TYPE struct_zones : +STRUCT + masterBedroom: struct_room_outs; + masterBathroom: struct_room_outs; + bedroom_1: struct_room_outs; + bedroom_2: struct_room_outs; + bathroom: struct_room_outs; + guest_wc: struct_room_outs; + kitchen: struct_room_outs; + pantry: struct_room_outs; + livingRoom: struct_room_outs; + diningRoom: struct_room_outs; + entrance: struct_room_outs; + hallway: struct_room_outs; + veranda: struct_room_outs; + front: struct_room_outs; + back: struct_room_outs; + side: struct_room_outs; +END_STRUCT +END_TYPE + +(* === IMPLEMENTATION === *) diff --git a/codesys-tree/Device/Plc Logic/Application/1.Variables/GVL.st b/codesys-tree/Device/Plc Logic/Application/1.Variables/GVL.st new file mode 100644 index 0000000..67f43e1 --- /dev/null +++ b/codesys-tree/Device/Plc Logic/Application/1.Variables/GVL.st @@ -0,0 +1,8 @@ +(* === DECLARATION === *) +{attribute 'qualified_only'} +VAR_GLOBAL + // ---------- Option C: relay feedback (copy of output) ---------- + +END_VAR + +(* === IMPLEMENTATION === *) diff --git a/codesys-tree/Device/Plc Logic/Application/1.Variables/NVL_Receiver.st b/codesys-tree/Device/Plc Logic/Application/1.Variables/NVL_Receiver.st new file mode 100644 index 0000000..ede0014 --- /dev/null +++ b/codesys-tree/Device/Plc Logic/Application/1.Variables/NVL_Receiver.st @@ -0,0 +1,26 @@ +(* === DECLARATION === *) +//This gobal variable list is received via the network. +//Protocol: UDP +//Layout matches Node-RED nvl-send (no legacy struct_switches — removed on PLC) + +{attribute 'qualified_only'} +VAR_GLOBAL + cmd_livingRoom : struct_room_cmds; + cmd_dinningRoom : struct_room_cmds; + cmd_kitchenArea : struct_room_cmds; + cmd_hallway : struct_room_cmds; + cmd_office : struct_room_cmds; + cmd_bedroom : struct_room_cmds; + cmd_masterBedroom : struct_room_cmds; + cmd_masterBath : struct_room_cmds; + cmd_shower : struct_room_cmds; + cmd_guestWc : struct_room_cmds; + cmd_outFront : struct_room_cmds; + cmd_coveredVeranda : struct_room_cmds; + cmd_outYard : struct_room_cmds; + cmd_outBack : struct_room_cmds; + boiler_cmd : struct_boiler_cmd; + +END_VAR + +(* === IMPLEMENTATION === *) diff --git a/codesys-tree/Device/Plc Logic/Application/1.Variables/NVL_Sender.st b/codesys-tree/Device/Plc Logic/Application/1.Variables/NVL_Sender.st new file mode 100644 index 0000000..d3ace7d --- /dev/null +++ b/codesys-tree/Device/Plc Logic/Application/1.Variables/NVL_Sender.st @@ -0,0 +1,23 @@ +(* === DECLARATION === *) +{attribute 'qualified_only'} + +VAR_GLOBAL + light_livingRoom : struct_room_outs; + light_dinningRoom : struct_room_outs; + light_kitchenArea : struct_room_outs; + light_hallway : struct_room_outs; + light_office : struct_room_outs; + light_bedroom : struct_room_outs; + light_masterBedroom : struct_room_outs; + light_masterBath : struct_room_outs; + light_shower : struct_room_outs; + light_guestWc : struct_room_outs; + light_outFront : struct_room_outs; + light_coveredVeranda : struct_room_outs; + light_outYard : struct_room_outs; + light_outBack : struct_room_outs; + boiler_status : struct_boiler_status; + +END_VAR + +(* === IMPLEMENTATION === *) diff --git a/codesys-tree/Device/Plc Logic/Application/3.Function Blocks/fb_boiler.st b/codesys-tree/Device/Plc Logic/Application/3.Function Blocks/fb_boiler.st new file mode 100644 index 0000000..2a34952 --- /dev/null +++ b/codesys-tree/Device/Plc Logic/Application/3.Function Blocks/fb_boiler.st @@ -0,0 +1,151 @@ +(* === DECLARATION === *) +FUNCTION_BLOCK fb_boiler +VAR_INPUT + // Control Commands + ha_on: BOOL; // Home Assistant ON command + ha_off: BOOL; // Home Assistant OFF command + schedule_on: BOOL; // Scheduled ON + schedule_off: BOOL; // Scheduled OFF + + // Safety + emergency_stop: BOOL; // Emergency stop input + + // Configuration + max_on_time: TIME := T#8H; // Maximum ON time (default 8 hours) +END_VAR +VAR_OUTPUT + // Outputs + relay_control: BOOL; // Control output to relay + + // Status + state: BOOL; // Current state + on_time_seconds: DINT; // Current ON time in seconds + remaining_seconds: DINT; // Remaining time in seconds + + // Safety Status + emergency_active: BOOL; // Emergency stop active + time_limit_reached: BOOL; // Time limit was reached + error_state: BOOL; // Error flag + error_code: INT; // Error code +END_VAR +VAR + // Internal State + internal_state: BOOL := FALSE; + last_state: BOOL := FALSE; + + // 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; + f_trig_emergency: F_TRIG; + + // Timers + on_timer: TON; // Counts ON time + + // Time tracking + max_on_seconds: DINT; +END_VAR + +(* === IMPLEMENTATION === *) +// ===================================================== +// fb_boiler - Implementation +// ===================================================== +// Priority (highest to lowest): +// 1. Emergency Stop - Immediate shutdown +// 2. Time Limit Exceeded - Auto shutdown +// 3. OFF Commands - Turn off +// 4. ON Commands - Turn on (if safe) +// ===================================================== + +// Calculate max time in seconds +max_on_seconds := TIME_TO_DINT(max_on_time) / 1000; + +// Edge detection for commands +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); +f_trig_emergency(CLK := emergency_stop); + +// ===================================================== +// SAFETY CHECKS (highest priority) +// ===================================================== + +// Priority 1: Emergency Stop +IF emergency_stop THEN + internal_state := FALSE; + emergency_active := TRUE; + error_state := TRUE; + error_code := 1; // Emergency stop active + +// Priority 2: Time Limit Check +ELSIF on_timer.Q THEN + internal_state := FALSE; + time_limit_reached := TRUE; + error_state := TRUE; + error_code := 2; // Time limit exceeded + +// ===================================================== +// CONTROL LOGIC (only when safe) +// ===================================================== +ELSE + // Clear safety flags when emergency released + IF f_trig_emergency.Q THEN + emergency_active := FALSE; + error_state := FALSE; + error_code := 0; + END_IF; + + // Clear time limit flag when boiler turned off + IF NOT internal_state THEN + time_limit_reached := FALSE; + IF error_code = 2 THEN + error_state := FALSE; + error_code := 0; + END_IF; + END_IF; + + // Priority 3: OFF Commands (HA OFF or Schedule OFF) + IF r_trig_ha_off.Q OR r_trig_schedule_off.Q THEN + internal_state := FALSE; + + // Priority 4: ON Commands (HA ON or Schedule ON) + ELSIF (r_trig_ha_on.Q OR r_trig_schedule_on.Q) AND NOT time_limit_reached THEN + internal_state := TRUE; + END_IF; +END_IF; + +// ===================================================== +// TIMER MANAGEMENT +// ===================================================== + +// ON timer - counts total ON time +on_timer( + IN := internal_state AND NOT emergency_active, + PT := max_on_time +); + +// Calculate current ON time in seconds +IF internal_state THEN + on_time_seconds := TIME_TO_DINT(on_timer.ET) / 1000; + remaining_seconds := max_on_seconds - on_time_seconds; + IF remaining_seconds < 0 THEN + remaining_seconds := 0; + END_IF; +ELSE + on_time_seconds := 0; + remaining_seconds := max_on_seconds; +END_IF; + +// ===================================================== +// OUTPUT ASSIGNMENT +// ===================================================== + +// Relay control - only ON when state is ON and no emergency +relay_control := internal_state AND NOT emergency_active; + +// State output +state := internal_state; diff --git a/codesys-tree/Device/Plc Logic/Application/3.Function Blocks/fb_light.st b/codesys-tree/Device/Plc Logic/Application/3.Function Blocks/fb_light.st new file mode 100644 index 0000000..4728209 --- /dev/null +++ b/codesys-tree/Device/Plc Logic/Application/3.Function Blocks/fb_light.st @@ -0,0 +1,57 @@ +(* === DECLARATION === *) +FUNCTION_BLOCK fb_light +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 triggers + r_trig_ha_on: R_TRIG; + r_trig_ha_off: R_TRIG; + r_trig_zigbee: R_TRIG; + + // Internal state + light_state: BOOL := FALSE; +END_VAR + +(* === IMPLEMENTATION === *) +// ===================================================== +// fb_light - Implementation (per redesign) +// ===================================================== +// Priority (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 +// ===================================================== + +// Edge detection for commands +r_trig_ha_on(CLK := ha_on); +r_trig_ha_off(CLK := ha_off); +r_trig_zigbee(CLK := zigbee_sw); + +// State machine +IF r_trig_ha_off.Q THEN + light_state := FALSE; +ELSIF r_trig_ha_on.Q THEN + light_state := TRUE; +ELSIF r_trig_zigbee.Q THEN + light_state := NOT light_state; +END_IF; + +// Output assignment +light_output := light_state; + +// Status feedback (read from actual relay) +light_status := relay_status; diff --git a/codesys-tree/Device/Plc Logic/Application/3.Function Blocks/fb_room.st b/codesys-tree/Device/Plc Logic/Application/3.Function Blocks/fb_room.st new file mode 100644 index 0000000..9c3ecbe --- /dev/null +++ b/codesys-tree/Device/Plc Logic/Application/3.Function Blocks/fb_room.st @@ -0,0 +1,58 @@ +(* === DECLARATION === *) +FUNCTION_BLOCK fb_room +VAR_INPUT + switches: struct_room_cmds; + relay_status: struct_room_outs; // Read from EtherCAT outputs (actual relay states) +END_VAR +VAR_OUTPUT + lights: struct_room_outs; +END_VAR +VAR + l1: fb_light; + l2: fb_light; + l3: fb_light; + l4: fb_light; + l5: fb_light; + l6: fb_light; +END_VAR + +(* === IMPLEMENTATION === *) +// ===================================================== +// fb_room - Implementation (per redesign) +// ===================================================== + +// Light 1..6 (relay_status = previous cycle output for Option C) +l1(ha_on := switches.ha_l1_on, ha_off := switches.ha_l1_off, zigbee_sw := switches.zigbee_sw1, relay_status := relay_status.l_1); +lights.l_1 := l1.light_output; + +l2(ha_on := switches.ha_l2_on, ha_off := switches.ha_l2_off, zigbee_sw := switches.zigbee_sw2, relay_status := relay_status.l_2); +lights.l_2 := l2.light_output; + +l3(ha_on := switches.ha_l3_on, ha_off := switches.ha_l3_off, zigbee_sw := switches.zigbee_sw3, relay_status := relay_status.l_3); +lights.l_3 := l3.light_output; + +l4(ha_on := switches.ha_l4_on, ha_off := switches.ha_l4_off, zigbee_sw := switches.zigbee_sw4, relay_status := relay_status.l_4); +lights.l_4 := l4.light_output; + +l5(ha_on := switches.ha_l5_on, ha_off := switches.ha_l5_off, zigbee_sw := switches.zigbee_sw5, relay_status := relay_status.l_5); +lights.l_5 := l5.light_output; + +l6(ha_on := switches.ha_l6_on, ha_off := switches.ha_l6_off, zigbee_sw := switches.zigbee_sw6, relay_status := relay_status.l_6); +lights.l_6 := l6.light_output; + +// Global Commands (overwrite outputs per redesign) +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; diff --git a/codesys-tree/Device/Plc Logic/Application/3.Function Blocks/fb_switch.st b/codesys-tree/Device/Plc Logic/Application/3.Function Blocks/fb_switch.st new file mode 100644 index 0000000..e01b070 --- /dev/null +++ b/codesys-tree/Device/Plc Logic/Application/3.Function Blocks/fb_switch.st @@ -0,0 +1,20 @@ +(* === DECLARATION === *) +FUNCTION_BLOCK fb_switch +VAR_INPUT + switches: struct_switches; +END_VAR +VAR_OUTPUT + lights: struct_lights; +END_VAR +VAR + all_off: fb_toogleButton; + all_on: fb_toogleButton; + sw_1: fb_toogleButton; + sw_2: fb_toogleButton; + sw_3: fb_toogleButton; + sw_4: fb_toogleButton; + sw_5: fb_toogleButton; + sw_6: fb_toogleButton; +END_VAR + +(* === IMPLEMENTATION === *) diff --git a/codesys-tree/Device/Plc Logic/Application/3.Function Blocks/fb_toogleButton.st b/codesys-tree/Device/Plc Logic/Application/3.Function Blocks/fb_toogleButton.st new file mode 100644 index 0000000..2ac736d --- /dev/null +++ b/codesys-tree/Device/Plc Logic/Application/3.Function Blocks/fb_toogleButton.st @@ -0,0 +1,25 @@ +(* === DECLARATION === *) +FUNCTION_BLOCK fb_toogleButton +VAR_INPUT + +Input :BOOL; +END_VAR +VAR_OUTPUT +Output :BOOL; + +END_VAR +VAR + Switch: BOOL; + Light: BOOL; + Flag1: BOOL; + Flag2: BOOL; + RT: R_TRIG; + Pulse: TP; + CTU_0: CTU; + CTD_0: CTD; + CTU_1: CTU; + CTU_2: CTU; + TOF_0: TOF; +END_VAR + +(* === IMPLEMENTATION === *) diff --git a/codesys-tree/Device/Plc Logic/Application/4.Programs/Boiler_PRG.st b/codesys-tree/Device/Plc Logic/Application/4.Programs/Boiler_PRG.st new file mode 100644 index 0000000..dc1b81a --- /dev/null +++ b/codesys-tree/Device/Plc Logic/Application/4.Programs/Boiler_PRG.st @@ -0,0 +1,33 @@ +(* === DECLARATION === *) +(* Water heater — fb_boiler; coil mapped to NVL_Sender.boiler_status.relay_output (%QX5.4). *) +PROGRAM Boiler_PRG +VAR + heater : fb_boiler; + max_on_minutes : INT; + max_on_time : TIME; +END_VAR + +(* === IMPLEMENTATION === *) +max_on_minutes := NVL_Receiver.boiler_cmd.max_on_time_minutes; +IF max_on_minutes <= 0 THEN + max_on_minutes := 480; +END_IF; +max_on_time := T#1M * max_on_minutes; + +heater( + ha_on := NVL_Receiver.boiler_cmd.ha_on, + ha_off := NVL_Receiver.boiler_cmd.ha_off, + schedule_on := NVL_Receiver.boiler_cmd.schedule_on, + schedule_off := NVL_Receiver.boiler_cmd.schedule_off, + emergency_stop := NVL_Receiver.boiler_cmd.emergency_stop, + max_on_time := max_on_time +); + +NVL_Sender.boiler_status.state := heater.state; +NVL_Sender.boiler_status.relay_output := heater.relay_control; +NVL_Sender.boiler_status.on_time_minutes := DINT_TO_INT(heater.on_time_seconds / 60); +NVL_Sender.boiler_status.remaining_minutes := DINT_TO_INT(heater.remaining_seconds / 60); +NVL_Sender.boiler_status.emergency_active := heater.emergency_active; +NVL_Sender.boiler_status.time_limit_reached := heater.time_limit_reached; +NVL_Sender.boiler_status.error_state := heater.error_state; +NVL_Sender.boiler_status.error_code := heater.error_code; diff --git a/codesys-tree/Device/Plc Logic/Application/4.Programs/Lights.st b/codesys-tree/Device/Plc Logic/Application/4.Programs/Lights.st new file mode 100644 index 0000000..bc6bae9 --- /dev/null +++ b/codesys-tree/Device/Plc Logic/Application/4.Programs/Lights.st @@ -0,0 +1,22 @@ +(* === DECLARATION === *) +PROGRAM Lights +VAR + 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; +END_VAR + +(* === IMPLEMENTATION === *) diff --git a/codesys-tree/Device/Plc Logic/Application/4.Programs/Lights_new.st b/codesys-tree/Device/Plc Logic/Application/4.Programs/Lights_new.st new file mode 100644 index 0000000..c5db4b5 --- /dev/null +++ b/codesys-tree/Device/Plc Logic/Application/4.Programs/Lights_new.st @@ -0,0 +1,116 @@ +(* === DECLARATION === *) +(* Room lighting (redesign). Shower channels l_1..l_5 only — l_6 is water heater (Boiler_PRG). *) +PROGRAM Lights_new +VAR + Living_Room: FB_ROOM; + Dininng_Room: fb_room; + Kitchen_Area: fb_room; + Hallway: fb_room; + Office: fb_room; + Bedroom: fb_room; + Master_Bedroom: fb_room; + Master_bath: fb_room; + Shower: fb_room; + Guest_wc: fb_room; + Outside_front: fb_room; + Covered_Veranda: fb_room; + Yard: fb_room; + Outside_back: fb_room; +END_VAR + +(* === IMPLEMENTATION === *) +Living_Room( + switches := NVL_Receiver.cmd_livingRoom, + relay_status := NVL_Sender.light_livingRoom +); +NVL_Sender.light_livingRoom := Living_Room.lights; + +Dininng_Room( + switches := NVL_Receiver.cmd_dinningRoom, + relay_status := NVL_Sender.light_dinningRoom +); +NVL_Sender.light_dinningRoom := Dininng_Room.lights; + +Kitchen_Area( + switches := NVL_Receiver.cmd_kitchenArea, + relay_status := NVL_Sender.light_kitchenArea +); +NVL_Sender.light_kitchenArea := Kitchen_Area.lights; + +Hallway( + switches := NVL_Receiver.cmd_hallway, + relay_status := NVL_Sender.light_hallway +); +NVL_Sender.light_hallway := Hallway.lights; + +Office( + switches := NVL_Receiver.cmd_office, + relay_status := NVL_Sender.light_office +); +NVL_Sender.light_office := Office.lights; + +Bedroom( + switches := NVL_Receiver.cmd_bedroom, + relay_status := NVL_Sender.light_bedroom +); +NVL_Sender.light_bedroom := Bedroom.lights; + +Master_Bedroom( + switches := NVL_Receiver.cmd_masterBedroom, + relay_status := NVL_Sender.light_masterBedroom +); +NVL_Sender.light_masterBedroom := Master_Bedroom.lights; + +Master_bath( + switches := NVL_Receiver.cmd_masterBath, + relay_status := NVL_Sender.light_masterBath +); +NVL_Sender.light_masterBath := Master_bath.lights; + +Shower( + switches := NVL_Receiver.cmd_shower, + relay_status := NVL_Sender.light_shower +); +// l_6 not used for shower — water heater uses boiler_status.relay_output (%QX5.4) +NVL_Sender.light_shower.l_6 := FALSE; +NVL_Sender.light_shower.l_6_status := FALSE; +NVL_Sender.light_shower.l_1 := Shower.lights.l_1; +NVL_Sender.light_shower.l_2 := Shower.lights.l_2; +NVL_Sender.light_shower.l_3 := Shower.lights.l_3; +NVL_Sender.light_shower.l_4 := Shower.lights.l_4; +NVL_Sender.light_shower.l_5 := Shower.lights.l_5; +NVL_Sender.light_shower.l_1_status := Shower.lights.l_1; +NVL_Sender.light_shower.l_2_status := Shower.lights.l_2; +NVL_Sender.light_shower.l_3_status := Shower.lights.l_3; +NVL_Sender.light_shower.l_4_status := Shower.lights.l_4; +NVL_Sender.light_shower.l_5_status := Shower.lights.l_5; + +Guest_wc( + switches := NVL_Receiver.cmd_guestWc, + relay_status := NVL_Sender.light_guestWc +); +NVL_Sender.light_guestWc := Guest_wc.lights; + +Outside_front( + switches := NVL_Receiver.cmd_outFront, + relay_status := NVL_Sender.light_outFront +); +NVL_Sender.light_outFront := Outside_front.lights; + +Covered_Veranda( + switches := NVL_Receiver.cmd_coveredVeranda, + relay_status := NVL_Sender.light_coveredVeranda +); +NVL_Sender.light_coveredVeranda := Covered_Veranda.lights; + +Yard( + switches := NVL_Receiver.cmd_outYard, + relay_status := NVL_Sender.light_outYard +); +NVL_Sender.light_outYard := Yard.lights; + +Outside_back( + switches := NVL_Receiver.cmd_outBack, + relay_status := NVL_Sender.light_outBack +); +NVL_Sender.light_outBack := Outside_back.lights; diff --git a/codesys-tree/Device/Plc Logic/Application/PLC_PRG.st b/codesys-tree/Device/Plc Logic/Application/PLC_PRG.st new file mode 100644 index 0000000..2acb1e0 --- /dev/null +++ b/codesys-tree/Device/Plc Logic/Application/PLC_PRG.st @@ -0,0 +1,10 @@ +(* === DECLARATION === *) +PROGRAM PLC_PRG +VAR + masterBedroom: fb_switch; +END_VAR + +(* === IMPLEMENTATION === *) +// Room lights first; Boiler_PRG last (dedicated boiler_status.relay_output, not shower l_6). +Lights_new(); +Boiler_PRG(); diff --git a/codesys-tree/README.md b/codesys-tree/README.md new file mode 100644 index 0000000..f1fd8d2 --- /dev/null +++ b/codesys-tree/README.md @@ -0,0 +1,118 @@ +# Local CODESYS Tree + +Local mirror of the CODESYS project staged at: + +`C:\codesys-projects\Codesys_Home_Automation\Home_Automation.project` + +This tree was generated from the `read_project_inventory` MCP call so you can edit +POUs, GVLs, DUTs and Programs directly in Cursor on macOS, then push the changes +back to the Windows VM with the `sync_project` MCP tool. + +## Layout + +Mirrors the CODESYS POU pane: + +``` +Device/ + Plc Logic/ + Application/ + PLC_PRG.st + 0.Structures/ - DUTs (struct_*) + 1.Variables/ - GVLs (NVL_Sender, NVL_Receiver, GVL) + 2.Functions/ - (empty) + 3.Function Blocks/ + 4.Programs/ +``` + +Hardware / non-textual nodes (`Device`, `EtherCAT_Master`, `EK1100`, `K1..K4`, +`GPIOs_A_B`, `Onewire`, `Camera device`, `SPI`, `I?C`, `Task Configuration`, +`Library Manager`, `Project Settings`, `__VisualizationStyle`) are **not** mirrored +as files — they live only in the .project XML and are listed in +`_manifest.json` under `non_textual_tree` for reference. + +## File format (.st files) + +Each file contains a single CODESYS object. Two sections are delimited by sentinel +banner comments so the splitter is unambiguous: + +``` +(* === DECLARATION === *) + + +(* === IMPLEMENTATION === *) + +``` + +If a section is empty, leave the line after its banner blank — do not delete the +banner. The sync helper expects both banners to be present. + +## Manifest + +`_manifest.json` maps every `.st` file back to its CODESYS `object_path` +(e.g. `Device / Plc Logic / Application / 3.Function Blocks / fb_light`) and +records whether the object originally had a declaration and/or implementation. +This is what the sync step uses to build the `textual_updates` payload. + +## I/O snapshot + +`_io.json` is the raw output of `read_device_io` (all devices + channels + +variable mappings + IEC addresses). + +`_io.md` is the human-readable summary: per-device tables, current +mappings against `NVL_Sender.*`, plus a "findings worth flagging" section +that highlights duplicates, unmapped channels, and gaps between the FB logic +and the physical wiring. + +Refresh both with `read_device_io` then regenerate. The IO snapshot is +**read-only here** — to push mapping changes back, use the `write_io_mapping` +tool (or the `io_mappings` list inside `sync_project`). + +## Workflow + +### 1. Edit locally + +Open any `.st` file in Cursor and edit either the declaration or the implementation +section. Don't move the banner lines. + +### 2. Dry-run sync (preview) + +Call the MCP tool `sync_project` with `dry_run: true` and a `textual_updates` +list built from the files you changed. Each entry looks like: + +```json +{ + "object_path": "Device / Plc Logic / Application / 3.Function Blocks / fb_light", + "declaration": "FUNCTION_BLOCK fb_light\nVAR_INPUT\n ...\nEND_VAR", + "implementation": "// ST body here" +} +``` + +### 3. Apply + +Re-run `sync_project` with `dry_run: false`. The agent opens the project once, +applies all updates in a single open/save cycle, and reports back per-object +status. + +### 4. Build + +After a successful sync, run `build_project` to verify the project still +compiles on the VM. + +## Re-syncing from CODESYS → local + +If the project changes on the Windows side, re-run `read_project_inventory` and +regenerate the affected `.st` files plus `_manifest.json`. Treat the VM project +file as the source of truth for the **structure** (folders, object kinds, IO +mappings); treat the local tree as the source of truth only for content you've +edited here. + +## Not included + +These items live in the project but are not mirrored as files: + +- `Library Manager` (library refs — managed in CODESYS UI) +- `Task Configuration` / `MainTask` / `EtherCAT_Task` (task settings) +- Device / fieldbus nodes (`Device`, `EtherCAT_Master`, `EK1100`, `K1..K4`, + `GPIOs_A_B`, `Onewire`, `Camera device`, `SPI`, `I?C`) — manage via + `manage_device`, `read_device_io`, `write_io_mapping`. +- `Project Settings`, `SoftMotion General Axis Pool`, `__VisualizationStyle`. diff --git a/codesys-tree/_io.json b/codesys-tree/_io.json new file mode 100644 index 0000000..c76e84a --- /dev/null +++ b/codesys-tree/_io.json @@ -0,0 +1,2204 @@ +{ + "project_path": "C:\\codesys-projects\\Codesys_Home_Automation\\Home_Automation.project", + "ok": true, + "warnings": [], + "device_count": 9, + "channels_only": true, + "devices": [ + { + "parameters": [], + "name": "Device", + "device_id": { + "version": "4.15.0.0", + "id": "0000 0011", + "type": 4102 + }, + "parameter_count": 0, + "path": "Device", + "connectors": [ + { + "interface": "Common.PCI", + "role": "parent", + "host_parameters": [], + "connector_id": 0 + }, + { + "interface": "GPIOSysfs", + "role": "parent", + "host_parameters": [], + "connector_id": 1 + }, + { + "interface": "Common.SoftMotion.General", + "role": "parent", + "host_parameters": [], + "connector_id": 2 + }, + { + "interface": "Raspberry.GPIOs", + "role": "parent", + "host_parameters": [], + "connector_id": 3 + }, + { + "interface": "SafetyInterface", + "role": "parent", + "host_parameters": [], + "connector_id": 4 + }, + { + "interface": "Raspberry.OnewireMaster", + "role": "parent", + "host_parameters": [], + "connector_id": 5 + }, + { + "interface": "Raspberry.Camera2", + "role": "parent", + "host_parameters": [], + "connector_id": 6 + }, + { + "interface": "Raspberry.SPIMaster", + "role": "parent", + "host_parameters": [], + "connector_id": 7 + }, + { + "interface": "Raspberry.I2CMaster", + "role": "parent", + "host_parameters": [], + "connector_id": 8 + } + ] + }, + { + "parameters": [], + "name": "GPIOs_A_B", + "device_id": { + "version": "4.15.0.0", + "id": "0000 0000", + "type": 504 + }, + "parameter_count": 0, + "path": "Device/GPIOs_A_B", + "connectors": [ + { + "interface": "Raspberry.GPIOs", + "role": "child", + "host_parameters": [ + { + "bit_size": 32, + "name": "digital inputs (GPIO0..GPIO31)", + "iec_type": "", + "id": 1000, + "channel_type": "input", + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%ID1" + }, + "type_string": "DWORD", + "sub_elements": [ + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX4.0" + }, + "name": "Bit0", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX4.1" + }, + "name": "Bit1", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX4.2" + }, + "name": "Bit2", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX4.3" + }, + "name": "Bit3", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX4.4" + }, + "name": "Bit4", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX4.5" + }, + "name": "Bit5", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX4.6" + }, + "name": "Bit6", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX4.7" + }, + "name": "Bit7", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX5.0" + }, + "name": "Bit8", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX5.1" + }, + "name": "Bit9", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX5.2" + }, + "name": "Bit10", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX5.3" + }, + "name": "Bit11", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX5.4" + }, + "name": "Bit12", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX5.5" + }, + "name": "Bit13", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX5.6" + }, + "name": "Bit14", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX5.7" + }, + "name": "Bit15", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX6.0" + }, + "name": "Bit16", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX6.1" + }, + "name": "Bit17", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX6.2" + }, + "name": "Bit18", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX6.3" + }, + "name": "Bit19", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX6.4" + }, + "name": "Bit20", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX6.5" + }, + "name": "Bit21", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX6.6" + }, + "name": "Bit22", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX6.7" + }, + "name": "Bit23", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX7.0" + }, + "name": "Bit24", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX7.1" + }, + "name": "Bit25", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX7.2" + }, + "name": "Bit26", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX7.3" + }, + "name": "Bit27", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX7.4" + }, + "name": "Bit28", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX7.5" + }, + "name": "Bit29", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX7.6" + }, + "name": "Bit30", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX7.7" + }, + "name": "Bit31", + "iec_type": "" + } + ], + "section": "" + }, + { + "bit_size": 32, + "name": "digital outputs (GPIO0..GPIO31)", + "iec_type": "", + "id": 2000, + "channel_type": "output", + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QD2" + }, + "type_string": "DWORD", + "sub_elements": [ + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX8.0" + }, + "name": "Bit0", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX8.1" + }, + "name": "Bit1", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX8.2" + }, + "name": "Bit2", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX8.3" + }, + "name": "Bit3", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX8.4" + }, + "name": "Bit4", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX8.5" + }, + "name": "Bit5", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX8.6" + }, + "name": "Bit6", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX8.7" + }, + "name": "Bit7", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX9.0" + }, + "name": "Bit8", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX9.1" + }, + "name": "Bit9", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX9.2" + }, + "name": "Bit10", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX9.3" + }, + "name": "Bit11", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX9.4" + }, + "name": "Bit12", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX9.5" + }, + "name": "Bit13", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX9.6" + }, + "name": "Bit14", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX9.7" + }, + "name": "Bit15", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX10.0" + }, + "name": "Bit16", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX10.1" + }, + "name": "Bit17", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX10.2" + }, + "name": "Bit18", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX10.3" + }, + "name": "Bit19", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX10.4" + }, + "name": "Bit20", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX10.5" + }, + "name": "Bit21", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX10.6" + }, + "name": "Bit22", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX10.7" + }, + "name": "Bit23", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX11.0" + }, + "name": "Bit24", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX11.1" + }, + "name": "Bit25", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX11.2" + }, + "name": "Bit26", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX11.3" + }, + "name": "Bit27", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX11.4" + }, + "name": "Bit28", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX11.5" + }, + "name": "Bit29", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX11.6" + }, + "name": "Bit30", + "iec_type": "" + }, + { + "bit_size": 1, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX11.7" + }, + "name": "Bit31", + "iec_type": "" + } + ], + "section": "" + } + ], + "connector_id": 1 + } + ] + }, + { + "parameters": [], + "name": "", + "device_id": { + "version": "3.0.0.0", + "id": "0000 0000", + "type": 0 + }, + "parameter_count": 0, + "path": "Device/Camera device/", + "connectors": [ + { + "interface": "Raspberry.Camera2", + "role": "child", + "host_parameters": [], + "connector_id": 1 + } + ] + }, + { + "parameters": [], + "name": "EtherCAT_Master", + "device_id": { + "version": "4.10.0.0", + "id": "0000 0001", + "type": 64 + }, + "parameter_count": 0, + "path": "Device/EtherCAT_Master", + "connectors": [ + { + "interface": "Common.PCI", + "role": "child", + "host_parameters": [], + "connector_id": 1 + }, + { + "interface": "Common.Ethercat", + "role": "parent", + "host_parameters": [], + "connector_id": 2 + } + ] + }, + { + "parameters": [], + "name": "EK1100", + "device_id": { + "version": "Revision=16#00120000", + "id": "2_044C2C5200120000", + "type": 65 + }, + "parameter_count": 0, + "path": "Device/EtherCAT_Master/EK1100", + "connectors": [ + { + "interface": "Common.Ethercat", + "role": "child", + "host_parameters": [], + "connector_id": 1 + }, + { + "interface": "Common.Ethercat.Module", + "role": "parent", + "host_parameters": [], + "connector_id": 2 + } + ] + }, + { + "parameters": [], + "name": "K1", + "device_id": { + "version": "Revision=16#00120000", + "id": "2_0711305200120000", + "type": 65 + }, + "parameter_count": 0, + "path": "Device/EtherCAT_Master/EK1100/K1", + "connectors": [ + { + "interface": "Common.Ethercat.Module", + "role": "child", + "host_parameters": [ + { + "bit_size": 1, + "name": "Input", + "channel_type": "input", + "iec_type": "", + "id": 33554434, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX0.0" + }, + "type_string": "BIT", + "section": "16#1A00 Channel 1" + }, + { + "bit_size": 1, + "name": "Input", + "channel_type": "input", + "iec_type": "", + "id": 33554435, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX0.1" + }, + "type_string": "BIT", + "section": "16#1A01 Channel 2" + }, + { + "bit_size": 1, + "name": "Input", + "channel_type": "input", + "iec_type": "", + "id": 33554436, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX0.2" + }, + "type_string": "BIT", + "section": "16#1A02 Channel 3" + }, + { + "bit_size": 1, + "name": "Input", + "channel_type": "input", + "iec_type": "", + "id": 33554437, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX0.3" + }, + "type_string": "BIT", + "section": "16#1A03 Channel 4" + }, + { + "bit_size": 1, + "name": "Input", + "channel_type": "input", + "iec_type": "", + "id": 33554438, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX0.4" + }, + "type_string": "BIT", + "section": "16#1A04 Channel 5" + }, + { + "bit_size": 1, + "name": "Input", + "channel_type": "input", + "iec_type": "", + "id": 33554439, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX0.5" + }, + "type_string": "BIT", + "section": "16#1A05 Channel 6" + }, + { + "bit_size": 1, + "name": "Input", + "channel_type": "input", + "iec_type": "", + "id": 33554440, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX0.6" + }, + "type_string": "BIT", + "section": "16#1A06 Channel 7" + }, + { + "bit_size": 1, + "name": "Input", + "channel_type": "input", + "iec_type": "", + "id": 33554441, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX0.7" + }, + "type_string": "BIT", + "section": "16#1A07 Channel 8" + }, + { + "bit_size": 1, + "name": "Input", + "channel_type": "input", + "iec_type": "", + "id": 33554442, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX1.0" + }, + "type_string": "BIT", + "section": "16#1A08 Channel 9" + }, + { + "bit_size": 1, + "name": "Input", + "channel_type": "input", + "iec_type": "", + "id": 33554443, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX1.1" + }, + "type_string": "BIT", + "section": "16#1A09 Channel 10" + }, + { + "bit_size": 1, + "name": "Input", + "channel_type": "input", + "iec_type": "", + "id": 33554444, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX1.2" + }, + "type_string": "BIT", + "section": "16#1A0A Channel 11" + }, + { + "bit_size": 1, + "name": "Input", + "channel_type": "input", + "iec_type": "", + "id": 33554445, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX1.3" + }, + "type_string": "BIT", + "section": "16#1A0B Channel 12" + }, + { + "bit_size": 1, + "name": "Input", + "channel_type": "input", + "iec_type": "", + "id": 33554446, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX1.4" + }, + "type_string": "BIT", + "section": "16#1A0C Channel 13" + }, + { + "bit_size": 1, + "name": "Input", + "channel_type": "input", + "iec_type": "", + "id": 33554447, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX1.5" + }, + "type_string": "BIT", + "section": "16#1A0D Channel 14" + }, + { + "bit_size": 1, + "name": "Input", + "channel_type": "input", + "iec_type": "", + "id": 33554448, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX1.6" + }, + "type_string": "BIT", + "section": "16#1A0E Channel 15" + }, + { + "bit_size": 1, + "name": "Input", + "channel_type": "input", + "iec_type": "", + "id": 33554449, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%IX1.7" + }, + "type_string": "BIT", + "section": "16#1A0F Channel 16" + } + ], + "connector_id": 1 + } + ] + }, + { + "parameters": [], + "name": "K2", + "device_id": { + "version": "Revision=16#00120000", + "id": "2_0AF9305200120000", + "type": 65 + }, + "parameter_count": 0, + "path": "Device/EtherCAT_Master/EK1100/K2", + "connectors": [ + { + "interface": "Common.Ethercat.Module", + "role": "child", + "host_parameters": [ + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777218, + "io_mapping": { + "variable": "Application.NVL_Sender.light_dinningRoom.l_1", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX0.0" + }, + "type_string": "BIT", + "section": "16#1600 Channel 1" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777219, + "io_mapping": { + "variable": "Application.NVL_Sender.l_entrance.l_1", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX0.1" + }, + "type_string": "BIT", + "section": "16#1601 Channel 2" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777220, + "io_mapping": { + "variable": "Application.NVL_Sender.light_dinningRoom.l_2", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX0.2" + }, + "type_string": "BIT", + "section": "16#1602 Channel 3" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777221, + "io_mapping": { + "variable": "Application.NVL_Sender.light_dinningRoom.l_3", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX0.3" + }, + "type_string": "BIT", + "section": "16#1603 Channel 4" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777222, + "io_mapping": { + "variable": "Application.NVL_Sender.light_guestWc.l_1", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX0.4" + }, + "type_string": "BIT", + "section": "16#1604 Channel 5" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777223, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX0.5" + }, + "type_string": "BIT", + "section": "16#1605 Channel 6" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777224, + "io_mapping": { + "variable": "Application.NVL_Sender.light_dinningRoom.l_4", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX0.6" + }, + "type_string": "BIT", + "section": "16#1606 Channel 7" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777225, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX0.7" + }, + "type_string": "BIT", + "section": "16#1607 Channel 8" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777226, + "io_mapping": { + "variable": "Application.NVL_Sender.l_entrance.l_2", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX1.0" + }, + "type_string": "BIT", + "section": "16#1608 Channel 9" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777227, + "io_mapping": { + "variable": "Application.NVL_Sender.light_livingRoom.l_1", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX1.1" + }, + "type_string": "BIT", + "section": "16#1609 Channel 10" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777228, + "io_mapping": { + "variable": "Application.NVL_Sender.light_livingRoom.l_2", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX1.2" + }, + "type_string": "BIT", + "section": "16#160A Channel 11" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777229, + "io_mapping": { + "variable": "Application.NVL_Sender.light_livingRoom.l_3", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX1.3" + }, + "type_string": "BIT", + "section": "16#160B Channel 12" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777230, + "io_mapping": { + "variable": "Application.NVL_Sender.light_livingRoom.l_4", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX1.4" + }, + "type_string": "BIT", + "section": "16#160C Channel 13" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777231, + "io_mapping": { + "variable": "Application.NVL_Sender.light_kitchenArea.l_1", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX1.5" + }, + "type_string": "BIT", + "section": "16#160D Channel 14" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777232, + "io_mapping": { + "variable": "Application.NVL_Sender.light_kitchenArea.l_2", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX1.6" + }, + "type_string": "BIT", + "section": "16#160E Channel 15" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777233, + "io_mapping": { + "variable": "Application.NVL_Sender.light_kitchenArea.l_3", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX1.7" + }, + "type_string": "BIT", + "section": "16#160F Channel 16" + } + ], + "connector_id": 1 + } + ] + }, + { + "parameters": [], + "name": "K3", + "device_id": { + "version": "Revision=16#00120000", + "id": "2_0AF9305200120000", + "type": 65 + }, + "parameter_count": 0, + "path": "Device/EtherCAT_Master/EK1100/K3", + "connectors": [ + { + "interface": "Common.Ethercat.Module", + "role": "child", + "host_parameters": [ + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777218, + "io_mapping": { + "variable": "Application.NVL_Sender.light_kitchenArea.l_4", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX2.0" + }, + "type_string": "BIT", + "section": "16#1600 Channel 1" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777219, + "io_mapping": { + "variable": "Application.NVL_Sender.light_hallway.l_1", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX2.1" + }, + "type_string": "BIT", + "section": "16#1601 Channel 2" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777220, + "io_mapping": { + "variable": "Application.NVL_Sender.light_office.l_1", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX2.2" + }, + "type_string": "BIT", + "section": "16#1602 Channel 3" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777221, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX2.3" + }, + "type_string": "BIT", + "section": "16#1603 Channel 4" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777222, + "io_mapping": { + "variable": "Application.NVL_Sender.light_shower.l_1", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX2.4" + }, + "type_string": "BIT", + "section": "16#1604 Channel 5" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777223, + "io_mapping": { + "variable": "Application.NVL_Sender.light_shower.l_2", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX2.5" + }, + "type_string": "BIT", + "section": "16#1605 Channel 6" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777224, + "io_mapping": { + "variable": "Application.NVL_Sender.light_hallway.l_2", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX2.6" + }, + "type_string": "BIT", + "section": "16#1606 Channel 7" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777225, + "io_mapping": { + "variable": "Application.NVL_Sender.light_bedroom.l_1", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX2.7" + }, + "type_string": "BIT", + "section": "16#1607 Channel 8" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777226, + "io_mapping": { + "variable": "Application.NVL_Sender.light_bedroom.l_2", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX3.0" + }, + "type_string": "BIT", + "section": "16#1608 Channel 9" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777227, + "io_mapping": { + "variable": "Application.NVL_Sender.light_masterBedroom.l_1", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX3.1" + }, + "type_string": "BIT", + "section": "16#1609 Channel 10" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777228, + "io_mapping": { + "variable": "Application.NVL_Sender.light_masterBedroom.l_2", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX3.2" + }, + "type_string": "BIT", + "section": "16#160A Channel 11" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777229, + "io_mapping": { + "variable": "Application.NVL_Sender.light_masterBedroom.l_3", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX3.3" + }, + "type_string": "BIT", + "section": "16#160B Channel 12" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777230, + "io_mapping": { + "variable": "Application.NVL_Sender.light_masterBedroom.l_4", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX3.4" + }, + "type_string": "BIT", + "section": "16#160C Channel 13" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777231, + "io_mapping": { + "variable": "Application.NVL_Sender.light_masterBath.l_1", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX3.5" + }, + "type_string": "BIT", + "section": "16#160D Channel 14" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777232, + "io_mapping": { + "variable": "Application.NVL_Sender.light_masterBath.l_2", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX3.6" + }, + "type_string": "BIT", + "section": "16#160E Channel 15" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777233, + "io_mapping": { + "variable": "Application.NVL_Sender.light_masterBath.l_3", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX3.7" + }, + "type_string": "BIT", + "section": "16#160F Channel 16" + } + ], + "connector_id": 1 + } + ] + }, + { + "parameters": [], + "name": "K4", + "device_id": { + "version": "Revision=16#00120000", + "id": "2_0AF9305200120000", + "type": 65 + }, + "parameter_count": 0, + "path": "Device/EtherCAT_Master/EK1100/K4", + "connectors": [ + { + "interface": "Common.Ethercat.Module", + "role": "child", + "host_parameters": [ + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777218, + "io_mapping": { + "variable": "Application.NVL_Sender.light_livingRoom.l_5", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX4.0" + }, + "type_string": "BIT", + "section": "16#1600 Channel 1" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777219, + "io_mapping": { + "variable": "Application.NVL_Sender.light_outBack.l_1", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX4.1" + }, + "type_string": "BIT", + "section": "16#1601 Channel 2" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777220, + "io_mapping": { + "variable": "Application.NVL_Sender.light_outBack.l_2", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX4.2" + }, + "type_string": "BIT", + "section": "16#1602 Channel 3" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777221, + "io_mapping": { + "variable": "Application.NVL_Sender.light_outFront.l_2", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX4.3" + }, + "type_string": "BIT", + "section": "16#1603 Channel 4" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777222, + "io_mapping": { + "variable": "Application.NVL_Sender.light_coveredVeranda.l_2", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX4.4" + }, + "type_string": "BIT", + "section": "16#1604 Channel 5" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777223, + "io_mapping": { + "variable": "Application.NVL_Sender.light_coveredVeranda.l_3", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX4.5" + }, + "type_string": "BIT", + "section": "16#1605 Channel 6" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777224, + "io_mapping": { + "variable": "Application.NVL_Sender.light_coveredVeranda.l_4", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX4.6" + }, + "type_string": "BIT", + "section": "16#1606 Channel 7" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777225, + "io_mapping": { + "variable": "Application.NVL_Sender.l_outSide.l_1", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX4.7" + }, + "type_string": "BIT", + "section": "16#1607 Channel 8" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777226, + "io_mapping": { + "variable": "Application.NVL_Sender.l_outFront.l_1", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX5.0" + }, + "type_string": "BIT", + "section": "16#1608 Channel 9" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777227, + "io_mapping": { + "variable": "Application.NVL_Sender.l_outVeranda.l_4", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX5.1" + }, + "type_string": "BIT", + "section": "16#1609 Channel 10" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777228, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX5.2" + }, + "type_string": "BIT", + "section": "16#160A Channel 11" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777229, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX5.3" + }, + "type_string": "BIT", + "section": "16#160B Channel 12" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777230, + "io_mapping": { + "variable": "Application.NVL_Sender.boiler_status.relay_output", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX5.4" + }, + "type_string": "BIT", + "section": "16#160C Channel 13" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777231, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX5.5" + }, + "type_string": "BIT", + "section": "16#160D Channel 14" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777232, + "io_mapping": { + "variable": "", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX5.6" + }, + "type_string": "BIT", + "section": "16#160E Channel 15" + }, + { + "bit_size": 1, + "name": "Output", + "channel_type": "output", + "iec_type": "", + "id": 16777233, + "io_mapping": { + "variable": "Application.NVL_Sender.light_livingRoom.l_1", + "maps_to_existing": true, + "default_variable": "", + "creates_variable": false, + "auto_iec_address": true, + "iec_address": "%QX5.7" + }, + "type_string": "BIT", + "section": "16#160F Channel 16" + } + ], + "connector_id": 1 + } + ] + } + ], + "action": "read_device_io" +} \ No newline at end of file diff --git a/codesys-tree/_io.md b/codesys-tree/_io.md new file mode 100644 index 0000000..6c113b4 --- /dev/null +++ b/codesys-tree/_io.md @@ -0,0 +1,164 @@ +# Device I/O Inventory + +Snapshot of all hardware I/O channels and their CODESYS variable mappings. +Source: `read_device_io` against `C:\codesys-projects\Codesys_Home_Automation\Home_Automation.project` +(channels_only=true). Raw data: `_io.json`. + +## Device tree (hardware) + +| Device | Path | In | Out | Mapped out | Addr range | +|--------|------|---:|---:|---:|------------| +| GPIOs_A_B | `Device/GPIOs_A_B` | 1 (DWORD) | 1 (DWORD) | 0 | `%ID1` / `%QD2` | +| K1 | `Device/EtherCAT_Master/EK1100/K1` | 16 BOOL | 0 | — | `%IX0.0..%IX1.7` | +| K2 | `Device/EtherCAT_Master/EK1100/K2` | 0 | 16 BOOL | 14 / 16 | `%QX0.0..%QX1.7` | +| K3 | `Device/EtherCAT_Master/EK1100/K3` | 0 | 16 BOOL | 15 / 16 | `%QX2.0..%QX3.7` | +| K4 | `Device/EtherCAT_Master/EK1100/K4` | 0 | 16 BOOL | 12 / 16 | `%QX4.0..%QX5.7` | + +`Onewire`, `Camera device`, `SPI`, `I?C`, `EtherCAT_Master` and `EK1100` +return no host parameters / channels of their own (they're parents/couplers). + +**Totals:** 16 digital inputs (none mapped), 48 digital outputs (41 mapped, 7 free). + +## Findings worth flagging + +### Duplicate output mapping (likely a bug) + +`Application.NVL_Sender.light_livingRoom.l_1` is written by **two** physical output channels: + +| Where | IEC addr | +|-------|----------| +| K2 ch 10 | `%QX1.1` | +| K4 ch 16 | `%QX5.7` | + +Both physical relays will switch together whenever the variable is written. +One of these should be remapped — usually `K4 %QX5.7` would be the one to free up +(based on K4's role as the spillover terminal). + +### Unmapped output channels + +| Device | IEC addr | Channel | +|--------|----------|---------| +| GPIOs_A_B | `%QD2` | (DWORD) | +| K2 | `%QX0.5` | Ch 6 | +| K2 | `%QX0.7` | Ch 8 | +| K3 | `%QX2.3` | Ch 4 | +| K4 | `%QX5.2` | Ch 11 | +| K4 | `%QX5.3` | Ch 12 | +| K4 | `%QX5.5` | Ch 14 | +| K4 | `%QX5.6` | Ch 15 | + +### No inputs mapped + +K1 (16-channel digital input terminal, `%IX0.0..%IX1.7`) is wired up at hardware +level but has zero variable mappings. If you have physical switches on K1, the +PLC currently can't see them. + +### Variable target + +All mapped outputs write into `Application.NVL_Sender.*` — i.e. the global +variable list that is *broadcast over UDP* to the receiver. This is the "Option C" +relay-feedback pattern referenced in the `GVL` comment: the program writes the +desired relay state into the NVL_Sender variable, the EtherCAT terminal switches +the actual relay, and the value read back is exactly the value just written. + +No outputs are mapped against the application's room/light function block outputs +(`fb_room.lights.*` / `fb_light.light_output`). The new `fb_room` / `fb_light` +logic is therefore not yet driving the physical relays — that wiring would need +to be done in `PLC_PRG` (or `Lights_new`) by assigning each FB output into the +corresponding `NVL_Sender.light_*.l_N` slot before the IO write phase. + +## Per-channel detail + +### K1 (`Device/EtherCAT_Master/EK1100/K1`) + +| Ch | IEC addr | Dir | Variable | +|---:|----------|-----|----------| +| 1 | `%IX0.0` | INP | _(unmapped)_ | +| 2 | `%IX0.1` | INP | _(unmapped)_ | +| 3 | `%IX0.2` | INP | _(unmapped)_ | +| 4 | `%IX0.3` | INP | _(unmapped)_ | +| 5 | `%IX0.4` | INP | _(unmapped)_ | +| 6 | `%IX0.5` | INP | _(unmapped)_ | +| 7 | `%IX0.6` | INP | _(unmapped)_ | +| 8 | `%IX0.7` | INP | _(unmapped)_ | +| 9 | `%IX1.0` | INP | _(unmapped)_ | +| 10 | `%IX1.1` | INP | _(unmapped)_ | +| 11 | `%IX1.2` | INP | _(unmapped)_ | +| 12 | `%IX1.3` | INP | _(unmapped)_ | +| 13 | `%IX1.4` | INP | _(unmapped)_ | +| 14 | `%IX1.5` | INP | _(unmapped)_ | +| 15 | `%IX1.6` | INP | _(unmapped)_ | +| 16 | `%IX1.7` | INP | _(unmapped)_ | + +### K2 (`Device/EtherCAT_Master/EK1100/K2`) + +| Ch | IEC addr | Dir | Variable | +|---:|----------|-----|----------| +| 1 | `%QX0.0` | OUT | `Application.NVL_Sender.light_dinningRoom.l_1` | +| 2 | `%QX0.1` | OUT | `Application.NVL_Sender.l_entrance.l_1` | +| 3 | `%QX0.2` | OUT | `Application.NVL_Sender.light_dinningRoom.l_2` | +| 4 | `%QX0.3` | OUT | `Application.NVL_Sender.light_dinningRoom.l_3` | +| 5 | `%QX0.4` | OUT | `Application.NVL_Sender.light_guestWc.l_1` | +| 6 | `%QX0.5` | OUT | _(unmapped)_ | +| 7 | `%QX0.6` | OUT | `Application.NVL_Sender.light_dinningRoom.l_4` | +| 8 | `%QX0.7` | OUT | _(unmapped)_ | +| 9 | `%QX1.0` | OUT | `Application.NVL_Sender.l_entrance.l_2` | +| 10 | `%QX1.1` | OUT | `Application.NVL_Sender.light_livingRoom.l_1` ⚠ duplicated on K4 ch 16 | +| 11 | `%QX1.2` | OUT | `Application.NVL_Sender.light_livingRoom.l_2` | +| 12 | `%QX1.3` | OUT | `Application.NVL_Sender.light_livingRoom.l_3` | +| 13 | `%QX1.4` | OUT | `Application.NVL_Sender.light_livingRoom.l_4` | +| 14 | `%QX1.5` | OUT | `Application.NVL_Sender.light_kitchenArea.l_1` | +| 15 | `%QX1.6` | OUT | `Application.NVL_Sender.light_kitchenArea.l_2` | +| 16 | `%QX1.7` | OUT | `Application.NVL_Sender.light_kitchenArea.l_3` | + +### K3 (`Device/EtherCAT_Master/EK1100/K3`) + +| Ch | IEC addr | Dir | Variable | +|---:|----------|-----|----------| +| 1 | `%QX2.0` | OUT | `Application.NVL_Sender.light_kitchenArea.l_4` | +| 2 | `%QX2.1` | OUT | `Application.NVL_Sender.light_hallway.l_1` | +| 3 | `%QX2.2` | OUT | `Application.NVL_Sender.light_office.l_1` | +| 4 | `%QX2.3` | OUT | _(unmapped)_ | +| 5 | `%QX2.4` | OUT | `Application.NVL_Sender.light_shower.l_1` | +| 6 | `%QX2.5` | OUT | `Application.NVL_Sender.light_shower.l_2` | +| 7 | `%QX2.6` | OUT | `Application.NVL_Sender.light_hallway.l_2` | +| 8 | `%QX2.7` | OUT | `Application.NVL_Sender.light_bedroom.l_1` | +| 9 | `%QX3.0` | OUT | `Application.NVL_Sender.light_bedroom.l_2` | +| 10 | `%QX3.1` | OUT | `Application.NVL_Sender.light_masterBedroom.l_1` | +| 11 | `%QX3.2` | OUT | `Application.NVL_Sender.light_masterBedroom.l_2` | +| 12 | `%QX3.3` | OUT | `Application.NVL_Sender.light_masterBedroom.l_3` | +| 13 | `%QX3.4` | OUT | `Application.NVL_Sender.light_masterBedroom.l_4` | +| 14 | `%QX3.5` | OUT | `Application.NVL_Sender.light_masterBath.l_1` | +| 15 | `%QX3.6` | OUT | `Application.NVL_Sender.light_masterBath.l_2` | +| 16 | `%QX3.7` | OUT | `Application.NVL_Sender.light_masterBath.l_3` | + +### K4 (`Device/EtherCAT_Master/EK1100/K4`) + +| Ch | IEC addr | Dir | Variable | +|---:|----------|-----|----------| +| 1 | `%QX4.0` | OUT | `Application.NVL_Sender.light_livingRoom.l_5` | +| 2 | `%QX4.1` | OUT | `Application.NVL_Sender.light_outBack.l_1` | +| 3 | `%QX4.2` | OUT | `Application.NVL_Sender.light_outBack.l_2` | +| 4 | `%QX4.3` | OUT | `Application.NVL_Sender.light_outFront.l_2` | +| 5 | `%QX4.4` | OUT | `Application.NVL_Sender.light_coveredVeranda.l_2` | +| 6 | `%QX4.5` | OUT | `Application.NVL_Sender.light_coveredVeranda.l_3` | +| 7 | `%QX4.6` | OUT | `Application.NVL_Sender.light_coveredVeranda.l_4` | +| 8 | `%QX4.7` | OUT | `Application.NVL_Sender.l_outSide.l_1` | +| 9 | `%QX5.0` | OUT | `Application.NVL_Sender.l_outFront.l_1` | +| 10 | `%QX5.1` | OUT | `Application.NVL_Sender.l_outVeranda.l_4` | +| 11 | `%QX5.2` | OUT | _(unmapped)_ | +| 12 | `%QX5.3` | OUT | _(unmapped)_ | +| 13 | `%QX5.4` | OUT | `Application.NVL_Sender.boiler_status.relay_output` | +| 14 | `%QX5.5` | OUT | _(unmapped)_ | +| 15 | `%QX5.6` | OUT | _(unmapped)_ | +| 16 | `%QX5.7` | OUT | `Application.NVL_Sender.light_livingRoom.l_1` ⚠ duplicates K2 ch 10 | + +### GPIOs_A_B (`Device/GPIOs_A_B`) + +| Ch | IEC addr | Dir | Variable | +|---:|----------|-----|----------| +| GPIOs in | `%ID1` | INP (DWORD) | _(unmapped)_ | +| GPIOs out | `%QD2` | OUT (DWORD) | _(unmapped)_ | + +The Raspberry Pi onboard GPIO bank exists in the project but is not wired to any +variables. diff --git a/codesys-tree/_manifest.json b/codesys-tree/_manifest.json new file mode 100644 index 0000000..b2ac595 --- /dev/null +++ b/codesys-tree/_manifest.json @@ -0,0 +1,185 @@ +{ + "project_path": "C:\\codesys-projects\\Codesys_Home_Automation\\Home_Automation.project", + "staged_project_path": "C:\\codesys-projects\\Codesys_Home_Automation\\Home_Automation_Boiler_Phase1\\Home_Automation.project", + "staged_project_folder": "Home_Automation_Boiler_Phase1", + "active_application": "Application", + "generated_at": "2026-05-24T20:24:00+03:00", + "inventory_refreshed_at": "2026-05-24T20:24:00+03:00", + "io_snapshot": { + "file": "_io.json", + "summary": "_io.md", + "captured_at": "2026-05-24T20:24:00+03:00", + "device_count": 9, + "channels_only": true + }, + "convention": { + "declaration_marker": "(* === DECLARATION === *)", + "implementation_marker": "(* === IMPLEMENTATION === *)", + "notes": "Each .st file contains the declaration section followed by the implementation section, separated by the markers above. Empty section after a marker means CODESYS reported no content for that section." + }, + "objects": [ + { + "file": "Device/Plc Logic/Application/PLC_PRG.st", + "object_path": "Device / Plc Logic / Application / PLC_PRG", + "kind": "program", + "has_declaration": true, + "has_implementation": true + }, + { + "file": "Device/Plc Logic/Application/0.Structures/struct_switches.st", + "object_path": "Device / Plc Logic / Application / 0.Structures / struct_switches", + "kind": "dut", + "has_declaration": true, + "has_implementation": false + }, + { + "file": "Device/Plc Logic/Application/0.Structures/struct_lights.st", + "object_path": "Device / Plc Logic / Application / 0.Structures / struct_lights", + "kind": "dut", + "has_declaration": true, + "has_implementation": false + }, + { + "file": "Device/Plc Logic/Application/0.Structures/struct_room_outs.st", + "object_path": "Device / Plc Logic / Application / 0.Structures / struct_room_outs", + "kind": "dut", + "has_declaration": true, + "has_implementation": false + }, + { + "file": "Device/Plc Logic/Application/0.Structures/struct_room_cmds.st", + "object_path": "Device / Plc Logic / Application / 0.Structures / struct_room_cmds", + "kind": "dut", + "has_declaration": true, + "has_implementation": false + }, + { + "file": "Device/Plc Logic/Application/0.Structures/struct_boiler_cmd.st", + "object_path": "Device / Plc Logic / Application / 0.Structures / struct_boiler_cmd", + "kind": "dut", + "has_declaration": true, + "has_implementation": false + }, + { + "file": "Device/Plc Logic/Application/0.Structures/struct_boiler_status.st", + "object_path": "Device / Plc Logic / Application / 0.Structures / struct_boiler_status", + "kind": "dut", + "has_declaration": true, + "has_implementation": false + }, + { + "file": "Device/Plc Logic/Application/0.Structures/struct_zones.st", + "object_path": "Device / Plc Logic / Application / 0.Structures / struct_zones", + "kind": "dut", + "has_declaration": true, + "has_implementation": false + }, + { + "file": "Device/Plc Logic/Application/1.Variables/NVL_Sender.st", + "object_path": "Device / Plc Logic / Application / 1.Variables / NVL_Sender", + "kind": "gvl", + "has_declaration": true, + "has_implementation": false + }, + { + "file": "Device/Plc Logic/Application/1.Variables/NVL_Receiver.st", + "object_path": "Device / Plc Logic / Application / 1.Variables / NVL_Receiver", + "kind": "gvl", + "has_declaration": true, + "has_implementation": false + }, + { + "file": "Device/Plc Logic/Application/1.Variables/GVL.st", + "object_path": "Device / Plc Logic / Application / 1.Variables / GVL", + "kind": "gvl", + "has_declaration": true, + "has_implementation": false + }, + { + "file": "Device/Plc Logic/Application/3.Function Blocks/fb_toogleButton.st", + "object_path": "Device / Plc Logic / Application / 3.Function Blocks / fb_toogleButton", + "kind": "function_block", + "has_declaration": true, + "has_implementation": false + }, + { + "file": "Device/Plc Logic/Application/3.Function Blocks/fb_switch.st", + "object_path": "Device / Plc Logic / Application / 3.Function Blocks / fb_switch", + "kind": "function_block", + "has_declaration": true, + "has_implementation": false + }, + { + "file": "Device/Plc Logic/Application/3.Function Blocks/fb_light.st", + "object_path": "Device / Plc Logic / Application / 3.Function Blocks / fb_light", + "kind": "function_block", + "has_declaration": true, + "has_implementation": true + }, + { + "file": "Device/Plc Logic/Application/3.Function Blocks/fb_room.st", + "object_path": "Device / Plc Logic / Application / 3.Function Blocks / fb_room", + "kind": "function_block", + "has_declaration": true, + "has_implementation": true + }, + { + "file": "Device/Plc Logic/Application/3.Function Blocks/fb_boiler.st", + "object_path": "Device / Plc Logic / Application / 3.Function Blocks / fb_boiler", + "kind": "function_block", + "has_declaration": true, + "has_implementation": true + }, + { + "file": "Device/Plc Logic/Application/4.Programs/Lights.st", + "object_path": "Device / Plc Logic / Application / 4.Programs / Lights", + "kind": "program", + "has_declaration": true, + "has_implementation": false + }, + { + "file": "Device/Plc Logic/Application/4.Programs/Lights_new.st", + "object_path": "Device / Plc Logic / Application / 4.Programs / Lights_new", + "kind": "program", + "has_declaration": true, + "has_implementation": true + }, + { + "file": "Device/Plc Logic/Application/4.Programs/Boiler_PRG.st", + "object_path": "Device / Plc Logic / Application / 4.Programs / Boiler_PRG", + "kind": "program", + "has_declaration": true, + "has_implementation": true + } + ], + "non_textual_tree": [ + {"depth": 0, "kind": "node", "name": "Project Settings"}, + {"depth": 0, "kind": "device", "name": "Device"}, + {"depth": 1, "kind": "node", "name": "Plc Logic"}, + {"depth": 2, "kind": "application", "name": "Application"}, + {"depth": 3, "kind": "node", "name": "Library Manager"}, + {"depth": 3, "kind": "node", "name": "Task Configuration"}, + {"depth": 4, "kind": "node", "name": "MainTask"}, + {"depth": 5, "kind": "node", "name": "PLC_PRG"}, + {"depth": 4, "kind": "node", "name": "EtherCAT_Task"}, + {"depth": 3, "kind": "folder", "name": "3.Function Blocks"}, + {"depth": 3, "kind": "folder", "name": "1.Variables"}, + {"depth": 3, "kind": "folder", "name": "2.Functions"}, + {"depth": 3, "kind": "folder", "name": "4.Programs"}, + {"depth": 3, "kind": "folder", "name": "0.Structures"}, + {"depth": 1, "kind": "node", "name": "SoftMotion General Axis Pool"}, + {"depth": 1, "kind": "device", "name": "GPIOs_A_B"}, + {"depth": 1, "kind": "node", "name": "Onewire"}, + {"depth": 1, "kind": "node", "name": "Camera device"}, + {"depth": 2, "kind": "device", "name": ""}, + {"depth": 1, "kind": "node", "name": "SPI"}, + {"depth": 1, "kind": "node", "name": "I?C"}, + {"depth": 1, "kind": "device", "name": "EtherCAT_Master"}, + {"depth": 2, "kind": "device", "name": "EK1100"}, + {"depth": 3, "kind": "device", "name": "K1"}, + {"depth": 3, "kind": "device", "name": "K2"}, + {"depth": 3, "kind": "device", "name": "K3"}, + {"depth": 3, "kind": "device", "name": "K4"}, + {"depth": 0, "kind": "node", "name": "__VisualizationStyle"} + ] +} diff --git a/docs/integration-home-assistant-nodered.md b/docs/integration-home-assistant-nodered.md new file mode 100644 index 0000000..4feb157 --- /dev/null +++ b/docs/integration-home-assistant-nodered.md @@ -0,0 +1,265 @@ +# CODESYS ↔ Node-RED ↔ Home Assistant integration + +**Documented:** 2026-05-24 +**Sources:** Live config on `root@10.77.50.5` (`/srv/docker-volumes/…`) and local `codesys-tree/` mirror. + +**CODESYS Phase 1 project (copy, does not overwrite original):** +`C:\codesys-projects\Codesys_Home_Automation\Home_Automation_Boiler_Phase1\Home_Automation.project` +Original remains: `C:\codesys-projects\Codesys_Home_Automation\Home_Automation.project` + +This stack runs home automation logic on a **CODESYS PLC** (EtherCAT relays, room function blocks). **Node-RED** is the protocol bridge: it maps Home Assistant and Zigbee events into PLC Network Variables (NVL) over **UDP**, and mirrors PLC relay state back into HA. **Home Assistant** is the UI, automations, and helper entities—not the real-time I/O path to the PLC. + +--- + +## Infrastructure overview + +| Role | Host / container | VLAN70 IP | Notes | +|------|------------------|-----------|--------| +| Docker host (mgmt) | Proxmox CT, SSH `root@10.77.50.5` | `10.77.50.5` | Runs `home_infra` compose; macvlan services live on VLAN70 | +| Mosquitto | `mqtt` | `10.77.70.6` | Port `1883`, auth required (`allow_anonymous false`) | +| Home Assistant | `homeassistant` | `10.77.70.11` | Config: `/srv/docker-volumes/config/homeassistant/config` | +| Node-RED | `nodered` | `10.77.70.12` | Data: `/srv/docker-volumes/config/nodered/data` | +| Zigbee2MQTT | `zigbee2mqtt` | `10.77.70.13` | MQTT `mqtt://10.77.70.6`, base topic `zigbee2mqtt` | +| **CODESYS PLC** | (device, not in this compose) | **`10.77.70.5`** | NVL UDP port **`1202`** | + +Compose file: `/srv/docker-volumes/stacks/home_infra/docker-compose.yml`. + +```mermaid +flowchart LR + subgraph HA["Home Assistant 10.77.70.11"] + UI[Dashboard / automations] + Helpers[input_boolean helpers] + end + + subgraph NR["Node-RED 10.77.70.12"] + HA2NVL[HA to NVL] + Z2NVL[Zigbee to NVL] + NVL2HA[NVL to HA Sync] + NVLSend[nvl-send] + NVLRecv[nvl-receive] + end + + subgraph MQTT["Mosquitto 10.77.70.6"] + end + + subgraph Z2M["Zigbee2MQTT 10.77.70.13"] + end + + subgraph PLC["CODESYS PLC 10.77.70.5"] + RecvGVL[NVL_Receiver] + SendGVL[NVL_Sender] + FB[fb_light / fb_room / fb_boiler] + IO[EtherCAT K1–K4] + end + + UI --> Helpers + Helpers <-->|WebSocket API| HA2NVL + Helpers <-->|WebSocket API| NVL2HA + Z2M --> MQTT + MQTT --> NR + Z2M --> Z2NVL + HA2NVL --> NVLSend + Z2NVL --> NVLSend + NVLSend -->|UDP :1202| RecvGVL + SendGVL -->|UDP :1202| NVLRecv + NVLRecv --> NVL2HA + RecvGVL --> FB --> IO + IO --> SendGVL +``` + +--- + +## CODESYS side (this repository) + +### Network Variable Lists (UDP) + +| GVL | Direction (from PLC view) | Node-RED node | Purpose | +|-----|---------------------------|---------------|---------| +| `NVL_Receiver` | **Inbound** to PLC | `nvl-send` → `udp out` | Commands: HA, Zigbee, schedules | +| `NVL_Sender` | **Outbound** from PLC | `udp in` → `nvl-receive` | Status: relay commands + feedback | + +`NVL_Receiver.st` is documented as received via **UDP**. Node-RED sends to **`10.77.70.5:1202`**; Node-RED listens on **`1202`** for PLC telegrams. + +### Key structures + +**`struct_room_cmds`** (in `NVL_Receiver` as `cmd_*` rooms)—one instance per room, e.g. `cmd_livingRoom`: + +| Field group | Members | Used by | +|-------------|---------|---------| +| HA pulses | `ha_l1_on` … `ha_l6_on`, `ha_l1_off` … `ha_l6_off` | `fb_light` via `R_TRIG` (rising edge only) | +| Zigbee pulses | `zigbee_sw1` … `zigbee_sw6` | `fb_light` toggle on edge | +| Room-wide | `ha_all_on`, `ha_all_off` | Room-level FBs | + +**`struct_room_outs`** (in `NVL_Sender` as `light_*` rooms)—per-room outputs: + +| Field | Meaning | +|-------|---------| +| `l_1` … `l_6` | Commanded relay state (written to NVL before EtherCAT mapping) | +| `l_1_status` … `l_6_status` | Actual relay feedback | + +Physical mapping: see `codesys-tree/_io.md` (EtherCAT `%QX*` → `Application.NVL_Sender.light_*`). + +**Legacy / parallel structs** in `NVL_Sender` / `NVL_Receiver`: `struct_lights` (`l_*` rooms) and `struct_switches` (`sw_*`, `all_on`/`all_off`)—still present in GVLs; the active redesign path for rooms is `cmd_*` + `light_*` + `fb_light`. + +**Boiler (Phase 1 on PLC):** `NVL_Receiver.boiler_cmd` → `fb_boiler` in `Boiler_PRG`; status on `NVL_Sender.boiler_status`; **coil** on `boiler_status.relay_output` (`%QX5.4` on K4 ch 13) — **not** `light_shower.l_6`. Node-RED must use **`boiler_cmd`** / **`boiler_status`** (Phase 2); legacy `cmd_shower.ha_l6_*` no longer drives the heater on the PLC. + +### Logic priority (`fb_light`) + +1. HA OFF (`ha_off` / `ha_lN_off` pulse) +2. HA ON (`ha_on` / `ha_lN_on` pulse) +3. Zigbee toggle (`zigbee_sw` pulse) +4. Hold state + +Outputs feed `NVL_Sender`; EtherCAT maps those BOOLs to contactors. + +--- + +## Node-RED side + +### Main tabs (flows) + +| Tab | Role | +|-----|------| +| **House Lights (PLC)** | Production path: NVL UDP, HA sync, Zigbee dispatch, `room-config.js` | +| **Zigbee** | MQTT subscription to `zigbee2mqtt/#` (switches; some direct light control) | +| **Water heater** | Boiler HA entities, timers, safety shutoff, bridge to `cmd_shower` | +| **PLC Troubleshoot (cmd_*)** | Dashboard UI to pulse any `cmd_*` field for testing | +| Flow 7 | Disabled (legacy) | + +### NVL pipeline (House Lights tab) + +1. **Inputs** accumulate into `flow.nvlInState` (`{ rooms: {}, boiler: {} }`): + - **HA to NVL** — `server-state-changed` on helpers matching `plc_*`, `living_room`, etc.; sets `ha_lN_on` or `ha_lN_off` for the mapped `cmd_*` room. + - **Zigbee to NVL** — MQTT actions → `zigbee_swN` pulse on the bound `cmd_*` room. + - **Zigbee switch dispatch** — routes each button to PLC, direct Zigbee2MQTT light, or HA `switch.*` per `switchBindings` in `room-config.js`. + +2. **STATE TO NVL** — calls `roomConfigLib.buildNvlSendPayload()` with all `roomNames` from config. + +3. **`nvl-send`** — serializes GVL matching **`NVL_Receiver`** (see node `definition` in flows; aligns with `NVL_Receiver.st` in the tree). + +4. **`udp out`** → `10.77.70.5:1202`. + +5. **Return path:** **`udp in`** `:1202` → **`nvl-receive`** (parses **`NVL_Sender`**) → **NVL to HA Sync** → Home Assistant `api-call-service` (`turn_on` / `turn_off` on mapped helpers). + +6. **Edge clearing** — after ~80 ms, **Clear NVL edges** / **Clear zigbee edge** reset pulse bits so the PLC sees rising edges on the next command (matches `R_TRIG` in ST). + +### Configuration files (single source of truth) + +| File | Purpose | +|------|---------| +| `/data/room-config.js` | Room list, `plcRoomToCmd`, `lightEntityMap`, `switchBindings`, Zigbee `deviceIdToName` | +| `/data/room-config-lib.js` | `buildNvlSendPayload`, HA slug/entity helpers, Zigbee action parsing, switch dispatch | +| `settings.js` | Loads `room-config.js` at startup; exposes `global.roomConfig` / `global.roomConfigLib` | + +`roomNames` in `room-config.js` must match CODESYS `cmd_*` variables in `NVL_Receiver.st`. + +Example mapping entry: + +```javascript +{ room: 'light_livingRoom', light: 1, cmdRoom: 'cmd_livingRoom', entityId: 'input_boolean.living_room_new', name: 'Living Room Main' } +``` + +- `room` — key in **NVL_Sender** payload (`light_*`). +- `cmdRoom` — key in **NVL_Receiver** payload (`cmd_*`). +- `entityId` — HA helper updated on PLC state changes. + +--- + +## Home Assistant side + +### Core config + +- `configuration.yaml` — `default_config`, reverse proxy trust for `10.77.70.1` / `10.77.10.1`, packages. +- **MQTT** integration — broker `10.77.70.6:1883` (credentials in `.storage`, not in git). +- **Node-RED** custom integration (`custom_components/nodered`) — links HA events to Node-RED flows. +- Public URL: `ha.paraskeva.net` (via OPNsense Caddy). + +### PLC light helpers + +Package `packages/lights_by_room.yaml` defines `input_boolean` helpers; naming convention `_`. The active House Lights flow also uses helpers like `input_boolean.living_room_new` and auto-provisioned `input_boolean.plc__lN` from `lightEntityMap` (via **Create HA helper** nodes on boot). + +Helpers are **mirrors** of PLC state, not the authority for relay coils—the PLC is. + +### Echo suppression + +When Node-RED writes a helper from NVL→HA, it sets a short **`haEchoSuppress`** window so the resulting HA state-change event does not loop back through HA→NVL. + +--- + +## Zigbee path + +1. Switches publish to `zigbee2mqtt//action` (and related topics). +2. Node-RED **zigbee2mqtt-in** nodes receive events. +3. **Zigbee switch dispatch** looks up `switchBindings[device][button]`: + - **PLC:** `{ room: 'cmd_hallway', light: 2 }` → `zigbee_sw2` pulse. + - **Direct Z2M light:** `{ z2m: { device: 'Master Bedroom Spots', command: 'state', payload: 'toggle' } }`. + - **HA switch:** `{ ha: { entity_id: 'switch.xxx', service: 'toggle' } }`. + +Coordinator: SLZB-06 (`10.77.70.60`) — separate from the PLC path. + +--- + +## Water heater / boiler + +| Layer | Mechanism | +|-------|-----------| +| HA | `Water_boiler` entities, timer remaining sensor, automations in **Water heater** tab | +| Node-RED | **Boiler → boiler_cmd pulse** → NVL; feedback from **`boiler_status`** → `input_boolean.water_boiler` | +| PLC | `fb_boiler` / `struct_boiler_cmd` (full boiler FB exists; shower light channel used as integration shortcut) | +| Safety | Startup **Safety Check** turns boiler off if PLC reports ON; **3-Hour Safety Timer** in Node-RED | + +Boiler command fields in `struct_boiler_cmd` (`ha_on`, `ha_off`, `schedule_*`, `emergency_stop`, `max_on_time_minutes`) are also packed in legacy **Build NVL_In** buffer logic for older flows; the current Water heater tab primarily uses the `cmd_shower` / `light_shower.l_6` bridge. + +--- + +## MQTT vs UDP (when each is used) + +| Transport | Between | Data | +|-----------|---------|------| +| **UDP :1202** | Node-RED ↔ PLC | Binary NVL (GVL mirror)—**time-critical I/O** | +| **MQTT** | Zigbee2MQTT ↔ Node-RED; HA ↔ MQTT devices | Zigbee events; non-PLC devices; optional HA entities | +| **HA WebSocket** | Node-RED ↔ Home Assistant | Helper state, services, entity registry | + +The PLC does **not** speak MQTT in this architecture. + +--- + +## Adding a new PLC-controlled light + +1. **CODESYS:** Ensure `cmd_` and `light_` exist in `NVL_Receiver` / `NVL_Sender`; wire `fb_light` / `fb_room`; map `NVL_Sender.light_* .l_N` in device IO (`_io.md` / `write_io_mapping`). +2. **Node-RED:** Add entry to `lightEntityMap` and, if needed, `switchBindings` in `room-config.js`; confirm `cmd_` is in `roomNames`. +3. **Home Assistant:** Deploy helper (manual YAML package or let Node-RED **Create HA helper** on boot). +4. **Test:** Use **PLC Troubleshoot** tab to pulse `ha_l1_on`, then confirm NVL→HA sync updates the helper. + +--- + +## Operational notes + +- **Re-sync local tree:** After CODESYS changes on the Windows VM, run `read_project_inventory` and refresh `codesys-tree/` (see `codesys-tree/README.md`). +- **Node-RED backups:** Multiple `flows.json.bak-*` files exist under `/data`; production tab is **House Lights (PLC)**. +- **Known IO issue:** `light_livingRoom.l_1` is mapped to two EtherCAT outputs (K2 ch 10 and K4 ch 16)—see `_io.md`. +- **Secrets:** MQTT and HA tokens live in Docker volumes / HA `.storage`; do not commit them to this repo. + +--- + +## Quick reference: variable name alignment + +| Concept | CODESYS (`NVL_Receiver`) | CODESYS (`NVL_Sender`) | Node-RED `nvl-send` / `nvl-receive` | +|---------|--------------------------|------------------------|-------------------------------------| +| Living room commands | `cmd_livingRoom` : `struct_room_cmds` | — | `payload.cmd_livingRoom` | +| Living room outputs | — | `light_livingRoom` : `struct_room_outs` | `payload.light_livingRoom` | +| Hallway switch → PLC | `cmd_hallway.zigbee_sw*` | — | Via `switchBindings` | +| HA toggle living room 1 | `cmd_livingRoom.ha_l1_on/off` | — | From `input_boolean.living_room_new` | + +--- + +## Related files in this repo + +| Path | Content | +|------|---------| +| `codesys-tree/Device/.../NVL_Receiver.st` | Inbound GVL (commands) | +| `codesys-tree/Device/.../NVL_Sender.st` | Outbound GVL (status + coil image) | +| `codesys-tree/Device/.../struct_room_cmds.st` | HA/Zigbee command struct | +| `codesys-tree/Device/.../struct_room_outs.st` | Per-light outputs + status | +| `codesys-tree/Device/.../fb_light.st` | Single-light logic | +| `codesys-tree/_io.md` | EtherCAT ↔ `NVL_Sender` mapping | diff --git a/scripts/nodered_fast_toggle_patch.py b/scripts/nodered_fast_toggle_patch.py new file mode 100644 index 0000000..e4596ea --- /dev/null +++ b/scripts/nodered_fast_toggle_patch.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +"""Patch Node-RED flows for fast HA toggle reliability.""" +import json +import shutil +from datetime import datetime + +path = "/srv/docker-volumes/config/nodered/data/flows.json" +backup = path + ".bak-fasttoggle-" + datetime.now().strftime("%Y%m%d-%H%M%S") +shutil.copy2(path, backup) +print("backup:", backup) + +with open(path) as f: + flows = json.load(f) + +HA_TO_NVL = r"""const lib = global.get('roomConfigLib'); +const config = global.get('roomConfig') || {}; +const parsed = msg.plcLight || (lib ? lib.parseHaStateMessage(msg, config) : null); +if (!parsed) return null; + +const ROOM_NAME = parsed.cmdRoom; +const lightNum = parsed.lightNum; +const isOn = parsed.isOn; + +// User toggle: update mirror cache so NVL→HA feedback does not re-write HA or set echo suppress. +const flowKey = 'nvlToHa_' + parsed.entityId.replace(/\./g, '_'); +flow.set(flowKey, isOn); +const echo = flow.get('haEchoSuppress') || {}; +delete echo[parsed.entityId]; +flow.set('haEchoSuppress', echo); + +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]; +const onKey = 'ha_l' + lightNum + '_on'; +const offKey = 'ha_l' + lightNum + '_off'; +r[onKey] = false; +r[offKey] = false; +if (isOn) r[onKey] = true; else r[offKey] = true; +flow.set('nvlInState', state); + +const seq = (flow.get('nvlClearSeq') || 0) + 1; +flow.set('nvlClearSeq', seq); + +node.warn('[HA to NVL] ' + parsed.entityId + ' → ' + ROOM_NAME + ' ' + (isOn ? onKey : offKey)); + +msg.payload = { buildAndSend: true }; +msg.zigbeeClear = { + seq, + list: [ + { room: ROOM_NAME, key: onKey }, + { room: ROOM_NAME, key: offKey }, + ], +}; +return msg;""" + +CLEAR_NVL = r"""if (msg.zigbeeClear) { + const spec = Array.isArray(msg.zigbeeClear) + ? { seq: msg.zigbeeClearSeq, list: msg.zigbeeClear } + : msg.zigbeeClear; + const list = spec.list || (spec.room ? [spec] : []); + const seq = spec.seq != null ? spec.seq : msg.zigbeeClearSeq; + if (seq != null && seq !== flow.get('nvlClearSeq')) { + return null; + } + const state = flow.get('nvlInState') || { rooms: {}, boiler: {} }; + for (const item of list) { + if (item.room === 'boiler') { + if (!state.boiler) state.boiler = {}; + if (item.key) state.boiler[item.key] = false; + continue; + } + const r = state.rooms[item.room]; + if (r && item.key) r[item.key] = false; + } + flow.set('nvlInState', state); +} +msg.payload = { buildAndSend: true }; +return msg;""" + +NVL_TO_HA = r"""const config = global.get('roomConfig'); +const LIGHT_ENTITY_MAP = (config && config.lightEntityMap && config.lightEntityMap.length) + ? config.lightEntityMap + : []; + +const ECHO_MS = 400; +const payload = msg.payload || {}; +const calls = []; +const echo = flow.get('haEchoSuppress') || {}; +const until = Date.now() + ECHO_MS; + +for (const entry of LIGHT_ENTITY_MAP) { + const room = payload[entry.room] || {}; + const li = entry.light; + const isOn = !!(room['l_' + li + '_status'] || room['l_' + li] || room['l' + li]); + const flowKey = 'nvlToHa_' + entry.entityId.replace(/\./g, '_'); + const last = flow.get(flowKey); + if (last === isOn) continue; + flow.set(flowKey, isOn); + echo[entry.entityId] = until; + + const domain = entry.entityId.split('.')[0] || 'input_boolean'; + const service = isOn ? 'turn_on' : 'turn_off'; + calls.push({ + payload: { + action: domain + '.' + service, + target: { entity_id: [entry.entityId] }, + }, + }); +} + +flow.set('haEchoSuppress', echo); +if (!calls.length) return null; +return calls;""" + +patches = { + "4045239f675b77d2": HA_TO_NVL, + "44822e048dbaee31": CLEAR_NVL, + "d5cdc38171230744": NVL_TO_HA, +} + +for n in flows: + nid = n.get("id") + if nid in patches: + n["func"] = patches[nid] + print("patched", nid, n.get("name")) + + if nid == "wh_pulse_fn" and "nvlClearSeq" not in n.get("func", ""): + n["func"] = n["func"].replace( + "return {\n payload: { buildAndSend: true },\n zigbeeClear: { room: 'boiler', key: key },\n};", + """const seq = (flow.get('nvlClearSeq') || 0) + 1; +flow.set('nvlClearSeq', seq); +return { + payload: { buildAndSend: true }, + zigbeeClear: { seq, list: [{ room: 'boiler', key }] }, +};""", + ) + print("patched wh_pulse_fn") + + if nid == "plc_tt_pulse" and "nvlClearSeq" not in n.get("func", ""): + n["func"] = n["func"].replace( + "return {\n payload: { buildAndSend: true },\n zigbeeClear: { room: room, key: key },\n};", + """const seq = (flow.get('nvlClearSeq') || 0) + 1; +flow.set('nvlClearSeq', seq); +return { + payload: { buildAndSend: true }, + zigbeeClear: { seq, list: [{ room: room, key: key }] }, +};""", + ) + print("patched plc_tt_pulse") + + if nid == "718ba68ae647874f" and "nvlClearSeq" not in n.get("func", ""): + n["func"] = n["func"].replace( + " outPlc.push({\n payload: { buildAndSend: true },\n zigbeeClear: clearList,\n });", + """ const seq = (flow.get('nvlClearSeq') || 0) + 1; + flow.set('nvlClearSeq', seq); + outPlc.push({ + payload: { buildAndSend: true }, + zigbeeClear: { seq, list: clearList }, + });""", + ) + print("patched zigbee dispatch") + +with open(path, "w") as f: + json.dump(flows, f) + +print("done") diff --git a/scripts/nodered_global_nvl_state_patch.py b/scripts/nodered_global_nvl_state_patch.py new file mode 100644 index 0000000..06b7a85 --- /dev/null +++ b/scripts/nodered_global_nvl_state_patch.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +"""Use global context for nvlInState/nvlClearSeq (shared across Node-RED tabs).""" +import json +import shutil +from datetime import datetime + +path = "/srv/docker-volumes/config/nodered/data/flows.json" +backup = path + ".bak-global-nvl-" + datetime.now().strftime("%Y%m%d-%H%M%S") +shutil.copy2(path, backup) +print("backup:", backup) + +with open(path) as f: + flows = json.load(f) + +replacements = [ + ("flow.get('nvlInState')", "global.get('nvlInState')"), + ('flow.get("nvlInState")', 'global.get("nvlInState")'), + ("flow.set('nvlInState'", "global.set('nvlInState'"), + ('flow.set("nvlInState"', 'global.set("nvlInState"'), + ("flow.get('nvlClearSeq')", "global.get('nvlClearSeq')"), + ("flow.set('nvlClearSeq'", "global.set('nvlClearSeq'"), +] + +count = 0 +for n in flows: + fn = n.get("func") + if not fn: + continue + original = fn + for old, new in replacements: + fn = fn.replace(old, new) + if fn != original: + n["func"] = fn + count += 1 + print("patched", n.get("id"), n.get("name")) + +with open(path, "w") as f: + json.dump(flows, f) + +print("patched nodes:", count, "done")