diff --git a/Cargo.lock b/Cargo.lock index 49e0998c3ace1b4b119de1254aa903219be68001..b6d03cebbdea8dfbabf1b0bfeeb05442ddb885e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,6 +7,9 @@ name = "arrayvec" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +dependencies = [ + "serde", +] [[package]] name = "atomic-polyfill" @@ -46,9 +49,9 @@ checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "bitvec" @@ -84,8 +87,10 @@ dependencies = [ "nmea", "num-traits", "panic-reset", + "postcard", "rand", "rf4463", + "serde", "stm32f4xx-hal", "systick-monotonic", "usb-device", @@ -101,13 +106,19 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb" dependencies = [ "num-traits", ] +[[package]] +name = "cobs" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" + [[package]] name = "cortex-m" version = "0.7.7" @@ -138,7 +149,7 @@ checksum = "f0f6f3e36f203cfedbc78b357fb28730aa2c6dc1ab060ee5c2405e843988d3c7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -166,7 +177,7 @@ dependencies = [ "proc-macro2", "quote", "rtic-syntax", - "syn", + "syn 1.0.109", ] [[package]] @@ -207,9 +218,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "deranged" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", ] @@ -307,7 +318,7 @@ dependencies = [ [[package]] name = "ham-cats" version = "0.2.0" -source = "git+https://gitlab.scd31.com/cats/ham-cats#3f423d2bd2aff5321a2d3e8c66740c936e1f5cdf" +source = "git+https://gitlab.scd31.com/cats/ham-cats#846af8f55b2398da94da31737287b57caf419505" dependencies = [ "arrayvec", "bitvec", @@ -343,6 +354,7 @@ dependencies = [ "atomic-polyfill", "hash32", "rustc_version 0.4.0", + "serde", "spin", "stable_deref_trait", ] @@ -366,8 +378,7 @@ dependencies = [ [[package]] name = "labrador-ldpc" version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23c19ea22fc166b77441be6ea377e5aa20121490cdec0af18a14356d390f45b5" +source = "git+https://github.com/adamgreig/labrador-ldpc#a6b1b1feb65ec3eae5bb1797f2e4798d4fcee92e" [[package]] name = "libm" @@ -387,9 +398,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "minimal-lexical" @@ -436,6 +447,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-traits" version = "0.2.17" @@ -461,6 +478,17 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "postcard" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a55c51ee6c0db07e68448e336cf8ea4131a620edefebf9893e759b2d793420f8" +dependencies = [ + "cobs", + "heapless", + "serde", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -476,7 +504,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check", ] @@ -493,18 +521,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -533,7 +561,7 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" [[package]] name = "rf4463" version = "0.1.0" -source = "git+https://gitlab.scd31.com/stephen/rf4463-lib#79c8def87540f8ab2663bfa3c9fb13db344ef84e" +source = "git+https://gitlab.scd31.com/stephen/rf4463-lib#e6ef9a2fd2c64bff5ea7c1716dcc21943add0c2d" dependencies = [ "embedded-hal 0.2.7", ] @@ -559,7 +587,7 @@ dependencies = [ "indexmap", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -577,7 +605,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.20", + "semver 1.0.21", ] [[package]] @@ -597,9 +625,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" [[package]] name = "semver-parser" @@ -607,6 +635,26 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "serde" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "snafu" version = "0.7.5" @@ -626,7 +674,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -693,6 +741,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "synopsys-usb-otg" version = "0.3.2" @@ -724,11 +783,12 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "time" -version = "0.3.30" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +checksum = "00b24b79b7a07f10209f19e683ca1e289d80b1e76ffa8c2b779718566a083679" dependencies = [ "deranged", + "num-conv", "powerfmt", "time-core", ] diff --git a/Cargo.toml b/Cargo.toml index 1b7123349b229b16c788a2e80cbfc91c15cf56b9..6412f1dc6c3ea68c827d14ae04e770ec99faa79d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,11 +26,13 @@ ham-cats = { git = "https://gitlab.scd31.com/cats/ham-cats" } half = { version = "2.3.1", default-features = false } # TODO can get rid of some of these features nmea = { version = "0.6.0", default-features = false, features = ["VTG", "GGA", "RMC", "GNS", "GLL"] } -arrayvec = { version = "0.7.4", default-features = false } +arrayvec = { version = "0.7.4", default-features = false, features = ["serde"] } ushell = "0.3.6" usbd-serial = "0.1.1" usb-device = "0.2.9" crc = "3.0.1" embedded-storage = "0.2.0" num-traits = { version = "0.2.17", default-features = false, features = ["libm"] } -rand = { version = "0.8", default_features = false, features = ["small_rng"] } +rand = { version = "0.8", default-features = false, features = ["small_rng"] } +postcard = "1.0.8" +serde = { version = "1.0.196", default-features = false, features = ["derive"] } diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000000000000000000000000000000000000..3a26366d4da66e27c821e462eed5ef057a896684 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +edition = "2021" diff --git a/src/config.rs b/src/config.rs index 4d0f42c5ac8e64b3cb18641edea4ec3fa07198ae..d84cd28cb53d93cc90d2b366fb22f0422dab5de4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,15 +3,18 @@ use core::fmt::Display; use arrayvec::ArrayString; use crc::{Crc, CRC_32_ISCSI}; use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; +use serde::{Deserialize, Serialize}; use stm32f4xx_hal::flash::{FlashExt, LockedFlash}; use crate::geo::distance_km; -const CONFIG_LEN: usize = 844; +const DEFAULT_FREQUENCY: u32 = 430_500_000; +const CONFIG_LEN: usize = 2048; const CASTAGNOLI: Crc<u32> = Crc::<u32>::new(&CRC_32_ISCSI); -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] pub struct Config { + pub frequency: Frequency, pub callsign: ArrayString<252>, pub icon: u16, pub ssid: u8, @@ -59,88 +62,32 @@ impl Config { // version // allows us to update the config structure in the future // while maintaining backwards compatibility - buf[0] = 0; - - buf[1..3].copy_from_slice(&self.icon.to_le_bytes()); - buf[3] = self.ssid; - buf[4] = self.max_hops; - buf[5..13].copy_from_slice(&self.transmit_period_seconds.to_le_bytes()); - match self.gps { - GpsSetting::Disabled => { - buf[13] = 0; - } - - GpsSetting::Fixed(lat, lon) => { - buf[13] = 1; - buf[14..22].copy_from_slice(&lat.to_le_bytes()); - buf[22..30].copy_from_slice(&lon.to_le_bytes()); - } - - GpsSetting::Receiver => { - buf[13] = 2; - } - } - - let enable_tx: u8 = self.enable_transmit.into(); - let enable_digi: u8 = self.enable_digipeating.into(); - buf[30] = enable_tx | (enable_digi << 1); - - buf[31] = self.callsign.len().try_into().unwrap(); - buf[32..(32 + self.callsign.len())].copy_from_slice(self.callsign.as_bytes()); - buf[284..286].copy_from_slice(&u16::try_from(self.comment.len()).unwrap().to_le_bytes()); - buf[286..(286 + self.comment.len())].copy_from_slice(self.comment.as_bytes()); - - self.black_hole - .encode((&mut buf[798..823]).try_into().unwrap()); - - match self.antenna_height { - Maybe::Some(height) => { - buf[823] = 1; - buf[824] = height; - } - Maybe::None => buf[823] = 0, - } - - match self.antenna_gain { - Maybe::Some(gain) => { - buf[825] = 1; - buf[826..830].copy_from_slice(&gain.to_le_bytes()); - } - Maybe::None => buf[825] = 0, - } + buf[0] = 1; - match self.tx_power { - Maybe::Some(power) => { - buf[830] = 1; - buf[831..835].copy_from_slice(&power.to_le_bytes()); - } - Maybe::None => { - buf[830] = 0; - } - } - - match self.min_voltage { - Maybe::Some(v) => { - buf[835] = 1; - buf[836..840].copy_from_slice(&v.to_le_bytes()); - } - Maybe::None => { - buf[835] = 0; - } - } + postcard::to_slice(self, &mut buf[1..]).unwrap(); - // Checksum - let checksum = CASTAGNOLI.checksum(&buf[0..840]); - buf[840..844].copy_from_slice(&checksum.to_le_bytes()); + let checksum = CASTAGNOLI.checksum(&buf[0..2044]); + buf[2044..2048].copy_from_slice(&checksum.to_le_bytes()); } fn deserialize(buf: &[u8; CONFIG_LEN]) -> Option<Self> { match buf[0] { 0 => Self::deserialize_v0(buf), + 1 => Self::deserialize_v1(buf), _ => None, } } + fn deserialize_v1(buf: &[u8; CONFIG_LEN]) -> Option<Self> { + let expected_checksum = CASTAGNOLI.checksum(&buf[0..2044]); + let actual_checksum = u32::from_le_bytes(buf[2044..2048].try_into().unwrap()); + if expected_checksum != actual_checksum { + return None; + } + + postcard::from_bytes(&buf[1..2044]).ok() + } + fn deserialize_v0(buf: &[u8; CONFIG_LEN]) -> Option<Self> { let expected_checksum = CASTAGNOLI.checksum(&buf[0..840]); let actual_checksum = u32::from_le_bytes(buf[840..844].try_into().unwrap()); @@ -205,6 +152,7 @@ impl Config { }; Some(Config { + frequency: Frequency::default(), callsign, icon, ssid, @@ -227,6 +175,7 @@ impl Config { impl Default for Config { fn default() -> Self { Config { + frequency: Frequency::default(), callsign: "N0CALL".try_into().unwrap(), icon: 0, ssid: 0, @@ -246,7 +195,7 @@ impl Default for Config { } } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Serialize, Deserialize)] pub enum GpsSetting { Disabled, Fixed(f64, f64), @@ -263,7 +212,7 @@ impl Display for GpsSetting { } } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Serialize, Deserialize)] pub enum MaybeBlackHole { Some(BlackHoleSetting), None, @@ -281,19 +230,6 @@ impl MaybeBlackHole { } } - fn encode(&self, buf: &mut [u8; 25]) { - match self { - Self::None => { - buf[0] = 0; - } - - Self::Some(bhs) => { - buf[0] = 1; - bhs.encode((&mut buf[1..]).try_into().unwrap()); - } - } - } - fn decode(buf: &[u8; 25]) -> Option<Self> { match buf[0] { 0 => Some(Self::None), @@ -314,7 +250,7 @@ impl Display for MaybeBlackHole { } } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Serialize, Deserialize)] pub struct BlackHoleSetting { pub latitude: f64, pub longitude: f64, @@ -322,12 +258,6 @@ pub struct BlackHoleSetting { } impl BlackHoleSetting { - fn encode(&self, buf: &mut [u8; 24]) { - buf[0..8].copy_from_slice(&self.latitude.to_le_bytes()); - buf[8..16].copy_from_slice(&self.longitude.to_le_bytes()); - buf[16..24].copy_from_slice(&self.radius_meters.to_le_bytes()); - } - fn decode(buf: &[u8; 24]) -> Self { let latitude = f64::from_le_bytes(buf[0..8].try_into().unwrap()); let longitude = f64::from_le_bytes(buf[8..16].try_into().unwrap()); @@ -352,7 +282,7 @@ impl Display for BlackHoleSetting { } // Like Option, but local, so we can implement Display on it -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Serialize, Deserialize)] pub enum Maybe<T> { Some(T), None, @@ -378,3 +308,44 @@ impl<T> From<Maybe<T>> for Option<T> { } } } + +#[derive(Copy, Clone, Serialize, Deserialize)] +pub struct Frequency(u32); + +impl Frequency { + pub fn new(f: u32) -> Option<Self> { + if Self::validate_freq(f) { + Some(Self(f)) + } else { + None + } + } + + pub fn new_mhz(f: f64) -> Option<Self> { + let hz = u32::try_from((f * 1_000_000.0) as u64).ok()?; + + Self::new(hz) + } + + pub fn hz(&self) -> u32 { + self.0 + } + + fn validate_freq(f: u32) -> bool { + (420_000_000..=450_000_000).contains(&f) + } +} + +impl Default for Frequency { + fn default() -> Self { + Self(DEFAULT_FREQUENCY) + } +} + +impl Display for Frequency { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let freq_mhz = self.0 as f64 / 1_000_000.0; + + write!(f, "{freq_mhz:.3} MHz") + } +} diff --git a/src/radio.rs b/src/radio.rs index eebb0549ee4bc32c30cd9f86dbfb98670ef6694f..328d73385541c3483022950b1344004bf4849792 100644 --- a/src/radio.rs +++ b/src/radio.rs @@ -40,8 +40,7 @@ impl<'a> RadioManager<'a> { ) -> Option<Self> { let mut radio = Rf4463::new(spi, sdn, cs, delay, &mut RADIO_CONFIG_CATS.clone()).ok()?; - // sets us up for the default CATS frequency, 430.500 MHz - radio.set_channel(20); + radio.set_frequency(config.frequency.hz()).ok()?; let enable_digipeating = config.enable_digipeating; let seed = rand_seed_from_str(&config.callsign) ^ u64::from(config.ssid); diff --git a/src/shell.rs b/src/shell.rs index ff0e0a97c130d7428c5d882e946eaeec2a4357cd..4bf8851ceb358ab8b8c0da5071efa02fdccfd02f 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -10,7 +10,7 @@ use usbd_serial::SerialPort; use ushell::{autocomplete::StaticAutocomplete, history::LRUHistory, Input, ShellError, UShell}; use crate::{ - config::{BlackHoleSetting, Config, GpsSetting, Maybe, MaybeBlackHole}, + config::{BlackHoleSetting, Config, Frequency, GpsSetting, Maybe, MaybeBlackHole}, voltage::VoltMeter, }; @@ -19,8 +19,8 @@ type ShellType = const SAVE_TEXT: &str = "Saved your settings. Remove USB and press the reset button when you're ready.\r\n\ - The board won't transmit when USB is attached, even if enable_transmit is true!\r\n\ - "; + The board won't transmit when USB is attached, even if enable_transmit is true!\r\n\ + "; const HELP_TEXT: &str = concat!( "\r\n\ @@ -28,14 +28,14 @@ const HELP_TEXT: &str = concat!( Firmware version v", env!("CARGO_PKG_VERSION"), "\r\n\ - Available commands:\r\n\ - \r\n\ - help Display this text\r\n\r\n\ - get Show the current configuration\r\n\r\n\ - set [property] [value] Update the current configuration\r\n For a list of properties, see the `get` command\r\n\r\n\ - save Save the current settings to flash memory for persistence\r\n\r\n\ - volts Show the current input voltage. Only reads from the screw terminals - will show ~0V if only connected to USB\r\n\ - " + Available commands:\r\n\ + \r\n\ + help Display this text\r\n\r\n\ + get Show the current configuration\r\n\r\n\ + set [property] [value] Update the current configuration\r\n For a list of properties, see the `get` command\r\n\r\n\ + save Save the current settings to flash memory for persistence\r\n\r\n\ + volts Show the current input voltage. Only reads from the screw terminals - will show ~0V if only connected to USB\r\n\ + " ); pub struct Shell { @@ -62,6 +62,7 @@ impl Shell { match cmd { "get" => { let Config { + frequency, callsign, icon, ssid, @@ -88,27 +89,28 @@ impl Shell { write!( ushell, "\r\n\ - callsign {callsign}\r\n\ - ssid {ssid}\r\n\ - icon {icon}\r\n\ - max_hops {max_hops}\r\n\ - comment {comment}\r\n\ - transmit_period {transmit_period_seconds} (seconds)\r\n\ - gps {gps}\r\n\ - black_hole {black_hole}\r\n\ - min_voltage {min_voltage:.2}\r\n\ - enable_transmit {enable_transmit}\r\n\ - enable_digipeating {enable_digipeating}\r\n\ - \r\n\ - The following settings do not change the behaviour of the transceiver.\r\n\ - Instead, they only change the contents of the NodeInfo whisker.\r\n\ - Info configuration:\r\n\ - antenna_height {antenna_height:.1} (meters)\r\n\ - antenna_gain {antenna_gain:.1} (dBi)\r\n\ - tx_power {tx_power} (dBm)\r\n\ - \r\n\ - To change a setting, type `set <property> <value>`\r\n\ - For example, `set transmit_period 300`\r\n" + frequency {frequency}\r\n\ + callsign {callsign}\r\n\ + ssid {ssid}\r\n\ + icon {icon}\r\n\ + max_hops {max_hops}\r\n\ + comment {comment}\r\n\ + transmit_period {transmit_period_seconds} (seconds)\r\n\ + gps {gps}\r\n\ + black_hole {black_hole}\r\n\ + min_voltage {min_voltage:.2}\r\n\ + enable_transmit {enable_transmit}\r\n\ + enable_digipeating {enable_digipeating}\r\n\ + \r\n\ + The following settings do not change the behaviour of the transceiver.\r\n\ + Instead, they only change the contents of the NodeInfo whisker.\r\n\ + Info configuration:\r\n\ + antenna_height {antenna_height:.1} (meters)\r\n\ + antenna_gain {antenna_gain:.1} (dBi)\r\n\ + tx_power {tx_power} (dBm)\r\n\ + \r\n\ + To change a setting, type `set <property> <value>`\r\n\ + For example, `set transmit_period 300`\r\n" ) .ok(); } @@ -116,6 +118,13 @@ impl Shell { let (property, value) = args.split_once(' ').unwrap_or((args, "")); let res = match property { + "frequency" => match parse_frequency(value) { + Ok(v) => { + self.config.frequency = v; + Ok(()) + } + Err(e) => Err(e), + }, "callsign" => { if value.trim().is_empty() { Err("Blank callsign is invalid") @@ -231,9 +240,9 @@ impl Shell { }; match res { - Ok(()) => write!(ushell, "\r\nOK. Don't forget to save your settings when you're done with `save`!\r\n").ok(), - Err(e) => write!(ushell, "\r\nError: {}\r\n", e).ok() - }; + Ok(()) => write!(ushell, "\r\nOK. Don't forget to save your settings when you're done with `save`!\r\n").ok(), + Err(e) => write!(ushell, "\r\nError: {}\r\n", e).ok() + }; } "save" => { write!(ushell, "\r\n").ok(); @@ -261,16 +270,20 @@ impl Shell { } // Used for QA only "quicktest" => { + let comment = ArrayString::from( + "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. \ + Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. \ + Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. \ + Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, \ + venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibu").unwrap(); + self.config = Config { + frequency: Frequency::new(430_450_000).unwrap(), callsign: args.try_into().unwrap(), icon: 0, ssid: 255, max_hops: 0, - comment: ArrayString::from("Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. \ - Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. \ - Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. \ - Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, \ - venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibu").unwrap(), + comment, transmit_period_seconds: 3, gps: GpsSetting::Receiver, enable_transmit: true, @@ -279,7 +292,7 @@ impl Shell { antenna_height: Maybe::None, antenna_gain: Maybe::None, tx_power: Maybe::Some(30.0), - min_voltage: Maybe::Some(9.0), + min_voltage: Maybe::Some(9.0), }; self.config.save_to_flash(flash); @@ -302,6 +315,12 @@ impl Shell { } } +fn parse_frequency(val: &str) -> Result<Frequency, &'static str> { + const ERR: &str = "Invalid value. Expected a frequency between 420 and 450 MHz."; + + val.parse().ok().and_then(Frequency::new_mhz).ok_or(ERR) +} + fn parse_arrstring<const N: usize>(val: &str) -> Result<ArrayString<N>, &'static str> { match val.try_into() { Ok(x) => Ok(x), @@ -353,9 +372,9 @@ fn parse_maybe_f32(val: &str) -> Result<Maybe<f32>, &'static str> { fn parse_gps(val: &str) -> Result<GpsSetting, &'static str> { const GENERIC_ERR: &str = "Invalid value. Expected one of the following options:\r\n\ - The literal `disabled` (set gps disable) - Do not set GPS data in transmitted packets\r\n\ - The literal `receiver` (set gps receiver) - Use a connected GPS module\r\n\ - A lat/lon pair (set gps 12.34 56.78) - Use the specified fixed coordinates"; + The literal `disabled` (set gps disable) - Do not set GPS data in transmitted packets\r\n\ + The literal `receiver` (set gps receiver) - Use a connected GPS module\r\n\ + A lat/lon pair (set gps 12.34 56.78) - Use the specified fixed coordinates"; match val { "disabled" => Ok(GpsSetting::Disabled), @@ -372,8 +391,8 @@ fn parse_gps(val: &str) -> Result<GpsSetting, &'static str> { fn parse_black_hole(val: &str) -> Result<MaybeBlackHole, &'static str> { const ERR: &str = "Invalid value. Expected `lat lon radius`, where radius is in meters.\r\n\ - For example, `set black_hole 12.34 56.78 1000` to exclude 1000m around (12.34, 56.78).\r\n\ - Alternatively, clear this setting with `set black_hole none`."; + For example, `set black_hole 12.34 56.78 1000` to exclude 1000m around (12.34, 56.78).\r\n\ + Alternatively, clear this setting with `set black_hole none`."; if val.trim().eq_ignore_ascii_case("none") { return Ok(MaybeBlackHole::None);