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"
serde = "1"
serde_derive = "1"
env_logger = "0.6"
[dependencies.rumqtt]
git = "https://github.com/AtherEnergy/rumqtt"

View File

@ -1,7 +1,7 @@
use crate::light::{Command, Light, Status, Update, Value};
use crossbeam_channel::RecvTimeoutError;
use lifxi::http::prelude::*;
use log::warn;
use log::{debug, info, trace, warn};
use std::time::Duration;
pub struct Lifx {
@ -29,7 +29,10 @@ impl Lifx {
let response = self.client.select(Selector::All).list().send();
match response {
Ok(mut json_response) => match json_response.json() {
Ok(light) => light,
Ok(light) => {
trace!("{:#?}", light);
light
}
Err(err) => {
warn!("{}", err);
None
@ -43,13 +46,15 @@ impl Lifx {
}
pub fn listen(&mut self) {
info!("Listening for lifx commands and updates");
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),
Err(RecvTimeoutError::Disconnected) => return,
Err(RecvTimeoutError::Timeout) => {}
}
debug!("Updating lights");
self.update_lights();
}
}
@ -67,7 +72,7 @@ impl Lifx {
}
}
// find removed lamps
// find removed lights
self.lights
.iter()
.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) {
match command.command {
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);
}
}
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);
}
}
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);
}
}
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);
}
}
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);
}
}
};
}
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
.select(Selector::Id(id))
.select(Selector::Label(name))
.change_state()
.power(value)
.send()
.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
.select(Selector::Id(id))
.select(Selector::Label(name))
.change_state()
.brightness(value)
.send()
.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
.select(Selector::Id(id))
.select(Selector::Label(name))
.change_state()
.hue(value)
.hue(value as i16)
.send()
.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
.select(Selector::Id(id))
.select(Selector::Label(name))
.change_state()
.saturation(value)
.send()
.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
.select(Selector::Id(id))
.select(Selector::Label(name))
.change_state()
.kelvin(value)
.send()

View File

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

View File

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

View File

@ -1,5 +1,5 @@
use crate::light::{Command, Value};
use log::warn;
use log::{error, info, warn};
use regex::Regex;
use rumqtt;
use rumqtt::{Notification, Publish, Receiver};
@ -21,10 +21,11 @@ impl MqttCommands {
}
pub fn listen(&self) {
info!("Listening for mqtt commands");
loop {
match self.notifications.recv() {
Ok(notification) => {
println!("MQTT notification received: {:#?}", notification);
info!("MQTT notification received: {:#?}", notification);
match notification {
Notification::Publish(data) => self.handle_publish(data),
Notification::PubAck(_) => {}
@ -36,7 +37,7 @@ impl MqttCommands {
}
}
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) {
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();
}
let mut matching = RE.find_iter(&data.topic_name);
let lamp = match matching.next() {
Some(lamp) => lamp.as_str(),
let matching = match RE.captures(&data.topic_name) {
None => {
warn!("Failed to parse command (lamp)");
warn!("Failed to parse command: returned None");
return;
}
};
let command = match matching.next() {
Some(command) => command.as_str(),
None => {
warn!("Failed to parse command (command)");
Some(matching) => {
if matching.len() != 3 {
warn!("Failed to parse command: match length = {}", matching.len());
return;
} else {
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()) {
Some(value) => {
if let Err(err) = self.commands.send(Command {
lampname: lamp.to_owned(),
lightname: light_name.to_owned(),
command: value,
}) {
warn!("{}", err);

View File

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