Command-Line Tools
Command-line host tools live here. Most hardware-facing tools link the Qt-free
tagcore target from ../libraries/tagcore. dataprocessing links the
Qt-free sensoranalysis_core and SQLite instead, and is built in any host
build; like every tool here it uses no Qt.
Rust ports of the tools that need only the tag interface are in
../commandline-rs.
Distributed tools -- these are the ones in host_cli_install_targets in
../CMakeLists.txt, the ones documented in the user guide, and the ones that
reach a release:
dataprocessing: copy SQLite logs and materialize derived/calibrated sensor streams. The initial implementation supports CompassTag calibrated vectors and canonical orientation streams.tag-dwnld: download tag logs using the shared tag log writer interface.tag-info: inspect tag/base information.tag-reset: reset a tag.tag-start: start logging.tag-stop: stop logging and print the resulting tag status.tag-cal: calibration helper.tag-test: run RTC checks and tag self-tests. Despite the name this is a field tool -- it is what confirms a freshly programmed board works -- so it ships.tag-txt-import: convert a legacy BitTag or PresTag.txtlog to SQLite, verified by rendering it back to text. User page:cli/tag-txt-import.md. See below.
Developer and bench tools -- built from this directory, never installed. They
exist to exercise hardware and firmware during development, they carry no user
guide pages, and adding one to the install list is what put an unsigned,
undocumented tag-attach-cycle.app into every macOS release until it was
caught by the signature check in host/tools/release-macos.sh:
tag-test-example: minimal example that sets the tag RTC, waits, and prints the measured clock error. A worked example of the tagcore API, not a tool.tag-monitor-test: exercises the debug monitor interface through the link adapter.tag-attach-cycle: repeatedly attaches and detaches a tag over USB, to shake out enumeration and attach-path faults.tag-peek: read arbitrary memory through the monitor interface.tag-cal-write: write an identity (no-op) calibration constant set, to unblock a mass-erased CompassTag. See below.tag-log-import: write atagclientJSONL download as a SQLite log, with no tag. See below.qtmonitor-fixture-capture: captureTagInfo, defaultConfig,Status, and voltage from a real tag into the fixture JSON consumed by qtmonitor documentation screenshot automation.
All of the tag-attached tools share -d/--debug, -b/--base BUS:DEVICE
and -h/--help.
Keep direct tag-operation tools independent of Qt so they remain lightweight and usable in scripts. Processing tools may link host analysis libraries when that avoids duplicating sensor math.
Example qtmonitor fixture capture preserving the real tag identity:
qtmonitor-fixture-capture \
--id compasstag \
--label CompassTag \
--state idle \
--fallback-config embedded/proto-c/compasstag-proto-c/default-config.json \
--output host/docs/fixtures/qtmonitor/compasstag.json \
--print-summary
Example sanitized capture for screenshots that should not show a real device UUID:
qtmonitor-fixture-capture \
--id prestag \
--label PresTag \
--state idle \
--sanitize \
--fallback-config embedded/proto-c/prestag-proto-c/default-config.json \
--output host/docs/fixtures/qtmonitor/prestag.json
--state names the captured status slot in the fixture; it does not drive the
tag into that state. Put the tag in the desired state before running the tool.
tag-capture
Captures a tag's state over SWD without booting its firmware: the backup
registers (where the tag keeps its pState), the reset flags, option bytes,
flash ECC registers, RTC, unique ID, OTP and the whole internal flash. Run it
first on a returned tag, before tag-info or any other tool here. Those
attach a monitor, which lets the firmware boot, and the boot clears the reset
flags and can rewrite pState and the marker log.
build-host/bin/tag-capture --reason "returned from site 3, would not start"
It writes captures/capture-YYYYmmdd-HHMMSS/ with one .bin per region and a
manifest.json holding the decoded registers and each file's SHA-256. The tag
is then reset and boots normally. --sram also captures SRAM, which is rarely
useful because Shutdown and Standby do not keep it. --leave-halted leaves the
core stopped.
It then captures the external flash. It reads the tag's identity record from
the internal flash just captured, finds the loader the record names
(<loader>.stldr under --loader-dir, $TAG_LOADER_DIR or
build-host/embedded/loaders), and checks the part's JEDEC ID against the
record. The output is:
- for NOR: external_flash.bin;
- for SPI NAND: external_raw.bin, external_ecc.bin and
external_pages.csv, with blank blocks skipped (--external-full reads
them).
--loader names the image explicitly, for an image with no identity record.
--no-external skips this stage. See
SWD Capture and Recovery Library.
tag-xflash
Reads a tag's external flash over SWD, again without booting its firmware.
It halts the tag at its reset vector, downloads an external-flash loader from
embedded/loaders into SRAM, and calls the loader's Init and Read from the
host. CubeProgrammer is not involved, and internal flash is not written.
build-host/bin/tag-xflash dump \
--loader build-host/embedded/loaders/AT25XE_PresTagv3/AT25XE_PresTagv3/build/AT25XE_PresTagv3.stldr \
-o xflash.bin
A loader with a Serve() entry point (the loaders in this tree) is driven
through it, and the tool prints the part's JEDEC ID and status register as
found. Any other .stldr is driven through the STM32CubeProgrammer entry
points; --st forces that path. --offset and --length read part of the
device; by default the whole part is read, its size taken from the loader.
For SPI NAND, tag-xflash nand -l <loader> -o <dir> reads whole pages, data
and spare, both raw and through the part's on-die ECC:
- page 0 of each block is read raw, and a blank block is skipped (--full
reads every block);
- --first-block and --blocks select a range;
- it writes raw.bin, ecc.bin, pages.csv (each page's ECC verdict and
status registers) and summary.txt.
On an IMUTagNandBmp581 the whole 256 MiB part scans in about 75 s when
only one block is in use. The loader must match the board; for now
it is chosen by hand. Downloading the loader overwrites the start of SRAM1, so
run tag-capture first if SRAM matters. A 4 MiB part takes about a minute.
See Loader Runtime Design.
tag-sramcall
Calls one function of an SRAM-resident image on a halted tag and prints the
transfer buffer. It halts the tag at its reset vector, downloads the image into
SRAM1 with the same convention as the external-flash loaders, calls the named
function with up to four arguments, hex-dumps the buffer, and resets the tag.
An argument written buf becomes the buffer's address.
build-host/bin/tag-sramcall \
--image build-host/embedded/loaders/RV3028_PresTagv3/RV3028_PresTagv3/build/RV3028_PresTagv3.elf \
--call Rv3028ReadRegs --args buf,0x30,8 --dump 8
That reads the RV3028's configuration registers, its factory clock offset among them, with no firmware on the tag. See RV3028_PresTagv3. Exit status: 0 when the function returned 1, 2 for any other result, 1 when the call could not be made.
tag-rebuild
Rebuilds a SQLite download from a tag-capture directory, without the tag.
The capture's internal flash, backup registers and external flash are turned
back into the messages the firmware would have served, and written by the same
SQLite writer tag-dwnld uses:
build-host/bin/tag-rebuild <capture-dir> -o tag.db3
The file is "as captured": the tag as it was found, before the reset recovery
a live attach would run. Its info table records source = capture, the
capture directory and the capture time. Families with a capture decoder are
accepted: IMUTag, PresTag, CompassTag, UIUCTag and BitTag. A capture whose
layout version the decoder does not know is refused. How to capture, and how
to keep the decoders in step with the firmware, is in
Capturing a tag; the design choice is
decision 0018.
tag-log-import
Writes a download in the JSONL format of tagclient's
tag_dwnld.py as a SQLite log,
with the same writer tag-dwnld uses. It needs no tag:
build-host/bin/tag-log-import download.jsonl -o tag.db3
The input has two parts:
- The first line is the header.
configandinfoare required.states(State messages) andcalibration(CalibrationConstants, oldest first) are optional. Other members are ignored, such astag_shaandstatus. - Every later line is one Ack in protobuf JSON. Each is written with one
writeLog()call, in order.
A line the writer takes no records from is an error: it means the Acks do not match the header's tag type. The tool is tag-agnostic.
The info table gains source = import and import_file, and
--info KEY=VALUE adds more rows. On failure, the partial output is removed.
tagsim uses it to turn simulated IMUTag pages into a genuine log.
tag-txt-import
Converts a legacy BitTag or PresTag .txt log to a SQLite log, and proves
the conversion before keeping it
(proposal):
build-host/bin/tag-txt-import log.txt -o log.db3 --report log.json
- What it reads: variants B (BitTag V5 header), C (the current header,
including the C3/C4 packed word on
V:lines), D (PresTag) and E (no header), each checked as below; and variant A, translated only. Files that are not logs are refused with exit status 2. - Variant A (BitTagRTCv4 firmware, 2019–2020; written by
DumpPersistent.cin the old accel-tag repository) has no writer here to render it back, so it is decoded as btviz decodes it and not round-trip checked: each 5-minute hex word is five 6-bit counts of active seconds per minute, the minute atepoch − i·60(i = 5…1) in bits(i−1)·6, ascount / 0.6percent;temperature T vdd Vlines becomeCoreTemperatureandVoltage. Bits 30–31 are dropped as btviz drops them (the report counts words with bit 30 set). The log'sinfosaystxt_translation; the tool checks only that the rows read back as decoded. - How it writes: with
SqliteTagLogWriter::writeSamples(), into the same tables and streams a download writes. - The check, on every conversion: the written log is read back, rebuilt
into the records the text writer consumes, rendered with
TextTagLogWriter, and compared with the source: data lines byte for byte or, for older writers, within the source's printed precision; header fields one by one. A failure writes no log (--keep-failedkeeps it, marked) and exits 1. - Repairs, all recorded: exact duplicate records removed; conflicting duplicates kept and counted; records before 2015 or after the read time dropped; FIVEMIN files from the pre-2021 host (nine 7-bit buckets per record) re-decoded as seven 9-bit ones and checked back through the buggy layout; CRLF and comment lines handled. Removed lines are kept verbatim in the report.
- Not guessed at: a
V:line with no activity before it, or activity noV:line closes, marks exports run together; the conversion fails. - PresTag voltages: PresTag firmware put a second supply voltage in the
core-temperature slot, so
TC:is a voltage. The tool warns, stores the mean ofV:andTC:as oneVoltagerow (noCoreTemperature), and the round trip writes that mean back into both slots. - Provenance in
info:source=txt-import, the source path (relative to--root), SHA-256 and size, the variant, the importer version, the verbatim header,txt_roundtripandtxt_conversion_report;--report FILEwrites the report as JSON too.
On the legacy collection (2026-10-22), 398 files of variants B–E convert and
pass the check and all 195 variant A files translate; 2 fail as exports run
together; 24 non-logs (20 macOS AppleDouble files, 3 empty files, 1 page of
notes) are refused. The check
itself is exercised on anonymised fixtures by
test/txt_import_check.py.
tag-cal-write
Several CompassTag-family state handlers refuse Start with "Device must be
calibrated" (config.c, sensorsHaveCalibration()) until at least one
magnetometer calibration entry is present in flash. A mass-erase reflash
(STM32_Programmer_CLI -e all, or any full-chip erase) wipes that flash
region along with everything else, which then blocks tag-start on an
otherwise-healthy board with no other symptom.
tag-cal-write writes a single identity calibration entry (M' = A(M-V)
with A = identity, V = 0, B = 1) so the tag will start again:
build-host/bin/tag-cal-write
build-host/bin/tag-reset --set-rtc
build-host/bin/tag-start --start-now
This unblocks power/lifecycle testing on a freshly reflashed board without a
physical rotation-based calibration pass. It is not a real calibration --
magnetometer output taken under it is uncalibrated raw data passed straight
through. Run the interactive tag-cal (or qtcalibrate) for a real
calibration before trusting compass output from a board this was used on.
DataProcessing
dataprocessing is a post-processing tool for SQLite logs. It keeps the input
database untouched, copies it to a new output path, writes derived tables and
stream metadata, and records processing provenance in a ProcessingRun table.
The design, output tables and provenance format are in
design/dataprocessing.md; open work is in
TODO.md.
Current processors:
compass-calibrated: writesCompassCalibratedwith offset-corrected acceleration beside calibrated magnetometer x/y/z values.compass-orientation: writesCompassOrientationwith the magnetic-frame orientation quaternion, dip, field strength and acceleration magnitude. Euler angles are not stored; see Downstream Heading Conversion.imu-compass-orientation: the same orientation columns for an IMUTag log, written toImuCompassOrientationonElapsedUs. Each magnetometer row is paired with the mean of its superframe's accelerometer rows. SensorViz plots the result when it opens the processed file.
Example:
dataprocessing \
--input host/docs/fixtures/sensorviz/compasstag.db3 \
--output /tmp/compasstag-processed.db3 \
--processor compass-calibrated \
--processor compass-orientation \
--if-exists replace \
--print-summary
dataprocessing \
--input imutag.db3 \
--output imutag-processed.db3 \
--processor imu-compass-orientation \
--print-summary
Use --list-processors to see whether a log contains the inputs needed by the
current processors. Heading is not materialized; downstream tools can convert
the stored yaw to a display heading by applying their own declination and
mounting convention.