Custom TC4: Agent JSON reference
Use this contract to generate a HiBean import file from real device evidence. See the human guide for preparation, import, and device verification.
Source baseline: 2026-10-19, App 1.5.4+189, commit d12c9d07bc61e9d2e908f9e718bfd78301b6e3f6. This identifies the inspected source, not availability to every user. File profileVersion: 1 and schemaVersion: 2 are independent of the App version.
Agent workflow and deliverables
- Obtain device model, firmware version, user platform, and transport. Start with firmware source, manufacturer protocol, actual GATT services and characteristics, serial settings, and raw transmitted/received data. Do not infer a protocol from a product name or another device's profile.
- Confirm the read command, outgoing terminator, response terminator, a complete raw response, and each field's meaning and unit. Temperatures must be Celsius: custom devices interpret temperatures as Celsius and JSON provides no conversion.
- For device controls, also confirm command templates, limits, steps, safe values, and corresponding state readback. A safe value depends on the equipment and operating conditions; zero is not universally safe.
- If required facts are missing, ask focused questions or request the specific raw evidence. Do not label guessed profiles ready to use. Use
actuators: {}for monitoring when that meets the user's goal; do not silently remove requested controls. - Generate a UTF-8
.jsonfile no larger than 1 MiB, using only the keys below. No comments, trailing commas, or Markdown fences in the file. Do not present placeholder endpoints as completed configurations. - Outside the JSON, provide parameter sources, unresolved facts, verification status, and human next steps. Import must still be followed by connection, consecutive reads, control verification if enabled, manual recording settings, and save.
Keep three conclusions separate: codec validation passed establishes the file contract; hardware reads passed establishes connectivity and parsing; hardware control proof passed additionally requires target and safe-value readback. Import does not carry verification proofs or automatically create a device.
Envelope and config
Unknown keys are rejected at every object level. Required keys cannot be omitted or replaced with null.
| Path | Type / value | Rule |
|---|---|---|
kind | string | Required; exactly hibean.tc4.custom.profile |
profileVersion | integer | Required; exactly 1 |
config | object | Required; only the seven keys below |
config.schemaVersion | integer | Required; exactly 2 |
config.transportType | string | Required; socket, websocket, classic_bluetooth, ble, or serial |
config.transport | object | Required; matches transport type |
config.initializationCommands | string[] | Required; [] when unused; each command nonempty and without CR/LF |
config.statusPolling | object | Required; see reading contract |
config.actuatorCommandSeparator | string | Required; "\n", "\r", or "\r\n" |
config.actuators | object | Required; {} for no device controls |
Use JSON numbers and booleans, not strings such as "1000" or "true". Prefer integer literals for integers. There are no name, id, description, platform, recordProfile, proofs, or testValue properties. A transport's endpoint identifier is distinct from the device identity assigned by the App on save.
Transport contract
Only the listed keys are allowed for each transport. All are required unless marked optional. Strings must be nonempty after trimming.
| transportType | transport fields | Constraints |
|---|---|---|
socket | host: string; port: integer | Raw TCP; port 1–65535; host without URL scheme |
websocket | host: string; port: integer; websocketPath: string | Port 1–65535; path starts with /; current implementation uses wss only for port 443, otherwise ws |
classic_bluetooth | identifier: string | Actual Classic Bluetooth target on the current platform |
ble | identifier, serviceUuid, writeCharacteristicUuid: string; optional notifyCharacteristicUuid: string | Use actual scan/GATT evidence; omit unused optional value rather than supplying an empty string |
serial | portName: string; connectionParams: object | Actual system port name; settings below |
There are no URL, authentication header, TLS switch, BLE write mode, MTU, or chunk-size properties. BLE identifiers may be iOS UUIDs or Bluetooth addresses on other platforms; do not promise cross-platform portability. Service and characteristic UUIDs are different roles.
BLE reception depends on notification subscriptions; there is no characteristic Read fallback. An optional notifyCharacteristicUuid in JSON does not mean arbitrary devices work without a notification setup: confirm that the App can select a usable notifying characteristic. Current writing sets preferWriteWithoutResponse=false; adding invented JSON keys cannot support hardware requiring only writes without response.
Wizard platform policy: Android supports all except serial; iOS supports BLE, TCP, and WebSocket; macOS and Windows support all five; Linux is unsupported. This does not guarantee OS driver or hardware compatibility.
serial.connectionParams
| Key | Type | Constraint |
|---|---|---|
baudRate | integer | Required; positive |
dataBits | integer | Required; 5–8 |
stopBits | integer | Required; 1 or 2 |
parity | string | Required; none, even, odd, mark, or space |
flowControl | string | Required; none, xonxoff, rtscts, or dtrdsr |
timeoutSeconds | number | Optional; positive and finite; a serial setting, not the status-frame timeout |
Initialization, reading, and framing
statusPolling must contain exactly these four keys:
| Key | Type | Constraint |
|---|---|---|
command | string | Nonempty, no CR/LF; obtained from the device protocol |
intervalMillis | integer | At least 1000 milliseconds |
messageSeparator | string | Only "\n", "\r", or "\r\n" |
fields | object[] | Nonempty; each item has only signal, type, and index |
Despite its name, actuatorCommandSeparator terminates all outgoing initialization, read, and control commands. Incoming data uses messageSeparator; the two may differ. JSON "\n" decodes to LF; "\\n" is a literal backslash and n and is invalid as a separator. Do not also append terminators inside commands or templates.
The connection step opens the transport and selects GATT resources as needed. Initialization commands are sent in order during status verification, before sample and baseline reads. There are no configurable initialization ACKs, delays, or automatic unit-change commands. Add commands such as UNITS;C only if supported by the actual firmware.
TCP, BLE, Classic Bluetooth, and serial wait for a complete delimiter. The current frame timeout is 5 seconds and the buffer limit is 64 Ki characters. A complete WebSocket message can serve as a frame without a trailing delimiter, but messageSeparator is still required in JSON. Parsing uses the first nonempty frame, splits on commas, and trims fields. Empty fields keep their index positions; mapping an empty field fails.
Fields and signals
index is a zero-based integer from 0 to 255. Neither indexes nor signals may repeat. Omit unwanted mappings while retaining original indexes. type is exactly double or integer, never int.
| signal | Meaning | Type constraints |
|---|---|---|
bean_temperature | Bean temperature (BT) | Required mapping; normally double |
environment_temperature | Environment temperature (ET) | Normally double |
inlet_temperature | Inlet temperature | Normally double |
heater_power | Heater readback | Prefer integer for control |
fan_speed | Fan readback | Prefer integer for control |
drum_speed | Drum readback | Prefer integer for control |
target_temperature | Target temperature readback | Must be integer; no corresponding target-temperature actuator |
cooling_fan_switch | Cooling fan readback | Prefer integer for control |
There is no unknown signal; leave unmapped fields out of the array. Values must parse as finite numbers without suffixes such as °C or %. integer requires mathematically integral values (180.0 is accepted). There are no field delimiter, scale, offset, regex, formula, or temperature conversion properties. Incompatible firmware output requires a protocol change or separate adapter, not invented JSON keys.
Actuators and proof
Only heater, fan, drum, and coolingFan are allowed; note the camelCase spelling of the last key. Use {} for no controls and omit unused items.
| Field in each actuator | Type | Constraint |
|---|---|---|
enabled | boolean | Required |
mode | string | Required; range or toggle |
commandTemplate | string | Required; when enabled, nonempty, contains exact {value}, and no CR/LF |
safeValue | integer | Required; valid safe value when enabled |
range | object | Required in range mode; exactly integer min, max, step; omit in toggle mode |
Enabled range controls require min < max, step > 0, and (max-min) % step == 0. The safe value must be in range and satisfy (safeValue-min) % step == 0. Enabled toggles use 0/1, including the safe value. Enabled coolingFan supports only toggle mode. Templates replace {value} with an integer; there is no general expression language.
Disabled items still require the correct object shape and types; a disabled range item still needs a range object with a positive integer step. Prefer omission over meaningless placeholder controls.
| Enabled actuator | Required mapped readback |
|---|---|
heater | heater_power |
fan | fan_speed |
drum | drum_speed |
coolingFan | cooling_fan_switch |
Control readbacks must be integral and satisfy the configured range, step, or toggle bounds, including baseline reads. Temperature change is not heater-power readback.
After three consecutive successful reads, the person chooses a valid test value different from the safe value: write test value → read matching state → write safe value → read matching safe state. The test value is not a JSON field. An explicit target mismatch triggers a safe restore attempt and then fails proof. If a write/read result is uncertain, the session is closed/invalidated without a speculative second command. Failed safe readback means safety is unconfirmed.
Manual recording settings select values a person records during roasting; they are not device controls and are not part of portable JSON. JSON also carries no automated tests, PID, or automatic cooling workflow.
Complete examples and downloads
These are synthetic contract examples, not verified customer profiles. Addresses, UUIDs, READ, and OT1 are not universal defaults. Replace them using real device evidence before delivering a file for a user to try importing.
Monitor bean temperature over TCP
Assumes the device receives READ followed by LF and returns 180.5 followed by LF, with Celsius BT in column 0 and no enabled controls.
{
"kind": "hibean.tc4.custom.profile",
"profileVersion": 1,
"config": {
"schemaVersion": 2,
"transportType": "socket",
"transport": {
"host": "192.0.2.10",
"port": 4000
},
"initializationCommands": [],
"statusPolling": {
"command": "READ",
"intervalMillis": 1000,
"messageSeparator": "\n",
"fields": [
{
"signal": "bean_temperature",
"type": "double",
"index": 0
}
]
},
"actuatorCommandSeparator": "\n",
"actuators": {}
}
}Monitor bean temperature and control a heater over TCP
Also assumes a reply of 180.5,0 followed by LF; integer heater power in column 1; OT1;integer followed by LF sets power from 0–100 in steps of 1; and the manufacturer confirms zero is safe under the current operating conditions. Do not reuse this control example without all these facts.
{
"kind": "hibean.tc4.custom.profile",
"profileVersion": 1,
"config": {
"schemaVersion": 2,
"transportType": "socket",
"transport": {
"host": "192.0.2.10",
"port": 4000
},
"initializationCommands": [],
"statusPolling": {
"command": "READ",
"intervalMillis": 1000,
"messageSeparator": "\n",
"fields": [
{
"signal": "bean_temperature",
"type": "double",
"index": 0
},
{
"signal": "heater_power",
"type": "integer",
"index": 1
}
]
},
"actuatorCommandSeparator": "\n",
"actuators": {
"heater": {
"enabled": true,
"mode": "range",
"commandTemplate": "OT1;{value}",
"safeValue": 0,
"range": {
"min": 0,
"max": 100,
"step": 1
}
}
}
}
}- socket-monitor.json
- websocket-monitor.json
- ble-monitor.json
- classic_bluetooth-monitor.json
- serial-monitor.json
- socket-heater.json
The other monitoring files retain the same READ / Celsius BT / LF assumptions and change transport only. WebSocket /ws, serial 115200/8/N/1/no flow control, and BLE FFE0 service / FFE2 write / FFE1 notification are illustrative settings.
Troubleshooting and maintenance sources
| Failure stage | Check first |
|---|---|
| Import | Envelope, versions, unknown keys, types, duplicate indexes, required BT, control readback mappings |
| Connection | Platform support, address/port, serial permissions, Bluetooth target, service and characteristic UUIDs |
| Connected but no read | Initialization timing, actual read command, outgoing terminator, notification subscription, reply delimiter |
| Data received but parse failed | First frame being an ACK rather than status, comma positions, numeric units/scaling, integer types |
| Control proof | Command/readback represent the same quantity, bounds/step, device execution, target and safe readbacks |
Corrections from the earlier guide: replaced an empty config illustration with a full import contract; moved initialization timing from connection to read verification; clarified fixed comma parsing and BLE notifications; explained the outgoing terminator applies to READ and initialization; added manual recording and separated import validation from hardware verification.
For maintenance, recheck these App sources rather than inferring new capabilities from examples:
lib/model/manufacture/tc4/custom/tc4_custom_profile.dart — Tc4CustomProfileCodec / Tc4CustomConfigJsonCodeclib/model/manufacture/tc4/custom/tc4_custom_models.dart — enums / Tc4CustomConfigValidatorlib/model/manufacture/tc4/custom/tc4_text_protocol.dart — framing / parsing / readbacklib/model/manufacture/tc4/custom/tc4_custom_tester.dart — transport / initialization / writeslib/model/manufacture/tc4/custom/onboarding/tc4_setup_controller.dart — platform / baseline / prooflib/model/manufacture/tc4/custom/onboarding/tc4_custom_provisioner.dart — savelib/page/discovery/tc4/tc4_custom_setup_v2_page.dart — workflow / manual recordinglib/model/manufacture/manufacture_constants.dart — default Celsius policylib/model/manufacture/tc4/custom/tc4_custom_roaster.dart — runtime state publicationlib/model/persistence/serial_port_enums.dart — serial enum valueslib/model/manufacture/tc4/custom/tc4_custom_profile_file_service.dart — UTF-8 / file size
