use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PowerAction {
On,
Off,
Toggle,
}
impl PowerAction {
pub fn as_str(&self) -> &'static str {
match self {
PowerAction::On => "on",
PowerAction::Off => "off",
PowerAction::Toggle => "toggle",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum RelayState {
On,
Off,
Unknown(String),
}
impl RelayState {
pub fn as_str(&self) -> &str {
match self {
RelayState::On => "on",
RelayState::Off => "off",
RelayState::Unknown(raw) => raw,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Relay {
pub index: u8,
pub state: RelayState,
pub raw: String,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct Energy {
pub power_w: Option<f64>,
pub today_kwh: Option<f64>,
pub total_kwh: Option<f64>,
pub voltage_v: Option<f64>,
pub current_a: Option<f64>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Firmware {
pub version: Option<String>,
pub update_available: Option<bool>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct NetInfo {
pub ip: Option<String>,
pub mac: Option<String>,
pub hostname: Option<String>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Signal {
pub quality_percent: Option<u8>,
pub rssi_dbm: Option<i64>,
}
impl Signal {
pub fn from_quality_percent(pct: i64) -> Self {
Signal {
quality_percent: Some(pct.clamp(0, 100) as u8),
rssi_dbm: None,
}
}
pub fn from_dbm(dbm: i64) -> Self {
Signal {
quality_percent: None,
rssi_dbm: Some(dbm),
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Capabilities {
pub metering: bool,
pub multi_channel: bool,
pub firmware_ota: bool,
pub config_backup: bool,
pub console: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct DeviceSnapshot {
pub host: String,
pub name: Option<String>,
pub model: Option<String>,
pub generation: Option<String>,
pub capabilities: Capabilities,
pub relays: Vec<Relay>,
pub energy: Option<Energy>,
pub signal: Option<Signal>,
pub temperature_c: Option<f64>,
pub firmware: Option<Firmware>,
pub net: NetInfo,
pub uptime: Option<String>,
}
impl DeviceSnapshot {
pub fn display_name(&self) -> &str {
match &self.name {
Some(n) if !n.is_empty() => n,
_ => &self.host,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn relay_state_unknown_preserves_raw_text() {
let state = RelayState::Unknown("x".to_string());
assert_eq!(state.as_str(), "x");
}
#[test]
fn signal_from_quality_percent_clamps_and_leaves_dbm_none() {
let signal = Signal::from_quality_percent(150);
assert_eq!(signal.quality_percent, Some(100));
assert_eq!(signal.rssi_dbm, None);
}
#[test]
fn signal_from_dbm_leaves_quality_percent_none() {
let signal = Signal::from_dbm(-55);
assert_eq!(signal.rssi_dbm, Some(-55));
assert_eq!(signal.quality_percent, None);
}
#[test]
fn energy_default_is_all_none() {
let energy = Energy::default();
assert_eq!(energy.power_w, None);
assert_eq!(energy.today_kwh, None);
assert_eq!(energy.total_kwh, None);
assert_eq!(energy.voltage_v, None);
assert_eq!(energy.current_a, None);
}
#[test]
fn device_snapshot_display_name_falls_back_to_host() {
let snapshot = DeviceSnapshot {
host: "192.0.2.10".to_string(),
..Default::default()
};
assert_eq!(snapshot.display_name(), "192.0.2.10");
}
#[test]
fn device_snapshot_display_name_prefers_name() {
let snapshot = DeviceSnapshot {
host: "192.0.2.10".to_string(),
name: Some("Freezer".to_string()),
..Default::default()
};
assert_eq!(snapshot.display_name(), "Freezer");
}
#[test]
fn device_snapshot_default_telemetry_is_absent() {
let snapshot = DeviceSnapshot::default();
assert!(snapshot.energy.is_none());
assert!(snapshot.signal.is_none());
assert!(snapshot.temperature_c.is_none());
assert!(snapshot.firmware.is_none());
assert!(snapshot.uptime.is_none());
assert!(snapshot.name.is_none());
assert!(snapshot.model.is_none());
assert!(snapshot.generation.is_none());
assert!(snapshot.relays.is_empty());
assert_eq!(snapshot.capabilities, Capabilities::default());
assert_eq!(snapshot.net, NetInfo::default());
}
}