Move Sud step's target temperature from ramp.temp to step.temperature
A step's target temperature applies to the whole step (it's what a
ramp ramps to and what a hold then holds at), not just its ramp phase
- promote it from a nested "ramp": {"temp": ...} to a step-level
"temperature" field, defaulted like descr/grain_mass/etc. Updates all
consumers (Sud._build_step, SudTask, the GUI's forecast estimator,
demo_sud.py) and migrates sude/sud_0010.json and the README.
This commit is contained in:
@@ -130,17 +130,19 @@ environment (`$WORKON_HOME`/`$BREWPI_HOME`) on a Raspberry Pi-style deployment.
|
|||||||
|
|
||||||
Files under `sude/` describe a brew's mash schedule ("Sud"): `pot_mass`,
|
Files under `sude/` describe a brew's mash schedule ("Sud"): `pot_mass`,
|
||||||
`pot_material` (fixed for the whole brew), and a `steps` list, each a ramp, a
|
`pot_material` (fixed for the whole brew), and a `steps` list, each a ramp, a
|
||||||
hold, or both:
|
hold, or both, plus a step-level `temperature` (the target a ramp step ramps
|
||||||
|
to, and what a hold step holds at):
|
||||||
|
|
||||||
- a ramp — `"ramp": {"rate": ..., "temp": ...}` — ramp to `temp` at `rate`
|
- a ramp — `"ramp": {"rate": ...}` — ramp to `temperature` at `rate`
|
||||||
(°C/min), and/or
|
(°C/min), and/or
|
||||||
- a hold — `"hold": {"duration": ...}` — hold the current target for
|
- a hold — `"hold": {"duration": ...}` — hold the current target for
|
||||||
`duration` minutes (omit/`0` for an immediate step).
|
`duration` minutes (omit/`0` for an immediate step).
|
||||||
|
|
||||||
A step with both ramps to `temp` and then holds there for `duration` -
|
A step with both ramps to `temperature` and then holds there for `duration`
|
||||||
useful for a mash rest ("ramp to 63°C, then hold 40 min") without needing two
|
- useful for a mash rest ("ramp to 63°C, then hold 40 min") without needing
|
||||||
separate schedule entries. `user_wait_for_continue`/`user_message` (below)
|
two separate schedule entries. `user_wait_for_continue`/`user_message`
|
||||||
apply once the whole step is done, i.e. after the hold phase if there is one.
|
(below) apply once the whole step is done, i.e. after the hold phase if
|
||||||
|
there is one.
|
||||||
|
|
||||||
Both `ramp` and `hold` carry a `stirrer` block (`speed`, `interval_time`,
|
Both `ramp` and `hold` carry a `stirrer` block (`speed`, `interval_time`,
|
||||||
`on_ratio`): `interval_time: 0` runs the stirrer continuously at `speed`;
|
`on_ratio`): `interval_time: 0` runs the stirrer continuously at `speed`;
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ class SudForecastPlot(FigureCanvasQTAgg):
|
|||||||
hold = step.get('hold')
|
hold = step.get('hold')
|
||||||
if ramp is not None:
|
if ramp is not None:
|
||||||
rate = ramp.get('rate', 0)
|
rate = ramp.get('rate', 0)
|
||||||
target = ramp['temp']
|
target = step['temperature']
|
||||||
if rate > 0:
|
if rate > 0:
|
||||||
t.append(t[-1] + abs(target - theta[-1]) / rate * 60.0)
|
t.append(t[-1] + abs(target - theta[-1]) / rate * 60.0)
|
||||||
theta.append(target)
|
theta.append(target)
|
||||||
|
|||||||
+1
-1
@@ -38,7 +38,7 @@ def _build_step(number, defaults, raw_step):
|
|||||||
ramp or a hold depending on which of those keys it specifies; the other
|
ramp or a hold depending on which of those keys it specifies; the other
|
||||||
is left out rather than synthesized from defaults."""
|
is left out rather than synthesized from defaults."""
|
||||||
step = {'number': number}
|
step = {'number': number}
|
||||||
for key in ('descr', 'user_message', 'user_wait_for_continue', 'grain_mass', 'water_mass'):
|
for key in ('descr', 'user_message', 'user_wait_for_continue', 'grain_mass', 'water_mass', 'temperature'):
|
||||||
step[key] = raw_step.get(key, defaults.get(key))
|
step[key] = raw_step.get(key, defaults.get(key))
|
||||||
for key in ('ramp', 'hold'):
|
for key in ('ramp', 'hold'):
|
||||||
if key in raw_step:
|
if key in raw_step:
|
||||||
|
|||||||
@@ -81,15 +81,15 @@ if __name__ == '__main__':
|
|||||||
# A ramp step whose target is below the current temperature can
|
# A ramp step whose target is below the current temperature can
|
||||||
# only be reached by passive cooling - force the heater off,
|
# only be reached by passive cooling - force the heater off,
|
||||||
# mirroring tasks/sud.py's SudTask.
|
# mirroring tasks/sud.py's SudTask.
|
||||||
cooling = ramping and 'ramp' in step and step['ramp']['temp'] < ctrl.get_theta_ist() - TEMP_REACHED_TOLERANCE
|
cooling = bool(ramping and 'ramp' in step and step['temperature'] < ctrl.get_theta_ist() - TEMP_REACHED_TOLERANCE)
|
||||||
ctrl.set_cooling(cooling)
|
ctrl.set_cooling(cooling)
|
||||||
if cooling:
|
if cooling:
|
||||||
print(" -> cooling down to {}".format(step['ramp']['temp']))
|
print(" -> cooling down to {}".format(step['temperature']))
|
||||||
|
|
||||||
print(f"Step {step['number']}: {step['descr']}")
|
print(f"Step {step['number']}: {step['descr']}")
|
||||||
apply_plant_params(step)
|
apply_plant_params(step)
|
||||||
if ramping and 'ramp' in step:
|
if ramping and 'ramp' in step:
|
||||||
ctrl.set_theta_soll(step['ramp']['temp'])
|
ctrl.set_theta_soll(step['temperature'])
|
||||||
ctrl.set_heatrate_soll(step['ramp']['rate'])
|
ctrl.set_heatrate_soll(step['ramp']['rate'])
|
||||||
apply_stirrer(phase)
|
apply_stirrer(phase)
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -10,9 +10,9 @@
|
|||||||
"user_wait_for_continue": false,
|
"user_wait_for_continue": false,
|
||||||
"grain_mass": 5.21,
|
"grain_mass": 5.21,
|
||||||
"water_mass": 22,
|
"water_mass": 22,
|
||||||
|
"temperature": 0,
|
||||||
"ramp": {
|
"ramp": {
|
||||||
"rate": 1.0,
|
"rate": 1.0,
|
||||||
"temp": 0,
|
|
||||||
"stirrer": {
|
"stirrer": {
|
||||||
"speed": 50,
|
"speed": 50,
|
||||||
"interval_time": 0,
|
"interval_time": 0,
|
||||||
@@ -32,18 +32,18 @@
|
|||||||
"steps": [
|
"steps": [
|
||||||
{
|
{
|
||||||
"grain_mass": 0,
|
"grain_mass": 0,
|
||||||
"descr": "Heizen f\u00fcr Einmaischen",
|
"descr": "Heizen für Einmaischen",
|
||||||
|
"temperature": 57,
|
||||||
"ramp": {
|
"ramp": {
|
||||||
"rate": 1.8,
|
"rate": 1.8,
|
||||||
"temp": 57,
|
|
||||||
"stirrer": {
|
"stirrer": {
|
||||||
"speed": 75
|
"speed": 75
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"descr": "Einmaischen einf\u00fcllen",
|
"descr": "Einmaischen einfüllen",
|
||||||
"user_message": "Bitte Malz einf\u00fcllen und best\u00e4tigen",
|
"user_message": "Bitte Malz einfüllen und bestätigen",
|
||||||
"user_wait_for_continue": true,
|
"user_wait_for_continue": true,
|
||||||
"hold": {
|
"hold": {
|
||||||
"stirrer": {
|
"stirrer": {
|
||||||
@@ -59,9 +59,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"descr": "Glucose-Rast",
|
"descr": "Glucose-Rast",
|
||||||
|
"temperature": 63,
|
||||||
"ramp": {
|
"ramp": {
|
||||||
"rate": 1.0,
|
"rate": 1.0
|
||||||
"temp": 63
|
|
||||||
},
|
},
|
||||||
"hold": {
|
"hold": {
|
||||||
"duration": 40,
|
"duration": 40,
|
||||||
@@ -74,9 +74,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"descr": "Verzuckerungs-Rast",
|
"descr": "Verzuckerungs-Rast",
|
||||||
|
"temperature": 72,
|
||||||
"ramp": {
|
"ramp": {
|
||||||
"rate": 1.0,
|
"rate": 1.0
|
||||||
"temp": 72
|
|
||||||
},
|
},
|
||||||
"hold": {
|
"hold": {
|
||||||
"duration": 30,
|
"duration": 30,
|
||||||
@@ -91,9 +91,9 @@
|
|||||||
"descr": "Abmaischen",
|
"descr": "Abmaischen",
|
||||||
"user_message": "Quittieren zum Abmaischen",
|
"user_message": "Quittieren zum Abmaischen",
|
||||||
"user_wait_for_continue": true,
|
"user_wait_for_continue": true,
|
||||||
|
"temperature": 76,
|
||||||
"ramp": {
|
"ramp": {
|
||||||
"rate": 1.0,
|
"rate": 1.0
|
||||||
"temp": 76
|
|
||||||
},
|
},
|
||||||
"hold": {
|
"hold": {
|
||||||
"duration": 30,
|
"duration": 30,
|
||||||
@@ -103,4 +103,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -69,13 +69,13 @@ class SudTask(ATask):
|
|||||||
# bool(...): get_theta_ist() can be a numpy.float64 (Pot's transport
|
# bool(...): get_theta_ist() can be a numpy.float64 (Pot's transport
|
||||||
# delay line is a numpy array internally) - the '<' comparison would
|
# delay line is a numpy array internally) - the '<' comparison would
|
||||||
# then yield numpy.bool_, which json.dumps() can't serialize.
|
# then yield numpy.bool_, which json.dumps() can't serialize.
|
||||||
cooling = bool(ramping and ramp is not None and ramp['temp'] < self.tc.get_theta_ist() - TEMP_REACHED_TOLERANCE)
|
cooling = bool(ramping and ramp is not None and step['temperature'] < self.tc.get_theta_ist() - TEMP_REACHED_TOLERANCE)
|
||||||
self.tc.set_cooling(cooling)
|
self.tc.set_cooling(cooling)
|
||||||
|
|
||||||
if step is not None:
|
if step is not None:
|
||||||
self.apply_plant_params(step)
|
self.apply_plant_params(step)
|
||||||
if ramping and ramp is not None:
|
if ramping and ramp is not None:
|
||||||
self.tc.set_theta_soll(ramp['temp'])
|
self.tc.set_theta_soll(step['temperature'])
|
||||||
self.tc.set_heatrate_soll(ramp['rate'])
|
self.tc.set_heatrate_soll(ramp['rate'])
|
||||||
self.apply_stirrer(phase)
|
self.apply_stirrer(phase)
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ class SudTask(ATask):
|
|||||||
'Index': self.sud.index,
|
'Index': self.sud.index,
|
||||||
'Type': 'ramp' if ramping and 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,
|
'Descr': step.get('descr') if step else None,
|
||||||
'Temp': ramp.get('temp') if ramp else None,
|
'Temp': step.get('temperature') if ramp is not None else None,
|
||||||
'Rate': ramp.get('rate') if ramp else None,
|
'Rate': ramp.get('rate') if ramp else None,
|
||||||
'Duration': hold.get('duration') if (hold is not None and not ramping) 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,
|
'WaitForUser': step.get('user_wait_for_continue', False) if step else None,
|
||||||
|
|||||||
Reference in New Issue
Block a user