Fix bugs, should work now

This commit is contained in:
Lara 2019-02-09 18:32:23 +01:00
parent 63442206e1
commit e7f7afe4de
7 changed files with 340 additions and 266 deletions

484
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -13,6 +13,7 @@ log = "0.4"
regex = "1" regex = "1"
serde = "1" serde = "1"
serde_derive = "1" serde_derive = "1"
env_logger = "0.6"
[dependencies.rumqtt] [dependencies.rumqtt]
git = "https://github.com/AtherEnergy/rumqtt" git = "https://github.com/AtherEnergy/rumqtt"

View File

@ -1,7 +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 log::{debug, info, trace, warn};
use std::time::Duration; use std::time::Duration;
pub struct Lifx { pub struct Lifx {
@ -29,7 +29,10 @@ impl Lifx {
let response = self.client.select(Selector::All).list().send(); let response = self.client.select(Selector::All).list().send();
match response { match response {
Ok(mut json_response) => match json_response.json() { Ok(mut json_response) => match json_response.json() {
Ok(light) => light, Ok(light) => {
trace!("{:#?}", light);
light
}
Err(err) => { Err(err) => {
warn!("{}", err); warn!("{}", err);
None None
@ -43,13 +46,15 @@ impl Lifx {
} }
pub fn listen(&mut self) { pub fn listen(&mut self) {
info!("Listening for lifx commands and updates");
loop { loop {
match self.commands.recv_timeout(Duration::from_secs(1)) { match self.commands.recv_timeout(Duration::from_secs(5)) {
Ok(command) => self.handle_command(command), Ok(command) => self.handle_command(command),
Err(RecvTimeoutError::Disconnected) => return, Err(RecvTimeoutError::Disconnected) => return,
Err(RecvTimeoutError::Timeout) => {} Err(RecvTimeoutError::Timeout) => {}
} }
debug!("Updating lights");
self.update_lights(); self.update_lights();
} }
} }
@ -67,7 +72,7 @@ impl Lifx {
} }
} }
// find removed lamps // find removed lights
self.lights self.lights
.iter() .iter()
.filter(|o| new_lights.iter().find(|n| n.id == o.id).is_none()) .filter(|o| new_lights.iter().find(|n| n.id == o.id).is_none())
@ -104,72 +109,72 @@ impl Lifx {
fn handle_command(&self, command: Command) { fn handle_command(&self, command: Command) {
match command.command { match command.command {
Value::Power(val) => { Value::Power(val) => {
if let Err(err) = self.set_power(command.lampname, val == "on") { if let Err(err) = self.set_power(command.lightname, val == "on") {
warn!("{}", err); warn!("{}", err);
} }
} }
Value::Brightness(val) => { Value::Brightness(val) => {
if let Err(err) = self.set_brightness(command.lampname, val) { if let Err(err) = self.set_brightness(command.lightname, val) {
warn!("{}", err); warn!("{}", err);
} }
} }
Value::Hue(val) => { Value::Hue(val) => {
if let Err(err) = self.set_hue(command.lampname, val) { if let Err(err) = self.set_hue(command.lightname, val) {
warn!("{}", err); warn!("{}", err);
} }
} }
Value::Saturation(val) => { Value::Saturation(val) => {
if let Err(err) = self.set_saturation(command.lampname, val) { if let Err(err) = self.set_saturation(command.lightname, val) {
warn!("{}", err); warn!("{}", err);
} }
} }
Value::Kelvin(val) => { Value::Kelvin(val) => {
if let Err(err) = self.set_kelvin(command.lampname, val) { if let Err(err) = self.set_kelvin(command.lightname, val) {
warn!("{}", err); warn!("{}", err);
} }
} }
}; };
} }
fn set_power(&self, id: String, value: bool) -> Result<(), lifxi::http::Error> { fn set_power(&self, name: String, value: bool) -> Result<(), lifxi::http::Error> {
self.client self.client
.select(Selector::Id(id)) .select(Selector::Label(name))
.change_state() .change_state()
.power(value) .power(value)
.send() .send()
.and(Ok(())) .and(Ok(()))
} }
fn set_brightness(&self, id: String, value: f32) -> Result<(), lifxi::http::Error> { fn set_brightness(&self, name: String, value: f32) -> Result<(), lifxi::http::Error> {
self.client self.client
.select(Selector::Id(id)) .select(Selector::Label(name))
.change_state() .change_state()
.brightness(value) .brightness(value)
.send() .send()
.and(Ok(())) .and(Ok(()))
} }
fn set_hue(&self, id: String, value: i16) -> Result<(), lifxi::http::Error> { fn set_hue(&self, name: String, value: f32) -> Result<(), lifxi::http::Error> {
self.client self.client
.select(Selector::Id(id)) .select(Selector::Label(name))
.change_state() .change_state()
.hue(value) .hue(value as i16)
.send() .send()
.and(Ok(())) .and(Ok(()))
} }
fn set_saturation(&self, id: String, value: f32) -> Result<(), lifxi::http::Error> { fn set_saturation(&self, name: String, value: f32) -> Result<(), lifxi::http::Error> {
self.client self.client
.select(Selector::Id(id)) .select(Selector::Label(name))
.change_state() .change_state()
.saturation(value) .saturation(value)
.send() .send()
.and(Ok(())) .and(Ok(()))
} }
fn set_kelvin(&self, id: String, value: i16) -> Result<(), lifxi::http::Error> { fn set_kelvin(&self, name: String, value: i16) -> Result<(), lifxi::http::Error> {
self.client self.client
.select(Selector::Id(id)) .select(Selector::Label(name))
.change_state() .change_state()
.kelvin(value) .kelvin(value)
.send() .send()

View File

@ -2,7 +2,7 @@ use log::warn;
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
pub struct Color { pub struct Color {
pub hue: i16, pub hue: f32,
pub saturation: f32, pub saturation: f32,
pub kelvin: i16, pub kelvin: i16,
} }
@ -26,7 +26,7 @@ pub const KELVIN: &str = "kelvin";
pub enum Value { pub enum Value {
Power(String), Power(String),
Brightness(f32), Brightness(f32),
Hue(i16), Hue(f32),
Saturation(f32), Saturation(f32),
Kelvin(i16), Kelvin(i16),
} }
@ -43,7 +43,7 @@ impl Value {
.ok()?, .ok()?,
)), )),
BRIGHTNESS => Some(Value::Brightness(vec_to_f32(value)?)), BRIGHTNESS => Some(Value::Brightness(vec_to_f32(value)?)),
HUE => Some(Value::Hue(vec_to_i16(value)?)), HUE => Some(Value::Hue(vec_to_f32(value)?)),
SATURATION => Some(Value::Saturation(vec_to_f32(value)?)), SATURATION => Some(Value::Saturation(vec_to_f32(value)?)),
KELVIN => Some(Value::Kelvin(vec_to_i16(value)?)), KELVIN => Some(Value::Kelvin(vec_to_i16(value)?)),
_ => unimplemented!(), _ => unimplemented!(),
@ -78,19 +78,19 @@ fn vec_to_i16(value: Vec<u8>) -> Option<i16> {
} }
pub struct Command { pub struct Command {
pub lampname: String, pub lightname: String,
pub command: Value, pub command: Value,
} }
pub struct Update { pub struct Update {
pub lampname: String, pub lightname: String,
pub status: Value, pub status: Value,
} }
impl Update { impl Update {
pub fn new(lampname: &str, status: Value) -> Self { pub fn new(lightname: &str, status: Value) -> Self {
Update { Update {
lampname: lampname.to_owned(), lightname: lightname.to_owned(),
status, status,
} }
} }

View File

@ -4,6 +4,9 @@ extern crate lazy_static;
extern crate regex; extern crate regex;
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
#[macro_use]
extern crate log;
extern crate env_logger;
mod lifx; mod lifx;
mod light; mod light;
@ -14,11 +17,18 @@ mod mqtt_updates;
use clap::App; use clap::App;
use clap::Arg; use clap::Arg;
use crossbeam_channel::unbounded; use crossbeam_channel::unbounded;
use env_logger::Builder;
use log::info;
use log::LevelFilter;
use std::thread; use std::thread;
pub const MQTT_ID: &str = "lifx-mqtt-bridge"; pub const MQTT_ID: &str = "lifx-mqtt-bridge";
fn main() { fn main() {
Builder::from_default_env()
.filter_level(LevelFilter::Info)
.init();
let matches = App::new(MQTT_ID) let matches = App::new(MQTT_ID)
.version("0.1") .version("0.1")
.about("Lifx Mqtt Bridge") .about("Lifx Mqtt Bridge")
@ -55,7 +65,7 @@ fn main() {
let lifx_secret = matches let lifx_secret = matches
.value_of("lifx-secret") .value_of("lifx-secret")
.expect("Invalid lifx-secret"); .expect("Invalid lifx-secret");
println!("Connecting to {}:{}", host, port); info!("Connecting to {}:{}", host, port);
let (s_commands, r_commands) = unbounded(); let (s_commands, r_commands) = unbounded();
let (s_updates, r_updates) = unbounded(); let (s_updates, r_updates) = unbounded();

View File

@ -1,5 +1,5 @@
use crate::light::{Command, Value}; use crate::light::{Command, Value};
use log::warn; use log::{error, info, warn};
use regex::Regex; use regex::Regex;
use rumqtt; use rumqtt;
use rumqtt::{Notification, Publish, Receiver}; use rumqtt::{Notification, Publish, Receiver};
@ -21,10 +21,11 @@ impl MqttCommands {
} }
pub fn listen(&self) { pub fn listen(&self) {
info!("Listening for mqtt commands");
loop { loop {
match self.notifications.recv() { match self.notifications.recv() {
Ok(notification) => { Ok(notification) => {
println!("MQTT notification received: {:#?}", notification); info!("MQTT notification received: {:#?}", notification);
match notification { match notification {
Notification::Publish(data) => self.handle_publish(data), Notification::Publish(data) => self.handle_publish(data),
Notification::PubAck(_) => {} Notification::PubAck(_) => {}
@ -36,7 +37,7 @@ impl MqttCommands {
} }
} }
Err(recv_error) => { Err(recv_error) => {
println!("MQTT channel closed: {}", recv_error); error!("MQTT channel closed: {}", recv_error);
} }
} }
} }
@ -44,29 +45,32 @@ impl MqttCommands {
fn handle_publish(&self, data: Publish) { fn handle_publish(&self, data: Publish) {
lazy_static! { lazy_static! {
static ref matchStr: String = format!(r"^{}/(\w+)/command/(\w+)$", crate::MQTT_ID); static ref matchStr: String =
format!(r"^{}/lights/([^/]+)/command/(\w+)$", crate::MQTT_ID);
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 matching = match RE.captures(&data.topic_name) {
let lamp = match matching.next() {
Some(lamp) => lamp.as_str(),
None => { None => {
warn!("Failed to parse command (lamp)"); warn!("Failed to parse command: returned None");
return; return;
} }
}; Some(matching) => {
let command = match matching.next() { if matching.len() != 3 {
Some(command) => command.as_str(), warn!("Failed to parse command: match length = {}", matching.len());
None => { return;
warn!("Failed to parse command (command)"); } else {
return; matching
}
} }
}; };
let light_name = &matching[1];
let command = &matching[2];
debug!("light_name: {}, command: {}", light_name, command);
match Value::new(command, data.payload.to_vec()) { match Value::new(command, data.payload.to_vec()) {
Some(value) => { Some(value) => {
if let Err(err) = self.commands.send(Command { if let Err(err) = self.commands.send(Command {
lampname: lamp.to_owned(), lightname: light_name.to_owned(),
command: value, command: value,
}) { }) {
warn!("{}", err); warn!("{}", err);

View File

@ -12,14 +12,15 @@ impl MqttUpdates {
pub fn new(client: MqttClient, updates: crossbeam_channel::Receiver<Status>) -> Self { pub fn new(client: MqttClient, updates: crossbeam_channel::Receiver<Status>) -> Self {
MqttUpdates { client, updates } MqttUpdates { client, updates }
} }
pub fn add_light(&mut self, id: &str, lampname: &str) -> Result<(), rumqtt::ClientError> { pub fn add_light(&mut self, id: &str, lightname: &str) -> Result<(), rumqtt::ClientError> {
info!("Add light: {}", lightname);
self.client.publish( self.client.publish(
format!("{}/lights", crate::MQTT_ID), format!("{}/lights", crate::MQTT_ID),
QoS::AtLeastOnce, QoS::AtLeastOnce,
false, false,
format!("{}:{}", id, lampname), format!("{}:{}", id, lightname),
)?; )?;
let base_url = format!("{}/{}/", crate::MQTT_ID, lampname); let base_url = format!("{}/lights/{}/", crate::MQTT_ID, lightname);
self.client.publish( self.client.publish(
base_url.clone() + "status/connected", base_url.clone() + "status/connected",
QoS::AtLeastOnce, QoS::AtLeastOnce,
@ -64,7 +65,7 @@ impl MqttUpdates {
} }
Status::Remove(_name) => { Status::Remove(_name) => {
if let Err(err) = self.client.publish( if let Err(err) = self.client.publish(
format!("{}/{}/status/connected", crate::MQTT_ID, _name), format!("{}/lights/{}/status/connected", crate::MQTT_ID, _name),
QoS::AtLeastOnce, QoS::AtLeastOnce,
true, true,
"false", "false",
@ -80,7 +81,12 @@ 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();
if let Err(err) = self.client.publish( if let Err(err) = self.client.publish(
format!("{}/{}/status/{}", crate::MQTT_ID, update.lampname, detail), format!(
"{}/lights/{}/status/{}",
crate::MQTT_ID,
update.lightname,
detail
),
QoS::AtLeastOnce, QoS::AtLeastOnce,
true, true,
value, value,