-
Notifications
You must be signed in to change notification settings - Fork 40
/
pid.py
89 lines (74 loc) · 3.06 KB
/
pid.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
from time import time
import logging
# Based on Arduino PID Library
# See https://github.com/br3ttb/Arduino-PID-Library
class PIDArduino(object):
"""A proportional-integral-derivative controller.
Args:
sampletime (float): The interval between calc() calls.
kp (float): Proportional coefficient.
ki (float): Integral coefficient.
kd (float): Derivative coefficient.
out_min (float): Lower output limit.
out_max (float): Upper output limit.
time (function): A function which returns the current time in seconds.
"""
def __init__(self, sampletime, kp, ki, kd, out_min=float('-inf'),
out_max=float('inf'), time=time):
if kp is None:
raise ValueError('kp must be specified')
if ki is None:
raise ValueError('ki must be specified')
if kd is None:
raise ValueError('kd must be specified')
if sampletime <= 0:
raise ValueError('sampletime must be greater than 0')
if out_min >= out_max:
raise ValueError('out_min must be less than out_max')
self._logger = logging.getLogger(type(self).__name__)
self._Kp = kp
self._Ki = ki * sampletime
self._Kd = kd / sampletime
self._sampletime = sampletime * 1000
self._out_min = out_min
self._out_max = out_max
self._integral = 0
self._last_input = 0
self._last_output = 0
self._last_calc_timestamp = 0
self._time = time
def calc(self, input_val, setpoint):
"""Adjusts and holds the given setpoint.
Args:
input_val (float): The input value.
setpoint (float): The target value.
Returns:
A value between `out_min` and `out_max`.
"""
now = self._time() * 1000
if (now - self._last_calc_timestamp) < self._sampletime:
return self._last_output
# Compute all the working error variables
error = setpoint - input_val
input_diff = input_val - self._last_input
# In order to prevent windup, only integrate if the process is not saturated
if self._last_output < self._out_max and self._last_output > self._out_min:
self._integral += self._Ki * error
self._integral = min(self._integral, self._out_max)
self._integral = max(self._integral, self._out_min)
p = self._Kp * error
i = self._integral
d = -(self._Kd * input_diff)
# Compute PID Output
self._last_output = p + i + d
self._last_output = min(self._last_output, self._out_max)
self._last_output = max(self._last_output, self._out_min)
# Log some debug info
self._logger.debug('P: {0}'.format(p))
self._logger.debug('I: {0}'.format(i))
self._logger.debug('D: {0}'.format(d))
self._logger.debug('output: {0}'.format(self._last_output))
# Remember some variables for next time
self._last_input = input_val
self._last_calc_timestamp = now
return self._last_output