Remove "unwrap" calls
This commit is contained in:
parent
d523c51e53
commit
8971f8b92a
7 changed files with 223 additions and 81 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -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)",
|
||||||
|
|
|
@ -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"
|
||||||
|
|
150
src/lifx.rs
150
src/lifx.rs
|
@ -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(()))
|
||||||
}
|
}
|
||||||
|
|
48
src/light.rs
48
src/light.rs
|
@ -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 {
|
||||||
|
|
12
src/main.rs
12
src/main.rs
|
@ -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();
|
||||||
|
|
|
@ -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"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue