#![allow(clippy::needless_update)]
use crate::resource::{self, Adjust, Alert, ColorMode, Effect};
use crate::Color;
use derive_setters::Setters;
use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer};
#[derive(Clone, Debug, PartialEq, Deserialize)]
pub struct Light {
#[serde(skip)]
pub id: String,
pub name: String,
#[serde(rename = "type")]
pub kind: String,
pub state: State,
#[serde(rename = "modelid")]
pub model_id: String,
#[serde(rename = "uniqueid")]
pub unique_id: String,
#[serde(rename = "productid")]
pub product_id: Option<String>,
#[serde(rename = "productname")]
pub product_name: Option<String>,
#[serde(rename = "manufacturername")]
pub manufacturer_name: Option<String>,
#[serde(rename = "swversion")]
pub software_version: String,
#[serde(rename = "swupdate")]
pub software_update: SoftwareUpdate,
pub config: Config,
pub capabilities: Capabilities,
}
impl Light {
pub(crate) fn with_id(self, id: String) -> Self {
Self { id, ..self }
}
}
impl resource::Resource for Light {}
#[derive(Clone, Debug, PartialEq, Deserialize)]
pub struct State {
pub on: Option<bool>,
#[serde(rename = "bri")]
pub brightness: Option<u8>,
pub hue: Option<u16>,
#[serde(rename = "sat")]
pub saturation: Option<u8>,
#[serde(rename = "xy")]
pub color_space_coordinates: Option<(f32, f32)>,
#[serde(rename = "ct")]
pub color_temperature: Option<u16>,
pub alert: Option<Alert>,
pub effect: Option<Effect>,
#[serde(rename = "colormode")]
pub color_mode: Option<ColorMode>,
pub reachable: bool,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
pub struct SoftwareUpdate {
pub state: SoftwareUpdateState,
#[serde(rename = "lastinstall")]
pub last_install: Option<chrono::NaiveDateTime>,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SoftwareUpdateState {
NoUpdates,
NotUpdatable,
Transferring,
ReadyToInstall,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
pub struct Config {
#[serde(rename = "archetype")]
pub arche_type: String,
pub function: String,
pub direction: String,
pub startup: Option<StartupConfig>,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
pub struct StartupConfig {
pub mode: String,
pub configured: bool,
}
#[derive(Clone, Debug, PartialEq, Deserialize)]
pub struct Capabilities {
pub certified: bool,
pub control: ControlCapabilities,
pub streaming: StreamingCapabilities,
}
#[derive(Clone, Debug, PartialEq, Deserialize)]
pub struct ControlCapabilities {
#[serde(rename = "mindimlevel")]
pub min_dimlevel: Option<usize>,
#[serde(rename = "maxlumen")]
pub max_lumen: Option<usize>,
#[serde(rename = "colorgamut")]
pub color_gamut: Option<Vec<(f32, f32)>>,
#[serde(rename = "colorgamuttype")]
pub color_gamut_type: Option<String>,
#[serde(rename = "ct")]
pub color_temperature: Option<ColorTemperatureCapabilities>,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
pub struct ColorTemperatureCapabilities {
pub min: usize,
pub max: usize,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
pub struct StreamingCapabilities {
pub renderer: bool,
pub proxy: bool,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Setters)]
#[setters(strip_option, prefix = "with_")]
pub struct AttributeModifier {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
impl AttributeModifier {
pub fn new() -> Self {
Self::default()
}
}
impl resource::Modifier for AttributeModifier {
type Id = String;
fn url_suffix(id: Self::Id) -> String {
format!("lights/{}", id)
}
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Setters)]
#[setters(strip_option, prefix = "with_")]
pub struct StaticStateModifier {
#[serde(skip_serializing_if = "Option::is_none")]
pub on: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none", rename = "bri")]
pub brightness: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hue: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none", rename = "sat")]
pub saturation: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none", rename = "xy")]
pub color_space_coordinates: Option<(f32, f32)>,
#[serde(skip_serializing_if = "Option::is_none", rename = "ct")]
pub color_temperature: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub effect: Option<Effect>,
#[serde(skip_serializing_if = "Option::is_none", rename = "transitiontime")]
pub transition_time: Option<u16>,
}
impl StaticStateModifier {
pub fn new() -> Self {
Self::default()
}
pub fn with_color(self, value: Color) -> Self {
let mut modifier = Self {
color_space_coordinates: Some(value.space_coordinates),
..self
};
if let Some(brightness) = value.brightness {
modifier.brightness = Some(brightness);
}
modifier
}
}
impl resource::Modifier for StaticStateModifier {
type Id = String;
fn url_suffix(id: Self::Id) -> String {
format!("lights/{}/state", id)
}
}
#[derive(Clone, Debug, Default, PartialEq, Setters)]
#[setters(strip_option, prefix = "with_")]
pub struct StateModifier {
pub on: Option<bool>,
pub brightness: Option<Adjust<u8>>,
pub hue: Option<Adjust<u16>>,
pub saturation: Option<Adjust<u8>>,
pub color_space_coordinates: Option<Adjust<(f32, f32)>>,
pub color_temperature: Option<Adjust<u16>>,
pub alert: Option<Alert>,
pub effect: Option<Effect>,
pub transition_time: Option<u16>,
}
impl StateModifier {
pub fn new() -> Self {
Self::default()
}
pub fn with_color(self, value: Color) -> Self {
let mut modifier = Self {
color_space_coordinates: Some(Adjust::Override(value.space_coordinates)),
..self
};
if let Some(brightness) = value.brightness {
modifier.brightness = Some(Adjust::Override(brightness));
}
modifier
}
}
impl resource::Modifier for StateModifier {
type Id = String;
fn url_suffix(id: Self::Id) -> String {
format!("lights/{}/state", id)
}
}
impl Serialize for StateModifier {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
custom_serialize! {
serializer, "StateModifier";
on => (&self.on),
bri => (&self.brightness, to_override),
bri_inc => (&self.brightness, to_increment, i16),
hue => (&self.hue, to_override),
hue_inc => (&self.hue, to_increment, i32),
sat => (&self.saturation, to_override),
sat_inc => (&self.saturation, to_increment, i16),
xy => (&self.color_space_coordinates, to_override),
xy_inc => (&self.color_space_coordinates, to_increment_tuple, f32),
ct => (&self.color_temperature, to_override),
ct_inc => (&self.color_temperature, to_increment, i32),
alert => (&self.alert),
effect => (&self.effect),
transitiontime => (&self.transition_time),
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Setters)]
#[setters(strip_option, prefix = "with_")]
pub struct Scanner {
#[serde(skip_serializing_if = "Option::is_none", rename = "deviceid")]
pub device_ids: Option<Vec<String>>,
}
impl Scanner {
pub fn new() -> Self {
Self::default()
}
}
impl resource::Scanner for Scanner {
fn url_suffix() -> String {
"lights".to_owned()
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn serialize_attribute_modifier() {
let modifier = AttributeModifier::new();
let modifier_json = serde_json::to_value(modifier).unwrap();
let expected_json = json!({});
assert_eq!(modifier_json, expected_json);
let modifier = AttributeModifier {
name: Some("test".into()),
};
let modifier_json = serde_json::to_value(modifier).unwrap();
let expected_json = json!({"name": "test"});
assert_eq!(modifier_json, expected_json);
}
#[test]
fn serialize_static_state_modifier() {
let modifier = StaticStateModifier::new();
let modifier_json = serde_json::to_value(modifier).unwrap();
let expected_json = json!({});
assert_eq!(modifier_json, expected_json);
let modifier = StaticStateModifier {
on: Some(true),
brightness: Some(1),
hue: Some(2),
saturation: Some(3),
color_space_coordinates: None,
color_temperature: Some(4),
effect: Some(Effect::Colorloop),
transition_time: Some(4),
};
let modifier_json = serde_json::to_value(modifier).unwrap();
let expected_json = json!({
"on": true,
"bri": 1,
"hue": 2,
"sat": 3,
"ct": 4,
"effect": "colorloop",
"transitiontime": 4,
});
assert_eq!(modifier_json, expected_json);
let modifier = StaticStateModifier::new()
.with_brightness(1)
.with_color(Color::from_rgb(0, 0, 0));
let modifier_json = serde_json::to_value(modifier).unwrap();
let expected_json = json!({
"bri": 0,
"xy": [0.0, 0.0]
});
assert_eq!(modifier_json, expected_json);
}
#[test]
fn serialize_state_modifier() {
let modifier = StateModifier::new();
let modifier_json = serde_json::to_value(modifier).unwrap();
let expected_json = json!({});
assert_eq!(modifier_json, expected_json);
let modifier = StateModifier {
on: Some(true),
brightness: Some(Adjust::Increment(1)),
hue: Some(Adjust::Override(2)),
saturation: Some(Adjust::Decrement(3)),
color_space_coordinates: None,
color_temperature: Some(Adjust::Override(4)),
alert: Some(Alert::None),
effect: Some(Effect::Colorloop),
transition_time: Some(4),
};
let modifier_json = serde_json::to_value(modifier).unwrap();
let expected_json = json!({
"on": true,
"bri_inc": 1,
"hue": 2,
"sat_inc": -3,
"ct": 4,
"alert": "none",
"effect": "colorloop",
"transitiontime": 4,
});
assert_eq!(modifier_json, expected_json);
let modifier = StateModifier::new()
.with_brightness(Adjust::Increment(1))
.with_color(Color::from_rgb(0, 0, 0));
let modifier_json = serde_json::to_value(modifier).unwrap();
let expected_json = json!({
"bri": 0,
"xy": [0.0, 0.0]
});
assert_eq!(modifier_json, expected_json);
}
#[test]
fn serialize_scanner() {
let scanner = Scanner::new();
let scanner_json = serde_json::to_value(scanner).unwrap();
let expected_json = json!({});
assert_eq!(scanner_json, expected_json);
let scanner = Scanner {
device_ids: Some(vec!["1".into()]),
};
let scanner_json = serde_json::to_value(scanner).unwrap();
let expected_json = json!({
"deviceid": ["1"]
});
assert_eq!(scanner_json, expected_json);
}
}