LogicMachine Forum
Solar Heating Controller - Printable Version

+- LogicMachine Forum (https://forum.logicmachine.net)
+-- Forum: LogicMachine eco-system (https://forum.logicmachine.net/forumdisplay.php?fid=1)
+--- Forum: Scripting (https://forum.logicmachine.net/forumdisplay.php?fid=8)
+--- Thread: Solar Heating Controller (/showthread.php?tid=6327)



Solar Heating Controller - KoBra - 25.02.2026

Dears,

as i was not happy with the controlability of my Solar Heating System (mainly only read out not able to control)

I found a way to change 0-100% to 0-10V to PWM signal by using the Phoenix 2902032 module. As i am not a pro in coding and making it in FBeditor would be a painstaking process is asked AI to help me and came up with this.

Function wise i i tried and tested all and it works as i want it too.

Posting all here to have others be able to make use of it too!



This manual provides the definitive guide for the v6.5 Solar Control Logic. It explains how the script thinks, how it protects your hardware, and how the different parameters interact to maximize energy harvest.


1. The Core Philosophy: "Smart Delta T"
The system is built on a Differential Temperature ($\Delta T$) principle. It doesn't just look at how hot the roof is; it looks at how much hotter the roof is compared to the water you already have.
The Heating Cycle
  1. The Gap: The script calculates the difference: $T_{coll} - T_{tank}$.
  2. The Start: When this gap exceeds the Delta Start (e.g., 10°C), the system triggers a run.
  3. The Stop: As the tank warms up or the sun fades, the gap narrows. When it hits Delta Stop (e.g., 4°C), the pump stops to prevent wasting electricity.


2. Advanced Flow Management
A. The 100% Kickstart
Pumps can get "sticky" or air bubbles can get trapped in the collector.
  • Logic: Every time the pump starts from 0%, it ignores the PID calculation and runs at 100% speed for the duration of the Kickstart Seconds ($30/1/16$).
  • Benefit: Ensures flow is established immediately and clears any air pockets.
B. PID-Style Speed Modulation
Once the kickstart ends, the pump modulates its speed between Min Speed ($30/1/15$) and 100%.
  • Logic: If the temperature gap is only slightly above the Stop point, the pump runs slow. As the gap increases toward the PID Range ($30/1/14$), the pump speeds up.
  • Benefit: Keeps the "harvest" temperature high. It prevents moving water so fast that it doesn't have time to pick up heat.


3. Dual Boiler & Priority Logic
A. The Priority Rule
The system will always check the Priority Selection ($30/1/5$).
  • If set to 1, it will attempt to heat Boiler 1 first.
  • It will only switch to Boiler 2 if Boiler 1 has reached its Standard Target ($30/1/6$).
B. Target Hysteresis ($30/1/25$)
This is the "buffer" that prevents the 3-way valve from clicking back and forth constantly.
  • Scenario: If B1 hits its target of 60°C, the valve moves to B2.
  • The Rule: The system will refuse to switch back to B1 until its temperature drops below $60°C - Hysteresis$. If hysteresis is 2.0°C, B1 must drop to 58°C before it can "claim" the priority again.
C. Surplus Mode
When both boilers have hit their Standard targets, the system enters Surplus Mode.
  • Logic: It shifts the goalposts. It now targets the Max Temp ($30/1/7$ and $30/1/9$) for both tanks.
  • Benefit: On a very sunny day, you can store "extra" heat up to 90°C, providing more autonomy for cloudy days.


4. Maintenance & Safety Shields
I. Anti-Seize (Weekly Exercise)
Mechanical pumps hate standing still for weeks (common in winter).
  • Logic: If the pump has not run for 7 days, the script forces a 30-second full-speed run.
  • Benefit: Clears mineral deposits and ensures the impeller is free.
II. Reverse Flow Protection
  • Logic: If the Return Temp is higher than the Collector Temp, the pump is stopped.
  • Benefit: Prevents the solar panels from acting like "radiators" and cooling your house down at night.
III. Overheat & Stagnation
  • Overheat (110°C): The system ignores "Standard" targets and forces a Heat Dump into the tanks at 100% speed.
  • Danger (140°C): The system triggers a Stagnation Stop. It kills the pump to prevent steam from damaging the pump seals.


5. Status Messaging Guide
The Status Text ($30/1/24$) tells you exactly what the "brain" is doing:
  • "Idle": Everything is balanced; waiting for the sun.
  • "Kickstart Active": The initial burst of power is happening.
  • "Heating Tank X": Standard priority heating is active.
  • "Surplus Heating": Free energy is being stored beyond standard limits.
  • "Emergency Dump": The roof is too hot; the system is protecting itself.
  • "Anti-Seize": The weekly maintenance run is in progress.


Code:
--[[   SOLAR THERMAL CONTROL SYSTEM v6.5   ---------------------------------   Brief Technical Overview:   This script manages a dual-boiler solar thermal system using Differential   Temperature (Delta T) logic. It prioritizes Tank 1, moving to Tank 2 once   Tank 1 reaches its target. If both tanks are satisfied, it enters 'Surplus   Mode' to store extra energy up to a safety maximum.     Key Features:   - PID-style pump modulation based on heat intensity.   - Mandatory Kickstart (100% burst) to prime the system.   - Target Hysteresis: Prevents rapid valve switching at setpoints.   - Maintenance: 7-day Anti-Seize exercise and manual overrides.   - Safety: Protection against Stagnation, Overheating, and Reverse Flow. --]] -- --- 1. SENSOR INPUTS --- local ga_coll_temp = '30/1/1'      -- [DPT 9.001] Roof Collector local ga_ret_temp  = '30/1/2'      -- [DPT 9.001] Return Pipe local ga_b1_temp   = '30/1/3'      -- [DPT 9.001] Boiler 1 (Priority) local ga_b2_temp   = '30/1/4'      -- [DPT 9.001] Boiler 2 (Secondary) -- --- 2. OPERATIONAL SETTINGS --- local ga_priority = '30/1/5'       -- [DPT 5.005] 1 = B1, 2 = B2 local ga_b1_std_temp = '30/1/6'    -- [DPT 9.001] Target B1 local ga_b1_max_temp = '30/1/7'    -- [DPT 9.001] Safety Max B1 local ga_b2_std_temp = '30/1/8'    -- [DPT 9.001] Target B2 local ga_b2_max_temp = '30/1/9'    -- [DPT 9.001] Safety Max B2 local ga_target_hyst = '30/1/25'   -- [DPT 9.001] Target Hysteresis (e.g., 2.0) -- --- 3. DIFFERENTIAL (DELTA T) SETTINGS --- local ga_b1_delta_start = '30/1/10' -- Delta T to start B1 local ga_b1_delta_stop  = '30/1/11' -- Delta T to stop B1 local ga_b2_delta_start = '30/1/12' -- Delta T to start B2 local ga_b2_delta_stop  = '30/1/13' -- Delta T to stop B2 -- --- 4. PUMP & PID BEHAVIOR --- local ga_pid_range      = '30/1/14' -- Scaling range for speed local ga_min_pump_speed = '30/1/15' -- Speed floor (e.g., 20%) local ga_kickstart_sec  = '30/1/16' -- Startup burst (seconds) local ga_min_run_min    = '30/1/17' -- Min runtime (minutes) -- --- 5. PROTECTION & MAINTENANCE --- local ga_coll_overheat  = '30/1/18' -- Heat dump threshold local ga_coll_danger    = '30/1/19' -- Stagnation stop threshold local ga_maint_mode     = '30/1/20' -- 0=Auto, 1=OFF, 2=ON -- --- 6. OUTPUTS & STATUS --- local ga_pump_speed = '30/1/21'     -- Speed (0-100%) local ga_pump_run   = '30/1/22'     -- Power Relay (On/Off) local ga_valve_pos  = '30/1/23'     -- Valve (False=B1, True=B2) local ga_status_text = '30/1/24'    -- Visu status message -- --- DATA FETCHING --- local t_coll = grp.getvalue(ga_coll_temp) local t_ret  = grp.getvalue(ga_ret_temp) local t_b1, t_b2 = grp.getvalue(ga_b1_temp), grp.getvalue(ga_b2_temp) local maint_mode = grp.getvalue(ga_maint_mode) or 0 local now = os.time() local kick_duration = grp.getvalue(ga_kickstart_sec) or 10 local min_speed = grp.getvalue(ga_min_pump_speed) or 20 local b1_std, b1_max = grp.getvalue(ga_b1_std_temp) or 60, grp.getvalue(ga_b1_max_temp) or 90 local b2_std, b2_max = grp.getvalue(ga_b2_std_temp) or 60, grp.getvalue(ga_b2_max_temp) or 90 local t_hyst = grp.getvalue(ga_target_hyst) or 2.0 -- State Tracking local last_speed = grp.getvalue(ga_pump_speed) local current_valve = grp.getvalue(ga_valve_pos) local pump_was_off = (last_speed == 0) -- Anti-Seize Logic local last_run_time = storage.get('solar_last_run_time') or now local seize_interval = 7 * 24 * 60 * 60 local is_seize_run = (now - last_run_time > seize_interval) -- Safety & Surplus Condition local both_satisfied = (t_b1 >= b1_std) and (t_b2 >= b2_std) local is_overheating = (t_coll >= (grp.getvalue(ga_coll_overheat) or 110)) local is_danger = (t_coll >= (grp.getvalue(ga_coll_danger) or 140)) local is_reverse = (not pump_was_off) and (t_ret > t_coll + 2) -- --- FUNCTION: BOILER CHECK --- function check_boiler(id, t_curr, t_std, t_max)     local d_start = (id == 1) and (grp.getvalue(ga_b1_delta_start) or 8) or (grp.getvalue(ga_b2_delta_start) or 8)     local d_stop  = (id == 1) and (grp.getvalue(ga_b1_delta_stop) or 3) or (grp.getvalue(ga_b2_delta_stop) or 3)     local diff = t_coll - t_curr         local limit = (is_overheating or both_satisfied) and t_max or t_std     local currently_active = (not pump_was_off and ((id == 1 and not current_valve) or (id == 2 and current_valve)))     -- Apply Hysteresis: Require a drop below (Limit - Hyst) to restart heating/switch back     local active_limit = currently_active and limit or (limit - t_hyst)     if t_curr >= active_limit then return false, 0 end         if currently_active then         if diff > d_stop and not is_reverse then return true, diff end     else         if diff > d_start then return true, diff end     end     return false, 0 end -- --- MAIN LOGIC --- local target_boiler = 0 local active_diff = 0 local speed = 0 local run_relay = false local status_msg = "" if maint_mode == 1 then     status_msg = "Maint: Forced OFF" elseif maint_mode == 2 then     status_msg = "Maint: Forced ON (100%)"     target_boiler = 1; speed = 100; run_relay = true elseif is_seize_run then     target_boiler = 1; speed = 100; run_relay = true     status_msg = "Anti-Seize: Weekly Exercise"     if (now - last_run_time > (seize_interval + 30)) then         storage.set('solar_last_run_time', now)     end else     local b1_ok, b1_diff = check_boiler(1, t_b1, b1_std, b1_max)     local b2_ok, b2_diff = check_boiler(2, t_b2, b2_std, b2_max)     if is_danger then         status_msg = "ALARM: Stagnation Stop"     elseif is_reverse then         status_msg = "Safety: Reverse Flow Stop"     else         local priority = grp.getvalue(ga_priority) or 1         if priority == 1 then             if b1_ok then target_boiler, active_diff = 1, b1_diff             elseif b2_ok then target_boiler, active_diff = 2, b2_diff end         else             if b2_ok then target_boiler, active_diff = 2, b2_diff             elseif b1_ok then target_boiler, active_diff = 1, b1_diff end         end     end     if target_boiler > 0 then         run_relay = true         storage.set('solar_last_run_time', now)                 local start_t = storage.get('solar_start_time')         if pump_was_off then             start_t = now; storage.set('solar_start_time', start_t)         end         if start_t and (now - start_t) < kick_duration then             speed = 100             status_msg = "Pump: Kickstart Active (" .. (kick_duration - (now - start_t)) .. "s)"         else             if is_overheating then                 speed = 100; status_msg = "Emergency Dump: Tank " .. target_boiler             else                 local stop_d = (target_boiler == 1) and (grp.getvalue(ga_b1_delta_stop) or 3) or (grp.getvalue(ga_b2_delta_stop) or 3)                 speed = ((active_diff - stop_d) / (grp.getvalue(ga_pid_range) or 12)) * 100                 if speed < min_speed then speed = min_speed end                 if speed > 100 then speed = 100 end                 status_msg = (both_satisfied and "Surplus" or "Heating") .. ": Tank " .. target_boiler             end         end     else         storage.set('solar_start_time', nil)         speed = 0; run_relay = false         if status_msg == "" then status_msg = "Idle: Waiting for Sun" end     end end -- --- FINAL OUTPUTS --- grp.write(ga_pump_run, run_relay) grp.write(ga_pump_speed, speed) grp.write(ga_status_text, status_msg) if target_boiler == 1 then grp.write(ga_valve_pos, false) elseif target_boiler == 2 then grp.write(ga_valve_pos, true) end

CSV for importing datapoints
Code:
address,name,datatype,export 30/1/1,Collector Temp,9.001,1 30/1/2,Return Temp,9.001,1 30/1/3,Boiler 1 Temp,9.001,1 30/1/4,Boiler 2 Temp,9.001,1 30/1/5,Priority Selection,5.005,1 30/1/6,B1 Standard Temp,9.001,1 30/1/7,B1 Max Temp,9.001,1 30/1/8,B2 Standard Temp,9.001,1 30/1/9,B2 Max Temp,9.001,1 30/1/10,B1 Delta Start,9.002,1 30/1/11,B1 Delta Stop,9.002,1 30/1/12,B2 Delta Start,9.002,1 30/1/13,B2 Delta Stop,9.002,1 30/1/14,PID Range,9.002,1 30/1/15,Min Pump Speed,5.001,1 30/1/16,Kickstart Duration,7.005,1 30/1/17,Min Run Time,5.005,1 30/1/18,Collector Overheat,9.001,1 30/1/19,Collector Danger,9.001,1 30/1/20,Maintenance Mode,5.005,1 30/1/21,Pump Speed Output,5.001,1 30/1/22,Pump Power Relay,1.001,1 30/1/23,3-Way Valve Position,1.001,1 30/1/24,System Status Text,16.001,1 30/1/25,Target Hysteresis,9.001,1