Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gateway configuration #1

Merged
merged 15 commits into from
Sep 6, 2024
38 changes: 33 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ version = "0.0.5"
edition = "2021"

[dependencies]
gateway-client = { path = "gateway-client" }
hftwo = "0.1.2"
uftwo = "0.1.0"
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"] }
Expand All @@ -26,6 +26,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)
Expand Down
7 changes: 7 additions & 0 deletions gateway-client/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "gateway-client"
version = "0.1.0"
edition = "2021"

[dependencies]
tokio-modbus = "0.14.0"
192 changes: 192 additions & 0 deletions gateway-client/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
use std::{
fmt::Display,
net::{IpAddr, Ipv4Addr, SocketAddr},
};
use tokio_modbus::{
client::{tcp::connect, Reader, Writer},
slave::SlaveContext,
Result as ModbusResult, Slave,
};

#[derive(Debug)]
pub struct Client {
modbus: tokio_modbus::client::Context,
}

impl Client {
pub async fn connect(ip: IpAddr) -> std::io::Result<Self> {
let mut modbus = connect(SocketAddr::new(ip, 502)).await?;
modbus.set_slave(Slave(1));

Ok(Self { modbus })
}

/// Get device identifier.
pub async fn device_identifier(
&mut self,
) -> ModbusResult<DeviceIdentifier> {
self.modbus
.read_holding_registers(0, 1)
.await
.map(|v| v.map(|v| v[0]).map(DeviceIdentifier::from))
}

/// Restart the gateway gracefully
pub async fn restart(&mut self) -> ModbusResult<()> {
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<Version> {
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) -> ModbusResult<Version> {
let version = self.modbus.read_holding_registers(4, 3).await?.unwrap();
Ok(Ok(Version {
major: version[0],
minor: version[1],
patch: version[2],
}))
}

/// Get serial number.
pub async fn serial(&mut self) -> ModbusResult<Serial> {
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<bool> {
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) -> ModbusResult<()> {
self.modbus.write_single_coil(1001, enabled).await
}

/// Get the configured IPv4 address.
pub async fn ipv4_address(&mut self) -> ModbusResult<Ipv4Addr> {
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) -> 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) -> ModbusResult<u16> {
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) -> ModbusResult<u16> {
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) -> ModbusResult<u32> {
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,
) -> 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) -> ModbusResult<u32> {
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,
) -> ModbusResult<()> {
let rate = (rate / 100) as u16;
self.modbus.write_single_register(2001, rate).await
}
}

#[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,
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)
}
}

/// 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,
/// "RS" like RS-232/RS-485
Serial,
/// Unkown but possibly valid identifier.
Unknown(u16),
}

impl From<u16> for DeviceIdentifier {
fn from(value: u16) -> Self {
match value {
0x4644 => Self::CanFd,
0x5253 => Self::Serial,
_ => Self::Unknown(value),
}
}
}
Loading
Loading