From 8971f8b92a604e679f5dd9dee55fa4fb79984af8 Mon Sep 17 00:00:00 2001 From: Lara Date: Sun, 20 Jan 2019 13:02:39 +0100 Subject: [PATCH] Remove "unwrap" calls --- Cargo.lock | 1 + Cargo.toml | 9 ++- src/lifx.rs | 150 ++++++++++++++++++++++++++++++------------- src/light.rs | 48 +++++++++++--- src/main.rs | 12 +++- src/mqtt_commands.rs | 34 +++++++--- src/mqtt_updates.rs | 50 ++++++++++----- 7 files changed, 223 insertions(+), 81 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 13199b9..e92609e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -481,6 +481,7 @@ dependencies = [ "crossbeam-channel 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "lifxi 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "rumqtt 0.30.0 (git+https://github.com/AtherEnergy/rumqtt)", "serde 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 9f0a557..4a78f97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,15 +1,18 @@ [package] -name = "lifx-mqtt-bridge" -version = "0.1.0" authors = ["Lara "] edition = "2018" +name = "lifx-mqtt-bridge" +version = "0.1.0" [dependencies] clap = "2" crossbeam-channel = "0.3" lazy_static = "1" lifxi = "0.1" +log = "0.4" regex = "1" -rumqtt = { git = "https://github.com/AtherEnergy/rumqtt" } serde = "1" serde_derive = "1" + +[dependencies.rumqtt] +git = "https://github.com/AtherEnergy/rumqtt" diff --git a/src/lifx.rs b/src/lifx.rs index c1b5eff..fe03829 100644 --- a/src/lifx.rs +++ b/src/lifx.rs @@ -1,6 +1,7 @@ use crate::light::{Command, Light, Status, Update, Value}; use crossbeam_channel::RecvTimeoutError; use lifxi::http::prelude::*; +use log::warn; use std::time::Duration; pub struct Lifx { @@ -24,14 +25,21 @@ impl Lifx { } } - pub fn get_lights(&self) -> Vec { - self.client - .select(Selector::All) - .list() - .send() - .unwrap() - .json() - .unwrap() + 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) => light, + Err(err) => { + warn!("{}", err); + None + } + }, + Err(err) => { + warn!("{}", err); + None + } + } } pub fn listen(&mut self) { @@ -47,69 +55,123 @@ impl Lifx { } fn update_lights(&mut self) { - let 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 { - self.updates.send(Status::New(new_light.clone())).unwrap(); + 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 lamps + 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; } - - // find removed lamps - self.lights - .iter() - .filter(|o| new_lights.iter().find(|n| n.id == o.id).is_none()) - .for_each(|l| { - self.updates.send(Status::Remove(l.label.clone())).unwrap(); - }); - - self.lights = new_lights; } fn find_diffs(&self, old_light: &Light, new_light: &Light) { if old_light.power != new_light.power { - self.updates - .send(Status::Update(Update::new( - &new_light.label, - Value::Power(new_light.power.clone()), - ))) - .unwrap(); + 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 { - self.updates - .send(Status::Update(Update::new( - &new_light.label, - Value::Brightness(new_light.brightness), - ))) - .unwrap(); + 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) => self.set_power(command.lampname, val == "on").unwrap(), - Value::Brightness(val) => self.set_brightness(command.lampname, val).unwrap(), + Value::Power(val) => { + if let Err(err) = self.set_power(command.lampname, val == "on") { + warn!("{}", err); + } + } + Value::Brightness(val) => { + if let Err(err) = self.set_brightness(command.lampname, val) { + warn!("{}", err); + } + } + Value::Hue(val) => { + if let Err(err) = self.set_hue(command.lampname, val) { + warn!("{}", err); + } + } + Value::Saturation(val) => { + if let Err(err) = self.set_saturation(command.lampname, val) { + warn!("{}", err); + } + } + Value::Kelvin(val) => { + if let Err(err) = self.set_kelvin(command.lampname, val) { + warn!("{}", err); + } + } }; } - fn set_power(&self, id: String, state: bool) -> Result<(), lifxi::http::Error> { + fn set_power(&self, id: String, value: bool) -> Result<(), lifxi::http::Error> { self.client .select(Selector::Id(id)) .change_state() - .power(state) + .power(value) .send() .and(Ok(())) } - fn set_brightness(&self, id: String, brightness: f32) -> Result<(), lifxi::http::Error> { + fn set_brightness(&self, id: String, value: f32) -> Result<(), lifxi::http::Error> { self.client .select(Selector::Id(id)) .change_state() - .brightness(brightness) + .brightness(value) + .send() + .and(Ok(())) + } + + fn set_hue(&self, id: String, value: i16) -> Result<(), lifxi::http::Error> { + self.client + .select(Selector::Id(id)) + .change_state() + .hue(value) + .send() + .and(Ok(())) + } + + fn set_saturation(&self, id: String, value: f32) -> Result<(), lifxi::http::Error> { + self.client + .select(Selector::Id(id)) + .change_state() + .saturation(value) + .send() + .and(Ok(())) + } + + fn set_kelvin(&self, id: String, value: i16) -> Result<(), lifxi::http::Error> { + self.client + .select(Selector::Id(id)) + .change_state() + .kelvin(value) .send() .and(Ok(())) } diff --git a/src/light.rs b/src/light.rs index 38a30fa..c42acb7 100644 --- a/src/light.rs +++ b/src/light.rs @@ -1,8 +1,10 @@ +use log::warn; + #[derive(Deserialize, Debug, Clone)] pub struct Color { - pub hue: f32, + pub hue: i16, pub saturation: f32, - pub kelvin: f32, + pub kelvin: i16, } #[derive(Deserialize, Debug, Clone)] @@ -17,17 +19,33 @@ pub struct Light { pub const POWER: &str = "power"; pub const BRIGHTNESS: &str = "brightness"; +pub const HUE: &str = "hue"; +pub const SATURATION: &str = "saturation"; +pub const KELVIN: &str = "kelvin"; pub enum Value { Power(String), Brightness(f32), + Hue(i16), + Saturation(f32), + Kelvin(i16), } impl Value { - pub fn new(label: &str, value: Vec) -> Self { + pub fn new(label: &str, value: Vec) -> Option { match label { - POWER => Value::Power(String::from_utf8(value).unwrap()), - BRIGHTNESS => Value::Brightness(vec_to_f32(value)), + POWER => Some(Value::Power( + String::from_utf8(value.clone()) + .or_else(|x| { + warn!("{:#?}: {}", value, x); + Err(x) + }) + .ok()?, + )), + BRIGHTNESS => Some(Value::Brightness(vec_to_f32(value)?)), + HUE => Some(Value::Hue(vec_to_i16(value)?)), + SATURATION => Some(Value::Saturation(vec_to_f32(value)?)), + KELVIN => Some(Value::Kelvin(vec_to_i16(value)?)), _ => unimplemented!(), } } @@ -36,13 +54,27 @@ impl Value { match self { Value::Power(val) => (POWER, val.into_bytes()), Value::Brightness(val) => (BRIGHTNESS, (val as u32).to_ne_bytes().to_vec()), + Value::Hue(val) => (HUE, (val as u32).to_ne_bytes().to_vec()), + Value::Saturation(val) => (SATURATION, (val as u32).to_ne_bytes().to_vec()), + Value::Kelvin(val) => (KELVIN, (val as u32).to_ne_bytes().to_vec()), } } } -fn vec_to_f32(value: Vec) -> f32 { - assert!(value.len() == 4); - u32::from_ne_bytes([value[0], value[1], value[2], value[3]]) as f32 +fn vec_to_f32(value: Vec) -> Option { + if value.len() == 4 { + Some(u32::from_ne_bytes([value[0], value[1], value[2], value[3]]) as f32) + } else { + None + } +} + +fn vec_to_i16(value: Vec) -> Option { + if value.len() == 2 { + Some(i16::from_ne_bytes([value[0], value[1]])) + } else { + None + } } pub struct Command { diff --git a/src/main.rs b/src/main.rs index 56d9fb0..f0bf95b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -46,9 +46,15 @@ fn main() { ) .get_matches(); - let host = matches.value_of("host").unwrap(); - let port: u16 = matches.value_of("port").unwrap().parse().unwrap(); - let lifx_secret = matches.value_of("lifx-secret").unwrap(); + let host = matches.value_of("host").expect("Invalid host"); + let port: u16 = matches + .value_of("port") + .expect("Invalid port") + .parse() + .expect("Invalid port"); + let lifx_secret = matches + .value_of("lifx-secret") + .expect("Invalid lifx-secret"); println!("Connecting to {}:{}", host, port); let (s_commands, r_commands) = unbounded(); diff --git a/src/mqtt_commands.rs b/src/mqtt_commands.rs index 7252a6f..94f9df5 100644 --- a/src/mqtt_commands.rs +++ b/src/mqtt_commands.rs @@ -1,4 +1,5 @@ use crate::light::{Command, Value}; +use log::warn; use regex::Regex; use rumqtt; use rumqtt::{Notification, Publish, Receiver}; @@ -47,14 +48,31 @@ impl MqttCommands { static ref RE: Regex = Regex::new(&matchStr).unwrap(); } let mut matching = RE.find_iter(&data.topic_name); - let lamp = matching.next().unwrap().as_str(); - let command = matching.next().unwrap().as_str(); + let lamp = match matching.next() { + Some(lamp) => lamp.as_str(), + None => { + warn!("Failed to parse command (lamp)"); + return; + } + }; + let command = match matching.next() { + Some(command) => command.as_str(), + None => { + warn!("Failed to parse command (command)"); + return; + } + }; - self.commands - .send(Command { - lampname: lamp.to_owned(), - command: Value::new(command, data.payload.to_vec()), - }) - .unwrap(); + match Value::new(command, data.payload.to_vec()) { + Some(value) => { + if let Err(err) = self.commands.send(Command { + lampname: lamp.to_owned(), + command: value, + }) { + warn!("{}", err); + } + } + None => warn!("Command value could not be created"), + } } } diff --git a/src/mqtt_updates.rs b/src/mqtt_updates.rs index 06b377b..f90356a 100644 --- a/src/mqtt_updates.rs +++ b/src/mqtt_updates.rs @@ -1,4 +1,5 @@ -use crate::light::{Status, Update, Value, BRIGHTNESS, POWER}; +use crate::light::{Status, Update, Value, BRIGHTNESS, HUE, KELVIN, POWER, SATURATION}; +use log::warn; use rumqtt; use rumqtt::{MqttClient, QoS}; @@ -29,6 +30,12 @@ impl MqttUpdates { .subscribe(base_url.clone() + "command/" + POWER, QoS::AtLeastOnce)?; self.client .subscribe(base_url.clone() + "command/" + BRIGHTNESS, QoS::AtLeastOnce)?; + self.client + .subscribe(base_url.clone() + "command/" + HUE, QoS::AtLeastOnce)?; + self.client + .subscribe(base_url.clone() + "command/" + SATURATION, QoS::AtLeastOnce)?; + self.client + .subscribe(base_url.clone() + "command/" + KELVIN, QoS::AtLeastOnce)?; Ok(()) } @@ -36,22 +43,35 @@ impl MqttUpdates { while let Ok(status) = self.updates.recv() { match status { Status::New(light) => { - self.add_light(&light.id, &light.label).unwrap(); + if let Err(err) = self.add_light(&light.id, &light.label) { + warn!("{}", err); + continue; + } self.handle_update(Update::new(&light.label, Value::Power(light.power))); self.handle_update(Update::new( &light.label, Value::Brightness(light.brightness), )); + self.handle_update(Update::new(&light.label, Value::Hue(light.color.hue))); + self.handle_update(Update::new( + &light.label, + Value::Saturation(light.color.saturation), + )); + self.handle_update(Update::new( + &light.label, + Value::Kelvin(light.color.kelvin), + )); } - Status::Remove(_name) => self - .client - .publish( + Status::Remove(_name) => { + if let Err(err) = self.client.publish( format!("{}/{}/status/connected", crate::MQTT_ID, _name), QoS::AtLeastOnce, true, "false", - ) - .unwrap(), + ) { + warn!("{}", err); + } + } Status::Update(update) => self.handle_update(update), } } @@ -59,13 +79,13 @@ impl MqttUpdates { fn handle_update(&mut self, update: Update) { let (detail, value) = update.status.unravel(); - self.client - .publish( - format!("{}/{}/status/{}", crate::MQTT_ID, update.lampname, detail), - QoS::AtLeastOnce, - true, - value, - ) - .unwrap(); + if let Err(err) = self.client.publish( + format!("{}/{}/status/{}", crate::MQTT_ID, update.lampname, detail), + QoS::AtLeastOnce, + true, + value, + ) { + warn!("{}", err); + } } }