import asyncio from tasks import ATask from ws.message import MsgIo from utils.value import ChangedFloat from components import APid, AStirrer from components.sud import Sud, SudState # How close theta_ist needs to be to theta_soll_set to count as "reached". TEMP_REACHED_TOLERANCE = 0.2 class SudTask(ATask): def __init__(self, sud: Sud, tc: APid, stirrer: AStirrer, interval, msg_handler: MsgIo): ATask.__init__(self, interval) self.sud = sud self.tc = tc self.stirrer = stirrer self.msg_handler = msg_handler msg_handler.set_recv_handler(self.recv) def on_rast_changed(self, rast): if rast is not None: self.tc.set_theta_soll(rast['temp']) self.tc.set_heatrate_soll(rast['heatRate']) asyncio.create_task(self.send({'Rast': { 'Index': self.sud.index, 'Temp': rast['temp'] if rast else None, 'HeatRate': rast['heatRate'] if rast else None, 'Time': rast['time'] if rast else None, 'WaitForUser': rast.get('waitForUser', False) if rast else None, }})) def on_state_changed(self, value): asyncio.create_task(self.send({'State': str(value)})) if value == SudState.RAMPING: # Stir continuously while ramping to a new target temperature. self.stirrer.set_duty_cycle(1.0) self.stirrer.set_speed(self.sud.stirr_speed_heat) elif value == SudState.HOLDING: # Stir intermittently while holding at the rest's temperature. self.stirrer.set_cycle_time(self.sud.stirr_cycle_time) self.stirrer.set_duty_cycle(self.sud.stirr_duty_rast) self.stirrer.set_speed(self.sud.stirr_speed_rast) elif value in (SudState.WAIT_USER, SudState.DONE): # Stop stirring while paused for the user or once the schedule is done. self.stirrer.set_duty_cycle(1.0) self.stirrer.set_speed(0) def on_hold_remaining_changed(self, value): asyncio.create_task(self.send({'HoldRemaining': value})) async def recv(self, data): for pair in data.items(): if 'Start' in pair[0]: self.sud.start() elif 'Confirm' in pair[0]: self.sud.confirm() async def send(self, data): await self.msg_handler.send(data) async def on_process(self): print("{}: Started with interval {} s".format(self.msg_handler.get_key(), self.interval)) self.sud.set_on_changed('rast', self.on_rast_changed) self.sud.set_on_changed('state', self.on_state_changed) self.sud.set_on_changed('hold_remaining', ChangedFloat(self.on_hold_remaining_changed, prec=0).set) asyncio.create_task(self.send({'Name': self.sud.name, 'Description': self.sud.description})) while True: if self.sud.state == SudState.RAMPING: # Compare against the controller's own live setpoint/measurement # rather than its state machine, which can still read HOLD from # the previous rest for one tick after a new target is pushed. if abs(self.tc.get_theta_ist() - self.tc.get_theta_soll_set()) < TEMP_REACHED_TOLERANCE: self.sud.temp_reached() self.sud.tick(self.interval) await asyncio.sleep(self.interval)