13 KiB
Guided Acquire — service-tool flow specification
A specification for a safe, operator-guided sensor acquisition feature in the new Python
service tool. It replaces both the raw "set Acquire = 1" register write and the field procedure of
button-press beep commands with a wizard that snapshots, guards, scans, verifies, and repairs.
Like the protocol reference, this document is self-contained and can be
copied into the service-tool repo. All firmware behavior is cited as file.c:line.
!!! note "Scope & baseline"
Targets the DA-12C build (wired SensorBus sensors only, MAX_SENSORS = 16). Firmware
changes are not authorized — everything here is host-side, built on the existing {…}
protocol (see the DA-12C Service-Tool Protocol reference, §4–§5).
Command builders and a decoder already exist in
reference/da12c_status.py.
1. Why a guided flow is needed
The firmware's acquire operation is wipe-then-rescan, and it has a failure mode that empties the station permanently:
- Every scan begins by deleting the database.
SensorsAcquire()callsSbRemoveAllSensors()before searching the bus (sensors.c:1130), which clears each sensor's EEPROM active bit viaRemoveSensor()→SetSensorActive(num, FALSE)(pointsix.c:108-129,eepromat.c:588). The wipe is persistent: at boot,InitSensors()only loads sensors whose active bit is set (pointsix.c:70). - In restricted mode, the rescan recovers nothing. Found bus devices are added through
GetP6Num(1, serial)(owi.c:945), and the restricted-mode gate rejects any type that is not an RF service-mode type (pointsix.c:693-704). Wired sensors are always added with temporary type1, which never passes. So triggering a scan while setting #3 (Sensor mode) = 1 wipes every sensor and re-adds none. This applies equally to the host-triggered scan (setting #4) and to the 3/4-beep button commands. - There is no automatic recovery. The firmware never rescans on its own — not at boot,
not periodically. The only production triggers for
SensorsAcquire()are the button commands (indicators.c:889,900,910) and a setting-#4 write (op05.c:1167-1168). - Mitigating fact that makes a guided flow viable: serial numbers, names, and calibration
survive the wipe (only the active bit is cleared). A later successful scan restores each
sensor into its old slot with settings intact via
FindSnIndex()(pointsix.c:714-730). Repeating a scan is therefore cheap and safe — if the mode guard holds.
The guided flow exists to make item 2 impossible, item 3 invisible to the operator, and item 4 automatic.
2. Firmware contract (what the tool builds on)
| Behavior | Detail | Source |
|---|---|---|
| Trigger | Writing setting #4 = 1 sets gStation.acquire and AcquireSensors |
op05.c:1167-1168 |
| Execution | Next main-loop pass runs SwitchesAcquire() + SensorsAcquire() synchronously; the loop is blocked for the duration (watchdog is fed inside) |
main.c:874-880 |
| Comm gap | While the scan runs, the unit emits nothing — the periodic {F} status frame (~1.2 s cadence) pauses, then resumes when the scan completes. This is the completion signal. |
op05.c:1644-1686 |
| Persistence | Setting #4 is deliberately not saved to EEPROM (num != 4 guard) |
op05.c:1303 |
| Lingering flag | gStation.acquire stays 1 after the scan (fast-blinking green LED) until written back to 0 |
indicators.c:670-674 |
| Mode guard | Setting #3 (Sensor mode / opMode) must be 0 when the scan runs; writes to #3 persist via SaveStationSettings() |
pointsix.c:693-704, op05.c:1303 |
| Echo | Every accepted setting write is echoed back as a {D}/{E} record — use as the write-ack |
op05.c:1409-1411 |
| Slot recovery | Re-found serials reclaim their old EEPROM slot, keeping name/scale/offset/units/alarms | pointsix.c:714-730 |
| New sensors | Genuinely new serials get a fresh slot with blank name and default settings | pointsix.c:614-651 (AddNewSensor) |
| Ghost entries | RemoveSensor() deactivates the wrong index for linked (dual-channel) sensors — the linked half keeps its active bit and can reappear as a database entry with no bus link (never polled) |
bug at pointsix.c:124 |
| Manual add | {S} (add by serial) calls AddNewSensor(serial, 1): persists and marks active, but type stays 1 and no bus link is built — the sensor is not functional until a successful scan; InitSensorSettings() also resets name/calibration |
op05.c:1353-1360, pointsix.c:372-392 |
!!! warning "The one invariant" Never write setting #4 = 1 while setting #3 = 1. Everything else in this flow is polish; this rule is the difference between an acquire and a wipe. The tool must enforce it in code, not in documentation — the raw #4 toggle should not be exposed in the UI at all.
3. The flow
Eight states. All wire commands below are the §4 inbound frames from the protocol reference
({ cmd typecode index value } + CR); use the builders in da12c_status.py rather than
hand-formatting.
S0 Connect → S1 Snapshot → S2 Mode guard → S3 Trigger → S4 Wait
→ S5 Verify/Diff → S6 Remediate (loop to S3) → S7 Cleanup & report
S0 — Connect
- Open the service port, 19200 8N1.
- Send
{Y}(refresh-all). Parse the full settings stream ({D}/{E}), sensor records ({A}), alarm settings ({G}), statistics ({H}). - Confirm the unit is a DA-12C (setting #20 firmware version, #5 MAC) and that
{F}frames are arriving on the ~1.2 s cadence — the cadence baseline is needed in S4.
Abort if: no {Y} response, or {F} cadence not established within 10 s.
S1 — Snapshot (the safety net)
Write a recovery file to local disk before any mutation. Contents (JSON, schema in §5):
- Station identity: MAC, name, firmware version, timestamp.
- All settings (#1–#33) with raw values — including the original value of #3.
- Every sensor record: index, serial, type, name, scale, offset, units, decimal digits, stats setting, alarm config (active, delay, 4 limits).
This file is the audit trail and the manual-repair source of last resort. The flow must not proceed until the file is written and re-read successfully.
S2 — Mode guard
- Read setting #3 from the S1 snapshot.
- If
#3 == 1(restricted): write#3 = 0and wait for the{E03…}echo confirming0. Surface this to the operator: "Station was in restricted sensor mode; switched to normal for acquisition." Note that this write persists to EEPROM (op05.c:1303) — intentional, and it also heals any RAM-vs-EEPROM mode drift left behind by earlier button commands (button cases 4/5 changeopModewithout saving —indicators.c:893,904). - Record
original_modefor S7.
Abort if: echo not received or echoes a non-zero value (write rejected/garbled).
S3 — Trigger
- Write
#4 = 1. Expect the{E04…}echo. - Mark the trigger timestamp. Do not send further commands until S4 completes — the main loop is about to block, and queued traffic complicates the gap detection.
S4 — Wait for completion
The scan blocks the firmware main loop, so the {F} cadence is the progress indicator:
- Wait for
{F}frames to stop (no frame for > 3 s ⇒ scan underway). - Wait for
{F}frames to resume (first frame after the gap ⇒ scan finished). - Timeout: 60 s from trigger. The bus walk visits every DS2413 switch tap with 3×
retries and 30–90 ms delays per failure (
switches.c:144-151,sensors.c:1125-1158), so a healthy 16-sensor unit finishes in seconds; a minute means a wedged bus.
Edge case: if {F} never pauses, the scan may have run between two frames (small bus) — treat
a stable post-trigger table in S5 as success regardless.
On timeout: skip to S7-cleanup, then report "scan did not complete — check 1-Wire bus wiring" alongside the S1 snapshot contents. Do not retry automatically into a dead bus.
S5 — Verify & diff
- Send
{Y}; parse the sensor table. Repeat until two consecutive reads are identical (guards against reading mid-stream). - Diff against the S1 snapshot, keyed by serial number:
| Category | Definition | Disposition |
|---|---|---|
| Recovered | serial in both snapshots | OK — old slot, name/calibration intact |
| New | serial only in post-scan table, has bus link | prompt for name in S6 |
| Missing | serial only in S1 snapshot | remediation list for S6 |
| Ghost | post-scan entry with no bus link / never updating (linked-sensor bug, pointsix.c:124) |
offer {R} removal in S6 |
- Present the diff to the operator by sensor name and serial, not index.
S6 — Remediate
- Missing sensors: show the list; operator checks wiring/taps; one click re-runs S3→S5. Repeats are safe — settings persist in EEPROM and recovered sensors reclaim their slots. Loop as many times as the operator wants.
- New sensors: prompt for names; push via
{B}(and{C}/{D}/{E}/{F}/alarms if the operator supplies calibration). Each write is echoed with the updated{A}record (op05.c:1415-1418). - Ghosts: offer one-click
{R}(remove by index) per ghost entry. - Unrecoverable missing sensor the operator insists on keeping registered:
{S}re-adds the serial as a persistent placeholder, then re-push its name/calibration from the snapshot (the{S}path resets them —pointsix.c:377-387). Display it clearly as inactive until a future scan finds it on the bus.
S7 — Cleanup & report
- Write
#4 = 0; confirm echo. (Otherwise the acquire flag and fast-blink LED persist until the next button press —indicators.c:670-674.) - Mode restore decision. If
original_mode == 1, ask the operator whether to restore it, with this default and warning:- Default: leave #3 = 0. On a wired-only DA-12C, restricted mode adds no security against neighboring RF traffic and makes every future acquire (button or tool) destructive (§1.2).
- If site policy requires restricted mode, write
#3 = 1only after S5 shows zero missing sensors.
- Final
{Y}; store the post-state next to the S1 snapshot as the after record. - Show the closing summary: recovered / new / removed / still-missing counts, mode
disposition, and the recovery-file path. Send
{Z}if disconnecting.
4. Safety invariants (MUSTs for implementation review)
- Never write #4 = 1 unless #3 is confirmed 0 in the same session (echo-verified, not assumed from a stale read).
- Never expose a raw #4 toggle in the UI.
- Never trigger a scan before the S1 recovery file is durably written.
- Always clear #4 in cleanup, including on every abort path.
- Always end with a verify pass; never report success from the trigger alone.
- Treat
{S}as a registration placeholder only — never present an{S}-added wired sensor as live. - All operator-facing output identifies sensors by name + serial; indices are unstable across acquires.
5. Recovery-file schema
{
"schema": "da12c-acquire-snapshot/1",
"taken_at": "2026-06-10T14:03:22Z",
"phase": "before",
"station": { "mac": "0090C2…", "name": "Cooler 3", "firmware": "4.21" },
"settings": { "1": "Cooler 3", "3": 1, "4": 0, "…": "raw values, all 33" },
"sensors": [
{
"index": 1,
"serial": "28A4E1020000009B",
"type": "0x2801",
"name": "Freezer A",
"scale": 1.0, "offset": 0.0,
"units": 1, "decimals": 1, "stats": 1,
"alarm": { "active": 1, "delay": 30, "limits": [-300, -250, 0x8000, 0x8000] }
}
]
}
One file per phase (before / after), named
acquire_<MAC>_<UTC timestamp>_<phase>.json, kept indefinitely — these are the audit trail.
6. Open items to verify on hardware
- Measure real scan duration on a fully-loaded unit (16 sensors, multiple switches) to
confirm the 60 s timeout and the
{F}-gap detection threshold. - Confirm
{F}emission genuinely pauses during the scan (expected, since the scan blocks the main loop that drivesOp05SendInfo, but unverified on hardware). - Confirm the
{E04}echo arrives before the scan blocks the loop (echo is sent from the same pass that parses the command —op05.c:1409; expected yes). - Characterize ghost-entry behavior end-to-end with a real linked (dual-channel) sensor.
7. Cross-references
- DA-12C Service-Tool Protocol reference — wire formats for every command used here (§2 encoding, §4 inbound commands, §5 settings table).
reference/da12c_status.py— decoder + command builders.- Doc vs. Code Discrepancies — registry; the restricted-mode acquire trap
and the linked-sensor
RemoveSensorbug originate from the button-command investigation. - Firmware:
indicators.c:834-929(DoButtonCommand),sensors.c:1125-1184(SensorsAcquire/SwitchesAcquire),pointsix.c:668-734(GetP6Numgate),owi.c:822-1010(OwSearch),op05.c:1063-1420(command parser).