use serde::{Deserialize, Serialize};
use std::{fmt, time::SystemTime};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum SpeedUnit {
Kilometers,
Miles,
}
impl fmt::Display for SpeedUnit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Kilometers => write!(f, "km/h"),
Self::Miles => write!(f, "mph"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DeviceMode {
Awake = 0,
Active = 1,
Idle = 2,
Unknown = 3,
}
impl From<u8> for DeviceMode {
fn from(value: u8) -> Self {
match value {
0 => Self::Awake,
1 => Self::Active,
2 => Self::Idle,
_ => Self::Unknown,
}
}
}
impl fmt::Display for DeviceMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Awake => write!(f, "Awake"),
Self::Active => write!(f, "Active"),
Self::Idle => write!(f, "Idle"),
Self::Unknown => write!(f, "Unknown"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum EmergencyStopState {
Normal,
Active,
ResetRequired,
}
impl fmt::Display for EmergencyStopState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Normal => write!(f, "Normal"),
Self::Active => write!(f, "Emergency Stop Active"),
Self::ResetRequired => write!(f, "Reset Required"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AuthenticationStatus {
NotAuthenticated,
InProgress,
Authenticated,
Failed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TemperatureStatus {
Normal = 0,
Stop = 1,
ReduceSpeed = 2,
Unknown = 3,
}
impl From<u8> for TemperatureStatus {
fn from(value: u8) -> Self {
match value {
0 => Self::Normal,
1 => Self::Stop,
2 => Self::ReduceSpeed,
_ => Self::Unknown,
}
}
}
impl fmt::Display for TemperatureStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Normal => write!(f, "Normal"),
Self::Stop => write!(f, "Emergency Stop - Critical Temperature"),
Self::ReduceSpeed => write!(f, "Warning - High Temperature"),
Self::Unknown => write!(f, "Temperature Sensor Error"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DeviceStatusCode {
NoError = 0,
HighTemperature = 1,
WifiScanning = 2,
PowerCycleRequired = 3,
RequestError = 4,
WifiNotConnected = 5,
WifiError = 6,
}
impl From<u8> for DeviceStatusCode {
fn from(value: u8) -> Self {
match value {
0 => Self::NoError,
1 => Self::HighTemperature,
2 => Self::WifiScanning,
3 => Self::PowerCycleRequired,
5 => Self::WifiNotConnected,
6 => Self::WifiError,
_ => Self::RequestError,
}
}
}
impl fmt::Display for DeviceStatusCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoError => write!(f, "No Error"),
Self::HighTemperature => write!(f, "High Temperature"),
Self::WifiScanning => write!(f, "WiFi Scanning"),
Self::PowerCycleRequired => write!(f, "Power Cycle Required"),
Self::RequestError => write!(f, "Request Error"),
Self::WifiNotConnected => write!(f, "WiFi Not Connected"),
Self::WifiError => write!(f, "WiFi Error"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ConnectionHealth {
Healthy,
Degraded,
Unstable,
Lost,
}
impl fmt::Display for ConnectionHealth {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Healthy => write!(f, "Healthy"),
Self::Degraded => write!(f, "Degraded"),
Self::Unstable => write!(f, "Unstable"),
Self::Lost => write!(f, "Lost"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct SpeedInfo {
pub current: f32,
pub target: f32,
pub minimum: f32,
pub maximum: f32,
pub unit: SpeedUnit,
}
impl SpeedInfo {
#[must_use]
pub const fn new(current: f32, target: f32, min: f32, max: f32, unit: SpeedUnit) -> Self {
Self {
current,
target,
minimum: min,
maximum: max,
unit,
}
}
#[must_use]
pub fn convert_to(&self, unit: SpeedUnit) -> Self {
if self.unit == unit {
return *self;
}
let (current, target, min, max) = match (self.unit, unit) {
(SpeedUnit::Kilometers, SpeedUnit::Miles) => (
self.current * 0.6214,
self.target * 0.6214,
self.minimum * 0.6214,
self.maximum * 0.6214,
),
(SpeedUnit::Miles, SpeedUnit::Kilometers) => (
self.current * 1.6093,
self.target * 1.6093,
self.minimum * 1.6093,
self.maximum * 1.6093,
),
_ => (self.current, self.target, self.minimum, self.maximum),
};
Self {
current,
target,
minimum: min,
maximum: max,
unit,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DeviceStatus {
pub speed: SpeedInfo,
pub mode: DeviceMode,
pub emergency_stop: EmergencyStopState,
pub power_on: bool,
pub handrail_enabled: bool,
pub distance: f32,
pub steps: u32,
pub temperature: f32,
pub temperature_status: TemperatureStatus,
pub device_status_code: DeviceStatusCode,
pub connection_health: ConnectionHealth,
pub authentication: AuthenticationStatus,
pub session_active: bool,
pub timestamp: SystemTime,
}
impl Default for DeviceStatus {
fn default() -> Self {
Self {
speed: SpeedInfo::new(0.0, 0.0, 0.0, 20.0, SpeedUnit::Kilometers),
mode: DeviceMode::Unknown,
emergency_stop: EmergencyStopState::Normal,
power_on: false,
handrail_enabled: true,
distance: 0.0,
steps: 0,
temperature: 0.0,
temperature_status: TemperatureStatus::Normal,
device_status_code: DeviceStatusCode::NoError,
connection_health: ConnectionHealth::Healthy,
authentication: AuthenticationStatus::NotAuthenticated,
session_active: false,
timestamp: SystemTime::now(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DeviceInfo {
pub name: String,
pub mac_address: Option<String>,
pub rssi: i16,
pub priority: i8,
pub firmware_version: Option<String>,
pub hardware_version: Option<String>,
pub serial_number: Option<String>,
}
impl DeviceInfo {
#[must_use]
pub const fn new(name: String, rssi: i16, priority: i8) -> Self {
Self {
name,
mac_address: None,
rssi,
priority,
firmware_version: None,
hardware_version: None,
serial_number: None,
}
}
}
#[derive(Debug, Clone)]
pub struct ConnectionParams {
pub timeout_ms: u64,
pub authenticate: bool,
pub retry_attempts: u32,
pub scan_timeout_ms: u64,
}
#[derive(Debug, Clone)]
pub struct TimeoutConfig {
pub default_timeout_ms: u64,
pub auth_timeout_ms: u64,
pub status_timeout_ms: u64,
pub emergency_stop_timeout_ms: u64,
pub speed_command_timeout_ms: u64,
pub power_command_timeout_ms: u64,
pub secure_auth_timeout_ms: u64,
pub connection_health_timeout_ms: u64,
pub max_retry_attempts: u32,
pub retry_delay_ms: u64,
}
impl Default for ConnectionParams {
fn default() -> Self {
Self {
timeout_ms: 30_000,
authenticate: true,
retry_attempts: 3,
scan_timeout_ms: 10_000,
}
}
}
impl Default for TimeoutConfig {
fn default() -> Self {
Self {
default_timeout_ms: 3_000,
auth_timeout_ms: 5_000,
status_timeout_ms: 2_000,
emergency_stop_timeout_ms: 5_000,
speed_command_timeout_ms: 3_000,
power_command_timeout_ms: 4_000,
secure_auth_timeout_ms: 8_000,
connection_health_timeout_ms: 30_000,
max_retry_attempts: 3,
retry_delay_ms: 500,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_speed_conversion() {
let speed_km = SpeedInfo::new(10.0, 12.0, 0.0, 20.0, SpeedUnit::Kilometers);
let speed_mph = speed_km.convert_to(SpeedUnit::Miles);
assert!((speed_mph.current - 6.214).abs() < 0.01);
assert_eq!(speed_mph.unit, SpeedUnit::Miles);
}
#[test]
fn test_device_mode_from_u8() {
assert_eq!(DeviceMode::from(0), DeviceMode::Awake);
assert_eq!(DeviceMode::from(1), DeviceMode::Active);
assert_eq!(DeviceMode::from(2), DeviceMode::Idle);
assert_eq!(DeviceMode::from(99), DeviceMode::Unknown);
}
#[test]
fn test_timeout_config_defaults() {
let config = TimeoutConfig::default();
assert_eq!(config.default_timeout_ms, 3_000);
assert_eq!(config.auth_timeout_ms, 5_000);
assert_eq!(config.status_timeout_ms, 2_000);
assert_eq!(config.emergency_stop_timeout_ms, 5_000);
assert_eq!(config.speed_command_timeout_ms, 3_000);
assert_eq!(config.power_command_timeout_ms, 4_000);
assert_eq!(config.secure_auth_timeout_ms, 8_000);
assert_eq!(config.connection_health_timeout_ms, 30_000);
assert_eq!(config.max_retry_attempts, 3);
assert_eq!(config.retry_delay_ms, 500);
}
#[test]
fn test_speed_unit_conversion() {
let speed_km = SpeedInfo::new(10.0, 12.0, 0.0, 20.0, SpeedUnit::Kilometers);
let speed_mph = speed_km.convert_to(SpeedUnit::Miles);
assert!((speed_mph.current - 6.214).abs() < 0.01);
assert!((speed_mph.target - 7.457).abs() < 0.01);
assert_eq!(speed_mph.unit, SpeedUnit::Miles);
let speed_km_back = speed_mph.convert_to(SpeedUnit::Kilometers);
assert!((speed_km_back.current - 10.0).abs() < 0.01);
assert!((speed_km_back.target - 12.0).abs() < 0.01);
}
#[test]
fn test_device_info_creation() {
let info = DeviceInfo::new("Test Treadmill".to_string(), -50, 1);
assert_eq!(info.name, "Test Treadmill");
assert_eq!(info.rssi, -50);
assert_eq!(info.priority, 1);
assert!(info.mac_address.is_none());
}
#[test]
fn test_connection_params_default() {
let params = ConnectionParams::default();
assert_eq!(params.timeout_ms, 30_000);
assert!(params.authenticate);
assert_eq!(params.retry_attempts, 3);
assert_eq!(params.scan_timeout_ms, 10_000);
}
}