Skip to main content

tag_cal/
tag-cal.rs

1//! `tag-cal`: put an IDLE tag in sensor-calibration mode and stream its raw
2//! samples until Ctrl-C.
3//!
4//! Port of `host/commandline/tag-calibrate.cc` (built as `tag-cal`). Syncs
5//! the clock, starts calibration, then repeatedly fetches the calibration
6//! log and prints each sample as `Raw:ax,ay,az,0,0,0,mx,my,mz` (CRLF line
7//! ends), the format calibration front ends read. Ctrl-C ends the stream and
8//! the tag is stopped.
9//!
10//! Exits 0 after streaming; 1 if the tag is not IDLE, or the attach, the
11//! first status read, the clock sync, the calibrate request or the final
12//! stop fails.
13//!
14//! Usage: `tag-cal [-d] [-b BUS:DEVICE]`. The clap command name is
15//! `tag-calibrate`, the C++ name, so help and errors print that.
16//!
17//! # Hardware side effects
18//!
19//! The attach resets the tag; `SetRtc` sets its clock from the host;
20//! `Calibrate` moves it into calibration mode, and `Stop` takes it out on
21//! the way out. The Ctrl-C handler is installed only after the attach, so a
22//! Ctrl-C during the attach kills the process; one after it, even before the
23//! stream starts, still ends with the stop. A tag left in calibration mode
24//! (a killed process or a failed stop) must be stopped with `tag-stop`. A
25//! tag family without a calibration mode refuses (BitTag, for example).
26
27use std::process::ExitCode;
28use std::sync::atomic::{AtomicBool, Ordering};
29use std::thread::sleep;
30use std::time::Duration;
31
32use clap::Parser;
33use tag_tools::{BaseArgs, init_logging, select_base, state_name};
34use tagcore::Tag;
35use tagcore::proto::{TagState, ack};
36
37/// starts calibration mode
38#[derive(Parser)]
39#[command(name = "tag-calibrate")]
40struct Cli {
41    /// The shared `--debug` and `--base` options.
42    #[command(flatten)]
43    base: BaseArgs,
44}
45
46/// Cleared by Ctrl-C to end the sample stream.
47static EXECUTE: AtomicBool = AtomicBool::new(true);
48
49/// Prints `message` to stderr and returns exit status 1.
50fn fail(message: impl std::fmt::Display) -> ExitCode {
51    eprintln!("{message}");
52    ExitCode::FAILURE
53}
54
55/// Streams the calibration log until Ctrl-C or a failed fetch, printing one
56/// `Raw:` line per sample. The firmware buffers samples between requests, so
57/// requests are 100 ms apart, qtcalibrate's streaming interval. Floats print with two decimals, as the C++ sets
58/// `std::fixed` with precision 2; a missing sensor prints as zeros.
59fn stream_samples(tag: &Tag) {
60    while EXECUTE.load(Ordering::SeqCst) {
61        let reply = match tag.calibration_log() {
62            Ok(reply) => reply,
63            Err(_) => {
64                eprintln!("GetCalibrationLog failed: {}", tag.debug_message());
65                break;
66            }
67        };
68        let samples = match reply.payload {
69            Some(ack::Payload::CalibrationLog(log)) => log.data,
70            _ => Vec::new(),
71        };
72        for sample in &samples {
73            let mut line = String::from("Raw:");
74            match &sample.accel {
75                Some(a) => line.push_str(&format!("{:.2},{:.2},{:.2},", a.ax, a.ay, a.az)),
76                None => line.push_str("0,0,0,"),
77            }
78            line.push_str("0,0,0,");
79            match &sample.mag {
80                Some(m) => line.push_str(&format!("{:.2},{:.2},{:.2}", m.mx, m.my, m.mz)),
81                None => line.push_str("0,0,0"),
82            }
83            print!("{line}\r\n");
84        }
85        sleep(Duration::from_millis(100));
86    }
87}
88
89/// Attaches, checks the tag is IDLE, syncs its clock, starts calibration,
90/// streams until Ctrl-C and stops the tag.
91fn main() -> ExitCode {
92    let cli = Cli::parse();
93    init_logging(&cli.base);
94
95    let tag = Tag::new();
96    let dev = select_base::<Cli>(&tag, &cli.base);
97    if !tag.attach(&dev) {
98        return fail("Attach failed");
99    }
100    // Ctrl-C ends the stream so the tag is stopped, rather than exiting.
101    let _ = ctrlc::set_handler(|| EXECUTE.store(false, Ordering::SeqCst));
102
103    // A discarded failure would report "Tag not idle" for a tag that never
104    // answered.
105    let status = match tag.status() {
106        Ok(status) => status,
107        Err(_) => return fail(format!("GetStatus failed: {}", tag.debug_message())),
108    };
109    if status.state != TagState::Idle as i32 {
110        return fail(format!("Tag not idle: {}", state_name(status.state)));
111    }
112
113    if tag.set_rtc().is_err() {
114        return fail(format!("SetRtc failed: {}", tag.debug_message()));
115    }
116    if tag.calibrate().is_err() {
117        return fail(format!("Calibrate failed: {}", tag.debug_message()));
118    }
119    stream_samples(&tag);
120    if tag.stop().is_err() {
121        return fail(format!("Stop failed: {}", tag.debug_message()));
122    }
123    ExitCode::SUCCESS
124}