import asyncio import json import os import time from tasks import ATask from components import APid, AHeater class ServerLogTask(ATask): """Continuously records temp/power samples from server start to shutdown. Writes a single log_{date_time}.json on close() in the same format as SudLogTask (Name/Samples) so analyze_log.py and replay_sim.py can consume it directly. Unlike SudLogTask, which only records during an active Sud run, this log covers the full server session regardless of Sud state — useful for verifying controller and heater behaviour outside of a scheduled brew.""" def __init__(self, tc: APid, heater: AHeater, heater_task, interval, path='./logs'): ATask.__init__(self, interval) self.tc = tc self.heater = heater self.heater_task = heater_task self.path = path self._samples = [] self._t0 = time.monotonic() self._run_id = time.strftime("%Y%m%dT%H%M%S", time.localtime()) async def on_process(self): while True: power_set = max(self.heater_task.power_soll, self.heater_task.power_actor) self._samples.append({ 't': time.monotonic() - self._t0, 'timestamp': time.time(), 'temp_ist': self.tc.theta_ist, 'temp_soll': self.tc.theta_soll_set, 'rate_ist': self.tc.heatrate_ist, 'rate_soll': self.tc.heatrate_soll, 'power_set': power_set, 'power_eff': self.heater.power_eff, }) await asyncio.sleep(self.interval) def write(self): if not self._samples: return os.makedirs(self.path, exist_ok=True) filename = 'log_{}.json'.format(self._run_id) path = os.path.join(self.path, filename) with open(path, 'w') as f: json.dump({'Name': '', 'Samples': self._samples}, f, indent='\t') print('Server log: wrote {} samples to {}'.format(len(self._samples), filename))