This forum uses cookies
This forum makes use of cookies to store your login information if you are registered, and your last visit if you are not. Cookies are small text documents stored on your computer; the cookies set by this forum can only be used on this website and pose no security risk. Cookies on this forum also track the specific topics you have read and when you last read them. Please confirm that you accept these cookies being set.

Solar Heating Controller
#1
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
Reply


Forum Jump: