How it works

Mapping a device's own names

A device keeps its own field names, units, topics and command syntax; a connection maps them.

A device keeps its own field names, units, topics and command syntax; its connection maps them. A Tasmota board that publishes {"MAX31855":{"Temperature":415.2}} in °F on tele/<name>/SENSOR, an ESPHome node with one number per sensor topic, a serial board that prints its own CSV, a Modbus controller with its own register layout, a roaster that speaks Artisan's WebSocket protocol: each is read without reflashing. A connection with no mapping expects Roastwarden's own names (connect your roaster).

Using it

  • Dashboard: Roasters → the roaster → Overview → a connection's Mapping (in Add roaster, the Check step's Map its fields). Choose what the device sends (°C or °F), then a row per field: Role or name, Path, Scale, Offset, Unit and, on MQTT, Own topic. Command templates below: Command, Topic, Payload, Scale, Decimals. Under it the newest message the device sent and what the mapping makes of it, before anything is saved. A Modbus roaster has no mapping: give each register its role in the register map's Role column.
  • Command line: roast roaster map <roaster> with --field, --units, --topic, --column, --register, --command and --command-topic. Add --try to see what the mapping makes of the newest message without saving it. Fields you give replace theirs and the rest stay; --field KEY= takes one out.

Roles and units

A role says what a value is. The mapping and the Modbus register map use the same words:

RoleWhatUnit
bt, etbean and environment temperaturetemperature
ambientroom temperaturetemperature
rorthe device's own rate of rise (Roastwarden still computes its own from BT)temperature per minute
heater, fanheater or burner, fan or air%
heater_appliedthe heater % after the device's interlocks%
drum, damperdrum speed, damper%
setpoint, modean onboard PID's target, and 0 manual / 1 autotemperature, 0 / 1
heater_enableheater power0 / 1

Any other name (letters, digits and _, up to 32 characters) is an extra channel: t3, exhaust, burnerGas. It is shown live; declare it to keep it with each roast (extra channels).

Units: the device sends °C (default) or °F. With °F, the temperature roles are converted to °C, and a setpoint is sent back in °F. An extra channel is converted only when its own unit says °F.

JSON fields (MQTT and WebSocket)

"ingest": {
  "units": "F",
  "fields": {
    "bt": "MAX31855.Temperature",
    "et": { "path": "probes[1].value" },
    "heater": { "path": "out.burner", "scale": 0.392157 },
    "exhaust": { "path": "probes[2].value", "unit": "F" }
  }
}
  • A path is dot-separated keys with [n] indexes (data.BT, probes[0].value; a leading $. is allowed); $ is the whole message, for a plain number.
  • A value may be a number, a numeric string, a boolean, or on / off / true / false; anything else leaves the field out of that sample.
  • The value is raw × scale + offset (defaults 1 and 0), then converted to °C.
  • Only mapped fields pass; a message that gives neither BT nor ET is not a sample. At most 32 fields, and at least one of bt and et.

MQTT topics

SettingDefaultWhat it does
Topic prefixroaster/<id>commands go to <prefix>/control/<command>
Telemetry topic<prefix>/telemetrythe topic whose messages are samples
Own topic (per field)noneread this field from its own topic (ESPHome, Home Assistant style)

With per-field topics, Roastwarden keeps each field's newest value and makes one sample when BT's message arrives (ET's when there is no BT), from values no older than 10 s. Topics take no wildcards, and a topic belongs to one roaster. Presence and caps stay on roaster/<id>/status.

WebSocket requests

Roastwarden dials the device. A device that pushes needs only the fields. One that answers requests (Artisan's WebSocket protocol) takes a poll: a message sent every interval (at least 200 ms), with {id} replaced by a counter. The preset artisan-websocket sets {"command":"getData","id":{id},"roasterID":0} every second. A device that dials the server instead speaks Roastwarden's own names and takes no mapping.

TC4 columns

  • Units: with °F the server sends UNITS;F at start and converts.
  • Columns: the reply's layout by position, a role or an extra name per column, empty to skip one: --column ambient,et,bt,heater,fan. -1 still means no probe.
  • An extra column's unit: a column named after a declared °C channel follows the device's units; another unit goes after a colon (exhaust:F, exhaust:C, gas:none for as printed).

Phidgets inputs

A Phidgets board (Phidgets boards) maps its inputs ch0 to ch7, with scale and offset; it always reports °C. With no mapping BT is ch0 and ET ch1, as in Artisan; roast roaster map <roaster> --field exhaust=ch2 adds a third. It only reads: no templates.

Modbus registers

Each register in the map gets a role, so its name is free: --register KIND:ADDRESS[@SLAVE]:TYPE[*SCALE]:ROLE[:UNIT], for example holding:0:int16*0.1:bt:°C (BT in tenths). A temperature register in °F is converted. A command is written to the writable holding register or coil with its role; a role is on at most one readable and one writable register, and emergency_stop is write only. @SLAVE puts a register on another controller of the same bus.

Command templates

"commands": {
  "heater": { "topic": "cmnd/roaster/Dimmer", "payload": "{value}", "decimals": 0 },
  "drum":   { "payload": "OT2;{value}" },
  "emergency_stop": { "payload": "STOP" }
}
  • Commands: setpoint, heater, fan, drum, damper, mode, heater_enable, emergency_stop. PID gains, feedforward, the lease and the RoR limit take no template.
  • {value} is the value (outputs in %, a setpoint in the device's unit, mode 0 / 1, heater enable 1 / 0, emergency stop 1), times scale (default 1), rounded to decimals (0–3, default 1, a trailing .0 dropped). No payload writes the value alone; mode writes auto or manual.
  • MQTT publishes payload on topic (default <prefix>/control/<command>). WebSocket sends the payload as the frame; {id} in it is a request number. Serial sends it as the line; \n splits several lines.
  • A command with no template goes out in Roastwarden's own syntax. A TC4 has no standard drum or damper line, so those need a template.
  • A template only says how a command is written, never whether it is sent: that is the control level and the outputs (roaster kind and control level).