1 # Copyright (C) 2011-2012 W. Trevor King <wking@tremily.us>
3 # This file is part of pypid.
5 # pypid is free software: you can redistribute it and/or modify it under the
6 # terms of the GNU General Public License as published by the Free Software
7 # Foundation, either version 3 of the License, or (at your option) any later
10 # pypid is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License along with
15 # pypid. If not, see <http://www.gnu.org/licenses/>.
17 """Assorted backends for interfacing with your particular hardware.
21 def _import_by_name(modname):
23 >>> mod = _import_by_name('pypid.backend.melcor')
24 >>> 'MelcorBackend' in dir(mod)
26 >>> _import_by_name('pypid.backend.highly_unlikely')
27 Traceback (most recent call last):
29 ImportError: No module named highly_unlikely
31 module = __import__(modname)
32 components = modname.split('.')
33 for comp in components[1:]:
34 module = getattr(module, comp)
37 def get_backend(name):
38 n = '%s.%s' % (__name__, name)
39 mod = _import_by_name(n)
41 obj = getattr(mod, attr)
43 if obj != Backend and issubclass(obj, Backend):
47 raise ValueError(name)
50 class Backend (object):
51 """Process-control backend
53 There are several common forms for a PID control formula. For the
54 purpose of setting heating and cooling gains (`.get_*_gains()` and
55 `.set_*_gains()`), we'll use the standard form::
57 MV(t) = K_p ( e(t) + 1/T_i \int_0^t e(\tau) d\tau + T_d de(t)/dt )
59 where `e(t) = SP - PV` is the error function, MV is the
60 manipulated variable, SP is the setpoint, and PV is the process
63 In this formulation, the parameter units will be:
65 * K_p: MV-units/PV-units (e.g. amp/K for a thermoelectric
66 controller). Don't confuse this `proportional gain` with the
67 `process gain` used in `TestBackend`.
68 * T_i, T_d: time (e.g. seconds)
77 "Release resources and disconnect from any hardware."
81 "Return the current process variable in PV-units"
82 raise NotImplementedError()
84 def get_ambient_pv(self):
85 "Return the ambient (bath) status in PV-units"
86 raise NotImplementedError()
88 def set_max_mv(self, max):
89 "Set the max manipulated variable in MV-units"
90 raise NotImplementedError()
92 def get_max_mvt(self):
93 "Get the max manipulated variable MV-units"
94 raise NotImplementedError()
97 """Return the calculated manipulated varaible in MV-units
99 The returned current is not the actual MV, but the MV that the
100 controller calculates it should generate. For example, if the
101 voltage required to generate an MV current exceeds the
102 controller's max voltage, then the physical current will be
103 less than the value returned here.
105 raise NotImplementedError()
108 "Return a list of control modes supported by this backend"
109 raise NotImplementedError()
112 "Return the current control mode"
113 raise NotImplementedError()
115 def set_mode(self, mode):
116 "Set the current control mode"
117 raise NotImplementedError
119 def dump_configuration(self):
122 raise NotImplementedError()
124 def restore_configuration(self):
127 raise NotImplementedError()
130 class ManualMixin (object):
131 def set_mv(self, current):
132 "Set the desired manipulated variable in MV-units"
133 raise NotImplementedError()
136 class PIDMixin (object):
137 def set_setpoint(self, setpoint):
138 "Set the process variable setpoint in PV-units"
139 raise NotImplementedError()
141 def get_setpoint(self, setpoint):
142 "Get the process variable setpoint in PV-units"
143 raise NotImplementedError()
145 def get_down_gains(self):
147 raise NotImplementedError()
149 def set_down_gains(self, proportional=None, integral=None,
154 raise NotImplementedError()
156 def get_up_gains(self):
158 raise NotImplementedError()
160 def set_up_gains(self, proportional=None, integral=None, derivative=None):
164 raise NotImplementedError()
166 def get_feedback_terms(self):
169 raise NotImplementedError()
171 def clear_integral_term(self):
172 """Reset the integral feedback turn (removing integrator windup)
174 Because the proportional term provides no control signal when
175 the system exactly matches the setpoint, a P-only algorithm
176 will tend to "droop" off the setpoint. The equlibrium
177 position is one where the droop-generated P term balances the
178 systems temperature leakage. To correct for this, we add the
179 integral feedback term, which adjusts the control signal to
180 minimize long-term differences between the output and setpoint.
182 One issue with the integral term is "integral windup". When
183 the signal spends a significant time away from the setpoint
184 (e.g. during a long ramp up to operating temperature), the
185 integral term can grow very large, causing overshoot once the
186 output reaches the setpoint. To allow our controller to avoid
187 this, this method manually clears the intergal term for the
190 raise NotImplementedError()
193 class TemperatureMixin (object):
195 def _convert_F_to_C(F):
199 def _convert_C_to_F(C):