1use anyhow::Result;
7
8use umsh::ulcp::{FrameLink, UlcpDevice};
9use umsh::ulcp_wire::ids::prop;
10
11use super::{decode_u32, persist};
12use crate::App;
13use crate::output::field;
14
15#[derive(Debug, clap::Subcommand)]
16pub enum PhyOp {
17 Show,
19 On,
21 Off,
23 Freq {
25 #[arg(value_name = "KHZ")]
26 khz: u32,
27 },
28 Sf {
30 #[arg(value_parser = clap::value_parser!(u8).range(5..=12))]
31 sf: u8,
32 },
33 Bw {
35 #[arg(value_name = "HZ")]
36 hz: u32,
37 },
38 Cr {
40 #[arg(value_parser = clap::value_parser!(u8).range(5..=8))]
41 cr: u8,
42 },
43 Power {
45 #[arg(value_name = "DBM", allow_hyphen_values = true)]
46 dbm: i8,
47 },
48}
49
50pub async fn run(app: &mut App, op: Option<PhyOp>) -> Result<()> {
51 let no_save = app.no_save;
52 let device = app.device()?;
53 match op.unwrap_or(PhyOp::Show) {
54 PhyOp::Show => return report(device).await,
55 PhyOp::On => set_enabled(device, true).await?,
56 PhyOp::Off => set_enabled(device, false).await?,
57 PhyOp::Freq { khz } => {
58 let echoed = device.set_prop(prop::PHY_FREQ, &khz.to_le_bytes()).await?;
59 println!("phy freq {} kHz", decode_u32(&echoed).unwrap_or(khz));
60 }
61 PhyOp::Sf { sf } => {
62 let echoed = device.set_prop(prop::PHY_LORA_SF, &[sf]).await?;
63 println!("phy SF{}", echoed.first().copied().unwrap_or(sf));
64 }
65 PhyOp::Bw { hz } => {
66 let echoed = device
67 .set_prop(prop::PHY_LORA_BW, &hz.to_le_bytes())
68 .await?;
69 println!("phy BW {} Hz", decode_u32(&echoed).unwrap_or(hz));
70 }
71 PhyOp::Cr { cr } => {
72 let echoed = device.set_prop(prop::PHY_LORA_CR, &[cr]).await?;
73 println!("phy CR 4/{}", echoed.first().copied().unwrap_or(cr));
74 }
75 PhyOp::Power { dbm } => {
76 let echoed = device.set_prop(prop::PHY_TX_POWER, &[dbm as u8]).await?;
77 let dbm = echoed.first().copied().map_or(dbm, |byte| byte as i8);
78 println!("phy TX {dbm} dBm");
79 }
80 }
81 persist(device, no_save).await
82}
83
84async fn set_enabled<L: FrameLink>(device: &mut UlcpDevice<L>, on: bool) -> Result<()> {
85 let echoed = device.set_prop(prop::PHY_ENABLED, &[on as u8]).await?;
86 let on = echoed.first().copied().unwrap_or(on as u8) != 0;
87 println!("phy {}", if on { "enabled" } else { "disabled" });
88 Ok(())
89}
90
91pub async fn report<L: FrameLink>(device: &mut UlcpDevice<L>) -> Result<()> {
93 let enabled = device
94 .get_prop(prop::PHY_ENABLED)
95 .await?
96 .first()
97 .copied()
98 .unwrap_or(0)
99 != 0;
100 let mut parts = vec![if enabled { "enabled" } else { "disabled" }.to_string()];
101 if let Some(freq) = device
102 .get_prop(prop::PHY_FREQ)
103 .await
104 .ok()
105 .and_then(|value| decode_u32(&value))
106 {
107 parts.push(format!("{freq} kHz"));
108 }
109 parts.extend(lora_parts(device).await);
110 parts.extend(power_part(device).await);
111 field("phy", parts.join(", "));
112 Ok(())
113}
114
115pub async fn lora_parts<L: FrameLink>(device: &mut UlcpDevice<L>) -> Vec<String> {
118 let mut parts = Vec::new();
119 if let Some(bw) = device
120 .get_prop(prop::PHY_LORA_BW)
121 .await
122 .ok()
123 .and_then(|value| decode_u32(&value))
124 {
125 parts.push(format!("BW {bw} Hz"));
126 }
127 if let Some(sf) = device
128 .get_prop(prop::PHY_LORA_SF)
129 .await
130 .ok()
131 .and_then(|value| value.first().copied())
132 {
133 parts.push(format!("SF{sf}"));
134 }
135 if let Some(cr) = device
136 .get_prop(prop::PHY_LORA_CR)
137 .await
138 .ok()
139 .and_then(|value| value.first().copied())
140 {
141 parts.push(format!("CR 4/{cr}"));
142 }
143 if let Some(sw) = device
144 .get_prop(prop::PHY_LORA_SW)
145 .await
146 .ok()
147 .and_then(|value| <[u8; 2]>::try_from(value.as_slice()).ok())
148 .map(u16::from_le_bytes)
149 {
150 parts.push(format!("sync 0x{sw:04x}"));
151 }
152 parts
153}
154
155pub async fn power_part<L: FrameLink>(device: &mut UlcpDevice<L>) -> Option<String> {
156 device
157 .get_prop(prop::PHY_TX_POWER)
158 .await
159 .ok()
160 .and_then(|value| value.first().copied())
161 .map(|power| format!("TX {} dBm", power as i8))
162}