-
Notifications
You must be signed in to change notification settings - Fork 38
Dryer
The Dryer V1 dries plastic granulate before extrusion, holding a target temperature and air volume so the material reaches a safe moisture level. It also supports a weekly schedule and a material-preset lookup that derives the right setpoints from the polymer being dried. A "Smart" hardware variant is auto-detected but behaves identically to V1 — the distinction is informational only.
| Machine ID | 16 |
| Schema | dryer_v1.yaml |
| Code | dryer_v1/ |
Not yet merged into
jse-control-v2— links above point atdryer-v2-pr. Update them once the branch lands.
The dryer is a Modbus RTU slave, not an EtherCAT terminal — it's wired through a USB-RS485 adapter and polled once a second from device.rs.
| Baud rate | 57,600 |
| Frame | 8 data bits, 1 stop bit, no parity |
| Poll |
ReadInputRegisters(0x00, 0x21) once a second |
| Variant probe | One-shot ReadHoldingRegisters(2000, 1) right after the first successful poll; a value of 4331 marks the hardware as the Smart variant (is_smart state) |
| Register | Address | Scale | Field |
|---|---|---|---|
| Input 0 | 0x00 | raw | status |
| Input 1 | 0x01 | ÷10 |
temp_process (°C) |
| Input 2 | 0x02 | ÷10 |
temp_safety (°C) |
| Input 3 | 0x03 | ÷10 |
temp_regen_in (°C) |
| Input 4 | 0x04 | ÷10 |
temp_regen_out (°C) |
| Input 5 | 0x05 | ÷10 |
temp_fan_inlet (°C) |
| Input 6 | 0x06 | raw |
pwm_fan1 (%) |
| Input 7 | 0x07 | raw |
pwm_fan2 (%) |
| Input 14 | 0x0E | raw |
alarm code |
| Input 15 | 0x0F | raw |
warning code |
| Input 19 | 0x13 | ÷10 |
temp_return_air (°C) |
| Input 23 | 0x17 | signed, raw |
temp_dew_point (°C) |
| Input 31 | 0x1F | raw |
power_process (W) |
| Input 32 | 0x20 | raw |
power_regen (W) |
| Coil 272 | 0x110 | pulse | Start/stop toggle |
| Coil 273 | 0x111 | pulse | Save data / apply setpoint latch (shared address) |
| Holding 0x2F | 47 | raw | Target temperature write, °C, clamped 50–180 |
| Holding 0x33 | 51 | raw | Air volume write |
| Holding 0x7F.. | 127–154 | HH*100+MM |
Weekly schedule, 14 start + 14 stop registers |
Start/stop and schedule/setpoint writes aren't simple register writes — they're a momentary coil pulse (true then false) that the device reads as a button press, followed by the save-data pulse to latch it.
flowchart TB
subgraph HW["Hardware"]
direction LR
MB["Modbus RTU<br/>57,600 8N1"]:::hardware
DEV["Physical dryer unit"]:::hardware
MB --> DEV
end
subgraph POLL["<a href='https://github.com/qitechgmbh/control/blob/dryer-v2-pr/qitech_control/src/machines/dryer_v1/device.rs'>DryerDevice</a>"]
direction LR
REQ["Poll every 1 s<br/>ReadInputRegisters"]:::control
PARSE["apply_input_registers<br/>parses status, temps, alarm, warning"]:::control
REQ --> PARSE
end
subgraph CTRL["<a href='https://github.com/qitechgmbh/control/blob/dryer-v2-pr/qitech_control/src/machines/dryer_v1/mod.rs'>DryerV1::act</a>"]
direction LR
STAT["status transition<br/>Standby ↔ running"]:::control
AUTO["check_auto_stop<br/>schedule takes priority over timer"]:::control
REM["compute_remaining_seconds"]:::control
STAT --> AUTO
STAT --> REM
end
MB --> POLL
POLL --> CTRL
CTRL -- "stop pulse, once per run" --> POLL
POLL --> MB
classDef frontend fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
classDef control fill:#dcfce7,stroke:#15803d,color:#14532d
classDef framework fill:#fef3c7,stroke:#b45309,color:#78350f
classDef lib fill:#ede9fe,stroke:#6d28d9,color:#4c1d95
classDef hardware fill:#f1f5f9,stroke:#475569,color:#0f172a
status (input register 0) drives both the UI label and is_running_status, which treats everything except Standby, Cooling and Fan Stop as "running":
| Code | Label | Running? |
|---|---|---|
| 0 | Starting | Yes |
| 1 | Standby | No |
| 2 | Heating | Yes |
| 3 | Starting Up | Yes |
| 4 | Drying | Yes |
| 5 | Cooling | No |
| 6 | Fan Stop | No |
| 7 | Finishing | Yes |
| 9 | Switching | Yes |
The drying-timer countdown starts the instant status leaves Standby (i.e. from Starting, not from Drying), and stops counting — along with the auto-stop check — the instant status reaches Cooling or Fan Stop. Any cool-down the physical unit runs afterward is outside the timed window.
Manual setpoints, pushed to the device as a register write plus an apply-setpoint pulse whenever changed externally.
| Parameter | Unit | Default | Range |
|---|---|---|---|
target_temperature |
°C | 60 | 50–180 |
air_volume |
raw device unit | 10 | ≥ 1, no documented physical quantity |
check_auto_stop runs every control-loop tick while the device is running:
- If today's schedule has a non-zero stop time and local wall-clock time has passed it, the machine stops.
- Otherwise, if the drying timer has elapsed since the run started (
running_since, a monotonicInstant), the machine stops.
Both checks fire the same start/stop coil pulse used for manual start/stop. Because that write is a momentary pulse rather than an idempotent "set stopped" command, check_auto_stop only sends it once per run — an auto_stop_sent flag (reset the next time the device starts running) guards against re-queuing the pulse on every tick while waiting for the device's status feedback to catch up, which previously caused the machine to visibly toggle on and off.
Weekly schedule times are stored as minutes-since-midnight in config, converted to the device's native HH*100+MM wire encoding (minutes_to_wire) only when written. 0 means "no scheduled action" on both sides of that conversion.
| Parameter | Unit | Default | Range |
|---|---|---|---|
drying_timer_minutes |
min | 240 | ≥ 1. Only used on days with no scheduled stop. |
schedule.<day>.start_minutes / .stop_minutes
|
min since midnight | 0 (unset) | 0–1439, one pair per weekday |
Local time comes from the host's own system clock (libc::localtime_r), not the device and not the frontend's browser clock.
material_presets.rs ships a ~90-entry table of plastics (bulk density, moisture limits, temperature range, drying time range, specific air volume in m³ per kg/h of throughput). ApplyMaterialPreset { abbrev, throughput_kg_per_h } looks the material up by abbreviation and derives both setpoints in one mutation:
target_temperature = clamp(preset.recommended_temp(), 50, 180) // (temp_min + temp_max) / 2
air_volume = round(preset.specific_air_volume × throughput_kg_per_h), minimum 1
An unknown abbreviation logs a warning and applies nothing. The frontend keeps its own copy of the same table plus a per-user favorites/recently-used list (dryerMaterialStore.ts, persisted locally) so the picker works without a round trip.
Raw codes from input registers 14/15, surfaced to the UI via a lookup table:
| Alarm | Meaning |
|---|---|
| 1 | Local control active |
| 2 | Keyboard error |
| 3–8 | Sensor break T1–T6 |
| 9 | Process temp exceeded |
| 11 | Thermal protection, regen |
| 12 | Thermal protection, fan |
| 14 | Fan over-temperature |
| 15 | Check process fan |
| Warning | Meaning |
|---|---|
| 2 | Filter cleaning required (pressure) |
| 3 | Filter cleaning required (time) |
| 4 | High dew point |
| 5 | Low process temperature |
| 6 | Slave dryer warning |
| 7 | MPM temperature reduced |
| 8 | MPM dryer in standby |
alarm_raised is an event emitted whenever the alarm code changes to a non-zero value, not just while one is active.
QiTech Control · GitHub · Framework wiki · Lib wiki · Report a docs problem
Getting Started
Guides
Machines
Developers
Related