Skip to content
khalil edited this page Oct 3, 2026 · 1 revision

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 at dryer-v2-pr. Update them once the branch lands.

Communication

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 map

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.

How it works

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
Loading

Status codes

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.

Controllers

Target temperature and air volume

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

Drying timer and schedule

check_auto_stop runs every control-loop tick while the device is running:

  1. If today's schedule has a non-zero stop time and local wall-clock time has passed it, the machine stops.
  2. Otherwise, if the drying timer has elapsed since the run started (running_since, a monotonic Instant), 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

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.

Alarms and warnings

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.

Clone this wiki locally