use std::collections::HashMap;
use std::net::{Ipv4Addr, UdpSocket};
use std::result::Result as StdResult;
use std::str::FromStr;
use std::time::Duration;
use log::debug;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
use utoipa::ToSchema;
use uuid::Uuid;
use crate::{Error, Result};
#[serde_with::skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
pub struct Room {
#[schema(min_length = 1, max_length = 100)]
name: String,
#[schema(max_items = 100)]
lights: Option<HashMap<Uuid, Light>>,
#[serde(skip)]
id: Uuid,
#[serde(skip)]
linked: bool,
}
impl Room {
pub fn new(name: &str) -> Self {
Room {
name: String::from(name),
lights: None,
id: Uuid::new_v4(),
linked: false,
}
}
pub fn link(&mut self, id: &Uuid) {
if self.linked {
panic!("refusing to overwrite id!")
}
self.id = *id;
self.linked = true;
}
pub fn get_status(&mut self) -> Result<Vec<LightingResponse>> {
let mut resp = Vec::new();
if let Some(lights) = &mut self.lights {
for light in lights.values_mut() {
let status = light.get_status()?;
resp.push(LightingResponse::status(light.ip, status));
}
}
Ok(resp)
}
pub fn new_light(&mut self, light: Light) -> Result<Uuid> {
self.validate_light(&light, None)?;
let mut id = Uuid::new_v4();
if let Some(lights) = self.lights.as_mut() {
while lights.contains_key(&id) {
id = Uuid::new_v4();
}
lights.insert(id, light);
} else {
self.lights = Some(HashMap::from([(id, light)]));
}
Ok(id)
}
pub fn delete_light(&mut self, light: &Uuid) -> Result<()> {
if let Some(lights) = self.lights.as_mut() {
match lights.remove(light) {
Some(_) => Ok(()),
None => Err(Error::light_not_found(&self.id, light)),
}
} else {
Err(Error::RoomNotFound(self.id))
}
}
pub fn update_light(&mut self, id: &Uuid, light: &Light) -> Result<()> {
if let Some(lights) = self.lights.as_mut() {
match lights.get_mut(id) {
Some(l) => {
if l.update(light) {
Ok(())
} else {
Err(Error::no_change_light(&self.id, id))
}
}
None => Err(Error::light_not_found(&self.id, id)),
}
} else {
Err(Error::NoLights(self.id))
}
}
pub fn list(&self) -> Option<Vec<&Uuid>> {
self.lights.as_ref().map(|lights| lights.keys().collect())
}
pub fn read(&self, light: &Uuid) -> Option<&Light> {
match &self.lights {
Some(lights) => lights.get(light),
None => None,
}
}
pub fn read_mut(&mut self, light: &Uuid) -> Option<&mut Light> {
if let Some(lights) = self.lights.as_mut() {
lights.get_mut(light)
} else {
None
}
}
pub fn process_reply(&mut self, resp: &LightingResponse) -> bool {
let mut any_update = false;
if let Some(lights) = self.lights.as_mut() {
for light in lights.values_mut() {
let light_update = light.process_reply(resp);
any_update = any_update || light_update;
}
}
any_update
}
pub fn name(&self) -> &str {
&self.name
}
pub fn update(&mut self, other: &Self) -> bool {
if self.name == other.name {
return false;
}
self.name = other.name.clone();
true
}
fn validate_light(&self, light: &Light, light_id: Option<&Uuid>) -> Result<()> {
let ip = light.ip();
if let Some(lights) = self.lights.as_ref() {
for (id, known) in lights {
if Some(id) == light_id {
continue;
}
if known.ip() == ip {
return Err(Error::invalid_ip(&ip, "already known"));
}
}
}
Ok(())
}
}
#[serde_with::skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
pub struct Light {
#[schema(
min_length = 1,
max_length = 15,
value_type = String,
example = "192.168.1.50",
pattern = r"^(((1[\d]{0,2})|(2([0-4]?[\d]|5[0-5]))|([3-9]?[\d])|[\d])\.){0,3}((1[\d]{0,2})|(2([0-4]?[\d]|5[0-5]))|([3-9]?[\d])|[\d])$")]
ip: Ipv4Addr,
#[schema(min_length = 1, max_length = 100)]
name: Option<String>,
status: Option<LightStatus>,
}
impl Light {
pub fn new(ip: Ipv4Addr, name: Option<&str>) -> Self {
Light {
ip,
name: name.map(String::from),
status: None,
}
}
pub fn ip(&self) -> Ipv4Addr {
self.ip
}
pub fn name(&self) -> Option<&str> {
match &self.name {
Some(s) => Some(s),
None => None,
}
}
pub fn status(&self) -> Option<&LightStatus> {
self.status.as_ref()
}
pub fn get_status(&self) -> Result<LightStatus> {
let resp = self.udp_response(&json!({"method": "getPilot"}))?;
let status: BulbStatus = match serde_json::from_value(resp) {
Ok(v) => v,
Err(e) => return Err(Error::JsonLoad(e)),
};
let status = LightStatus::from(&status);
Ok(status)
}
pub fn set(&self, payload: &Payload) -> Result<LightingResponse> {
if payload.is_valid() {
match serde_json::to_value(payload) {
Ok(msg) => match self.udp_response(&json!({
"method": "setPilot",
"params": msg,
})) {
Ok(v) => {
debug!("udp response: {:?}", v);
Ok(LightingResponse::payload(self.ip, payload.clone()))
}
Err(e) => Err(e),
},
Err(e) => Err(Error::JsonDump(e)),
}
} else {
Err(Error::NoAttribute)
}
}
pub fn set_power(&self, power: &PowerMode) -> Result<LightingResponse> {
match power {
PowerMode::On => self.toggle_power(true),
PowerMode::Off => self.toggle_power(false),
PowerMode::Reboot => self.power_cycle(),
}
}
fn toggle_power(&self, powered: bool) -> Result<LightingResponse> {
self.udp_response(&json!({"method": "setState","params": { "state": powered }}))?;
Ok(if powered {
LightingResponse::power(self.ip, PowerMode::On)
} else {
LightingResponse::power(self.ip, PowerMode::Off)
})
}
fn power_cycle(&self) -> Result<LightingResponse> {
self.udp_response(&json!({"method": "reboot"}))?;
Ok(LightingResponse::power(self.ip, PowerMode::Reboot))
}
fn update(&mut self, other: &Self) -> bool {
let mut any_update = false;
if self.name != other.name {
self.name = other.name.clone();
any_update = true;
}
if self.ip != other.ip {
self.ip = other.ip;
any_update = true;
}
any_update
}
pub fn process_reply(&mut self, resp: &LightingResponse) -> bool {
if resp.ip == self.ip {
match &resp.response {
LightingResponseType::Payload(payload) => self.update_status_from_payload(payload),
LightingResponseType::Power(power) => self.update_status_from_power(power),
LightingResponseType::Status(status) => self.update_status(status),
}
true
} else {
false
}
}
fn update_status(&mut self, status: &LightStatus) {
if let Some(known) = &mut self.status {
known.update(status);
} else {
self.status = Some(status.clone());
}
}
fn update_status_from_payload(&mut self, payload: &Payload) {
if let Some(status) = &mut self.status {
status.update_from_payload(payload);
} else {
self.status = Some(LightStatus::from(payload));
}
}
fn update_status_from_power(&mut self, power: &PowerMode) {
if let Some(status) = &mut self.status {
status.update_from_power(power);
} else {
self.status = Some(LightStatus::from(power));
}
}
fn udp_response(&self, msg: &Value) -> Result<Value> {
let msg = match serde_json::to_string(&msg) {
Ok(v) => v,
Err(e) => return Err(Error::JsonDump(e)),
};
let socket = match UdpSocket::bind("0.0.0.0:0") {
Ok(s) => s,
Err(e) => return Err(Error::socket("bind", e)),
};
match socket.set_write_timeout(Some(Duration::new(1, 0))) {
Ok(_) => {}
Err(e) => return Err(Error::socket("set_write_timeout", e)),
};
match socket.set_read_timeout(Some(Duration::new(1, 0))) {
Ok(_) => {}
Err(e) => return Err(Error::socket("set_read_timeout", e)),
};
match socket.connect(format!("{}:38899", self.ip)) {
Ok(_) => {}
Err(e) => return Err(Error::socket("connect", e)),
}
match socket.send(msg.as_bytes()) {
Ok(_) => {}
Err(e) => return Err(Error::socket("send", e)),
};
let mut buffer = [0; 4096];
let bytes = match socket.recv(&mut buffer) {
Ok(b) => b,
Err(e) => return Err(Error::socket("receive", e)),
};
let buffer = match String::from_utf8(buffer[..bytes].to_vec()) {
Ok(s) => s,
Err(e) => return Err(Error::Utf8Decode(e)),
};
match serde_json::from_str(&buffer) {
Ok(v) => Ok(v),
Err(e) => Err(Error::JsonLoad(e)),
}
}
}
#[derive(Default, Debug, Serialize, Deserialize, Clone, ToSchema)]
pub struct Brightness {
#[schema(minimum = 10, maximum = 100)]
value: u8,
}
impl Brightness {
pub fn new() -> Self {
Brightness { value: 100 }
}
pub fn value(&self) -> u8 {
self.value
}
pub fn create(value: u8) -> Option<Self> {
if Self::valid(value) {
Some(Brightness { value })
} else {
None
}
}
pub fn create_or(value: u8) -> Self {
Brightness {
value: if Self::valid(value) { value } else { 100 },
}
}
fn valid(value: u8) -> bool {
(10..=100).contains(&value)
}
}
#[derive(Default, Debug, Serialize, Deserialize, Clone, ToSchema)]
pub struct Speed {
#[schema(minimum = 20, maximum = 200)]
value: u8,
}
impl Speed {
pub fn new() -> Self {
Speed { value: 100 }
}
pub fn value(&self) -> u8 {
self.value
}
pub fn create(value: u8) -> Option<Self> {
if Self::valid(value) {
Some(Speed { value })
} else {
None
}
}
pub fn create_or(value: u8) -> Self {
Speed {
value: if Self::valid(value) { value } else { 100 },
}
}
fn valid(value: u8) -> bool {
(20..=200).contains(&value)
}
}
#[derive(Default, Debug, Serialize, Deserialize, Clone, ToSchema)]
pub struct Kelvin {
#[schema(minimum = 1000, maximum = 8000)]
kelvin: u16,
}
impl Kelvin {
pub fn new() -> Self {
Kelvin { kelvin: 1000 }
}
pub fn kelvin(&self) -> u16 {
self.kelvin
}
pub fn create(kelvin: u16) -> Option<Self> {
if (1000..=8000).contains(&kelvin) {
Some(Kelvin { kelvin })
} else {
None
}
}
}
#[derive(Default, Debug, Serialize, Deserialize, Clone, ToSchema)]
pub struct White {
#[schema(minimum = 1, maximum = 100)]
value: u8,
}
impl White {
pub fn new() -> Self {
White { value: 100 }
}
pub fn create(value: u8) -> Option<Self> {
if (1..=100).contains(&value) {
Some(White { value })
} else {
None
}
}
}
#[derive(Default, Debug, Serialize, Deserialize, Clone, ToSchema, PartialEq)]
pub struct Color {
#[schema(maximum = 255)]
red: u8,
#[schema(maximum = 255)]
green: u8,
#[schema(maximum = 255)]
blue: u8,
}
impl Color {
pub fn new() -> Self {
Color {
red: 0,
green: 0,
blue: 0,
}
}
pub fn red(&self) -> u8 {
self.red
}
pub fn green(&self) -> u8 {
self.green
}
pub fn blue(&self) -> u8 {
self.blue
}
}
impl FromStr for Color {
type Err = String;
fn from_str(s: &str) -> StdResult<Self, String> {
let parts: Vec<_> = s.split(',').map(|c| c.parse::<u8>().unwrap_or(0)).collect();
if parts.len() == 3 {
Ok(Color {
red: parts[0],
green: parts[1],
blue: parts[2],
})
} else {
Err("Invalid color string".to_string())
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
pub struct LightRequest {
brightness: Option<Brightness>,
color: Option<Color>,
speed: Option<Speed>,
temp: Option<Kelvin>,
scene: Option<SceneMode>,
power: Option<PowerMode>,
cool: Option<White>,
warm: Option<White>,
}
impl LightRequest {
pub fn power(&self) -> Option<&PowerMode> {
self.power.as_ref()
}
}
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
pub enum PowerMode {
Reboot,
On,
Off,
}
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema, EnumIter, PartialEq)]
pub enum SceneMode {
Ocean = 1,
Romance = 2,
Sunset = 3,
Party = 4,
Fireplace = 5,
Cozy = 6,
Forest = 7,
PastelColors = 8,
WakeUp = 9,
Bedtime = 10,
WarmWhite = 11,
Daylight = 12,
CoolWhite = 13,
NightLight = 14,
Focus = 15,
Relax = 16,
TrueColors = 17,
TvTime = 18,
Plantgrowth = 19,
Spring = 20,
Summer = 21,
Fall = 22,
Deepdive = 23,
Jungle = 24,
Mojito = 25,
Club = 26,
Christmas = 27,
Halloween = 28,
Candlelight = 29,
GoldenWhite = 30,
Pulse = 31,
Steampunk = 32,
Diwali = 33,
}
impl SceneMode {
pub fn create(value: u8) -> Option<Self> {
SceneMode::iter().find(|scene| scene.clone() as u8 == value)
}
}
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema, PartialEq)]
pub enum LastSet {
Color,
Scene,
Temp,
Cool,
Warm,
}
impl LastSet {
fn from(value: &Payload) -> Option<Self> {
if value.scene.is_some() {
return Some(LastSet::Scene);
}
if value.get_color().is_some() {
return Some(LastSet::Color);
}
if value.temp.is_some() {
return Some(LastSet::Temp);
}
if value.cool.is_some() {
return Some(LastSet::Cool);
}
if value.warm.is_some() {
return Some(LastSet::Warm);
}
None
}
}
#[serde_with::skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
pub struct LightStatus {
color: Option<Color>,
brightness: Option<Brightness>,
emitting: bool,
scene: Option<SceneMode>,
speed: Option<Speed>,
temp: Option<Kelvin>,
cool: Option<White>,
warm: Option<White>,
last: Option<LastSet>,
}
impl LightStatus {
pub fn last(&self) -> Option<&LastSet> {
self.last.as_ref()
}
pub fn color(&self) -> Option<&Color> {
self.color.as_ref()
}
pub fn brightness(&self) -> Option<&Brightness> {
self.brightness.as_ref()
}
pub fn emitting(&self) -> bool {
self.emitting
}
pub fn scene(&self) -> Option<&SceneMode> {
self.scene.as_ref()
}
pub fn speed(&self) -> Option<&Speed> {
self.speed.as_ref()
}
pub fn temp(&self) -> Option<&Kelvin> {
self.temp.as_ref()
}
pub fn cool(&self) -> Option<&White> {
self.cool.as_ref()
}
pub fn warm(&self) -> Option<&White> {
self.warm.as_ref()
}
pub fn update(&mut self, other: &Self) {
if let Some(color) = &other.color {
self.color = Some(color.clone());
}
if let Some(brightness) = &other.brightness {
self.brightness = Some(brightness.clone());
}
self.emitting = other.emitting;
self.scene = other.scene.clone();
if let Some(speed) = &other.speed {
self.speed = Some(speed.clone());
}
if let Some(temp) = &other.temp {
self.temp = Some(temp.clone());
}
if let Some(cool) = &other.cool {
self.cool = Some(cool.clone());
}
if let Some(warm) = &other.warm {
self.warm = Some(warm.clone());
}
if let Some(last) = &other.last {
self.last = Some(last.clone());
}
}
fn update_from_payload(&mut self, payload: &Payload) {
if let Some(color) = payload.get_color() {
self.color = Some(color);
self.last = Some(LastSet::Color);
}
if let Some(dimming) = payload.dimming {
self.brightness = Brightness::create(dimming);
}
if let Some(speed) = payload.speed {
self.speed = Speed::create(speed);
}
if let Some(temp) = payload.temp {
self.temp = Kelvin::create(temp);
self.last = Some(LastSet::Temp);
}
if let Some(scene) = payload.scene {
self.scene = SceneMode::create(scene);
self.last = Some(LastSet::Scene);
}
if let Some(cool) = payload.cool {
self.cool = White::create(cool);
self.last = Some(LastSet::Cool);
}
if let Some(warm) = payload.warm {
self.warm = White::create(warm);
self.last = Some(LastSet::Warm);
}
}
fn update_from_power(&mut self, power: &PowerMode) {
match power {
PowerMode::Off => self.emitting = false,
_ => self.emitting = true,
}
}
}
impl From<&Payload> for LightStatus {
fn from(payload: &Payload) -> Self {
let color = payload.get_color();
let brightness = if let Some(value) = payload.dimming {
Brightness::create(value)
} else {
None
};
let scene = if let Some(scene) = payload.scene {
SceneMode::create(scene)
} else {
None
};
let speed = if let Some(speed) = payload.speed {
Speed::create(speed)
} else {
None
};
let temp = if let Some(temp) = payload.temp {
Kelvin::create(temp)
} else {
None
};
let cool = if let Some(cool) = payload.cool {
White::create(cool)
} else {
None
};
let warm = if let Some(warm) = payload.warm {
White::create(warm)
} else {
None
};
LightStatus {
color,
brightness,
emitting: true, scene,
speed,
temp,
cool,
warm,
last: LastSet::from(payload),
}
}
}
impl From<&PowerMode> for LightStatus {
fn from(power: &PowerMode) -> Self {
LightStatus {
color: None,
brightness: None,
emitting: !matches!(power, PowerMode::Off),
scene: None,
speed: None,
temp: None,
cool: None,
warm: None,
last: None,
}
}
}
impl From<&BulbStatus> for LightStatus {
fn from(bulb: &BulbStatus) -> Self {
let res = &bulb.result;
LightStatus {
color: res.get_color(),
brightness: Brightness::create(res.dimming.unwrap_or(0)),
cool: White::create(res.cool.unwrap_or(0)),
warm: White::create(res.warm.unwrap_or(0)),
emitting: res.emitting,
scene: SceneMode::create(res.scene),
speed: None,
temp: None,
last: None,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
struct BulbStatus {
env: String,
method: String,
result: BulbStatusResult,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
struct BulbStatusResult {
#[serde(rename = "r")]
red: Option<u8>,
#[serde(rename = "g")]
green: Option<u8>,
#[serde(rename = "b")]
blue: Option<u8>,
dimming: Option<u8>,
mac: String,
#[serde(rename = "state")]
emitting: bool,
#[serde(rename = "sceneId")]
scene: u8,
rssi: i32,
#[serde(rename = "c")]
cool: Option<u8>,
#[serde(rename = "w")]
warm: Option<u8>,
}
impl BulbStatusResult {
fn get_color(&self) -> Option<Color> {
if let (Some(red), Some(green), Some(blue)) = (self.red, self.green, self.blue) {
Some(Color { red, green, blue })
} else {
None
}
}
}
#[derive(Debug)]
pub struct LightingResponse {
ip: Ipv4Addr,
response: LightingResponseType,
}
impl LightingResponse {
pub fn payload(ip: Ipv4Addr, payload: Payload) -> Self {
LightingResponse {
ip,
response: LightingResponseType::Payload(payload),
}
}
pub fn power(ip: Ipv4Addr, power: PowerMode) -> Self {
LightingResponse {
ip,
response: LightingResponseType::Power(power),
}
}
pub fn status(ip: Ipv4Addr, status: LightStatus) -> Self {
LightingResponse {
ip,
response: LightingResponseType::Status(status),
}
}
}
#[derive(Debug)]
pub enum LightingResponseType {
Payload(Payload),
Power(PowerMode),
Status(LightStatus),
}
#[serde_with::skip_serializing_none]
#[derive(Default, Debug, Serialize, Deserialize, Clone)]
pub struct Payload {
#[serde(rename = "sceneId")]
scene: Option<u8>,
dimming: Option<u8>,
speed: Option<u8>,
temp: Option<u16>,
#[serde(rename = "r")]
red: Option<u8>,
#[serde(rename = "g")]
green: Option<u8>,
#[serde(rename = "b")]
blue: Option<u8>,
#[serde(rename = "c")]
cool: Option<u8>,
#[serde(rename = "w")]
warm: Option<u8>,
}
impl Payload {
pub fn new() -> Self {
Payload {
scene: None,
dimming: None,
speed: None,
temp: None,
red: None,
green: None,
blue: None,
cool: None,
warm: None,
}
}
pub fn is_valid(&self) -> bool {
self.scene.is_some()
|| self.dimming.is_some()
|| self.temp.is_some()
|| (self.red.is_some() && self.green.is_some() && self.blue.is_some())
|| self.cool.is_some()
|| self.warm.is_some()
}
pub fn scene(&mut self, scene: &SceneMode) {
self.scene = Some(scene.clone() as u8);
}
pub fn brightness(&mut self, brightness: &Brightness) {
self.dimming = Some(brightness.value);
}
pub fn speed(&mut self, speed: &Speed) {
self.speed = Some(speed.value);
}
pub fn temp(&mut self, temp: &Kelvin) {
self.temp = Some(temp.kelvin);
}
pub fn color(&mut self, color: &Color) {
self.red = Some(color.red);
self.green = Some(color.green);
self.blue = Some(color.blue);
}
pub fn cool(&mut self, cool: &White) {
self.cool = Some(cool.value);
}
pub fn warm(&mut self, warm: &White) {
self.warm = Some(warm.value);
}
fn get_color(&self) -> Option<Color> {
if let (Some(red), Some(green), Some(blue)) = (self.red, self.green, self.blue) {
Some(Color { red, green, blue })
} else {
None
}
}
}
impl From<&SceneMode> for Payload {
fn from(scene: &SceneMode) -> Self {
let mut p = Payload::new();
p.scene(scene);
p
}
}
impl From<&Kelvin> for Payload {
fn from(kelvin: &Kelvin) -> Self {
let mut p = Payload::new();
p.temp(kelvin);
p
}
}
impl From<&Color> for Payload {
fn from(color: &Color) -> Self {
let mut p = Payload::new();
p.color(color);
p
}
}
impl From<&Speed> for Payload {
fn from(speed: &Speed) -> Self {
let mut p = Payload::new();
p.speed(speed);
p
}
}
impl From<&LightRequest> for Payload {
fn from(req: &LightRequest) -> Self {
let mut p = Payload::new();
if let Some(brightness) = &req.brightness {
p.brightness(brightness);
}
if let Some(color) = &req.color {
p.color(color);
}
if let Some(speed) = &req.speed {
p.speed(speed);
}
if let Some(temp) = &req.temp {
p.temp(temp);
}
if let Some(scene) = &req.scene {
p.scene(scene);
}
if let Some(cool) = &req.cool {
p.cool(cool);
}
if let Some(warm) = &req.warm {
p.warm(warm);
}
p
}
}
impl From<&Brightness> for Payload {
fn from(brightness: &Brightness) -> Self {
let mut p = Payload::new();
p.brightness(brightness);
p
}
}