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"
|
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"
|
||||||
|
|
45
src/lifx.rs
45
src/lifx.rs
|
@ -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()
|
||||||
|
|
14
src/light.rs
14
src/light.rs
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
12
src/main.rs
12
src/main.rs
|
@ -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();
|
||||||
|
|
|
@ -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 => {
|
|
||||||
warn!("Failed to parse command (command)");
|
|
||||||
return;
|
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()) {
|
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);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Add table
Reference in a new issue