Skip to content

Instantly share code, notes, and snippets.

@JeanOlivier
Created January 6, 2024 01:36
Show Gist options
  • Save JeanOlivier/08ebf167e77b14a6b28f1d75bd67bb18 to your computer and use it in GitHub Desktop.
Save JeanOlivier/08ebf167e77b14a6b28f1d75bd67bb18 to your computer and use it in GitHub Desktop.
Home Assistant pyscript faikin Daikin PID
# 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