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:
+49
-8
@@ -48,17 +48,12 @@ def _build_step(number, defaults, raw_step):
|
|||||||
class Sud(AttributeChange):
|
class Sud(AttributeChange):
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
AttributeChange.__init__(self)
|
AttributeChange.__init__(self)
|
||||||
|
self.path = path
|
||||||
with open(path) as f:
|
with open(path) as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
|
|
||||||
self.name = data.get('Name', '')
|
(self.name, self.description, self.schedule,
|
||||||
self.description = data.get('Description', '')
|
self.pot_mass, self.pot_material) = self._parse_data(data)
|
||||||
|
|
||||||
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.index = -1
|
self.index = -1
|
||||||
self.hold_remaining = 0.0
|
self.hold_remaining = 0.0
|
||||||
@@ -66,6 +61,52 @@ class Sud(AttributeChange):
|
|||||||
self.step = None
|
self.step = None
|
||||||
self.user_message = 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):
|
def derive_plant_params(self, grain_mass, water_mass):
|
||||||
"""Lumped (mass, specific heat) lifted from pot_mass/pot_material and
|
"""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"
|
the current step's grain_mass/water_mass, for use as Pot's "M"/"C"
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from components.sud import Sud, SudState
|
||||||
|
|
||||||
|
# Console-only walkthrough of Sud.download()/upload() (no plot - there's no
|
||||||
|
# numeric data here, just JSON plumbing). Runs against a scratch copy of a
|
||||||
|
# real sud.json so sude/sud_0010.json itself is never touched.
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
path = Path(tmp) / "sud.json"
|
||||||
|
shutil.copy("sude/sud_0010.json", path)
|
||||||
|
|
||||||
|
sud = Sud(str(path))
|
||||||
|
print(f"Loaded '{sud.name}', {len(sud.schedule)} steps")
|
||||||
|
|
||||||
|
doc = sud.download()
|
||||||
|
assert doc['Name'] == sud.name
|
||||||
|
print("download() returned the on-disk document")
|
||||||
|
|
||||||
|
sud.state = SudState.RAMPING
|
||||||
|
assert sud.upload(doc) is False
|
||||||
|
print("upload() refused while RAMPING")
|
||||||
|
|
||||||
|
sud.state = SudState.IDLE
|
||||||
|
doc['Name'] = 'Sud-Renamed'
|
||||||
|
doc['steps'][0]['descr'] = 'Edited step'
|
||||||
|
assert sud.upload(doc) is True
|
||||||
|
assert sud.name == 'Sud-Renamed'
|
||||||
|
assert sud.schedule[0]['descr'] == 'Edited step'
|
||||||
|
assert sud.state == SudState.IDLE
|
||||||
|
print("upload() applied the edit and reset to IDLE")
|
||||||
|
|
||||||
|
on_disk = json.loads(path.read_text())
|
||||||
|
assert on_disk['Name'] == 'Sud-Renamed'
|
||||||
|
print("upload() persisted the edit to disk")
|
||||||
|
|
||||||
|
assert sud.upload({'Name': 'broken'}) is False
|
||||||
|
assert sud.name == 'Sud-Renamed'
|
||||||
|
print("upload() rejected malformed data, left schedule untouched")
|
||||||
|
|
||||||
|
print("All sud upload/download checks passed")
|
||||||
@@ -69,6 +69,11 @@ class SudTask(ATask):
|
|||||||
self.sud.start()
|
self.sud.start()
|
||||||
elif 'Confirm' in pair[0]:
|
elif 'Confirm' in pair[0]:
|
||||||
self.sud.confirm()
|
self.sud.confirm()
|
||||||
|
elif 'Download' in pair[0]:
|
||||||
|
await self.send({'Json': self.sud.download()})
|
||||||
|
elif 'Upload' in pair[0]:
|
||||||
|
if self.sud.upload(pair[1]):
|
||||||
|
await self.send({'Name': self.sud.name, 'Description': self.sud.description})
|
||||||
|
|
||||||
async def send(self, data):
|
async def send(self, data):
|
||||||
await self.msg_handler.send(data)
|
await self.msg_handler.send(data)
|
||||||
|
|||||||
Reference in New Issue
Block a user