refactor: extract PID class to agebd package
Deploy / deploy (push) Canceled after 0s

This commit is contained in:
Benjamin Labrecque
2026-07-17 16:19:11 +02:00
parent 3289b368b1
commit 0d0414dbf1
3 changed files with 38 additions and 76 deletions
+36
View File
@@ -0,0 +1,36 @@
class PID:
# Proportional-Integral-Derivative (PID) controller class
# PID setup procedure:
# 1. set integral and derivative gains ki, kd to 0
# 2. set proportional gain kp to low, stable value
# 3. increase kp and create a disturbance, e.g., by changing the setpoint
# 4. increase kp until feedback parameter oscillates
# 5. set kp to 40 % of the critical value triggering oscillation
# 6. slowly increase ki to eliminate the steady state error
# 7. adjust it until setpoint is reached accurately without overshoot
# 8. tune kd to minimize oscillations and overshoots (caution: high kd can amplify noise)
def __init__(self, kp, ki, kd, setpoint):
# Proportional Gain >> The "present" term. It provides a response proportional to the current error. Increasing kp makes the system respond faster, but if pushed too high, it will cause violent overshoot and oscillation.
self.kp = kp
# Integral Gain >> The "past" term. It accumulates the errors over time to eliminate steady-state error. However, high ki values can cause sluggishness or instability (e.g., integral windup).
self.ki = ki
# Derivative Gain >> The "future" term. It measures the rate of change of the error, acting as a damping factor to reduce overshoot and smooth out rapid, unwanted movements
self.kd = kd
self.setpoint = setpoint
self.integral = 0
self.prev_error = 0
def update(self, measurement, dt):
error = self.setpoint - measurement
self.integral += error * dt
derivative = (error - self.prev_error) / dt if dt > 0 else 0
self.prev_error = error
# Calculate the final output using Kp, Ki, and Kd which is written to actuator
output = (self.kp * error) + (self.ki * self.integral) + (self.kd * derivative)
return output
@@ -3,6 +3,7 @@ from time import perf_counter
import numpy as np
from epics import dbr
from agebd.pid import PID
from agebd.pv import get_pv_class, get_pv_external_class
from agebd.service.base import BaseService
from agebd.service.pvs import BasePVs
@@ -59,44 +60,6 @@ class PVs(BasePVs):
self.CallbackPV = PV_EXTERNAL("AGEBD-TUNEBUMP:QX.VAL", auto_monitor=dbr.DBE_VALUE)
class PID:
# Proportional-Integral-Derivative (PID) controller class
# PID setup procedure:
# 1. set integral and derivative gains ki, kd to 0
# 2. set proportional gain kp to low, stable value
# 3. increase kp and create a disturbance, e.g., by changing the setpoint
# 4. increase kp until feedback parameter oscillates
# 5. set kp to 40 % of the critical value triggering oscillation
# 6. slowly increase ki to eliminate the steady state error
# 7. adjust it until setpoint is reached accurately without overshoot
# 8. tune kd to minimize oscillations and overshoots (caution: high kd can amplify noise)
def __init__(self, kp, ki, kd, setpoint):
# Proportional Gain >> The "present" term. It provides a response proportional to the current error. Increasing kp makes the system respond faster, but if pushed too high, it will cause violent overshoot and oscillation.
self.kp = kp
# Integral Gain >> The "past" term. It accumulates the errors over time to eliminate steady-state error. However, high ki values can cause sluggishness or instability (e.g., integral windup).
self.ki = ki
# Derivative Gain >> The "future" term. It measures the rate of change of the error, acting as a damping factor to reduce overshoot and smooth out rapid, unwanted movements
self.kd = kd
self.setpoint = setpoint
self.integral = 0
self.prev_error = 0
def update(self, measurement, dt):
error = self.setpoint - measurement
self.integral += error * dt
derivative = (error - self.prev_error) / dt if dt > 0 else 0
self.prev_error = error
# Calculate the final output using Kp, Ki, and Kd which is written to actuator
output = (self.kp * error) + (self.ki * self.integral) + (self.kd * derivative)
return output
class Service(BaseService[PVs]):
def __init__(
self,
@@ -3,6 +3,7 @@ from time import perf_counter
import numpy as np
from epics import dbr
from agebd.pid import PID
from agebd.pv import get_pv_class, get_pv_external_class
from agebd.service.base import BaseService
from agebd.service.pvs import BasePVs
@@ -59,44 +60,6 @@ class PVs(BasePVs):
self.CallbackPV = PV_EXTERNAL("AGEBD-TUNEBUMP:QY.VAL", auto_monitor=dbr.DBE_VALUE)
class PID:
# Proportional-Integral-Derivative (PID) controller class
# PID setup procedure:
# 1. set integral and derivative gains ki, kd to 0
# 2. set proportional gain kp to low, stable value
# 3. increase kp and create a disturbance, e.g., by changing the setpoint
# 4. increase kp until feedback parameter oscillates
# 5. set kp to 40 % of the critical value triggering oscillation
# 6. slowly increase ki to eliminate the steady state error
# 7. adjust it until setpoint is reached accurately without overshoot
# 8. tune kd to minimize oscillations and overshoots (caution: high kd can amplify noise)
def __init__(self, kp, ki, kd, setpoint):
# Proportional Gain >> The "present" term. It provides a response proportional to the current error. Increasing kp makes the system respond faster, but if pushed too high, it will cause violent overshoot and oscillation.
self.kp = kp
# Integral Gain >> The "past" term. It accumulates the errors over time to eliminate steady-state error. However, high ki values can cause sluggishness or instability (e.g., integral windup).
self.ki = ki
# Derivative Gain >> The "future" term. It measures the rate of change of the error, acting as a damping factor to reduce overshoot and smooth out rapid, unwanted movements
self.kd = kd
self.setpoint = setpoint
self.integral = 0
self.prev_error = 0
def update(self, measurement, dt):
error = self.setpoint - measurement
self.integral += error * dt
derivative = (error - self.prev_error) / dt if dt > 0 else 0
self.prev_error = error
# Calculate the final output using Kp, Ki, and Kd which is written to actuator
output = (self.kp * error) + (self.ki * self.integral) + (self.kd * derivative)
return output
class Service(BaseService[PVs]):
def __init__(
self,