import json from enum import Enum from utils.value import AttributeChange # Specific heat capacities [J/(kg*K)] used to derive a single lumped # (mass, specific heat) pair for the pot's contents from pot_mass/ # grain_mass/water_mass. Approximate, typical values. SPECIFIC_HEAT_WATER = 4190 SPECIFIC_HEAT_GRAIN = 1800 SPECIFIC_HEAT_BY_MATERIAL = { "Edelstahl 18/10": 500, } class SudState(Enum): IDLE = 0 RAMPING = 1 HOLDING = 2 WAIT_USER = 3 DONE = 4 class Sud(AttributeChange): def __init__(self, path): AttributeChange.__init__(self) with open(path) as f: data = json.load(f) self.name = data.get('Name', '') self.description = data.get('Description', '') self.schedule = data['schedule'] self.pot_mass = data.get('pot_mass', 0) self.pot_material = data.get('pot_material') self.grain_mass = data.get('grain_mass', 0) self.water_mass = data.get('water_mass', 0) self.index = -1 self.hold_remaining = 0.0 self.state = SudState.IDLE self.step = None self.user_message = None def derive_plant_params(self): """Lumped (mass, specific heat) lifted from pot_mass/pot_material/ grain_mass/water_mass, for use as Pot's "M"/"C" params.""" c_pot = SPECIFIC_HEAT_BY_MATERIAL.get(self.pot_material, SPECIFIC_HEAT_WATER) mass = self.water_mass + self.grain_mass + self.pot_mass capacitance = (self.water_mass * SPECIFIC_HEAT_WATER + self.grain_mass * SPECIFIC_HEAT_GRAIN + self.pot_mass * c_pot) return { 'M': mass, 'C': capacitance / mass if mass > 0 else SPECIFIC_HEAT_WATER, } def start(self): if self.state != SudState.IDLE: return self.index = -1 self._advance() def confirm(self): if self.state == SudState.WAIT_USER: self._advance() def temp_reached(self): if self.state == SudState.RAMPING: self._finish_step() def tick(self, dt): if self.state == SudState.HOLDING: self.hold_remaining -= dt if self.hold_remaining <= 0: self._finish_step() def _advance(self): self.index += 1 if self.index >= len(self.schedule): self.state = SudState.DONE self.user_message = None self.step = None return next_step = self.schedule[self.index] if next_step['type'] == 'heat': self.state = SudState.RAMPING else: self.hold_remaining = next_step.get('duration', 0) self.state = SudState.HOLDING self.user_message = next_step.get('user_message') self.step = next_step def _finish_step(self): if self.step.get('user_wait_for_continue', False): self.state = SudState.WAIT_USER else: self._advance()