Allow a Sud step to combine a ramp and a hold

A step can now carry both 'ramp' and 'hold': it ramps to the target
temp, then holds there for the given duration, instead of needing two
separate schedule entries. Sud.temp_reached() switches such a step
from its ramp phase into its hold phase in place (re-firing the
step-changed callback so SudTask/demo_sud re-apply the hold's stirrer
settings); user_wait_for_continue still applies once the whole step -
both phases - is done.

Merges sud_0010.json's three ramp/hold pairs into combined steps.
This commit is contained in:
2026-06-21 09:01:04 +02:00
parent 56de5cf4d6
commit 9bd9889cb0
6 changed files with 57 additions and 37 deletions
+14 -6
View File
@@ -37,8 +37,8 @@ class SudTask(ATask):
if hasattr(self.tc, 'set_model_params'):
self.tc.set_model_params(params['M'], params['C'])
def apply_stirrer(self, step):
stirrer_cfg = step.get('ramp', step.get('hold', {})).get('stirrer', {})
def apply_stirrer(self, phase):
stirrer_cfg = phase.get('stirrer', {})
speed = stirrer_cfg.get('speed', 0)
interval_time = stirrer_cfg.get('interval_time', 0)
on_ratio = stirrer_cfg.get('on_ratio', 1.0)
@@ -53,20 +53,28 @@ class SudTask(ATask):
def on_step_changed(self, step):
ramp = step.get('ramp') if step else None
hold = step.get('hold') if step else None
# A step may carry both 'ramp' and 'hold' (ramp to temp, then hold) -
# self.sud.state tells us which phase is currently active; it's
# already up to date by the time this callback fires, both on a
# full step transition and on the ramp->hold phase switch within
# one step (components/sud.py's temp_reached()).
ramping = self.sud.state == SudState.RAMPING
phase = ramp if ramping and ramp is not None else hold
if step is not None:
self.apply_plant_params(step)
if ramp is not None:
if ramping and ramp is not None:
self.tc.set_theta_soll(ramp['temp'])
self.tc.set_heatrate_soll(ramp['rate'])
self.apply_stirrer(step)
self.apply_stirrer(phase)
asyncio.create_task(self.send({'Step': {
'Index': self.sud.index,
'Type': 'ramp' if ramp is not None else 'hold' if hold is not None else None,
'Type': 'ramp' if ramping and ramp is not None else 'hold' if hold is not None else None,
'Descr': step.get('descr') if step else None,
'Temp': ramp.get('temp') if ramp else None,
'Rate': ramp.get('rate') if ramp else None,
'Duration': hold.get('duration') if hold else None,
'Duration': hold.get('duration') if (hold is not None and not ramping) else None,
'WaitForUser': step.get('user_wait_for_continue', False) if step else None,
}}))