use crate::light::{Command, Light, Status, Update, Value}; use crossbeam_channel::RecvTimeoutError; use lifxi::http::prelude::*; use log::{debug, info, trace, warn}; use std::time::Duration; pub struct Lifx { client: Client, updates: crossbeam_channel::Sender, commands: crossbeam_channel::Receiver, lights: Vec, } impl Lifx { pub fn new( secret: S, updates: crossbeam_channel::Sender, commands: crossbeam_channel::Receiver, ) -> Self { Lifx { client: Client::new(secret), updates, commands, lights: vec![], } } pub fn get_lights(&self) -> Option> { let response = self.client.select(Selector::All).list().send(); match response { Ok(mut json_response) => match json_response.json() { Ok(light) => { trace!("{:#?}", light); light } Err(err) => { warn!("{}", err); None } }, Err(err) => { warn!("{}", err); None } } } pub fn listen(&mut self) { info!("Listening for lifx commands and updates"); loop { match self.commands.recv_timeout(Duration::from_secs(5)) { Ok(command) => self.handle_command(command), Err(RecvTimeoutError::Disconnected) => return, Err(RecvTimeoutError::Timeout) => {} } debug!("Updating lights"); self.update_lights(); } } fn update_lights(&mut self) { if let Some(new_lights) = self.get_lights() { // find changes for new_light in &new_lights { if let Some(old_light) = self.lights.iter().find(|x| new_light.id == x.id) { self.find_diffs(old_light, new_light); } else { if let Err(err) = self.updates.send(Status::New(new_light.clone())) { warn!("{}", err); } } } // find removed lights self.lights .iter() .filter(|o| new_lights.iter().find(|n| n.id == o.id).is_none()) .for_each(|l| { if let Err(err) = self.updates.send(Status::Remove(l.label.clone())) { warn!("{}", err); } }); self.lights = new_lights; } } fn find_diffs(&self, old_light: &Light, new_light: &Light) { if old_light.power != new_light.power { if let Err(err) = self.updates.send(Status::Update(Update::new( &new_light.label, Value::Power(new_light.power.clone()), ))) { warn!("{}", err); } } if (old_light.brightness - new_light.brightness).abs() < 0.01 { if let Err(err) = self.updates.send(Status::Update(Update::new( &new_light.label, Value::Brightness(new_light.brightness), ))) { warn!("{}", err); } } } fn handle_command(&self, command: Command) { match command.command { Value::Power(val) => { if let Err(err) = self.set_power(command.lightname, val == "on") { warn!("{}", err); } } Value::Brightness(val) => { if let Err(err) = self.set_brightness(command.lightname, val) { warn!("{}", err); } } Value::Hue(val) => { if let Err(err) = self.set_hue(command.lightname, val) { warn!("{}", err); } } Value::Saturation(val) => { if let Err(err) = self.set_saturation(command.lightname, val) { warn!("{}", err); } } Value::Kelvin(val) => { if let Err(err) = self.set_kelvin(command.lightname, val) { warn!("{}", err); } } }; } fn set_power(&self, name: String, value: bool) -> Result<(), lifxi::http::Error> { self.client .select(Selector::Label(name)) .change_state() .power(value) .send() .and(Ok(())) } fn set_brightness(&self, name: String, value: f32) -> Result<(), lifxi::http::Error> { self.client .select(Selector::Label(name)) .change_state() .brightness(value) .send() .and(Ok(())) } fn set_hue(&self, name: String, value: f32) -> Result<(), lifxi::http::Error> { self.client .select(Selector::Label(name)) .change_state() .hue(value as i16) .send() .and(Ok(())) } fn set_saturation(&self, name: String, value: f32) -> Result<(), lifxi::http::Error> { self.client .select(Selector::Label(name)) .change_state() .saturation(value) .send() .and(Ok(())) } fn set_kelvin(&self, name: String, value: i16) -> Result<(), lifxi::http::Error> { self.client .select(Selector::Label(name)) .change_state() .kelvin(value) .send() .and(Ok(())) } }