Adapt Sud/SudTask/demo to the new flat heat/hold schedule schema

sude/sud_0010.json moved from a list of combined ramp+hold+wait
"Rasten" with global stirrer constants to a flat "schedule" list of
explicit {"type": "heat"|"hold", ...} steps, each carrying its own
stirrer_speed/stirrer_time_on/stirrer_time_off and an optional
user_wait_for_continue (+ user_message).

- components/sud.py: Sud now tracks the current `step` instead of
  `rast`; "heat" steps enter RAMPING (advanced externally via
  temp_reached()), "hold" steps enter HOLDING and count down
  `duration` minutes (0 if omitted). Either step type can pause in
  WAIT_USER via user_wait_for_continue, surfaced through the new
  user_message attribute.
- tasks/sud.py: SudTask only pushes theta_soll/heatrate_soll on
  "heat" steps, converts each step's stirrer_time_on/off into a
  duty_cycle/cycle_time on every step change (replacing the old
  state-based RAMPING/HOLDING stirrer switching), and broadcasts
  user_message changes. Stirring is now fully schedule-driven instead
  of implicitly stopped on WAIT_USER/DONE (DONE still stops it, since
  there's no step left to read settings from).
- scripts/demos/sud/demo_sud.py: mirrors the same step-driven wiring.
- README's Mash schedules section rewritten for the new schema.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-19 19:09:43 +02:00
co-authored by Claude Sonnet 4.6
parent 2845051350
commit b33f89a569
5 changed files with 176 additions and 96 deletions
+19 -17
View File
@@ -19,17 +19,13 @@ class Sud(AttributeChange):
self.name = data.get('Name', '')
self.description = data.get('Description', '')
self.rasten = data['Rasten']
self.stirr_speed_heat = data.get('stirrSpeedHeat', 0)
self.stirr_speed_rast = data.get('stirrSpeedRast', 0)
self.stirr_duty_rast = data.get('stirrDutyRast', 1.0)
self.stirr_cycle_time = data.get('stirrCycleTime', 1.0)
self.schedule = data['schedule']
self.index = -1
self.hold_remaining = 0.0
self.state = SudState.IDLE
self.rast = None
self.step = None
self.user_message = None
def start(self):
if self.state != SudState.IDLE:
@@ -43,28 +39,34 @@ class Sud(AttributeChange):
def temp_reached(self):
if self.state == SudState.RAMPING:
self.state = SudState.HOLDING
self._finish_step()
def tick(self, dt):
if self.state == SudState.HOLDING:
self.hold_remaining -= dt
if self.hold_remaining <= 0:
self._finish_rast()
self._finish_step()
def _advance(self):
self.index += 1
if self.index >= len(self.rasten):
if self.index >= len(self.schedule):
self.state = SudState.DONE
self.rast = None
self.user_message = None
self.step = None
return
next_rast = self.rasten[self.index]
self.hold_remaining = next_rast['time'] * 60.0
self.state = SudState.RAMPING
self.rast = next_rast
next_step = self.schedule[self.index]
if next_step['type'] == 'heat':
self.state = SudState.RAMPING
else:
self.hold_remaining = next_step.get('duration', 0) * 60.0
self.state = SudState.HOLDING
def _finish_rast(self):
if self.rast.get('waitForUser', False):
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()