Fork PyRPL on GitHub

Source code for pyrpl.software_modules.software_pid

from .loop import PlotLoop
from ..attributes import *
from ..modules import Module
import numpy as np

[docs]class SoftwarePidLoop(PlotLoop): @property def input(self): return recursive_getattr(self.parent, self.parent.input) @property def output(self): return recursive_getattr(self.parent, self.parent.output) @output.setter def output(self, value): return recursive_setattr(self.parent, self.parent.output, value)
[docs] def setup_loop(self): """ put your initialization routine here""" if self.parent.reset_ival_on_restart: self.parent._ival = 0 self.lasttime = self.time self.lasterror = 0
[docs] def loop(self): input = self.input if input is None or np.isnan(input): self._logger.error("Could not retrieve the input signal for %s.%s.", self.parent, self.name) return error = input - self.parent.setpoint dt, self.lasttime = self.time - self.lasttime, self.time self.parent._ival += self.parent.i * dt * 2.0 * np.pi * error self.parent._ival = self.saturate_output(self.parent._ival) out = self.parent._ival + self.parent.p * error + self.parent.d * 2.0 * np.pi / dt * (error-self.lasterror) out = self.saturate_output(out) self.output = out if self.parent.plot: self.plotappend(r=error, g=self.output) self.lasterror = error self.interval = self.parent.interval self.parent._loop_hook()
[docs] def saturate_output(self, v): if v > self.parent.output_max: v = self.parent.output_max elif v < self.parent.output_min: v = self.parent.output_min return v
[docs] def teardown_loop(self): """ put your destruction routine here""" self.parent.__class__.running.value_updated(self.parent, False)
[docs]class RunningProperty(LedProperty):
[docs] def get_value(self, obj): val = hasattr(obj, 'loop') and obj.loop is not None if val != super(RunningProperty, self).get_value(obj): setattr(obj, self.name, val) return val
[docs] def start(self, obj): """ starts a new loop """ self.stop(obj) obj.loop = SoftwarePidLoop(parent=obj, name="loop", interval=obj.interval, plot=True, #obj.plot, # obj.plot is handled in loop() above plotter="plotter")
[docs] def stop(self, obj): """ stops the running loop """ if hasattr(obj, 'loop') and obj.loop is not None: obj.loop._clear() obj.loop = None
true_function = start false_function = stop
[docs]class SoftwarePidController(Module): p = FloatProperty(default=-1.0) i = FloatProperty(default=0) d = FloatProperty(default=0) _ival = FloatProperty(default=0) reset_ival_on_restart = BoolProperty(default=True) setpoint = FloatProperty(default=0) input = StringProperty(default='pyrpl.rp.sampler.in1') output = StringProperty(default='pyrpl.rp.asg0.offset') output_max = FloatProperty(default=np.inf) output_min = FloatProperty(default=-np.inf) interval = FloatProperty(default=1.0, min=0) plot = BoolProperty(default=True) plotter = Plotter(legend='error (red, V) and output (green, V)') # plotting window running = RunningProperty(default=False) _setup_attributes = ['input', 'output', 'p', 'i', 'd', 'setpoint', 'reset_ival_on_restart', 'interval', 'plot', 'running'] _gui_attributes = _setup_attributes + ["plotter"]
[docs] def start(self): if not self.running: self.running = True
[docs] def stop(self): self.running = False
def _loop_hook(self): """ this function is called at the end of each loop. May be used for additional plotting, for example """ pass