umshctl/command/
repeater.rs1use anyhow::{Result, bail};
10
11use umsh::core::RegionCode;
12use umsh::ulcp_wire::ids::prop;
13
14use super::persist;
15use super::values::{MinRssiArg, MinSnrArg, OptRegionArg, RegionListArg};
16use crate::App;
17use crate::output::{subfield, warn};
18
19#[derive(Debug, clap::Subcommand)]
20pub enum RepeaterOp {
21 Show,
23 On,
25 Off,
27 Regions {
30 #[arg(value_name = "CODE[,...]|none")]
31 list: RegionListArg,
32 },
33 DefaultRegion {
36 #[arg(value_name = "CODE|none")]
37 code: OptRegionArg,
38 },
39 MinRssi {
41 #[arg(value_name = "DBM|none", allow_hyphen_values = true)]
42 dbm: MinRssiArg,
43 },
44 MinSnr {
46 #[arg(value_name = "DB|none", allow_hyphen_values = true)]
47 db: MinSnrArg,
48 },
49}
50
51pub async fn run(app: &mut App, op: Option<RepeaterOp>) -> Result<()> {
52 let no_save = app.no_save;
53 let device = app.device()?;
54 match op.unwrap_or(RepeaterOp::Show) {
55 RepeaterOp::Show => {
56 let Some(policy) = device.repeater_policy().await? else {
57 bail!("device does not advertise CAP_REPEATER");
58 };
59 print_enabled(Some(policy.enabled as u8));
60 subfield("regions", format_regions(&policy.regions));
61 match policy.default_region {
62 Some(code) => subfield("default", code),
63 None => subfield("default", "none (forwards untagged floods untagged)"),
64 }
65 match policy.min_rssi {
66 Some(dbm) => subfield("min rssi", format!("{dbm} dBm")),
67 None => subfield("min rssi", "any"),
68 }
69 match policy.min_snr {
70 Some(db) => subfield("min snr", format!("{db} dB")),
71 None => subfield("min snr", "any"),
72 }
73 if policy.enabled
74 && policy.default_region.is_some_and(|code| {
75 !policy.regions.is_empty() && !policy.regions.contains(&code)
76 })
77 {
78 warn(
83 "the default region is not in the forwarding list; floods this repeater \
84 tags will not be forwarded by it",
85 );
86 }
87 return Ok(());
88 }
89 RepeaterOp::On => set_enabled(device, true).await?,
90 RepeaterOp::Off => set_enabled(device, false).await?,
91 RepeaterOp::Regions { list } => {
92 let stored = device.set_repeater_regions(&list.0).await?;
93 println!("repeater regions {}", format_regions(&stored));
94 if stored.len() < list.0.len() {
95 warn(format!(
96 "the device kept {} of {} regions (capacity)",
97 stored.len(),
98 list.0.len()
99 ));
100 }
101 }
102 RepeaterOp::DefaultRegion { code } => {
103 match device.set_repeater_default_region(code.0).await? {
104 Some(code) => println!("repeater default region {code}"),
105 None => println!("repeater default region none (forwards untagged)"),
106 }
107 }
108 RepeaterOp::MinRssi { dbm } => match device.set_repeater_min_rssi(dbm.0).await? {
109 Some(dbm) => println!("repeater min rssi {dbm} dBm"),
110 None => println!("repeater min rssi any"),
111 },
112 RepeaterOp::MinSnr { db } => match device.set_repeater_min_snr(db.0).await? {
113 Some(db) => println!("repeater min snr {db} dB"),
114 None => println!("repeater min snr any"),
115 },
116 }
117 persist(device, no_save).await
118}
119
120async fn set_enabled(
121 device: &mut umsh::ulcp::UlcpDevice<crate::connection::SessionLink>,
122 enabled: bool,
123) -> Result<()> {
124 let echoed = device
125 .set_prop(prop::MAC_REPEATER_ENABLED, &[enabled as u8])
126 .await?;
127 print_enabled(echoed.first().copied());
128 Ok(())
129}
130
131fn print_enabled(byte: Option<u8>) {
132 match byte {
133 Some(0) => println!("repeater off"),
134 Some(_) => println!("repeater on (on-board node forwards overheard frames)"),
135 None => println!("repeater state unknown (empty value)"),
136 }
137}
138
139pub fn format_regions(regions: &[RegionCode]) -> String {
142 if regions.is_empty() {
143 return "any (no regional restriction)".to_string();
144 }
145 regions
146 .iter()
147 .map(RegionCode::to_string)
148 .collect::<Vec<_>>()
149 .join(", ")
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn the_empty_region_list_says_what_it_means() {
158 assert_eq!(format_regions(&[]), "any (no regional restriction)");
159 assert_eq!(
160 format_regions(&[RegionCode::from_iata("SJC").unwrap()]),
161 "SJC"
162 );
163 }
164}