From ca0d8f6b478c3296a00c769aee4bacf7d84a4d0b Mon Sep 17 00:00:00 2001 From: Liam Kinne Date: Sat, 27 Jul 2024 01:10:39 +1000 Subject: [PATCH 01/15] add gateway client --- Cargo.lock | 52 +++++++++++++++++++++++++++++++++-- Cargo.toml | 4 +++ gateway-client/Cargo.toml | 7 +++++ gateway-client/src/lib.rs | 58 +++++++++++++++++++++++++++++++++++++++ src/gateway/mod.rs | 6 ---- src/gateway/restart.rs | 15 ++-------- src/gateway/status.rs | 16 ++--------- 7 files changed, 124 insertions(+), 34 deletions(-) create mode 100644 gateway-client/Cargo.toml create mode 100644 gateway-client/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 6db565f..be85e61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -231,9 +231,9 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "crc" -version = "3.0.1" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" dependencies = [ "crc-catalog", ] @@ -345,6 +345,13 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "gateway-client" +version = "0.1.0" +dependencies = [ + "tokio-modbus 0.14.0", +] + [[package]] name = "gimli" version = "0.28.1" @@ -1134,6 +1141,26 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -1196,6 +1223,24 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "tokio-modbus" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "033b1b9843d693c3543e6b9c656a566ea45d2564e72ad5447e83233b9e2f3fe1" +dependencies = [ + "async-trait", + "byteorder", + "bytes", + "futures-core", + "futures-util", + "log", + "smallvec", + "thiserror", + "tokio", + "tokio-util", +] + [[package]] name = "tokio-native-tls" version = "0.3.1" @@ -1292,13 +1337,14 @@ dependencies = [ "anyhow", "clap", "colored", + "gateway-client", "hftwo", "open", "reqwest", "serde", "serde_json", "tokio", - "tokio-modbus", + "tokio-modbus 0.11.0", "uftwo", ] diff --git a/Cargo.toml b/Cargo.toml index fd009d2..faf1751 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ version = "0.0.5" edition = "2021" [dependencies] +gateway-client = { path = "gateway-client" } hftwo = "0.1.2" uftwo = "0.1.0" anyhow = "1.0.81" @@ -26,6 +27,9 @@ colored = "2.1.0" inherits = "release" lto = "thin" +[workspace] +members = ["gateway-client"] + # Config for 'cargo dist' [workspace.metadata.dist] # The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax) diff --git a/gateway-client/Cargo.toml b/gateway-client/Cargo.toml new file mode 100644 index 0000000..8511092 --- /dev/null +++ b/gateway-client/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "gateway-client" +version = "0.1.0" +edition = "2021" + +[dependencies] +tokio-modbus = "0.14.0" diff --git a/gateway-client/src/lib.rs b/gateway-client/src/lib.rs new file mode 100644 index 0000000..1e84bb5 --- /dev/null +++ b/gateway-client/src/lib.rs @@ -0,0 +1,58 @@ +use std::net::{IpAddr, SocketAddr}; +use tokio_modbus::{ + client::{tcp::connect, Reader, Writer}, + slave::SlaveContext, + Result, Slave, +}; + +#[derive(Debug)] +pub struct Client { + modbus: tokio_modbus::client::Context, +} + +impl Client { + pub async fn connect(ip: IpAddr) -> std::io::Result { + let mut modbus = connect(SocketAddr::new(ip, 502)).await?; + modbus.set_slave(Slave(1)); + + Ok(Self { modbus }) + } + + /// Get hardware version. + pub async fn hardware_version(&mut self) -> Result { + let version = self.modbus.read_holding_registers(1, 3).await?.unwrap(); + Ok(Ok(Version { + major: version[0], + minor: version[1], + patch: version[2], + })) + } + + /// Get firmware version. + pub async fn firmware_version(&mut self) -> Result { + let version = self.modbus.read_holding_registers(4, 3).await?.unwrap(); + Ok(Ok(Version { + major: version[0], + minor: version[1], + patch: version[2], + })) + } + + /// Restart the gateway gracefully + pub async fn restart(&mut self) -> Result<()> { + self.modbus.write_single_coil(1, true).await + } +} + +#[derive(Debug)] +pub struct Version { + pub major: u16, + pub minor: u16, + pub patch: u16, +} + +impl std::fmt::Display for Version { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "v{}.{}.{}", self.major, self.minor, self.patch) + } +} diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 5ef6cdd..ece71be 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -56,9 +56,3 @@ pub struct UpdateOptions { #[clap(long)] version: Option, } - -pub async fn connect_modbus(ip: IpAddr) -> Result { - let mut ctx = connect(SocketAddr::new(ip, 502)).await?; - ctx.set_slave(Slave(1)); - Ok(ctx) -} diff --git a/src/gateway/restart.rs b/src/gateway/restart.rs index a4f1a49..21a77fa 100644 --- a/src/gateway/restart.rs +++ b/src/gateway/restart.rs @@ -1,26 +1,17 @@ use crate::write_with_header; use colored::Colorize; use std::{net::IpAddr, time::Duration}; -use tokio_modbus::client::Writer; - -use super::connect_modbus; +use tokio::time::timeout; #[allow(unused_variables, unused_mut)] pub async fn command( mut output: impl std::io::Write, ip: IpAddr, ) -> anyhow::Result<()> { - let mut modbus = connect_modbus(ip).await?; + let mut client = gateway_client::Client::connect(ip).await?; write_with_header(&mut output, "Restarting".green(), " "); - - // write reset coil - let _ = tokio::time::timeout( - Duration::from_secs(1), - modbus.write_single_coil(1, true), - ) - .await; - + let _ = timeout(Duration::from_secs(1), client.restart()).await; write_with_header(&mut output, "Done".green(), " "); Ok(()) diff --git a/src/gateway/status.rs b/src/gateway/status.rs index 2e51f99..522fe3d 100644 --- a/src/gateway/status.rs +++ b/src/gateway/status.rs @@ -1,35 +1,25 @@ -use super::connect_modbus; use crate::write_with_header; use colored::Colorize; use std::{net::IpAddr, time::Instant}; -use tokio_modbus::client::Reader; pub async fn command( mut output: impl std::io::Write, ip: IpAddr, ) -> anyhow::Result<()> { - let mut modbus = connect_modbus(ip).await?; + let mut client = gateway_client::Client::connect(ip).await?; let start = Instant::now(); - let hardware_version = modbus.read_holding_registers(1, 3).await?; write_with_header( &mut output, "Hardware Version".green(), - &format!( - "v{}.{}.{}", - hardware_version[0], hardware_version[1], hardware_version[2] - ), + &format!("{}", client.hardware_version().await??), ); - let firmware_version = modbus.read_holding_registers(4, 3).await?; write_with_header( &mut output, "Firmware Version".green(), - &format!( - "v{}.{}.{}", - firmware_version[0], firmware_version[1], firmware_version[2], - ), + &format!("{}", client.firmware_version().await??,), ); writeln!(output, "Got status in {:?}", start.elapsed())?; From b55d037f382e0fcd4e6f3c080219f20064cdcee3 Mon Sep 17 00:00:00 2001 From: Liam Kinne Date: Sat, 27 Jul 2024 01:17:18 +1000 Subject: [PATCH 02/15] remove tokio-modbus from cli --- Cargo.lock | 20 +------------------- Cargo.toml | 1 - src/gateway/mod.rs | 10 +--------- 3 files changed, 2 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be85e61..9afcfe5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -349,7 +349,7 @@ dependencies = [ name = "gateway-client" version = "0.1.0" dependencies = [ - "tokio-modbus 0.14.0", + "tokio-modbus", ] [[package]] @@ -1206,23 +1206,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tokio-modbus" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d99561deee858c4d60b0cb170b67e715f7688c5923c371d2d30c9773ef7d2d8" -dependencies = [ - "async-trait", - "byteorder", - "bytes", - "futures-core", - "futures-util", - "log", - "smallvec", - "tokio", - "tokio-util", -] - [[package]] name = "tokio-modbus" version = "0.14.0" @@ -1344,7 +1327,6 @@ dependencies = [ "serde", "serde_json", "tokio", - "tokio-modbus 0.11.0", "uftwo", ] diff --git a/Cargo.toml b/Cargo.toml index faf1751..85a1b42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,6 @@ anyhow = "1.0.81" clap = { version = "4.5.2", features = ["derive"] } open = "5.1.2" tokio = { version = "1.37.0", features = ["full", "net"] } -tokio-modbus = "0.11.0" reqwest = { version = "0.12.4", features = ["json"] } serde_json = "1.0.117" serde = { version = "1.0.202", features = ["derive"] } diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index ece71be..35acd8e 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -4,15 +4,7 @@ mod status; mod update; use clap::{Parser, Subcommand}; -use std::{ - net::{IpAddr, SocketAddr}, - path::PathBuf, -}; -use tokio_modbus::{ - client::{tcp::connect, Context}, - slave::SlaveContext, - Slave, -}; +use std::{net::IpAddr, path::PathBuf}; #[derive(Subcommand)] pub enum Commands { From 8b207beb943a3da0c858acf465550589f2b76da8 Mon Sep 17 00:00:00 2001 From: Liam Kinne Date: Sat, 27 Jul 2024 01:42:08 +1000 Subject: [PATCH 03/15] cleanup --- gateway-client/src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gateway-client/src/lib.rs b/gateway-client/src/lib.rs index 1e84bb5..a424e33 100644 --- a/gateway-client/src/lib.rs +++ b/gateway-client/src/lib.rs @@ -18,6 +18,11 @@ impl Client { Ok(Self { modbus }) } + /// Restart the gateway gracefully + pub async fn restart(&mut self) -> Result<()> { + self.modbus.write_single_coil(1, true).await + } + /// Get hardware version. pub async fn hardware_version(&mut self) -> Result { let version = self.modbus.read_holding_registers(1, 3).await?.unwrap(); @@ -37,11 +42,6 @@ impl Client { patch: version[2], })) } - - /// Restart the gateway gracefully - pub async fn restart(&mut self) -> Result<()> { - self.modbus.write_single_coil(1, true).await - } } #[derive(Debug)] From 3b8776192ecc4f9c52e9653b7104fda1988c337e Mon Sep 17 00:00:00 2001 From: Liam Kinne Date: Fri, 2 Aug 2024 10:36:29 +1000 Subject: [PATCH 04/15] add gateway identification magic numbers --- src/gateway/mod.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 35acd8e..c0d4e40 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -6,6 +6,28 @@ mod update; use clap::{Parser, Subcommand}; use std::{net::IpAddr, path::PathBuf}; +/// Magic numbers used to identify Gateway types. +/// Must be u16 to fit in one Modbus register. +#[derive(Debug, Clone, Copy)] +enum MagicNumId { + /// "FD" like CAN FD + CanFd = 0x4644, + /// "RS" like RS-232/RS-485 + Serial = 0x5253, +} + +impl TryFrom for MagicNumId { + type Error = (); + + fn try_from(value: u16) -> Result { + match value { + x if x == MagicNumId::CanFd as u16 => Ok(MagicNumId::CanFd), + x if x == MagicNumId::Serial as u16 => Ok(MagicNumId::Serial), + _ => Err(()), + } + } +} + #[derive(Subcommand)] pub enum Commands { /// Show status From 9914f07bc04853d559b08d46afd0c7b3170b6a69 Mon Sep 17 00:00:00 2001 From: Liam Kinne Date: Fri, 2 Aug 2024 18:06:47 +1000 Subject: [PATCH 05/15] add device configuration --- gateway-client/src/lib.rs | 69 ++++++++++++++++- src/gateway/config.rs | 152 ++++++++++++++++++++++++++++++++++++++ src/gateway/mod.rs | 4 + 3 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 src/gateway/config.rs diff --git a/gateway-client/src/lib.rs b/gateway-client/src/lib.rs index a424e33..da72a5f 100644 --- a/gateway-client/src/lib.rs +++ b/gateway-client/src/lib.rs @@ -1,4 +1,4 @@ -use std::net::{IpAddr, SocketAddr}; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use tokio_modbus::{ client::{tcp::connect, Reader, Writer}, slave::SlaveContext, @@ -42,6 +42,73 @@ impl Client { patch: version[2], })) } + + /// Get DHCP enabled. + pub async fn dhcp(&mut self) -> Result { + let enabled = self.modbus.read_coils(1001, 1).await?.unwrap(); + Ok(Ok(enabled[0])) + } + + /// Set DHCP enabled. + pub async fn set_dhcp(&mut self, enabled: bool) -> Result<()> { + self.modbus.write_single_coil(1001, enabled).await + } + + /// Get the configured IPv4 address. + pub async fn ipv4_address(&mut self) -> Result { + let address = self.modbus.read_input_registers(1001, 4).await?.unwrap(); + Ok(Ok(Ipv4Addr::new( + address[0] as u8, + address[1] as u8, + address[2] as u8, + address[3] as u8, + ))) + } + + /// Set the IPv4 address. + pub async fn set_ipv4_address(&mut self, ip: Ipv4Addr) -> Result<()> { + let words = ip.octets().map(|o| o as u16); + self.modbus.write_multiple_registers(1001, &words).await + } + + /// Get CAN bus receive error count. + pub async fn canbus_receive_error_count(&mut self) -> Result { + let count = self.modbus.read_input_registers(2001, 1).await?.unwrap(); + Ok(Ok(count[0])) + } + + /// Get CAN bus transmit error count. + pub async fn canbus_transmit_error_count(&mut self) -> Result { + let count = self.modbus.read_input_registers(2002, 1).await?.unwrap(); + Ok(Ok(count[0])) + } + + /// Get the CAN bus nominal rate in bits per second. + pub async fn canbus_bitrate_nominal(&mut self) -> Result { + let rate = self.modbus.read_holding_registers(2001, 1).await?.unwrap(); + Ok(Ok(rate[0] as u32 * 100)) + } + + /// Set the CAN bus nominal rate in bits per second. + pub async fn set_canbus_bitrate_nominal( + &mut self, + rate: u32, + ) -> Result<()> { + let rate = (rate / 100) as u16; + self.modbus.write_single_register(2001, rate).await + } + + /// Get the CAN bus data rate in bits per second. + pub async fn canbus_bitrate_data(&mut self) -> Result { + let rate = self.modbus.read_holding_registers(2001, 1).await?.unwrap(); + Ok(Ok(rate[0] as u32 * 100)) + } + + /// Set the CAN bus data rate in bits per second. + pub async fn set_canbus_bitrate_data(&mut self, rate: u32) -> Result<()> { + let rate = (rate / 100) as u16; + self.modbus.write_single_register(2001, rate).await + } } #[derive(Debug)] diff --git a/src/gateway/config.rs b/src/gateway/config.rs new file mode 100644 index 0000000..86c70f8 --- /dev/null +++ b/src/gateway/config.rs @@ -0,0 +1,152 @@ +use clap::{error, Parser, Subcommand}; +use colored::Colorize; +use gateway_client::Client; +use std::net::Ipv4Addr; + +use crate::write_with_header; + +#[derive(Subcommand)] +enum Commands { + /// DHCPv4 enable/disable. + Dhcp(Dhcp), + /// IPv4 address. + Ipv4(Ipv4), + /// CAN Bus bitrate. + CanBitrate(CanBitrate), +} + +#[derive(Parser)] +struct Dhcp { + // Enable or disable DHCP. + #[arg(value_parser = parse_enable)] + enable: Option, +} + +#[derive(Parser)] +struct Ipv4 { + /// Set the static IPv4 address. + ip: Option, +} + +#[derive(Parser)] +struct CanBitrate { + /// Set the nominal data rate in bits per second. + nominal: Option, + /// Set the data bitrate in bits per second. (optional) + data: Option, +} + +#[derive(Parser)] +pub struct Cmd { + #[clap(subcommand)] + subcommand: Commands, +} + +impl Cmd { + pub async fn run( + self, + mut output: impl std::io::Write, + ip: std::net::IpAddr, + ) -> anyhow::Result<()> { + let mut client = Client::connect(ip).await?; + + match self.subcommand { + Commands::Dhcp(dhcp) => { + if let Some(enable) = dhcp.enable { + client.set_dhcp(enable).await??; + writeln!(output, "Done")?; + } else { + writeln!(output, "{}", client.dhcp().await??)?; + } + Ok(()) + } + Commands::Ipv4(ipv4) => { + if let Some(ip) = ipv4.ip { + client.set_ipv4_address(ip).await??; + writeln!(output, "Done")?; + } else { + writeln!(output, "{}", client.ipv4_address().await??)?; + } + + Ok(()) + } + Commands::CanBitrate(can_bitrate) => { + if let Some(nominal) = can_bitrate.nominal { + // use same as nominal if not specified + let data = can_bitrate.data.unwrap_or(nominal); + + if nominal < 10_000 { + return Err(anyhow::Error::msg( + "Nominal bitrate too low.", + )); + } + + if nominal > 5_000_000 { + return Err(anyhow::Error::msg( + "Nominal bitrate too high.", + )); + } + + if data < 10_000 { + return Err(anyhow::Error::msg( + "Data bitrate too low.", + )); + } + + if data > 5_000_000 { + return Err(anyhow::Error::msg( + "Data bitrate too high.", + )); + } + + client.set_canbus_bitrate_nominal(data / 100).await??; + client.set_canbus_bitrate_data(data / 100).await??; + + writeln!(output, "Done")?; + } else { + let nominal = client.canbus_bitrate_nominal().await??; + let data = client.canbus_bitrate_data().await??; + + write_with_header( + &mut output, + "Nominal bitrate".green(), + &format!("{} bit/s", nominal), + ); + + write_with_header( + &mut output, + "Data bitrate".green(), + &format!("{} bit/s", data), + ); + } + + Ok(()) + } + } + } +} + +/// A more general parser for boolean values such as "enable", "disable", "on" +/// and "off" as well as "true" and "false". +fn parse_enable(arg: &str) -> Result { + match arg { + "enable" => Ok(true), + "true" => Ok(true), + "on" => Ok(true), + "disable" => Ok(false), + "false" => Ok(false), + "off" => Ok(false), + _ => Err(error::Error::new(error::ErrorKind::InvalidValue)), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn custom_parse() { + assert_eq!(parse_enable("enable").unwrap(), true); + assert_eq!(parse_enable("disable").unwrap(), false); + } +} diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index c0d4e40..4a2784c 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -1,3 +1,4 @@ +mod config; mod manifest; mod restart; mod status; @@ -36,6 +37,8 @@ pub enum Commands { Update(UpdateOptions), /// Command the device to restart Restart, + /// Read and write configuration + Config(config::Cmd), } #[derive(Parser)] @@ -57,6 +60,7 @@ impl Cmd { update::command(output, options, self.ip).await } Commands::Restart => restart::command(output, self.ip).await, + Commands::Config(command) => command.run(output, self.ip).await, } } } From 56a2a9ac714165800f2b73795d26e0913b141df2 Mon Sep 17 00:00:00 2001 From: Liam Kinne Date: Fri, 2 Aug 2024 18:22:19 +1000 Subject: [PATCH 06/15] add device identifier checking --- gateway-client/src/lib.rs | 69 +++++++++++++++++++++++++++++++-------- src/gateway/config.rs | 11 ++++++- src/gateway/mod.rs | 22 ------------- 3 files changed, 65 insertions(+), 37 deletions(-) diff --git a/gateway-client/src/lib.rs b/gateway-client/src/lib.rs index da72a5f..d577f32 100644 --- a/gateway-client/src/lib.rs +++ b/gateway-client/src/lib.rs @@ -2,7 +2,7 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use tokio_modbus::{ client::{tcp::connect, Reader, Writer}, slave::SlaveContext, - Result, Slave, + Result as ModbusResult, Slave, }; #[derive(Debug)] @@ -18,13 +18,29 @@ impl Client { Ok(Self { modbus }) } + /// Get device identifier. + pub async fn device_identifier( + &mut self, + ) -> Result, ()> { + let id = self + .modbus + .read_holding_registers(0, 1) + .await + .unwrap() + .unwrap()[0]; + + let id = DeviceIdentifier::try_from(id)?; + + Ok(Ok(Ok(id))) + } + /// Restart the gateway gracefully - pub async fn restart(&mut self) -> Result<()> { + pub async fn restart(&mut self) -> ModbusResult<()> { self.modbus.write_single_coil(1, true).await } /// Get hardware version. - pub async fn hardware_version(&mut self) -> Result { + pub async fn hardware_version(&mut self) -> ModbusResult { let version = self.modbus.read_holding_registers(1, 3).await?.unwrap(); Ok(Ok(Version { major: version[0], @@ -34,7 +50,7 @@ impl Client { } /// Get firmware version. - pub async fn firmware_version(&mut self) -> Result { + pub async fn firmware_version(&mut self) -> ModbusResult { let version = self.modbus.read_holding_registers(4, 3).await?.unwrap(); Ok(Ok(Version { major: version[0], @@ -44,18 +60,18 @@ impl Client { } /// Get DHCP enabled. - pub async fn dhcp(&mut self) -> Result { + pub async fn dhcp(&mut self) -> ModbusResult { let enabled = self.modbus.read_coils(1001, 1).await?.unwrap(); Ok(Ok(enabled[0])) } /// Set DHCP enabled. - pub async fn set_dhcp(&mut self, enabled: bool) -> Result<()> { + pub async fn set_dhcp(&mut self, enabled: bool) -> ModbusResult<()> { self.modbus.write_single_coil(1001, enabled).await } /// Get the configured IPv4 address. - pub async fn ipv4_address(&mut self) -> Result { + pub async fn ipv4_address(&mut self) -> ModbusResult { let address = self.modbus.read_input_registers(1001, 4).await?.unwrap(); Ok(Ok(Ipv4Addr::new( address[0] as u8, @@ -66,25 +82,25 @@ impl Client { } /// Set the IPv4 address. - pub async fn set_ipv4_address(&mut self, ip: Ipv4Addr) -> Result<()> { + pub async fn set_ipv4_address(&mut self, ip: Ipv4Addr) -> ModbusResult<()> { let words = ip.octets().map(|o| o as u16); self.modbus.write_multiple_registers(1001, &words).await } /// Get CAN bus receive error count. - pub async fn canbus_receive_error_count(&mut self) -> Result { + pub async fn canbus_receive_error_count(&mut self) -> ModbusResult { let count = self.modbus.read_input_registers(2001, 1).await?.unwrap(); Ok(Ok(count[0])) } /// Get CAN bus transmit error count. - pub async fn canbus_transmit_error_count(&mut self) -> Result { + pub async fn canbus_transmit_error_count(&mut self) -> ModbusResult { let count = self.modbus.read_input_registers(2002, 1).await?.unwrap(); Ok(Ok(count[0])) } /// Get the CAN bus nominal rate in bits per second. - pub async fn canbus_bitrate_nominal(&mut self) -> Result { + pub async fn canbus_bitrate_nominal(&mut self) -> ModbusResult { let rate = self.modbus.read_holding_registers(2001, 1).await?.unwrap(); Ok(Ok(rate[0] as u32 * 100)) } @@ -93,19 +109,22 @@ impl Client { pub async fn set_canbus_bitrate_nominal( &mut self, rate: u32, - ) -> Result<()> { + ) -> ModbusResult<()> { let rate = (rate / 100) as u16; self.modbus.write_single_register(2001, rate).await } /// Get the CAN bus data rate in bits per second. - pub async fn canbus_bitrate_data(&mut self) -> Result { + pub async fn canbus_bitrate_data(&mut self) -> ModbusResult { let rate = self.modbus.read_holding_registers(2001, 1).await?.unwrap(); Ok(Ok(rate[0] as u32 * 100)) } /// Set the CAN bus data rate in bits per second. - pub async fn set_canbus_bitrate_data(&mut self, rate: u32) -> Result<()> { + pub async fn set_canbus_bitrate_data( + &mut self, + rate: u32, + ) -> ModbusResult<()> { let rate = (rate / 100) as u16; self.modbus.write_single_register(2001, rate).await } @@ -123,3 +142,25 @@ impl std::fmt::Display for Version { write!(f, "v{}.{}.{}", self.major, self.minor, self.patch) } } + +/// Magic numbers used to identify Gateway types. +/// Must be u16 to fit in one Modbus register. +#[derive(Debug, Clone, Copy)] +pub enum DeviceIdentifier { + /// "FD" like CAN FD + CanFd = 0x4644, + /// "RS" like RS-232/RS-485 + Serial = 0x5253, +} + +impl TryFrom for DeviceIdentifier { + type Error = (); + + fn try_from(value: u16) -> Result { + match value { + x if x == Self::CanFd as u16 => Ok(Self::CanFd), + x if x == Self::Serial as u16 => Ok(Self::Serial), + _ => Err(()), + } + } +} diff --git a/src/gateway/config.rs b/src/gateway/config.rs index 86c70f8..8026774 100644 --- a/src/gateway/config.rs +++ b/src/gateway/config.rs @@ -1,6 +1,6 @@ use clap::{error, Parser, Subcommand}; use colored::Colorize; -use gateway_client::Client; +use gateway_client::{Client, DeviceIdentifier}; use std::net::Ipv4Addr; use crate::write_with_header; @@ -71,6 +71,15 @@ impl Cmd { Ok(()) } Commands::CanBitrate(can_bitrate) => { + match client.device_identifier().await { + Ok(Ok(Ok(DeviceIdentifier::CanFd))) => {} + _ => { + return Err(anyhow::Error::msg( + "Device does not have a CAN interface.", + )); + } + } + if let Some(nominal) = can_bitrate.nominal { // use same as nominal if not specified let data = can_bitrate.data.unwrap_or(nominal); diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 4a2784c..490d7a8 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -7,28 +7,6 @@ mod update; use clap::{Parser, Subcommand}; use std::{net::IpAddr, path::PathBuf}; -/// Magic numbers used to identify Gateway types. -/// Must be u16 to fit in one Modbus register. -#[derive(Debug, Clone, Copy)] -enum MagicNumId { - /// "FD" like CAN FD - CanFd = 0x4644, - /// "RS" like RS-232/RS-485 - Serial = 0x5253, -} - -impl TryFrom for MagicNumId { - type Error = (); - - fn try_from(value: u16) -> Result { - match value { - x if x == MagicNumId::CanFd as u16 => Ok(MagicNumId::CanFd), - x if x == MagicNumId::Serial as u16 => Ok(MagicNumId::Serial), - _ => Err(()), - } - } -} - #[derive(Subcommand)] pub enum Commands { /// Show status From aec2c06d02e74d9c52d5de0ed43bc610017f56c5 Mon Sep 17 00:00:00 2001 From: Liam Kinne Date: Fri, 2 Aug 2024 18:36:21 +1000 Subject: [PATCH 07/15] cleanup result handling --- gateway-client/src/lib.rs | 30 ++++++++++++------------------ src/gateway/config.rs | 4 ++-- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/gateway-client/src/lib.rs b/gateway-client/src/lib.rs index d577f32..161357c 100644 --- a/gateway-client/src/lib.rs +++ b/gateway-client/src/lib.rs @@ -21,17 +21,11 @@ impl Client { /// Get device identifier. pub async fn device_identifier( &mut self, - ) -> Result, ()> { - let id = self - .modbus + ) -> ModbusResult { + self.modbus .read_holding_registers(0, 1) .await - .unwrap() - .unwrap()[0]; - - let id = DeviceIdentifier::try_from(id)?; - - Ok(Ok(Ok(id))) + .map(|v| v.map(|v| v[0]).map(DeviceIdentifier::from)) } /// Restart the gateway gracefully @@ -148,19 +142,19 @@ impl std::fmt::Display for Version { #[derive(Debug, Clone, Copy)] pub enum DeviceIdentifier { /// "FD" like CAN FD - CanFd = 0x4644, + CanFd, /// "RS" like RS-232/RS-485 - Serial = 0x5253, + Serial, + /// Unkown but possibly valid identifier. + Unknown(u16), } -impl TryFrom for DeviceIdentifier { - type Error = (); - - fn try_from(value: u16) -> Result { +impl From for DeviceIdentifier { + fn from(value: u16) -> Self { match value { - x if x == Self::CanFd as u16 => Ok(Self::CanFd), - x if x == Self::Serial as u16 => Ok(Self::Serial), - _ => Err(()), + x if x == 0x4644 => Self::CanFd, + x if x == 0x5253 => Self::Serial, + _ => Self::Unknown(value), } } } diff --git a/src/gateway/config.rs b/src/gateway/config.rs index 8026774..6f9aab5 100644 --- a/src/gateway/config.rs +++ b/src/gateway/config.rs @@ -71,8 +71,8 @@ impl Cmd { Ok(()) } Commands::CanBitrate(can_bitrate) => { - match client.device_identifier().await { - Ok(Ok(Ok(DeviceIdentifier::CanFd))) => {} + match client.device_identifier().await?? { + DeviceIdentifier::CanFd => {} _ => { return Err(anyhow::Error::msg( "Device does not have a CAN interface.", From 0d704314efb2b291bb9663d4749e08a552fd564a Mon Sep 17 00:00:00 2001 From: Liam Kinne Date: Mon, 5 Aug 2024 22:04:21 +1000 Subject: [PATCH 08/15] fix double conversion of bitrate --- src/gateway/config.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gateway/config.rs b/src/gateway/config.rs index 6f9aab5..bf6436d 100644 --- a/src/gateway/config.rs +++ b/src/gateway/config.rs @@ -108,8 +108,8 @@ impl Cmd { )); } - client.set_canbus_bitrate_nominal(data / 100).await??; - client.set_canbus_bitrate_data(data / 100).await??; + client.set_canbus_bitrate_nominal(data).await??; + client.set_canbus_bitrate_data(data).await??; writeln!(output, "Done")?; } else { From 8915a8ac2399b0f2b5725510fb5f204e0cb452f9 Mon Sep 17 00:00:00 2001 From: Liam Kinne Date: Thu, 29 Aug 2024 23:16:24 +1000 Subject: [PATCH 09/15] add factory reset --- gateway-client/src/lib.rs | 5 +++++ src/gateway/mod.rs | 6 +++++- src/gateway/reset.rs | 18 ++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 src/gateway/reset.rs diff --git a/gateway-client/src/lib.rs b/gateway-client/src/lib.rs index 161357c..d560547 100644 --- a/gateway-client/src/lib.rs +++ b/gateway-client/src/lib.rs @@ -33,6 +33,11 @@ impl Client { self.modbus.write_single_coil(1, true).await } + /// Reset the gateway to factory defaults + pub async fn reset(&mut self) -> ModbusResult<()> { + self.modbus.write_single_coil(2, true).await + } + /// Get hardware version. pub async fn hardware_version(&mut self) -> ModbusResult { let version = self.modbus.read_holding_registers(1, 3).await?.unwrap(); diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 490d7a8..a2be9b3 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -1,5 +1,6 @@ mod config; mod manifest; +mod reset; mod restart; mod status; mod update; @@ -13,7 +14,9 @@ pub enum Commands { Status, /// Update firmware Update(UpdateOptions), - /// Command the device to restart + /// Reset all configuration + Reset, + /// Restart Restart, /// Read and write configuration Config(config::Cmd), @@ -37,6 +40,7 @@ impl Cmd { Commands::Update(options) => { update::command(output, options, self.ip).await } + Commands::Reset => reset::command(output, self.ip).await, Commands::Restart => restart::command(output, self.ip).await, Commands::Config(command) => command.run(output, self.ip).await, } diff --git a/src/gateway/reset.rs b/src/gateway/reset.rs new file mode 100644 index 0000000..3ee7268 --- /dev/null +++ b/src/gateway/reset.rs @@ -0,0 +1,18 @@ +use crate::write_with_header; +use colored::Colorize; +use std::{net::IpAddr, time::Duration}; +use tokio::time::timeout; + +#[allow(unused_variables, unused_mut)] +pub async fn command( + mut output: impl std::io::Write, + ip: IpAddr, +) -> anyhow::Result<()> { + let mut client = gateway_client::Client::connect(ip).await?; + + write_with_header(&mut output, "Resetting".green(), " "); + let _ = timeout(Duration::from_secs(1), client.reset()).await; + write_with_header(&mut output, "Done".green(), " "); + + Ok(()) +} From 4b04dadbfd55311ae7d752a2cf1fea8b54e29919 Mon Sep 17 00:00:00 2001 From: Liam Kinne Date: Fri, 30 Aug 2024 02:07:14 +1000 Subject: [PATCH 10/15] get serial number with status --- gateway-client/src/lib.rs | 29 ++++++++++++++++++++++++++++- src/gateway/status.rs | 6 ++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/gateway-client/src/lib.rs b/gateway-client/src/lib.rs index d560547..ed366cc 100644 --- a/gateway-client/src/lib.rs +++ b/gateway-client/src/lib.rs @@ -1,4 +1,7 @@ -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::{ + fmt::Display, + net::{IpAddr, Ipv4Addr, SocketAddr}, +}; use tokio_modbus::{ client::{tcp::connect, Reader, Writer}, slave::SlaveContext, @@ -58,6 +61,17 @@ impl Client { })) } + /// Get serial number. + pub async fn serial(&mut self) -> ModbusResult { + let serial = self.modbus.read_holding_registers(7, 2).await?.unwrap(); + + Ok(Ok(Serial { + year: serial[0].to_le_bytes()[1], + week: serial[0].to_le_bytes()[0], + seq: serial[1], + })) + } + /// Get DHCP enabled. pub async fn dhcp(&mut self) -> ModbusResult { let enabled = self.modbus.read_coils(1001, 1).await?.unwrap(); @@ -129,6 +143,19 @@ impl Client { } } +#[derive(Debug, Clone, Copy)] +pub struct Serial { + pub year: u8, + pub week: u8, + pub seq: u16, +} + +impl Display for Serial { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:02}{:02}-{:04X}", self.year, self.week, self.seq) + } +} + #[derive(Debug)] pub struct Version { pub major: u16, diff --git a/src/gateway/status.rs b/src/gateway/status.rs index 522fe3d..d0945d9 100644 --- a/src/gateway/status.rs +++ b/src/gateway/status.rs @@ -10,6 +10,12 @@ pub async fn command( let start = Instant::now(); + write_with_header( + &mut output, + "Serial".green(), + &format!("{}", client.serial().await??), + ); + write_with_header( &mut output, "Hardware Version".green(), From 0fc2d1279674a8aef2f26dfe3393433a94bad626 Mon Sep 17 00:00:00 2001 From: Liam Kinne Date: Sat, 7 Sep 2024 09:39:56 +1000 Subject: [PATCH 11/15] cleanup format use --- src/gateway/update.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/gateway/update.rs b/src/gateway/update.rs index a5f85f1..c49c208 100644 --- a/src/gateway/update.rs +++ b/src/gateway/update.rs @@ -128,11 +128,7 @@ pub async fn command( } }; - write_with_header( - &mut output, - "Version".green(), - &format!("{}", firmware.0), - ); + write_with_header(&mut output, "Version".green(), &firmware.0); let binary = reqwest::get(&firmware.1.file).await?.bytes().await?; From bc17a4532beef361570c106235bd8843ab061183 Mon Sep 17 00:00:00 2001 From: Liam Kinne Date: Sat, 7 Sep 2024 09:42:30 +1000 Subject: [PATCH 12/15] simplify magic number use --- gateway-client/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gateway-client/src/lib.rs b/gateway-client/src/lib.rs index ed366cc..030e277 100644 --- a/gateway-client/src/lib.rs +++ b/gateway-client/src/lib.rs @@ -184,8 +184,8 @@ pub enum DeviceIdentifier { impl From for DeviceIdentifier { fn from(value: u16) -> Self { match value { - x if x == 0x4644 => Self::CanFd, - x if x == 0x5253 => Self::Serial, + 0x4644 => Self::CanFd, + 0x5253 => Self::Serial, _ => Self::Unknown(value), } } From d4789f06efac8db6e22512a9e7db7619c3efddd7 Mon Sep 17 00:00:00 2001 From: Liam Kinne Date: Sat, 7 Sep 2024 09:42:40 +1000 Subject: [PATCH 13/15] allow unused fields --- src/gateway/manifest.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gateway/manifest.rs b/src/gateway/manifest.rs index 204e46e..9459154 100644 --- a/src/gateway/manifest.rs +++ b/src/gateway/manifest.rs @@ -5,6 +5,7 @@ use std::collections::HashMap; /// /// Only deserializes the `schema` field. #[derive(Debug, Deserialize)] +#[allow(unused)] pub struct ManifestSchema { pub schema: String, } @@ -25,6 +26,7 @@ pub struct ManifestSchema { /// } /// ``` #[derive(Debug, Deserialize)] +#[allow(unused)] pub struct Manifest { /// Schema version. pub schema: String, @@ -42,6 +44,7 @@ pub struct Manifest { /// so devices can step-up to the latest firmware without issues due to /// breaking changes. #[derive(Debug, Deserialize)] +#[allow(unused)] pub struct FirmwareBinary { /// File link. pub file: String, From 77b77ddecb4f72361741801aac396869f3d7c292 Mon Sep 17 00:00:00 2001 From: Liam Kinne Date: Sat, 7 Sep 2024 09:43:00 +1000 Subject: [PATCH 14/15] explicitly ignore return value --- src/gateway/update.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gateway/update.rs b/src/gateway/update.rs index c49c208..294e041 100644 --- a/src/gateway/update.rs +++ b/src/gateway/update.rs @@ -93,7 +93,7 @@ pub async fn command( let meta = file.metadata().await?; let mut contents = vec![0; meta.len() as usize]; - file.read(&mut contents).await?; + let _ = file.read(&mut contents).await?; upgrade_firmware(output, ip, &contents).await?; } else { From f23e0b5e2995b3848629cbdbef9edcab150961ce Mon Sep 17 00:00:00 2001 From: Liam Kinne Date: Sat, 7 Sep 2024 09:43:06 +1000 Subject: [PATCH 15/15] ref not required --- src/gateway/update.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gateway/update.rs b/src/gateway/update.rs index 294e041..c742a24 100644 --- a/src/gateway/update.rs +++ b/src/gateway/update.rs @@ -128,7 +128,7 @@ pub async fn command( } }; - write_with_header(&mut output, "Version".green(), &firmware.0); + write_with_header(&mut output, "Version".green(), firmware.0); let binary = reqwest::get(&firmware.1.file).await?.bytes().await?;