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:
@@ -62,9 +62,10 @@ scripts/demos/ Standalone, eyeballed-plot demos (matplotlib)
|
|||||||
`python -m scripts.demos.sud.demo_sud`.
|
`python -m scripts.demos.sud.demo_sud`.
|
||||||
|
|
||||||
sude/ Mash schedules ("Sud" = brew/wort), each a
|
sude/ Mash schedules ("Sud" = brew/wort), each a
|
||||||
JSON list of temperature rests ("Rasten")
|
JSON list of "heat"/"hold" steps with target
|
||||||
with target temperature, heat rate, and
|
temperature/heat rate or hold duration,
|
||||||
optional pause for user confirmation.
|
per-step stirrer timing, and optional pause
|
||||||
|
for user confirmation.
|
||||||
|
|
||||||
config.json.templ Configuration template (real hardware).
|
config.json.templ Configuration template (real hardware).
|
||||||
config.json.sim Configuration template for simulation mode.
|
config.json.sim Configuration template for simulation mode.
|
||||||
@@ -128,26 +129,32 @@ environment (`$WORKON_HOME`/`$BREWPI_HOME`) on a Raspberry Pi-style deployment.
|
|||||||
## Mash schedules
|
## Mash schedules
|
||||||
|
|
||||||
Files under `sude/` describe a brew's mash schedule ("Sud"): pot weight,
|
Files under `sude/` describe a brew's mash schedule ("Sud"): pot weight,
|
||||||
malt/water weights, stirrer speed/duty, and a list of temperature rests
|
malt/water weights, and a flat `schedule` list of steps, each either:
|
||||||
("Rasten"), each with a target `temp`, `heatRate`, hold `time` (minutes), and
|
|
||||||
whether to `waitForUser` before continuing (e.g. to add malt or check
|
- `"type": "heat"` — ramp to `temp` at `rate` (°C/min), or
|
||||||
gravity).
|
- `"type": "hold"` — hold the current target for `duration` minutes (omit
|
||||||
|
for an immediate, zero-duration step).
|
||||||
|
|
||||||
|
Every step also carries its own stirrer timing (`stirrer_speed`,
|
||||||
|
`stirrer_time_on`/`stirrer_time_off` in seconds — `time_off: 0` means
|
||||||
|
continuous), and may set `user_wait_for_continue: true` (with an optional
|
||||||
|
`user_message`) to pause for confirmation once the step completes, e.g. to
|
||||||
|
add malt or check gravity, instead of advancing immediately.
|
||||||
|
|
||||||
Set the top-level `sud` config key to a path under `sude/` (see
|
Set the top-level `sud` config key to a path under `sude/` (see
|
||||||
`config.json.sim`/`config.json.templ`) to have the server step through it
|
`config.json.sim`/`config.json.templ`) to have the server step through it
|
||||||
automatically: `components/sud.py`'s `Sud` tracks the current rest and
|
automatically: `components/sud.py`'s `Sud` tracks the current step and
|
||||||
`tasks/sud.py`'s `SudTask` drives the temperature controller's
|
`tasks/sud.py`'s `SudTask` drives the temperature controller's
|
||||||
`theta_soll`/`heatrate_soll` from it, advancing once `theta_ist` settles
|
`theta_soll`/`heatrate_soll` from `"heat"` steps (advancing once `theta_ist`
|
||||||
close to the target and the rest's hold time has elapsed. It also switches
|
settles close to the target) and counts down `"hold"` steps' `duration`,
|
||||||
the stirrer between the schedule's `stirrSpeedHeat` (continuous, while
|
applying each step's stirrer timing as it goes (converting
|
||||||
ramping) and `stirrSpeedRast`/`stirrDutyRast`/`stirrCycleTime` (intermittent,
|
`stirrer_time_on`/`stirrer_time_off` into a duty cycle/cycle time). It
|
||||||
while holding), stopping it entirely during a `waitForUser` pause. It
|
exposes progress (current step, remaining hold time, state, any
|
||||||
exposes progress (current rest, remaining hold time, state) on the `"Sud"`
|
`user_message`) on the `"Sud"` WebSocket channel and accepts
|
||||||
WebSocket channel and accepts `{"Sud": {"Start": true}}` to begin the
|
`{"Sud": {"Start": true}}` to begin the schedule and `{"Sud": {"Confirm":
|
||||||
schedule and `{"Sud": {"Confirm": true}}` to acknowledge a `waitForUser`
|
true}}` to acknowledge a pause and move to the next step. Omit `sud` from
|
||||||
pause and move to the next rest. Omit `sud` from the config to
|
the config to run without schedule automation (manual
|
||||||
run without schedule automation (manual `theta_soll`/`heatrate_soll` control
|
`theta_soll`/`heatrate_soll` control via the GUI, as before).
|
||||||
via the GUI, as before).
|
|
||||||
|
|
||||||
## Logging & analysis
|
## Logging & analysis
|
||||||
|
|
||||||
|
|||||||
+18
-16
@@ -19,17 +19,13 @@ class Sud(AttributeChange):
|
|||||||
|
|
||||||
self.name = data.get('Name', '')
|
self.name = data.get('Name', '')
|
||||||
self.description = data.get('Description', '')
|
self.description = data.get('Description', '')
|
||||||
self.rasten = data['Rasten']
|
self.schedule = data['schedule']
|
||||||
|
|
||||||
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.index = -1
|
self.index = -1
|
||||||
self.hold_remaining = 0.0
|
self.hold_remaining = 0.0
|
||||||
self.state = SudState.IDLE
|
self.state = SudState.IDLE
|
||||||
self.rast = None
|
self.step = None
|
||||||
|
self.user_message = None
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
if self.state != SudState.IDLE:
|
if self.state != SudState.IDLE:
|
||||||
@@ -43,28 +39,34 @@ class Sud(AttributeChange):
|
|||||||
|
|
||||||
def temp_reached(self):
|
def temp_reached(self):
|
||||||
if self.state == SudState.RAMPING:
|
if self.state == SudState.RAMPING:
|
||||||
self.state = SudState.HOLDING
|
self._finish_step()
|
||||||
|
|
||||||
def tick(self, dt):
|
def tick(self, dt):
|
||||||
if self.state == SudState.HOLDING:
|
if self.state == SudState.HOLDING:
|
||||||
self.hold_remaining -= dt
|
self.hold_remaining -= dt
|
||||||
if self.hold_remaining <= 0:
|
if self.hold_remaining <= 0:
|
||||||
self._finish_rast()
|
self._finish_step()
|
||||||
|
|
||||||
def _advance(self):
|
def _advance(self):
|
||||||
self.index += 1
|
self.index += 1
|
||||||
if self.index >= len(self.rasten):
|
if self.index >= len(self.schedule):
|
||||||
self.state = SudState.DONE
|
self.state = SudState.DONE
|
||||||
self.rast = None
|
self.user_message = None
|
||||||
|
self.step = None
|
||||||
return
|
return
|
||||||
|
|
||||||
next_rast = self.rasten[self.index]
|
next_step = self.schedule[self.index]
|
||||||
self.hold_remaining = next_rast['time'] * 60.0
|
if next_step['type'] == 'heat':
|
||||||
self.state = SudState.RAMPING
|
self.state = SudState.RAMPING
|
||||||
self.rast = next_rast
|
else:
|
||||||
|
self.hold_remaining = next_step.get('duration', 0) * 60.0
|
||||||
|
self.state = SudState.HOLDING
|
||||||
|
|
||||||
def _finish_rast(self):
|
self.user_message = next_step.get('user_message')
|
||||||
if self.rast.get('waitForUser', False):
|
self.step = next_step
|
||||||
|
|
||||||
|
def _finish_step(self):
|
||||||
|
if self.step.get('user_wait_for_continue', False):
|
||||||
self.state = SudState.WAIT_USER
|
self.state = SudState.WAIT_USER
|
||||||
else:
|
else:
|
||||||
self._advance()
|
self._advance()
|
||||||
|
|||||||
@@ -52,24 +52,34 @@ if __name__ == '__main__':
|
|||||||
heater.set_on_changed('power_set', ctrl.set_model_power)
|
heater.set_on_changed('power_set', ctrl.set_model_power)
|
||||||
heater.set_on_changed('power_eff', plant.set_power)
|
heater.set_on_changed('power_eff', plant.set_power)
|
||||||
|
|
||||||
def on_rast_changed(rast):
|
def apply_stirrer(step):
|
||||||
if rast is not None:
|
speed = step.get('stirrer_speed', 0)
|
||||||
ctrl.set_theta_soll(rast['temp'])
|
on = step.get('stirrer_time_on', 0)
|
||||||
ctrl.set_heatrate_soll(rast['heatRate'])
|
off = step.get('stirrer_time_off', 0)
|
||||||
|
cycle = on + off
|
||||||
|
if cycle > 0:
|
||||||
|
duty = on / cycle
|
||||||
|
else:
|
||||||
|
cycle = 1.0
|
||||||
|
duty = 1.0 if speed > 0 else 0.0
|
||||||
|
|
||||||
|
stirrer.set_cycle_time(cycle)
|
||||||
|
stirrer.set_duty_cycle(duty)
|
||||||
|
stirrer.set_speed(speed)
|
||||||
|
|
||||||
|
def on_step_changed(step):
|
||||||
|
if step is not None:
|
||||||
|
if step['type'] == 'heat':
|
||||||
|
ctrl.set_theta_soll(step['temp'])
|
||||||
|
ctrl.set_heatrate_soll(step['rate'])
|
||||||
|
apply_stirrer(step)
|
||||||
|
|
||||||
def on_state_changed(state):
|
def on_state_changed(state):
|
||||||
if state == SudState.RAMPING:
|
if state == SudState.DONE:
|
||||||
stirrer.set_duty_cycle(1.0)
|
|
||||||
stirrer.set_speed(sud.stirr_speed_heat)
|
|
||||||
elif state == SudState.HOLDING:
|
|
||||||
stirrer.set_cycle_time(sud.stirr_cycle_time)
|
|
||||||
stirrer.set_duty_cycle(sud.stirr_duty_rast)
|
|
||||||
stirrer.set_speed(sud.stirr_speed_rast)
|
|
||||||
elif state in (SudState.WAIT_USER, SudState.DONE):
|
|
||||||
stirrer.set_duty_cycle(1.0)
|
stirrer.set_duty_cycle(1.0)
|
||||||
stirrer.set_speed(0)
|
stirrer.set_speed(0)
|
||||||
|
|
||||||
sud.set_on_changed('rast', on_rast_changed)
|
sud.set_on_changed('step', on_step_changed)
|
||||||
sud.set_on_changed('state', on_state_changed)
|
sud.set_on_changed('state', on_state_changed)
|
||||||
|
|
||||||
sud.start()
|
sud.start()
|
||||||
@@ -80,7 +90,7 @@ if __name__ == '__main__':
|
|||||||
_heatrate_ist = []
|
_heatrate_ist = []
|
||||||
_heatrate_soll = []
|
_heatrate_soll = []
|
||||||
_sud_state = []
|
_sud_state = []
|
||||||
_rast_index = []
|
_step_index = []
|
||||||
_stirrer_speed = []
|
_stirrer_speed = []
|
||||||
_stirrer_duty = []
|
_stirrer_duty = []
|
||||||
_heater_power_set = []
|
_heater_power_set = []
|
||||||
@@ -121,7 +131,7 @@ if __name__ == '__main__':
|
|||||||
_heatrate_ist.append(ctrl.get_heatrate_ist())
|
_heatrate_ist.append(ctrl.get_heatrate_ist())
|
||||||
_heatrate_soll.append(ctrl.get_heatrate_soll())
|
_heatrate_soll.append(ctrl.get_heatrate_soll())
|
||||||
_sud_state.append(sud.state.value)
|
_sud_state.append(sud.state.value)
|
||||||
_rast_index.append(sud.index)
|
_step_index.append(sud.index)
|
||||||
_stirrer_speed.append(stirrer.speed * stirrer.isOn)
|
_stirrer_speed.append(stirrer.speed * stirrer.isOn)
|
||||||
_stirrer_duty.append(stirrer.dutyCycle * 100)
|
_stirrer_duty.append(stirrer.dutyCycle * 100)
|
||||||
_heater_power_set.append(heater.power_set)
|
_heater_power_set.append(heater.power_set)
|
||||||
@@ -157,8 +167,8 @@ if __name__ == '__main__':
|
|||||||
legend(["Sud state"])
|
legend(["Sud state"])
|
||||||
grid(True)
|
grid(True)
|
||||||
subplot(3, 1, 2)
|
subplot(3, 1, 2)
|
||||||
step(_t_min, _rast_index, where='post', color='k')
|
step(_t_min, _step_index, where='post', color='k')
|
||||||
legend(["Rast index"])
|
legend(["Step index"])
|
||||||
grid(True)
|
grid(True)
|
||||||
subplot(3, 1, 3)
|
subplot(3, 1, 3)
|
||||||
plot(_t_min, _stirrer_speed, '-g', _t_min, _stirrer_duty, '-c', linewidth=1)
|
plot(_t_min, _stirrer_speed, '-g', _t_min, _stirrer_duty, '-c', linewidth=1)
|
||||||
|
|||||||
+69
-21
@@ -5,35 +5,83 @@
|
|||||||
"pot_material" : "Edelstahl 18/10",
|
"pot_material" : "Edelstahl 18/10",
|
||||||
"Schuettung_kg" : 5.21,
|
"Schuettung_kg" : 5.21,
|
||||||
"Wasser_kg" : 22,
|
"Wasser_kg" : 22,
|
||||||
"stirrSpeedHeat" : 30,
|
"schedule":
|
||||||
"stirrSpeedRast" : 30,
|
|
||||||
"stirrDutyRast" : 1.0,
|
|
||||||
"stirrCycleTime" : 120,
|
|
||||||
"Rasten" :
|
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"time" : 0,
|
"type": "heat",
|
||||||
"temp" : 57.0,
|
"descr": "Heizen für Einmaischen",
|
||||||
"heatRate" : 1.00,
|
"rate": 1.8,
|
||||||
"waitForUser" : true
|
"temp": 57,
|
||||||
|
"stirrer_speed": 50,
|
||||||
|
"stirrer_time_on": 90,
|
||||||
|
"stirrer_time_off": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"time" : 40,
|
"type": "hold",
|
||||||
"temp" : 63.0,
|
"descr": "Einmaischen einfüllen",
|
||||||
"heatRate" : 1.00,
|
"stirrer_speed": 0,
|
||||||
"waitForUser" : false
|
"user_message": "Bitte Malz einfüllen und bestätigen",
|
||||||
|
"user_wait_for_continue" : true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"time" : 30,
|
"type": "hold",
|
||||||
"temp" : 72.0,
|
"descr": "Einmaischen Rast",
|
||||||
"heatRate" : 1.00,
|
"duration" : 30,
|
||||||
"waitForUser" : true
|
"stirrer_speed": 30,
|
||||||
|
"stirrer_time_on": 30,
|
||||||
|
"stirrer_time_off": 30
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"time" : 0,
|
"type": "heat",
|
||||||
"temp" : 76.0,
|
"descr": "Heizen Glucose-Rast",
|
||||||
"heatRate" : 1.00,
|
"rate": 1.0,
|
||||||
"waitForUser" : true
|
"temp": 63,
|
||||||
|
"stirrer_speed": 50,
|
||||||
|
"stirrer_time_on": 90,
|
||||||
|
"stirrer_time_off": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "hold",
|
||||||
|
"descr": "Halten Glucose-Rast",
|
||||||
|
"duration" : 40,
|
||||||
|
"stirrer_speed": 30,
|
||||||
|
"stirrer_time_on": 30,
|
||||||
|
"stirrer_time_off": 30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "heat",
|
||||||
|
"descr": "Heizen Verzuckerungs-Rast",
|
||||||
|
"rate": 1.0,
|
||||||
|
"temp": 72,
|
||||||
|
"stirrer_speed": 50,
|
||||||
|
"stirrer_time_on": 90,
|
||||||
|
"stirrer_time_off": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "hold",
|
||||||
|
"descr": "Halten Verzuckerungs-Rast",
|
||||||
|
"duration" : 30,
|
||||||
|
"stirrer_speed": 30,
|
||||||
|
"stirrer_time_on": 30,
|
||||||
|
"stirrer_time_off": 30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "heat",
|
||||||
|
"descr": "Heizen Abmaischen",
|
||||||
|
"rate": 1.0,
|
||||||
|
"temp": 76,
|
||||||
|
"stirrer_speed": 50,
|
||||||
|
"stirrer_time_on": 90,
|
||||||
|
"stirrer_time_off": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "hold",
|
||||||
|
"descr": "Halten Abmaischen",
|
||||||
|
"duration" : 30,
|
||||||
|
"stirrer_speed": 30,
|
||||||
|
"stirrer_time_on": 30,
|
||||||
|
"stirrer_time_off": 30,
|
||||||
|
"user_wait_for_continue" : true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-22
@@ -18,36 +18,48 @@ class SudTask(ATask):
|
|||||||
self.msg_handler = msg_handler
|
self.msg_handler = msg_handler
|
||||||
msg_handler.set_recv_handler(self.recv)
|
msg_handler.set_recv_handler(self.recv)
|
||||||
|
|
||||||
def on_rast_changed(self, rast):
|
def apply_stirrer(self, step):
|
||||||
if rast is not None:
|
speed = step.get('stirrer_speed', 0)
|
||||||
self.tc.set_theta_soll(rast['temp'])
|
on = step.get('stirrer_time_on', 0)
|
||||||
self.tc.set_heatrate_soll(rast['heatRate'])
|
off = step.get('stirrer_time_off', 0)
|
||||||
|
cycle = on + off
|
||||||
|
if cycle > 0:
|
||||||
|
duty = on / cycle
|
||||||
|
else:
|
||||||
|
cycle = 1.0
|
||||||
|
duty = 1.0 if speed > 0 else 0.0
|
||||||
|
|
||||||
asyncio.create_task(self.send({'Rast': {
|
self.stirrer.set_cycle_time(cycle)
|
||||||
|
self.stirrer.set_duty_cycle(duty)
|
||||||
|
self.stirrer.set_speed(speed)
|
||||||
|
|
||||||
|
def on_step_changed(self, step):
|
||||||
|
if step is not None:
|
||||||
|
if step['type'] == 'heat':
|
||||||
|
self.tc.set_theta_soll(step['temp'])
|
||||||
|
self.tc.set_heatrate_soll(step['rate'])
|
||||||
|
self.apply_stirrer(step)
|
||||||
|
|
||||||
|
asyncio.create_task(self.send({'Step': {
|
||||||
'Index': self.sud.index,
|
'Index': self.sud.index,
|
||||||
'Temp': rast['temp'] if rast else None,
|
'Type': step['type'] if step else None,
|
||||||
'HeatRate': rast['heatRate'] if rast else None,
|
'Descr': step.get('descr') if step else None,
|
||||||
'Time': rast['time'] if rast else None,
|
'Temp': step.get('temp') if step else None,
|
||||||
'WaitForUser': rast.get('waitForUser', False) if rast else None,
|
'Rate': step.get('rate') if step else None,
|
||||||
|
'Duration': step.get('duration') if step else None,
|
||||||
|
'WaitForUser': step.get('user_wait_for_continue', False) if step else None,
|
||||||
}}))
|
}}))
|
||||||
|
|
||||||
def on_state_changed(self, value):
|
def on_state_changed(self, value):
|
||||||
asyncio.create_task(self.send({'State': str(value)}))
|
asyncio.create_task(self.send({'State': str(value)}))
|
||||||
|
|
||||||
if value == SudState.RAMPING:
|
if value == SudState.DONE:
|
||||||
# Stir continuously while ramping to a new target temperature.
|
|
||||||
self.stirrer.set_duty_cycle(1.0)
|
|
||||||
self.stirrer.set_speed(self.sud.stirr_speed_heat)
|
|
||||||
elif value == SudState.HOLDING:
|
|
||||||
# Stir intermittently while holding at the rest's temperature.
|
|
||||||
self.stirrer.set_cycle_time(self.sud.stirr_cycle_time)
|
|
||||||
self.stirrer.set_duty_cycle(self.sud.stirr_duty_rast)
|
|
||||||
self.stirrer.set_speed(self.sud.stirr_speed_rast)
|
|
||||||
elif value in (SudState.WAIT_USER, SudState.DONE):
|
|
||||||
# Stop stirring while paused for the user or once the schedule is done.
|
|
||||||
self.stirrer.set_duty_cycle(1.0)
|
self.stirrer.set_duty_cycle(1.0)
|
||||||
self.stirrer.set_speed(0)
|
self.stirrer.set_speed(0)
|
||||||
|
|
||||||
|
def on_user_message_changed(self, value):
|
||||||
|
asyncio.create_task(self.send({'UserMessage': value}))
|
||||||
|
|
||||||
def on_hold_remaining_changed(self, value):
|
def on_hold_remaining_changed(self, value):
|
||||||
asyncio.create_task(self.send({'HoldRemaining': value}))
|
asyncio.create_task(self.send({'HoldRemaining': value}))
|
||||||
|
|
||||||
@@ -64,8 +76,9 @@ class SudTask(ATask):
|
|||||||
async def on_process(self):
|
async def on_process(self):
|
||||||
print("{}: Started with interval {} s".format(self.msg_handler.get_key(), self.interval))
|
print("{}: Started with interval {} s".format(self.msg_handler.get_key(), self.interval))
|
||||||
|
|
||||||
self.sud.set_on_changed('rast', self.on_rast_changed)
|
self.sud.set_on_changed('step', self.on_step_changed)
|
||||||
self.sud.set_on_changed('state', self.on_state_changed)
|
self.sud.set_on_changed('state', self.on_state_changed)
|
||||||
|
self.sud.set_on_changed('user_message', self.on_user_message_changed)
|
||||||
self.sud.set_on_changed('hold_remaining', ChangedFloat(self.on_hold_remaining_changed, prec=0).set)
|
self.sud.set_on_changed('hold_remaining', ChangedFloat(self.on_hold_remaining_changed, prec=0).set)
|
||||||
|
|
||||||
asyncio.create_task(self.send({'Name': self.sud.name, 'Description': self.sud.description}))
|
asyncio.create_task(self.send({'Name': self.sud.name, 'Description': self.sud.description}))
|
||||||
@@ -74,7 +87,7 @@ class SudTask(ATask):
|
|||||||
if self.sud.state == SudState.RAMPING:
|
if self.sud.state == SudState.RAMPING:
|
||||||
# Compare against the controller's own live setpoint/measurement
|
# Compare against the controller's own live setpoint/measurement
|
||||||
# rather than its state machine, which can still read HOLD from
|
# rather than its state machine, which can still read HOLD from
|
||||||
# the previous rest for one tick after a new target is pushed.
|
# the previous step for one tick after a new target is pushed.
|
||||||
if abs(self.tc.get_theta_ist() - self.tc.get_theta_soll_set()) < TEMP_REACHED_TOLERANCE:
|
if abs(self.tc.get_theta_ist() - self.tc.get_theta_soll_set()) < TEMP_REACHED_TOLERANCE:
|
||||||
self.sud.temp_reached()
|
self.sud.temp_reached()
|
||||||
self.sud.tick(self.interval)
|
self.sud.tick(self.interval)
|
||||||
|
|||||||
Reference in New Issue
Block a user