use crate::{Alert, ColorMode, CoordinateModifierType, Effect, ModifierType};
use serde::{de, de::Error, Deserialize, Serialize};
use std::fmt;
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct Light {
#[serde(skip_deserializing)]
pub id: String,
pub name: String,
#[serde(rename(deserialize = "type"))]
pub kind: String,
pub state: State,
#[serde(rename(deserialize = "modelid"))]
pub model_id: String,
#[serde(rename(deserialize = "uniqueid"))]
pub unique_id: String,
#[serde(rename(deserialize = "productid"))]
pub product_id: Option<String>,
#[serde(rename(deserialize = "productname"))]
pub product_name: Option<String>,
#[serde(rename(deserialize = "manufacturername"))]
pub manufacturer_name: Option<String>,
#[serde(rename(deserialize = "swversion"))]
pub software_version: String,
#[serde(rename(deserialize = "swupdate"))]
pub software_update: SoftwareUpdate,
pub config: Config,
pub capabilities: Capabilities,
}
impl Light {
pub(crate) fn with_id(self, id: &str) -> Self {
Self {
id: id.to_owned(),
..self
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
pub struct State {
pub on: Option<bool>,
#[serde(rename(deserialize = "bri"))]
pub brightness: Option<u8>,
pub hue: Option<u16>,
#[serde(rename(deserialize = "sat"))]
pub saturation: Option<u8>,
#[serde(rename(deserialize = "xy"))]
pub color_space_coordinates: Option<(f32, f32)>,
#[serde(rename(deserialize = "ct"))]
pub color_temperature: Option<u16>,
pub alert: Option<Alert>,
pub effect: Option<Effect>,
#[serde(rename(deserialize = "colormode"))]
pub color_mode: Option<ColorMode>,
pub reachable: bool,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct SoftwareUpdate {
pub state: SoftwareUpdateState,
#[serde(rename(deserialize = "lastinstall"))]
pub last_install: Option<chrono::NaiveDateTime>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all(deserialize = "lowercase"))]
pub enum SoftwareUpdateState {
NoUpdates,
NotUpdatable,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct Config {
#[serde(rename(deserialize = "archetype"))]
pub arche_type: String,
pub function: String,
pub direction: String,
pub startup: Option<StartupConfig>,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct StartupConfig {
#[serde(rename(deserialize = "archetype"))]
pub mode: String,
pub configured: bool,
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct Capabilities {
pub certified: bool,
pub control: ControlCapabilities,
pub streaming: StreamingCapabilities,
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct ControlCapabilities {
#[serde(rename(deserialize = "mindimlevel"))]
pub min_dimlevel: Option<usize>,
#[serde(rename(deserialize = "maxlumen"))]
pub max_lumen: Option<usize>,
#[serde(rename(deserialize = "colorgamut"))]
pub color_gamut: Option<Vec<(f32, f32)>>,
#[serde(rename(deserialize = "colorgamuttype"))]
pub color_gamut_type: Option<String>,
#[serde(rename(deserialize = "ct"))]
pub color_temperature: Option<ColorTemperatureCapabilities>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct ColorTemperatureCapabilities {
pub min: usize,
pub max: usize,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct StreamingCapabilities {
pub renderer: bool,
pub proxy: bool,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
pub struct AttributeModifier {
#[serde(skip_serializing_if = "Option::is_none")]
name: Option<String>,
}
impl AttributeModifier {
pub fn new() -> Self {
Self {
..Default::default()
}
}
pub fn is_empty(&self) -> bool {
self.name == None
}
pub fn name(self, value: &str) -> Self {
Self {
name: Some(value.to_owned()),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize)]
pub struct StateModifier {
#[serde(skip_serializing_if = "Option::is_none")]
on: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none", rename(deserialize = "bri"))]
brightness: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
hue: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none", rename(deserialize = "sat"))]
saturation: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none", rename(deserialize = "xy"))]
color_space_coordinates: Option<(f32, f32)>,
#[serde(skip_serializing_if = "Option::is_none", rename(deserialize = "ct"))]
color_temperature: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
alert: Option<Alert>,
#[serde(skip_serializing_if = "Option::is_none")]
effect: Option<Effect>,
#[serde(
skip_serializing_if = "Option::is_none",
rename(deserialize = "transitiontime")
)]
transition_time: Option<u16>,
#[serde(
skip_serializing_if = "Option::is_none",
rename(deserialize = "bri_inc")
)]
brightness_increment: Option<i16>,
#[serde(
skip_serializing_if = "Option::is_none",
rename(deserialize = "hue_inc")
)]
hue_increment: Option<i32>,
#[serde(
skip_serializing_if = "Option::is_none",
rename(deserialize = "sat_inc")
)]
saturation_increment: Option<i16>,
#[serde(
skip_serializing_if = "Option::is_none",
rename(deserialize = "xy_inc")
)]
color_space_coordinates_increment: Option<(f32, f32)>,
#[serde(
skip_serializing_if = "Option::is_none",
rename(deserialize = "ct_inc")
)]
color_temperature_increment: Option<i32>,
}
impl StateModifier {
pub fn new() -> Self {
Self {
..Default::default()
}
}
pub fn is_empty(&self) -> bool {
self.on == None
&& self.brightness == None
&& self.hue == None
&& self.saturation == None
&& self.color_space_coordinates == None
&& self.color_temperature == None
&& self.alert == None
&& self.effect == None
&& self.transition_time == None
&& self.brightness_increment == None
&& self.hue_increment == None
&& self.saturation_increment == None
&& self.color_space_coordinates_increment == None
&& self.color_temperature_increment == None
}
pub fn on(self, value: bool) -> Self {
Self {
on: Some(value),
..self
}
}
pub fn brightness(self, modifier_type: ModifierType, value: u8) -> Self {
match modifier_type {
ModifierType::Override => Self {
brightness: Some(value),
..self
},
ModifierType::Increment => Self {
brightness_increment: Some(value as i16),
..self
},
ModifierType::Decrement => Self {
brightness_increment: Some(-(value as i16)),
..self
},
}
}
pub fn hue(self, modifier_type: ModifierType, value: u16) -> Self {
match modifier_type {
ModifierType::Override => Self {
hue: Some(value),
..self
},
ModifierType::Increment => Self {
hue_increment: Some(value as i32),
..self
},
ModifierType::Decrement => Self {
hue_increment: Some(-(value as i32)),
..self
},
}
}
pub fn saturation(self, modifier_type: ModifierType, value: u8) -> Self {
match modifier_type {
ModifierType::Override => Self {
saturation: Some(value),
..self
},
ModifierType::Increment => Self {
saturation_increment: Some(value as i16),
..self
},
ModifierType::Decrement => Self {
saturation_increment: Some(-(value as i16)),
..self
},
}
}
pub fn color_space_coordinates(
self,
modifier_type: CoordinateModifierType,
value: (f32, f32),
) -> Self {
match modifier_type {
CoordinateModifierType::Override => Self {
color_space_coordinates: Some(value),
..self
},
CoordinateModifierType::Increment => Self {
color_space_coordinates_increment: Some(value),
..self
},
CoordinateModifierType::Decrement => Self {
color_space_coordinates_increment: Some((-value.0, -value.1)),
..self
},
CoordinateModifierType::IncrementDecrement => Self {
color_space_coordinates_increment: Some((value.0, -value.1)),
..self
},
CoordinateModifierType::DecrementIncrement => Self {
color_space_coordinates_increment: Some((-value.0, value.1)),
..self
},
}
}
pub fn color_temperature(self, modifier_type: ModifierType, value: u16) -> Self {
match modifier_type {
ModifierType::Override => Self {
color_temperature: Some(value),
..self
},
ModifierType::Increment => Self {
color_temperature_increment: Some(value as i32),
..self
},
ModifierType::Decrement => Self {
color_temperature_increment: Some(-(value as i32)),
..self
},
}
}
pub fn alert(self, value: Alert) -> Self {
Self {
alert: Some(value),
..self
}
}
pub fn effect(self, value: Effect) -> Self {
Self {
effect: Some(value),
..self
}
}
pub fn transition_time(self, value: u16) -> Self {
Self {
transition_time: Some(value),
..self
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Scan {
pub last_scan: LastScan,
pub lights: Vec<ScanLight>,
}
impl<'de> Deserialize<'de> for Scan {
fn deserialize<D: de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
enum Field {
LastScan,
LightId(String),
}
impl<'de> Deserialize<'de> for Field {
fn deserialize<D: de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let value: String = Deserialize::deserialize(deserializer)?;
Ok(match value.as_ref() {
"lastscan" => Field::LastScan,
v => Field::LightId(v.to_owned()),
})
}
}
struct ScanVisitor;
impl<'de> de::Visitor<'de> for ScanVisitor {
type Value = Scan;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("struct LightScan")
}
fn visit_map<V: de::MapAccess<'de>>(self, mut map: V) -> Result<Scan, V::Error> {
let mut lights = Vec::new();
let mut last_scan = None;
while let Some(key) = map.next_key()? {
match key {
Field::LastScan => {
last_scan = serde_json::from_value(map.next_value()?)
.map_err(V::Error::custom)?
}
Field::LightId(v) => {
let light = ScanLight {
id: v,
name: map.next_value()?,
};
lights.push(light);
}
}
}
let last_scan = last_scan.ok_or_else(|| de::Error::missing_field("lastscan"))?;
Ok(Scan { lights, last_scan })
}
}
const FIELDS: &[&str] = &["lastscan", "lights"];
deserializer.deserialize_struct("LightScan", FIELDS, ScanVisitor)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum LastScan {
DateTime(chrono::NaiveDateTime),
Active,
None,
}
impl<'de> Deserialize<'de> for LastScan {
fn deserialize<D: de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let value: String = Deserialize::deserialize(deserializer)?;
Ok(match value.as_ref() {
"active" => LastScan::Active,
"none" => LastScan::None,
v => LastScan::DateTime(
chrono::NaiveDateTime::parse_from_str(v, "%Y-%m-%dT%H:%M:%S")
.map_err(D::Error::custom)?,
),
})
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ScanLight {
pub id: String,
pub name: String,
}