1 # Copyright (C) 2008-2011 W. Trevor King <wking@drexel.edu>
3 # This file is part of tempcontrol.
5 # tempcontrol 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 # tempcontrol 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 tempcontrol. 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('tempcontrol.backend.melcor')
26 >>> 'MelcorBackend' in dir(mod)
28 >>> _import_by_name('tempcontrol.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 """Temperature 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)
68 * T_i, T_d: time (e.g. seconds)
71 self._max_current = None
74 def _convert_F_to_C(F):
78 def _convert_C_to_F(C):
82 "Release resources and disconnect from any hardware."
86 "Return the current process temperature in degrees Celsius"
87 raise NotImplementedError()
89 def get_ambient_temp(self):
90 "Return room temperature in degrees Celsius"
91 raise NotImplementedError()
93 def set_max_current(self, max):
94 "Set the max current in Amps"
95 raise NotImplementedError()
97 def get_max_current(self):
98 "Get the max current in Amps"
99 raise NotImplementedError()
101 def get_current(self):
102 """Return the calculated control current in Amps"
104 The returned current is not the actual current, but the
105 current that the temperature controller calculates it should
106 generate. If the voltage required to generate that current
107 exceeds the controller's max voltage (15 V on mine), then the
108 physical current will be less than the value returned here.
110 raise NotImplementedError()
113 "Return a list of control modes supported by this backend"
114 raise NotImplementedError()
117 "Return the current control mode"
118 raise NotImplementedError()
120 def set_mode(self, mode):
121 "Set the current control mode"
122 raise NotImplementedError
124 def dump_configuration(self):
127 raise NotImplementedError()
129 def restore_configuration(self):
132 raise NotImplementedError()
135 class ManualMixin (object):
136 def set_current(self, current):
137 """Set the desired control current in Amps
139 raise NotImplementedError()
142 class PIDMixin (object):
143 def set_setpoint(self, setpoint):
144 "Set the temperature setpoint in degrees Celsius"
145 raise NotImplementedError()
147 def get_setpoint(self, setpoint):
148 "Get the temperature setpoint in degrees Celsius"
149 raise NotImplementedError()
151 def get_cooling_gains(self):
153 raise NotImplementedError()
155 def set_cooling_gains(self, proportional=None, integral=None,
160 raise NotImplementedError()
162 def get_heating_gains(self):
164 raise NotImplementedError()
166 def set_heating_gains(self, proportional=None, integral=None,
171 raise NotImplementedError()
173 def get_feedback_terms(self):
176 raise NotImplementedError()
178 def clear_integral_term(self):
179 """Reset the integral feedback turn (removing integrator windup)
181 Because the proportional term provides no control signal when
182 the system exactly matches the setpoint, a P-only algorithm
183 will tend to "droop" off the setpoint. The equlibrium
184 position is one where the droop-generated P term balances the
185 systems temperature leakage. To correct for this, we add the
186 integral feedback term, which adjusts the control signal to
187 minimize long-term differences between the output and setpoint.
189 One issue with the integral term is "integral windup". When
190 the signal spends a significant time away from the setpoint
191 (e.g. during a long ramp up to operating temperature), the
192 integral term can grow very large, causing overshoot once the
193 output reaches the setpoint. To allow our controller to avoid
194 this, this method manually clears the intergal term for the
197 raise NotImplementedError()