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}