Releases ·
Roastwarden 0.5.0
Everything since v0.4.0 (2026-10-26): roasting itself, then the paperwork again. Autotune 2
(the server's relay test and PI designs) and the device contract with the gains held by the
server; deploys that wait for the roaster and a drive that resumes after a restart; roast
analysis in the server (roast review, roast compare); auto DROP by time, BT or
development; the roaster's own thermal model and its live prediction; roasted coffee with
its rest window; a new bean's first roast; the hardening of the release review. Then labels
and reports polished from a review: label notes, quicker labels after a roast, a report
comparing two roasts, smarter plan and profile reports, a layout check on every report.
Added
- Compare two roasts on paper (ROADMAP Next 38; PDF reports): a one-page report for
an A/B experiment: both roasts' BT and RoR on one chart with their first crack and drop,
heater and fan strips, the phases on one time scale, first crack, drop, development and loss
as "A → B", every
roast comparerow with B − A, and each roast's notes.GET /api/reports/compare?roasts=A,B(one roast: it and the roaster's finished roast before; none: the newest two);roast report compare [A] [B]; Reports → "Compare two roasts", History → Compare → "Compare report"; eval task 37. - Label notes and a label name (ROADMAP Next 37; Bag labels, Green coffee):
a bean's own few tasting words ("chocolate · coconut · apple") and a shorter name for its
labels, migration 044; with none, the bean suggests notes from the supplier's (
Use …on the bean form). Labels print, first found: the notes asked for, the bean's label notes, the roast's cupping notes, the supplier's cut at a sentence.roast bean add|edit --label-notes --short-name; the skill says how to write them; eval task 36. - Gift labels link to the roast's share page: a design names its own QR default
(
qr: "share"; gift does) with a caption, sized by the code's modules. A gift PDF or print makes the roast's share link if it has none; previews still do not. - Printed it, in view: Download, Print and Printed it stay on screen on a roast's Labels page and Label Studio → Print; Printed it moves the sheet's next free label on there. One item editor on both pages. Labels and Report links on the after-roast summary (once weighed) and on each roasted lot on Beans.
- The report check: every report kind's tests lay it out and fail on a last page with under 20 mm of content, colliding text or text past the paper.
- The roast-day plan shows each planned coffee's last roast on its profile (first crack, drop, loss, the notes' result) and units in its write-in boxes; a profile with no roasts prints its target curve and what to expect.
roast profile edit --expect KEY=LOW-HIGH: a profile's own expected range (development, loss, DTR, colour) over its level's default, as the profile editor sets it;profile showlists them. Light espresso on production: development 1:45–2:45 (owner 2026-10-30), so its 2:15 is no longer flagged against the light level's 1:00–2:00.- A new bean's first roast (ROADMAP Next 33; A new bean's first roast): before the first roast
of a bean on a roaster, what to expect: the profile to start from, first crack's BT and time
on the profile from this roaster's own roasts (± 3 °C, ± 4 °C for naturals, honeys,
wet-hulled and unknown processes), a natural's quiet first crack, a "drop by development"
offer when the profile's auto DROP is by time, a wet-hulled, low-density or wet bean's slower
drying, and the supplier's level and espresso advice quoted. After a roast, while the bean
has fewer than three on its profile: where first crack came, how far to move the drop to keep
the profile's development, development or loss outside the level's range in 15 s steps, a
late first crack, and "if it tastes…". The bean page ("First roast" / "Next roast of this
bean"), the Roast page's step card (one line, the rest behind its ?), History → a roast.
GET /api/beans/{id}/guide;roast bean guide,roast reviewprints the next-roast lines; the skill's "A new bean", eval task 35. Nothing is stored and nothing changes a profile by itself. - Roasted coffee: resting and ready (ROADMAP Next 31; Roasted coffee): every finished,
weighed roast is roasted coffee with its rest window: resting until the bag label's "best
from" date (by purpose and level), ready until 35 days (light) or 28 (darker), then past its
best. The Beans page opens with it: per espresso and filter what is ready, what is resting
and when the next is ready, how fast it goes (from coffee recorded as used) and the day to
roast again so a new roast has rested in time, running low below 200 g; each roast's coffee
left with Used…, Gave… and Finished; History → a roast shows what is left of it. Settings →
Roasting has the best-until days and the low mark.
GET /api/roasted,GET /api/roasted/{id},POST /api/roasted/{id}/moves(used, given, discarded in grams, doses, bags or all; or a count of what is left),DELETE …/moves/{move}, migration 043.roast roasted [--for espresso|filter] [--ready|--resting],roasted show|use|give|toss|count|undo(a roast by id,#N,latestor name), the skill's "Roasted coffee", eval task 34. - Prediction from the roaster's own model (ROADMAP Next 30; Prediction): each
roaster gets a thermal model fitted from its own roasts (
dBT/dt = gain · lag(heater/fan) + g(BT), starting from the virtual roaster's physics), refitted after each roast and scored with every roast left out in turn: on esp32_roaster_01's 12 roasts BT two minutes ahead is within 1.6 °C RMS with the heater as it was, and with the roaster's PID on the plan (scored where the roaster stayed in auto), against the scope projection's 34.5 °C. While a roast runs the server predicts BT for the next two minutes under the plan (the drive's setpoints with the roaster's PID, a held setpoint, or held heater and fan) with a 90 % band and its reason, first crack from the BT it came at on this bean and profile, DROP from the profile, and when the heater would run out; every 15 s it keeps the prediction with the roast and scores it against the old projection. The scope's BT model series (off by default) besideBT →; Roasters → Overview → Model (scores, where BT settles, Refit).GET /api/roaster/{id}/model,POST …/model/fit,GET /api/roaster/{id}/prediction,GET /api/sessions/{id}/prediction, migration 042.roast roaster model [--fit],roast liveandroast reviewshow it; the skill, eval task 33. - Auto DROP (ROADMAP Next 29; Profile drive, "Auto DROP"): a profile can let Profile
drive mark DROP by itself, off by default: at its drop time, at its drop temperature (from
first crack on) or at its development time after first crack is marked. A countdown
(Settings → Roasting, 10 s by default) ends on the drop itself: the Roast page shows it with
Cancel (for that roast only; a resumed drive keeps it cancelled) and phones get a notice;
then DROP's step runs as for any DROP (heater off, fan to cool). It stays off when the
roaster's DROP step is off. The profile editor and page,
PATCH /replay{"auto_drop": false}, migration 041.roast profile edit --auto-drop time|bt|development|off,profile showandroast live(the countdown), the skill, eval task 32; drive-check drops its roast this way. - Roast analysis (ROADMAP Next 28; Roast review and compare): one reading of a roast in the
server: marks and phases, RoR (the early peak, each minute, the late low, the FC → drop
slope), BT against its profile (at 0:30–2:00, the overshoot after CHARGE, RMS from TP), how
the heater and fan were used (first 30 s, time at 90 % or more, the last minute), its numbers
judged against what was expected, and plain notes.
GET /api/sessions/{id}/analysis;GET /api/analysis/comparelines 2–8 roasts up against one (every number with its difference, the curves every 30 s, from when BT stays within 0.5 °C).roast review <roast> [--vs <roast>]androast compare, which take#12(a batch number) as well as an id orlatest; the skill's "Reading a roast"; eval task 31. - Resume a drive cut by a restart (ROADMAP Next 26; Profile drive, "Resume after a
restart"): on boot a Profile drive a crash, a power cut or a restart by hand cut mid-roast
is kept as a stopped run where it stopped; the Roast page offers Heater on and resume
drive with the old settings, and the profile picks up at the time it had reached, the time
lost added to its offset (the Drive tab shows "profile at 4:52 (+0:40)").
roast liveshows it with the resume call;POST /replaytakesresume_at_profile_seconds. Eval task 30. - Deploys wait for the roaster (ROADMAP Next 25; Running it):
GET /api/busysays whether a roaster is in the middle of something a restart would cut (a profile drive, the server's autotune, cooling after DROP, its heater on, an open roast with beans at 50 °C or more; the virtual roaster never counts). The deploy waits while a real roaster is busy (up to 2 h).roast busy [--wait MINUTES](exit 1 while busy),roast status, Settings → About → Roasters, the skill, eval task 29. - The workflow on the CI runners (
docs/DEVELOPMENT.md, "The loop"): the dashboard's browser checks (a driven roast and every page) run in CI against the image just built, and the deploy waits for them; after the deploy CI installs the skill androaston hermes (Agents, "Kept current"); Lint checks that a feature commit ships its agent path and docs (scripts/ci/agent-path.sh).scripts/dev/prepush.shandscripts/dev/ci-watch.shfor the laptop.RUSTROAST_MQTT=loopbackruns the server with no broker (the virtual roaster only); the local stack uses it. - The device contract (Control, "Device contract"; ROADMAP Next 24): what a roaster must do for rustRoast (BT/ET out, heater and fan % in, over-temperature cutoffs, sensor checks, the fan interlock, emergency stop, a comms watchdog, a retained presence) and what it may offer as caps (onboard PID, feedforward, lease, time-proportioned heater, oversampling, RoR limiter). The server runs the roast; the device keeps it safe.
- Gains after a boot: the server holds each roaster's PID gains (the newest stored change, or the gain schedule's for the fan) and sends them on the first sample after the server starts and when the roaster's uptime goes back (it rebooted), if the roaster reports others. Any transport.
- A roaster against the contract:
GET /api/roaster/{id}/contract,roast roaster contract, and Roasters → Overview → Device contract: its firmware, the readings it sends, its caps, and whether its PID gains are the ones the server holds, with the last check after a boot (what it came up with, what the server sent). Eval task 28. First used on the real roaster's 3.5.0 flash (2026-10-27): it came up on 5.5 / 0.085 / 0, the server sent back 5.561 / 0.08471 / 0. - The roaster page's header stacks on a phone (the name, "Selected roaster" and the status badge overlapped).
- Firmware 3.5.0 (Esp32_FluidBedCoffeeRoaster), per the contract: the onboard relay autotune
(with its overshoot guard and Ku/Tu rules) and the NVS-stored gains are gone. Gains live in
RAM and boot on the build's defaults (the Autotune 2 PI at 72 % fan) until the server sends
them;
control/pidand serialPID;Tno longer write flash. - Autotune 2 (Autotune; ROADMAP Next 23): the server runs the autotune on any
roaster that takes a heater % and reports BT, the virtual roaster too (manual heater, a
control lease): a heat-up that hands over early by the expected coast, the sensor's noise
for the switching band, a symmetric relay that finds the holding power, an asymmetric one
(the larger swing downward, where a heat-limited roaster has room). Every sample is kept;
the analysis fits integrator, first- and second-order models with dead time, checks them
against the oscillation's own harmonics and Berner's half-period ratio, and designs the PI
by SIMC (one knob, tight to smooth) with AMIGO and the old Ku/Tu rules beside it, each with
Ms, margins, what happens if the roaster changes, the heater jitter and the ramp lag. The
dashboard's Autotune tab shows the record against the model and the knob; the gain schedule
keeps each row's model and designs the gains between rows. API
…/tune/start|stop,GET …/tune,…/autotune/runs/{id}/samples|design|analyse;roast roaster tuning --run ID [--tau-c N]; eval task 27. The method is its own crate (crates/autotune), with no roaster knowledge, for reuse. Research:docs/research/autotune-2026-10-26/. Migration 038. - Firmware 3.4.0 (Esp32_FluidBedCoffeeRoaster): the thermocouples are read four times a second and averaged (about half the noise, finer than one 0.25 °C step); temperatures go out with two decimals. The virtual roaster reads the same way.
- The lookahead adapts (Profile drive, "Lookahead"; ROADMAP Next 22): Profile drive's
setpoint lead starts at the lookahead and, from a minute after CHARGE, follows how far BT
runs ahead of or behind the profile, between 0 and the lookahead. With the fixed 20 s lead
BT ran 10 s, then 20 s, ahead of the profile on the real roasts: first crack at 7:29
against the profile's 7:50. Settings → Roasting → "Adapt to the roaster" (on); the drive
tab shows the lead in use (
lookahead_now;adapt_lookaheadwhen starting a drive). - The gain schedule's advice (
docs/autotune.md, "Checking a schedule"): rows with a D term (on this sensor it turns each 0.25 °C step into a heater jump) and neighbouring rows that disagree, under the schedule editor and inroast roaster tuning. - Each telemetry sample of a roast keeps the control fields the roaster sent (applied
heater, RoR limiter, PID gains, mode, heater power, stop code, feedforward) as
extras(migration 037), and its time to the millisecond. - The Roast page keeps the screen on while a roast runs; the Dev readout says when BT reaches the profile's drop temperature at the RoR now.
Changed
- Settings → About and
roast statussay how far past its release a build is ("0.4.0 + 66 commits since its release", with the commit's date:buildinGET /api/about, fromgit describestamped by CI), and the dashboard says when an open tab is older than the server (by revision, not only by version, which moves only at a release). - A repeat Download of the same labels returns the run not yet printed (made in the last 24 h,
not sent or printed;
reused: true) instead of a new one;new: true/roast labels run --newalways makes one. - The message field follows the design: "To" with just the name for a design that prints its
own To, hidden for a design with a text
tosetting. - One brew recipe:
l.brewon labels is the share page's, by level (light espresso 1:2.5 at 94 °C, 18 g → 45 g; the share page said 1:2 for every espresso). - Label text: "Light · Espresso", weights as "250 g · 8.8 oz" rounded down like the net weight,
plain grows its type and centres on roomy labels (6874, 5163); the check's
cutsays how much of the label was free (report.free) and two soft issues leave a label ok:dropped(name, roast date or best-from not printed) andhint(shop with no brand). - The coffee report's small multiples are one overlay of every roast's BT and RoR, the newest
in colour (schema
coffee/2; fixes its A4 break); the coffee, profile and period tables name each roast. - The first-crack prompt goes by BT: within 2 °C of the profile's first-crack BT (else 196 °C), after dry end. The RoR-dip rule it replaces came 45 s to 2½ minutes late on every real roast (the PID hides the dip).
- Auto DRY end comes at the roast profile's own dry-end BT when it has one, else
auto_dry_temp. - The setpoint goes to the roaster in 0.2 °C steps (
follow_hysteresis_c; migration 035 moves a stored 1.0): 1 °C steps kicked the device PID every 7–12 s late in a roast. - An autotune does not start while a roast is on the roaster (409) or below 45 % fan;
mode: step_responseandaggressivenessare gone (the firmware has neither). - Fan assist judges the heater the roaster applied (
heaterApplied), not the commanded one. - The virtual roaster's PID works from the noisy 0.25 °C reading, as the firmware's does.
Removed
- The firmware's own autotune on the server side (firmware 3.5.0 has none; Autotune 2 replaces
it):
POST …/autotune/start|stop|apply,GET …/autotune/status[/latest]and…/autotune/results[/latest], theautotuneevents on both dashboard WebSockets, the dashboard's "Onboard (firmware)" mode and its scope overlay and banner,roast roaster tuning --messages, and theautotune_status/autotune_resultstables (migration 040). Stored firmware runs stay in the run history, their rules still apply. POST …/control/ror_limitanswers 409 for a roaster that lists its caps withoutror(firmware 3.4.1 dropped its RoR limiter and ignored the command).
Fixed
- "To for Sam" on gift labels (Next 35, L9): the owner's saved stamp (v2) and noel-tag (v3) designs drop a message's own "for" or "to" before their "To" and wrap a long message to two lines; the label check counts "To Sam" as the message "for Sam".
- Dates by the owner's time zone (Next 35, L8): labels, share pages, print runs, roasted
coffee and reports made by the CLI or an agent were dated in UTC (a late-evening roast printed
as the next day). A
time_zonesetting (Settings → Roasting, e.g.America/Chicago) dates any request that sends no offset of its own; the CLI sends its offset only when it is not UTC. - Labels and reports polish.
Labels: long names and gift messages shrink, then wrap to two balanced lines (round labels
too;
wrapinrustroast.typ); notes fill the room left (clampwith a height); previews and checks no longer create share links (a placeholder code; only a PDF, print or run makes one); the check no longer reports printed fields as missing (labels getroasted_iso,best_from_iso,rest_days); the calibration page reads offsets from mm scales 3 mm inside each label, clear of the printer's margin. Reports: rings on the spread strips no longer collide, no page holds only a closing note, the roast report's RoR stays on its chart, table numbers line up (grams below 1 kg), a year or month in progress says "so far" and folds empty months, "usual" ranges never go below zero (the dashboard too), overlay and heater/fan labels pushed apart, clearer wording ("Cost per kg roasted", "above light 1:00–2:00"). Dashboard: Enter in the Typst editor makes a new line instead of taking a completion; the cupping Scores, coffee and profile reports open on something real; a report preview clears on an error or a new kind and Download waits for one; Print finds roasts by coffee; a roast's label preview never shows a stale answer; Printers asks each printer again and shows the answer's age; Save version is disabled with nothing changed; Label History reads on a phone;GET /api/sessions/{id}/costanswersnullinstead of 404 when the cost is unknown. - The Modbus master reads each block of its register map from the block's first register (it read from address 0, so a map above register ~125 failed every poll) and never writes a register marked not writable.
- The "Profile drive stopped" banner made the Roast page's right column scroll at 1280×720 and 1366×768 (also the heater-cut one): below 820 px tall its explanation is a tooltip.
- Settings → About on a phone: the schema name of migration 040 was wider than the screen and scrolled the page sideways; it wraps now.
- Starting the server autotune right after a DROP: the roast's cooling ended inside the test and switched the fan off, so the fan interlock held the heater at 0 and the test never heated. Starting a test now ends the cooling first, as a new CHARGE does.
- "Behind profile" (and any
delta_*alarm) no longer fires after DROP: the profile holds its last BT past its end, so the cooling beans read 9 °C behind 7 s after DROP (batches #6, #8). - A drive on the real roaster stopped 4:08 into batch #6: firmware 3.4.0 read a lease renewal that landed mid-check as 49 days old and cut the heater (stop code 7) 0.5 s after the server renewed it. Firmware 3.4.1 signs the lease's age and reads it before the clock; the server's stop reason now says when the roaster missed a renewal instead of "the server went quiet".
- Firmware 3.4.1 drops the RoR limiter: on the firmware's 9 s RoR it cut the heater 19 times while the drive caught up after that stop (30 °C/min above 150 °C), fighting Profile drive. The over-temperature cutoffs, sensor checks, fan interlock and lease stay.
- A server restart after an Autotune 2 run stored the firmware's last autotune again as a new run (its retained results message was compared with the newest run of any kind, now with the newest firmware run). Migration 039 removes the copies.
- Autotune 2's hold-out check warned falsely on a drifting lag-dominant run (the real roaster from cold, 2026-10-27): fitted again without the last cycle, the main lag slid to 6000 s against the drift term. The hold-out now keeps the full fit's main lag.
- The gain schedule editor: emptying a gain box sent
nulland the save failed with a server error. An empty Kd now saves as 0; an empty Kp or Ki says so and stops the save. A row typed over by hand no longer shows the autotune rule it came from ("edited"). - First crack and dry end were counted from the session start where the roast time counts
from CHARGE (History's FC,
roast roasts, the Phases tab's shares and DTR); migration 036 corrects the stored times. The drying RoR starts at CHARGE. - Resuming a drive's setpoint or heater left the roaster in the mode pressed by hand, so BT stopped following while the drive showed live; it now restores the drive's mode.
- Pausing a roast stopped its drive's lease renewal and the roaster cut its heater 10 s later; Pause stops the recording only.
- A drive kept renewing its lease with no telemetry; it now lets the lease lapse after 8 s and stops at 20 s.
- A stop could let a tick's command land after DROP's heater off and cooling fan; engaging a ghost on the server loop sent the integral it built while watching (up to 100 %); a gain change or a stale reading stepped the server PID's output.
- A profile fan below 45 % reached the roaster, whose interlock then gated the heater.
- A new CHARGE while the last roast still cooled could get that roast's fan-off.
- Scheduled gains were not resent after the roaster rebooted; undo could store the schedule's RAM gains instead of the set stored before.
- "Behind profile" (and any rule armed after the turning point) never armed on a cold charge, which has no turning point.
- The MQTT consumer spun on a closed channel.
Security
- Hardening with no visible change: the audit
log masks secret settings (the webhook URL and notification tokens, one at a time or in a
batch) and reads at most 1 MB of a body before auth; a notification test never sends a
stored token to a server the test changed (type the token again to test another server);
label assets are served sandboxed, so an uploaded SVG runs no script; an
.alognested more than 64 deep is refused instead of overflowing the stack.