Created
January 6, 2024 01:36
-
-
Save JeanOlivier/08ebf167e77b14a6b28f1d75bd67bb18 to your computer and use it in GitHub Desktop.
Home Assistant pyscript faikin Daikin PID
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Trying to do a PID for the daikin | |
# | |
# This is a WIP prototype. Only heating was tested. | |
# Using log.error because I can't seem to be able to find how to read other log levels. | |
from random import choices | |
ENABLED = True # Disabled if False. If true, the PID takes over when the Daikin is on. | |
target_temp = 22.5 | |
# Tune those carefully | |
# Best yet is 1,45,80 | |
kp = 1 | |
ti = 45 | |
td = 80 | |
i_0 = -0 # initial i value | |
i_max = 2 # limit i term in amplitude # historic best is 2,-2 | |
i_min = -2 | |
pid_on_threshold = 2 # If we're more tha pid_on_threshold under target, we are powerful | |
double_down = False # Double "cooling" correction when above target | |
sample_period = 5 # seconds between samples (dx) | |
derivative_points = 120//sample_period # pts for dy/dx average | |
# Don't touch this, this scaling is for ti and td to be roughly timescales | |
ki = kp/ti | |
kd = kp*td | |
def limit_i(i): | |
return min(max(i, i_min), i_max) | |
def integrate(value, accumulator): | |
return limit_i(ki*value + accumulator) | |
def derivative(buffer): | |
if len(buffer) >= 2: | |
dy_list = [a-b for a,b in zip(buffer[1:], buffer[:-1])] | |
dy = sum(dy_list)/derivative_points | |
else: | |
dy = 0 | |
dx = sample_period | |
return dy/dx | |
def pid(d_buffer, i_accumulator): | |
p = kp*d_buffer[-1] | |
i = integrate(d_buffer[-1], i_accumulator) | |
d = kd*derivative(d_buffer) | |
u = p + i + d | |
log.error(f"p = {p:.6f}\ni = {i:.6f}\nd = {d:.6f}\nu = {u:.6f}") | |
return u, i | |
def set_temperature(name, temperature): | |
climate.set_temperature( | |
entity_id = f"climate.{name}", | |
temperature = temperature | |
) | |
def set_fan_mode(name, fan_mode): | |
climate.set_fan_mode( | |
entity_id = f"climate.{name}", | |
fan_mode = str(fan_mode) | |
) | |
@time_trigger | |
def pid_control(): | |
task.unique(f"Daikin_PID") | |
if not ENABLED: | |
log.error("Stopping Daikin PID...") | |
return | |
d_buffer = [] | |
i_accumulator = i_0 | |
old_target = climate.daikin.temperature | |
log.error(f"PID started. Target: {target_temp}°C, Old Target: {old_target}°C") | |
while True: | |
task.sleep(sample_period) | |
if climate.daikin == "off": | |
d_buffer = [] | |
continue | |
value = float(sensor.daikin_bletemp) | |
error = target_temp - value | |
if error > pid_on_threshold: | |
correction = 100 # Let's heat! | |
set_fan_mode("daikin", 5) | |
else: | |
set_fan_mode("daikin", 'auto') | |
d_buffer.append(error) | |
if len(d_buffer) > derivative_points: | |
d_buffer.pop(0) | |
correction, i_accumulator = pid(d_buffer, i_accumulator) | |
if correction < 0 and error < 0 and double_down: | |
correction *= 2 # to favorise stop heating | |
daikin_temp = climate.daikin.current_temperature | |
new_target = target_temp + correction | |
new_target = max(max(min(new_target, 32), 16), daikin_temp - 4) | |
dbl = 2*new_target | |
# better avarage precision by switching between discretization; disabled because it causes frequent target changes | |
# new_target = float(int(dbl) + choices([0, 1], [dbl%1, 1-dbl%1])[0])/2 | |
new_target = round(dbl)/2 # round(2*x)/2 is the same as rounding to nearest 0.5 | |
if new_target != old_target: # minimize useless communication | |
set_temperature("daikin", new_target) | |
old_target = new_target | |
log.error(f"v,e,dT,corr,dTarget: {(value, error, daikin_temp, correction, new_target)}") | |
log.error("PID: Done") | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment