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 think it will work and i will try and keep you posted. If somebody could check the coding i would really appriciate.

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

This script is a comprehensive control solution for a dual-boiler solar thermal system, designed for the LogicMachine environment. It balances heat optimization, equipment longevity, and multi-stage safety.
Here is the breakdown of how the logic operates during a typical daily cycle.


1. The Decision Engine (Priority & Selection)
The script first determines which boiler needs heat. It follows a Priority Logic:
  • Boiler 1 (DHW): Usually set as Priority 1. The system checks if it can heat this tank first.
  • Boiler 2 (Buffer): If Boiler 1 is "Full" (reached its Standard Temp) or if the sun isn't hot enough for Boiler 1 but is hot enough for Boiler 2, it switches the 3-way valve.
The function check_boiler handles the math. It calculates the difference ($\Delta T$) between the roof and the tank.
  • To Start: $T_{collector} > T_{boiler} + \Delta T_{start}$.
  • To Stay On: $T_{collector} > T_{boiler} + \Delta T_{stop}$.
    This "gap" between start and stop (Hysteresis) prevents the pump from clicking on and off rapidly.


2. Intelligent Pump Control (PID & Kickstart)
Once a boiler is selected, the script calculates how fast the pump should spin:
  • The Kickstart: Solar pumps often struggle with air bubbles or fluid inertia. For the first few seconds (ga_kickstart_sec), the pump is forced to 100%.
  • Proportional Speed (P-Logic): After kickstart, the speed scales. If the roof is only slightly warmer than the tank, the pump runs at your Minimum Speed (e.g., 25%) to move heat slowly. As the roof gets much hotter (approaching the PID Range), the pump speeds up to 100% to maximize heat transfer.
  • Anti-Cycling (Min Run Time): If the sun disappears behind a cloud, the min_run_sec timer keeps the pump running for a few minutes. This ensures the system doesn't stop just as the fluid in the pipes reaches the heat exchanger.


3. Multi-Stage Safety & Protection
Solar systems can reach dangerous temperatures in summer. This code includes three layers of defense:
  1. Standard Limit: Stops heating a tank at a setpoint (e.g., 60°C) to prevent scalding and save energy.
  2. Overheat Heat Dump: If the Collector hits a high limit (e.g., 110°C), the script ignores the Standard Limit and pushes heat into the boilers up to their Absolute Max (e.g., 90°C) to try and cool the roof down.
  3. Stagnation Stop: If the collector reaches the Danger Temp (e.g., 140°C), the fluid has likely turned to steam. Pumping steam destroys pumps. The script executes an Emergency Stop.
  4. Reverse Flow Protection: If the pipe returning from the roof is hotter than the roof itself, it means you are accidentally heating the outdoors. The script detects this and stops the pump.


4. Maintenance & Manual Override
The Maintenance Mode (Object 30/1/20) allows you to take manual control:
  • Auto (0): The logic described above runs the system.
  • Manual Off (1): Forces the pump and relay OFF. Use this for repairs.
  • Manual On (2): Forces the pump to 100% and opens the relay. Use this to bleed air or test the pump.


5. Hardware Interfacing (Outputs)
The script controls two different types of hardware:
  • The Relay (ga_pump_run): A binary ON/OFF command. This is used to physically power up the pump.
  • The Speed (ga_pump_speed): A 0–100% signal. This is usually converted by a LogicMachine AO (Analog Output) module to a 0–10V signal for the pump's PWM input.
  • The Valve (ga_valve_pos): A binary signal to the 3-way valve to switch between Tank 1 and Tank 2.

Code:
-- [[ SOLAR CONTROL SYSTEM: PID, KICKSTART, OVERHEAT, MIN-RUN & MAINTENANCE ]] --

-- --- 1. SENSOR INPUTS ---
local ga_coll_temp = '30/1/1'      -- Roof Collector Temperature
local ga_ret_temp  = '30/1/2'      -- Return Pipe Temperature (before roof)
local ga_b1_temp   = '30/1/3'      -- Boiler 1 Temperature (Priority)
local ga_b2_temp   = '30/1/4'      -- Boiler 2 Temperature (Secondary)

-- --- 2. OPERATIONAL SETTINGS ---
local ga_priority = '30/1/5'       -- Switch: 1 = Boiler 1, 2 = Boiler 2
local ga_b1_std_temp = '30/1/6'    -- Target Temp Boiler 1 (e.g. 60°C)
local ga_b1_max_temp = '30/1/7'    -- Safety Limit Boiler 1 (e.g. 90°C)
local ga_b2_std_temp = '30/1/8'    -- Target Temp Boiler 2
local ga_b2_max_temp = '30/1/9'    -- Safety Limit Boiler 2

-- --- 3. DIFFERENTIAL (DELTA T) SETTINGS ---
local ga_b1_delta_start = '30/1/10' -- Delta T needed to START heating B1
local ga_b1_delta_stop  = '30/1/11' -- Delta T where B1 heating STOPS
local ga_b2_delta_start = '30/1/12' -- Delta T needed to START heating B2
local ga_b2_delta_stop  = '30/1/13' -- Delta T where B2 heating STOPS

-- --- 4. PUMP PID & BEHAVIOR SETTINGS ---
local ga_pid_range      = '30/1/14' -- Delta T range for scaling pump to 100%
local ga_min_pump_speed = '30/1/15' -- Absolute minimum speed (e.g. 25%)
local ga_kickstart_sec  = '30/1/16' -- 100% power duration on startup (sec)
local ga_min_run_min    = '30/1/17' -- Minimum pump run time (minutes)

-- --- 5. PROTECTION & MAINTENANCE ---
local ga_coll_overheat  = '30/1/18' -- Temp to start emergency heat dump
local ga_coll_danger    = '30/1/19' -- Stagnation temp to STOP pump
local ga_maint_mode     = '30/1/20' -- NEW: 0=Auto, 1=Manual OFF, 2=Manual ON (100%)

-- --- 6. OUTPUTS ---
local ga_pump_speed = '30/1/21'     -- Pump Speed Output (0-100%)
local ga_pump_run   = '30/1/22'     -- Pump Power Relay (On/Off)
local ga_valve_pos  = '30/1/23'     -- Valve Position (0=B1, 1=B2)

-- --- 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 -- Default to Auto

local p1_start, p1_stop = grp.getvalue(ga_b1_delta_start) or 8, grp.getvalue(ga_b1_delta_stop) or 3
local p2_start, p2_stop = grp.getvalue(ga_b2_delta_start) or 8, grp.getvalue(ga_b2_delta_stop) or 3
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 min_speed = grp.getvalue(ga_min_pump_speed) or 20
local min_run_sec = (grp.getvalue(ga_min_run_min) or 3) * 60
local now = os.time()

-- Current Status
local last_speed = grp.getvalue(ga_pump_speed)
local current_valve = grp.getvalue(ga_valve_pos)
local pump_was_off = (last_speed == 0)

-- --- SAFETY CHECKS ---
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)

-- --- LOGIC FUNCTION ---
function check_boiler(id, t_curr, t_std, t_max, d_start, d_stop)
    local diff = t_coll - t_curr
    local currently_active = (not pump_was_off and ((id == 1 and not current_valve) or (id == 2 and current_valve)))
    local limit = is_overheating and t_max or t_std
   
    if t_curr >= t_max then return false, 0 end
   
    if currently_active then
        local start_t = storage.get('solar_start')
        local forced_run = start_t and (now - start_t < min_run_sec)
        if forced_run then return true, diff end
        if diff > d_stop and t_curr < limit and not is_reverse then return true, diff end
    else
        if diff > d_start and t_curr < limit then return true, diff end
    end
    return false, 0
end

-- --- MAIN LOGIC ---
local target_boiler = 0
local active_diff = 0
local priority = grp.getvalue(ga_priority) or 1
local speed = 0
local run_relay = false

-- MAINTENANCE OVERRIDE
if maint_mode == 1 then -- MANUAL OFF
    target_boiler = 0
    speed = 0
    run_relay = false
elseif maint_mode == 2 then -- MANUAL ON
    target_boiler = 1 -- Default to B1 for manual test
    speed = 100
    run_relay = true
else -- AUTOMATIC MODE
    local b1_ok, b1_diff = check_boiler(1, t_b1, b1_std, b1_max, p1_start, p1_stop)
    local b2_ok, b2_diff = check_boiler(2, t_b2, b2_std, b2_max, p2_start, p2_stop)

    if is_danger then
        target_boiler = 0
    elseif 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

    if target_boiler > 0 then
        run_relay = true
        if is_overheating then
            speed = 100
        else
            local stop_d = (target_boiler == 1) and p1_stop or p2_stop
            speed = ((active_diff - stop_d) / (grp.getvalue(ga_pid_range) or 12)) * 100
            if speed < min_speed then speed = min_speed end
        end

        -- Kickstart
        local start_t = storage.get('solar_start')
        if pump_was_off then
            storage.set('solar_start', now)
            speed = 100
        elseif start_t and (now - start_t) < (grp.getvalue(ga_kickstart_sec) or 5) then
            speed = 100
        end
    else
        storage.set('solar_start', nil)
        speed = 0
        run_relay = false
    end
end

-- --- FINAL OUTPUTS ---
grp.write(ga_pump_run, run_relay)
grp.write(ga_pump_speed, speed)
if target_boiler == 1 then grp.write(ga_valve_pos, false)
elseif target_boiler == 2 then grp.write(ga_valve_pos, true) end