Fix bugs, should work now
This commit is contained in:
parent
63442206e1
commit
e7f7afe4de
7 changed files with 340 additions and 266 deletions
484
Cargo.lock
generated
484
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -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"
|
||||
|
|
45
src/lifx.rs
45
src/lifx.rs
|
@ -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()
|
||||
|
|
14
src/light.rs
14
src/light.rs
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
12
src/main.rs
12
src/main.rs
|
@ -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();
|
||||
|
|
|
@ -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)");
|
||||
return;
|
||||
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);
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Reference in a new issue