use serde::{Deserialize, Serialize};
use crate::battery_percentage;
#[derive(Debug, Clone, Default)]
pub struct DeviceInfo {
pub mac_address: String,
pub battery_voltage: Option<f32>,
pub firmware_version: Option<String>,
pub rssi: Option<i32>,
pub refresh_rate: Option<u32>,
}
impl DeviceInfo {
pub fn new(mac_address: impl Into<String>) -> Self {
Self {
mac_address: mac_address.into(),
..Default::default()
}
}
#[must_use]
pub fn with_battery_voltage(mut self, voltage: f32) -> Self {
self.battery_voltage = Some(voltage);
self
}
#[must_use]
pub fn with_firmware_version(mut self, version: impl Into<String>) -> Self {
self.firmware_version = Some(version.into());
self
}
#[must_use]
pub fn with_rssi(mut self, rssi: i32) -> Self {
self.rssi = Some(rssi);
self
}
#[must_use]
pub fn with_refresh_rate(mut self, rate: u32) -> Self {
self.refresh_rate = Some(rate);
self
}
pub fn battery_voltage_mv(&self) -> Option<u32> {
self.battery_voltage.map(|v| (v * 1000.0) as u32)
}
pub fn battery_percentage(&self) -> Option<u8> {
self.battery_voltage_mv().map(battery_percentage)
}
pub fn short_id(&self) -> &str {
let len = self.mac_address.len();
if len >= 4 {
&self.mac_address[len - 4..]
} else {
&self.mac_address
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DisplayResponse {
pub status: u32,
pub image_url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub filename: Option<String>,
pub update_firmware: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub firmware_url: Option<String>,
pub refresh_rate: String,
pub reset_firmware: bool,
}
impl DisplayResponse {
pub fn new(image_url: impl Into<String>, filename: impl Into<String>) -> Self {
Self {
status: 0,
image_url: image_url.into(),
filename: Some(filename.into()),
update_firmware: false,
firmware_url: None,
refresh_rate: "60".to_string(),
reset_firmware: false,
}
}
#[must_use]
pub fn with_refresh_rate(mut self, seconds: u32) -> Self {
self.refresh_rate = seconds.to_string();
self
}
#[must_use]
pub fn with_firmware_update(mut self, firmware_url: impl Into<String>) -> Self {
self.update_firmware = true;
self.firmware_url = Some(firmware_url.into());
self
}
#[must_use]
pub fn with_reset(mut self) -> Self {
self.reset_firmware = true;
self
}
pub fn error() -> Self {
Self {
status: 1,
image_url: String::new(),
filename: None,
update_firmware: false,
firmware_url: None,
refresh_rate: "300".to_string(), reset_firmware: false,
}
}
}
impl Default for DisplayResponse {
fn default() -> Self {
Self {
status: 0,
image_url: String::new(),
filename: None,
update_firmware: false,
firmware_url: None,
refresh_rate: "60".to_string(),
reset_firmware: false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SetupResponse {
pub api_key: String,
pub friendly_id: String,
pub image_url: String,
pub message: String,
}
impl SetupResponse {
pub fn new(
friendly_id: impl Into<String>,
image_url: impl Into<String>,
message: impl Into<String>,
) -> Self {
Self {
api_key: "byos".to_string(),
friendly_id: friendly_id.into(),
image_url: image_url.into(),
message: message.into(),
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LogEntry {
#[serde(default)]
pub log_message: Option<String>,
#[serde(default)]
pub device_status_stamp: Option<DeviceStatusStamp>,
#[serde(flatten)]
pub extra: std::collections::HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct DeviceStatusStamp {
#[serde(default)]
pub battery_voltage: Option<f32>,
#[serde(default)]
pub wifi_rssi_level: Option<i32>,
#[serde(default)]
pub refresh_rate: Option<u32>,
#[serde(default)]
pub current_fw_version: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogResponse {
pub status: String,
}
impl Default for LogResponse {
fn default() -> Self {
Self {
status: "ok".to_string(),
}
}
}
impl LogResponse {
pub fn ok() -> Self {
Self::default()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_device_info() {
let device = DeviceInfo::new("AA:BB:CC:DD:EE:FF")
.with_battery_voltage(4.2)
.with_firmware_version("1.0.0")
.with_rssi(-50);
assert_eq!(device.mac_address, "AA:BB:CC:DD:EE:FF");
assert_eq!(device.battery_voltage, Some(4.2));
assert_eq!(device.battery_voltage_mv(), Some(4200));
assert_eq!(device.battery_percentage(), Some(100));
assert_eq!(device.short_id(), "E:FF");
}
#[test]
fn test_display_response_serialization() {
let response = DisplayResponse::new("https://example.com/screen.png", "screen.png")
.with_refresh_rate(120);
let json = serde_json::to_string(&response).unwrap();
assert!(json.contains("\"status\":0"));
assert!(json.contains("\"refresh_rate\":\"120\""));
assert!(json.contains("\"filename\":\"screen.png\""));
assert!(!json.contains("firmware_url"));
}
#[test]
fn test_setup_response() {
let response = SetupResponse::new("my-device", "https://example.com/setup.png", "Welcome!");
let json = serde_json::to_string(&response).unwrap();
assert!(json.contains("\"api_key\":\"byos\""));
assert!(json.contains("\"friendly_id\":\"my-device\""));
}
#[test]
fn test_log_entry_parsing() {
let json = r#"{"logMessage": "test", "deviceStatusStamp": {"battery_voltage": 4.1}}"#;
let entry: LogEntry = serde_json::from_str(json).unwrap();
assert_eq!(entry.log_message, Some("test".to_string()));
assert_eq!(
entry.device_status_stamp.as_ref().unwrap().battery_voltage,
Some(4.1)
);
}
}