Remove "unwrap" calls

This commit is contained in:
Lara 2019-01-20 13:02:39 +01:00
parent d523c51e53
commit 8971f8b92a
7 changed files with 223 additions and 81 deletions

1
Cargo.lock generated
View file

@ -481,6 +481,7 @@ dependencies = [
"crossbeam-channel 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "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)", "regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rumqtt 0.30.0 (git+https://github.com/AtherEnergy/rumqtt)", "rumqtt 0.30.0 (git+https://github.com/AtherEnergy/rumqtt)",
"serde 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)",

View file

@ -1,15 +1,18 @@
[package] [package]
name = "lifx-mqtt-bridge"
version = "0.1.0"
authors = ["Lara <git@lara.uber.space>"] authors = ["Lara <git@lara.uber.space>"]
edition = "2018" edition = "2018"
name = "lifx-mqtt-bridge"
version = "0.1.0"
[dependencies] [dependencies]
clap = "2" clap = "2"
crossbeam-channel = "0.3" crossbeam-channel = "0.3"
lazy_static = "1" lazy_static = "1"
lifxi = "0.1" lifxi = "0.1"
log = "0.4"
regex = "1" regex = "1"
rumqtt = { git = "https://github.com/AtherEnergy/rumqtt" }
serde = "1" serde = "1"
serde_derive = "1" serde_derive = "1"
[dependencies.rumqtt]
git = "https://github.com/AtherEnergy/rumqtt"

View file

@ -1,6 +1,7 @@
use crate::light::{Command, Light, Status, Update, Value}; use crate::light::{Command, Light, Status, Update, Value};
use crossbeam_channel::RecvTimeoutError; use crossbeam_channel::RecvTimeoutError;
use lifxi::http::prelude::*; use lifxi::http::prelude::*;
use log::warn;
use std::time::Duration; use std::time::Duration;
pub struct Lifx { pub struct Lifx {
@ -24,14 +25,21 @@ impl Lifx {
} }
} }
pub fn get_lights(&self) -> Vec<Light> { pub fn get_lights(&self) -> Option<Vec<Light>> {
self.client let response = self.client.select(Selector::All).list().send();
.select(Selector::All) match response {
.list() Ok(mut json_response) => match json_response.json() {
.send() Ok(light) => light,
.unwrap() Err(err) => {
.json() warn!("{}", err);
.unwrap() None
}
},
Err(err) => {
warn!("{}", err);
None
}
}
} }
pub fn listen(&mut self) { pub fn listen(&mut self) {
@ -47,69 +55,123 @@ impl Lifx {
} }
fn update_lights(&mut self) { fn update_lights(&mut self) {
let new_lights = self.get_lights(); if let Some(new_lights) = self.get_lights() {
// find changes
// find changes for new_light in &new_lights {
for new_light in &new_lights { if let Some(old_light) = self.lights.iter().find(|x| new_light.id == x.id) {
if let Some(old_light) = self.lights.iter().find(|x| new_light.id == x.id) { self.find_diffs(old_light, new_light);
self.find_diffs(old_light, new_light); } else {
} else { if let Err(err) = self.updates.send(Status::New(new_light.clone())) {
self.updates.send(Status::New(new_light.clone())).unwrap(); 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) { fn find_diffs(&self, old_light: &Light, new_light: &Light) {
if old_light.power != new_light.power { if old_light.power != new_light.power {
self.updates if let Err(err) = self.updates.send(Status::Update(Update::new(
.send(Status::Update(Update::new( &new_light.label,
&new_light.label, Value::Power(new_light.power.clone()),
Value::Power(new_light.power.clone()), ))) {
))) warn!("{}", err);
.unwrap(); }
} }
if (old_light.brightness - new_light.brightness).abs() < 0.01 { if (old_light.brightness - new_light.brightness).abs() < 0.01 {
self.updates if let Err(err) = self.updates.send(Status::Update(Update::new(
.send(Status::Update(Update::new( &new_light.label,
&new_light.label, Value::Brightness(new_light.brightness),
Value::Brightness(new_light.brightness), ))) {
))) warn!("{}", err);
.unwrap(); }
} }
} }
fn handle_command(&self, command: Command) { fn handle_command(&self, command: Command) {
match command.command { match command.command {
Value::Power(val) => self.set_power(command.lampname, val == "on").unwrap(), Value::Power(val) => {
Value::Brightness(val) => self.set_brightness(command.lampname, val).unwrap(), 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 self.client
.select(Selector::Id(id)) .select(Selector::Id(id))
.change_state() .change_state()
.power(state) .power(value)
.send() .send()
.and(Ok(())) .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 self.client
.select(Selector::Id(id)) .select(Selector::Id(id))
.change_state() .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() .send()
.and(Ok(())) .and(Ok(()))
} }

View file

@ -1,8 +1,10 @@
use log::warn;
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
pub struct Color { pub struct Color {
pub hue: f32, pub hue: i16,
pub saturation: f32, pub saturation: f32,
pub kelvin: f32, pub kelvin: i16,
} }
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
@ -17,17 +19,33 @@ pub struct Light {
pub const POWER: &str = "power"; pub const POWER: &str = "power";
pub const BRIGHTNESS: &str = "brightness"; pub const BRIGHTNESS: &str = "brightness";
pub const HUE: &str = "hue";
pub const SATURATION: &str = "saturation";
pub const KELVIN: &str = "kelvin";
pub enum Value { pub enum Value {
Power(String), Power(String),
Brightness(f32), Brightness(f32),
Hue(i16),
Saturation(f32),
Kelvin(i16),
} }
impl Value { impl Value {
pub fn new(label: &str, value: Vec<u8>) -> Self { pub fn new(label: &str, value: Vec<u8>) -> Option<Self> {
match label { match label {
POWER => Value::Power(String::from_utf8(value).unwrap()), POWER => Some(Value::Power(
BRIGHTNESS => Value::Brightness(vec_to_f32(value)), 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!(), _ => unimplemented!(),
} }
} }
@ -36,13 +54,27 @@ impl Value {
match self { match self {
Value::Power(val) => (POWER, val.into_bytes()), Value::Power(val) => (POWER, val.into_bytes()),
Value::Brightness(val) => (BRIGHTNESS, (val as u32).to_ne_bytes().to_vec()), 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<u8>) -> f32 { fn vec_to_f32(value: Vec<u8>) -> Option<f32> {
assert!(value.len() == 4); if value.len() == 4 {
u32::from_ne_bytes([value[0], value[1], value[2], value[3]]) as f32 Some(u32::from_ne_bytes([value[0], value[1], value[2], value[3]]) as f32)
} else {
None
}
}
fn vec_to_i16(value: Vec<u8>) -> Option<i16> {
if value.len() == 2 {
Some(i16::from_ne_bytes([value[0], value[1]]))
} else {
None
}
} }
pub struct Command { pub struct Command {

View file

@ -46,9 +46,15 @@ fn main() {
) )
.get_matches(); .get_matches();
let host = matches.value_of("host").unwrap(); let host = matches.value_of("host").expect("Invalid host");
let port: u16 = matches.value_of("port").unwrap().parse().unwrap(); let port: u16 = matches
let lifx_secret = matches.value_of("lifx-secret").unwrap(); .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); println!("Connecting to {}:{}", host, port);
let (s_commands, r_commands) = unbounded(); let (s_commands, r_commands) = unbounded();

View file

@ -1,4 +1,5 @@
use crate::light::{Command, Value}; use crate::light::{Command, Value};
use log::warn;
use regex::Regex; use regex::Regex;
use rumqtt; use rumqtt;
use rumqtt::{Notification, Publish, Receiver}; use rumqtt::{Notification, Publish, Receiver};
@ -47,14 +48,31 @@ impl MqttCommands {
static ref RE: Regex = Regex::new(&matchStr).unwrap(); static ref RE: Regex = Regex::new(&matchStr).unwrap();
} }
let mut matching = RE.find_iter(&data.topic_name); let mut matching = RE.find_iter(&data.topic_name);
let lamp = matching.next().unwrap().as_str(); let lamp = match matching.next() {
let command = matching.next().unwrap().as_str(); 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 match Value::new(command, data.payload.to_vec()) {
.send(Command { Some(value) => {
lampname: lamp.to_owned(), if let Err(err) = self.commands.send(Command {
command: Value::new(command, data.payload.to_vec()), lampname: lamp.to_owned(),
}) command: value,
.unwrap(); }) {
warn!("{}", err);
}
}
None => warn!("Command value could not be created"),
}
} }
} }

View file

@ -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;
use rumqtt::{MqttClient, QoS}; use rumqtt::{MqttClient, QoS};
@ -29,6 +30,12 @@ impl MqttUpdates {
.subscribe(base_url.clone() + "command/" + POWER, QoS::AtLeastOnce)?; .subscribe(base_url.clone() + "command/" + POWER, QoS::AtLeastOnce)?;
self.client self.client
.subscribe(base_url.clone() + "command/" + BRIGHTNESS, QoS::AtLeastOnce)?; .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(()) Ok(())
} }
@ -36,22 +43,35 @@ impl MqttUpdates {
while let Ok(status) = self.updates.recv() { while let Ok(status) = self.updates.recv() {
match status { match status {
Status::New(light) => { 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::Power(light.power)));
self.handle_update(Update::new( self.handle_update(Update::new(
&light.label, &light.label,
Value::Brightness(light.brightness), 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 Status::Remove(_name) => {
.client if let Err(err) = self.client.publish(
.publish(
format!("{}/{}/status/connected", crate::MQTT_ID, _name), format!("{}/{}/status/connected", crate::MQTT_ID, _name),
QoS::AtLeastOnce, QoS::AtLeastOnce,
true, true,
"false", "false",
) ) {
.unwrap(), warn!("{}", err);
}
}
Status::Update(update) => self.handle_update(update), Status::Update(update) => self.handle_update(update),
} }
} }
@ -59,13 +79,13 @@ impl MqttUpdates {
fn handle_update(&mut self, update: Update) { fn handle_update(&mut self, update: Update) {
let (detail, value) = update.status.unravel(); let (detail, value) = update.status.unravel();
self.client if let Err(err) = self.client.publish(
.publish( format!("{}/{}/status/{}", crate::MQTT_ID, update.lampname, detail),
format!("{}/{}/status/{}", crate::MQTT_ID, update.lampname, detail), QoS::AtLeastOnce,
QoS::AtLeastOnce, true,
true, value,
value, ) {
) warn!("{}", err);
.unwrap(); }
} }
} }