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