Add upload/download of sud.json over the Sud websocket channel

Sud.download() returns the on-disk document; Sud.upload() validates,
persists, and resets the schedule, refusing mid-brew or malformed input
so the current schedule is never left half-applied.
This commit is contained in:
2026-06-20 22:14:27 +02:00
parent 6fb85415eb
commit db57f3305c
3 changed files with 99 additions and 8 deletions
+49 -8
View File
@@ -48,17 +48,12 @@ def _build_step(number, defaults, raw_step):
class Sud(AttributeChange):
def __init__(self, path):
AttributeChange.__init__(self)
self.path = path
with open(path) as f:
data = json.load(f)
self.name = data.get('Name', '')
self.description = data.get('Description', '')
step_defaults = data.get('default', {}).get('step', {})
self.schedule = [_build_step(i + 1, step_defaults, raw) for i, raw in enumerate(data['steps'])]
self.pot_mass = data.get('pot_mass', 0)
self.pot_material = data.get('pot_material')
(self.name, self.description, self.schedule,
self.pot_mass, self.pot_material) = self._parse_data(data)
self.index = -1
self.hold_remaining = 0.0
@@ -66,6 +61,52 @@ class Sud(AttributeChange):
self.step = None
self.user_message = None
@staticmethod
def _parse_data(data):
"""Parses a sud.json document into the (name, description, schedule,
pot_mass, pot_material) tuple Sud needs. Computed up front rather
than assigned straight onto self, so a malformed upload() can't
leave a half-applied schedule in place."""
name = data.get('Name', '')
description = data.get('Description', '')
step_defaults = data.get('default', {}).get('step', {})
schedule = [_build_step(i + 1, step_defaults, raw) for i, raw in enumerate(data['steps'])]
pot_mass = data.get('pot_mass', 0)
pot_material = data.get('pot_material')
return name, description, schedule, pot_mass, pot_material
def download(self):
"""Returns the sud.json document currently on disk, for a client to
edit and hand back to upload()."""
with open(self.path) as f:
return json.load(f)
def upload(self, data):
"""Replaces the schedule with a new sud.json document and persists
it to disk, resetting to IDLE as if freshly loaded from that file.
Refused while a brew is in progress, or if data is malformed - in
either case the current schedule is left untouched."""
if self.state not in (SudState.IDLE, SudState.DONE):
return False
try:
parsed = self._parse_data(data)
except (KeyError, TypeError):
return False
self.name, self.description, self.schedule, self.pot_mass, self.pot_material = parsed
with open(self.path, 'w') as f:
json.dump(data, f, indent='\t')
self.index = -1
self.hold_remaining = 0.0
self.state = SudState.IDLE
self.step = None
self.user_message = None
return True
def derive_plant_params(self, grain_mass, water_mass):
"""Lumped (mass, specific heat) lifted from pot_mass/pot_material and
the current step's grain_mass/water_mass, for use as Pot's "M"/"C"