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)",
"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)",

View file

@ -1,15 +1,18 @@
[package]
name = "lifx-mqtt-bridge"
version = "0.1.0"
authors = ["Lara <git@lara.uber.space>"]
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"

View file

@ -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<Light> {
self.client
.select(Selector::All)
.list()
.send()
.unwrap()
.json()
.unwrap()
pub fn get_lights(&self) -> Option<Vec<Light>> {
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(()))
}

View file

@ -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<u8>) -> Self {
pub fn new(label: &str, value: Vec<u8>) -> Option<Self> {
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<u8>) -> 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<u8>) -> Option<f32> {
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<u8>) -> Option<i16> {
if value.len() == 2 {
Some(i16::from_ne_bytes([value[0], value[1]]))
} else {
None
}
}
pub struct Command {

View file

@ -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();

View file

@ -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"),
}
}
}

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::{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);
}
}
}