Connect lifx and mqtt
This commit is contained in:
parent
cd2c88b43b
commit
d523c51e53
6 changed files with 180 additions and 97 deletions
70
src/lifx.rs
70
src/lifx.rs
|
@ -1,10 +1,13 @@
|
|||
use crate::light::{Command, Light, Status};
|
||||
use crate::light::{Command, Light, Status, Update, Value};
|
||||
use crossbeam_channel::RecvTimeoutError;
|
||||
use lifxi::http::prelude::*;
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct Lifx {
|
||||
client: Client,
|
||||
updates: crossbeam_channel::Sender<Status>,
|
||||
commands: crossbeam_channel::Receiver<Command>,
|
||||
lights: Vec<Light>,
|
||||
}
|
||||
|
||||
impl Lifx {
|
||||
|
@ -17,10 +20,11 @@ impl Lifx {
|
|||
client: Client::new(secret),
|
||||
updates,
|
||||
commands,
|
||||
lights: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_lights(&self) -> Vec<Light> {
|
||||
pub fn get_lights(&self) -> Vec<Light> {
|
||||
self.client
|
||||
.select(Selector::All)
|
||||
.list()
|
||||
|
@ -30,6 +34,68 @@ impl Lifx {
|
|||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn listen(&mut self) {
|
||||
loop {
|
||||
match self.commands.recv_timeout(Duration::from_secs(1)) {
|
||||
Ok(command) => self.handle_command(command),
|
||||
Err(RecvTimeoutError::Disconnected) => return,
|
||||
Err(RecvTimeoutError::Timeout) => {}
|
||||
}
|
||||
|
||||
self.update_lights();
|
||||
}
|
||||
}
|
||||
|
||||
fn update_lights(&mut self) {
|
||||
let new_lights = self.get_lights();
|
||||
|
||||
// find changes
|
||||
for new_light in &new_lights {
|
||||
if let Some(old_light) = self.lights.iter().find(|x| new_light.id == x.id) {
|
||||
self.find_diffs(old_light, new_light);
|
||||
} else {
|
||||
self.updates.send(Status::New(new_light.clone())).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if old_light.power != new_light.power {
|
||||
self.updates
|
||||
.send(Status::Update(Update::new(
|
||||
&new_light.label,
|
||||
Value::Power(new_light.power.clone()),
|
||||
)))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
if (old_light.brightness - new_light.brightness).abs() < 0.01 {
|
||||
self.updates
|
||||
.send(Status::Update(Update::new(
|
||||
&new_light.label,
|
||||
Value::Brightness(new_light.brightness),
|
||||
)))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_command(&self, command: Command) {
|
||||
match command.command {
|
||||
Value::Power(val) => self.set_power(command.lampname, val == "on").unwrap(),
|
||||
Value::Brightness(val) => self.set_brightness(command.lampname, val).unwrap(),
|
||||
};
|
||||
}
|
||||
|
||||
fn set_power(&self, id: String, state: bool) -> Result<(), lifxi::http::Error> {
|
||||
self.client
|
||||
.select(Selector::Id(id))
|
||||
|
|
37
src/light.rs
37
src/light.rs
|
@ -1,11 +1,11 @@
|
|||
#[derive(Deserialize, Debug)]
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct Color {
|
||||
pub hue: f32,
|
||||
pub saturation: f32,
|
||||
pub kelvin: f32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct Light {
|
||||
pub id: String,
|
||||
pub label: String,
|
||||
|
@ -15,8 +15,8 @@ pub struct Light {
|
|||
pub brightness: f32,
|
||||
}
|
||||
|
||||
const POWER: &str = "power";
|
||||
const BRIGHTNESS: &str = "brightness";
|
||||
pub const POWER: &str = "power";
|
||||
pub const BRIGHTNESS: &str = "brightness";
|
||||
|
||||
pub enum Value {
|
||||
Power(String),
|
||||
|
@ -27,30 +27,24 @@ impl Value {
|
|||
pub fn new(label: &str, value: Vec<u8>) -> Self {
|
||||
match label {
|
||||
POWER => Value::Power(String::from_utf8(value).unwrap()),
|
||||
BRIGHTNESS => Value::Brightness(Self::vec_to_f32(value)),
|
||||
BRIGHTNESS => Value::Brightness(vec_to_f32(value)),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn vec_to_f32(vec: Vec<u8>) -> f32 {
|
||||
assert!(vec.len() == 4);
|
||||
let mut value_u32: u32 = 0;
|
||||
for val in vec.clone() {
|
||||
value_u32 = value_u32 << 8;
|
||||
value_u32 = value_u32 | val as u32;
|
||||
}
|
||||
println!("{:?} -> {}", vec, value_u32);
|
||||
f32::from_bits(value_u32)
|
||||
}
|
||||
|
||||
pub fn unravel(self) -> (&'static str, Vec<u8>) {
|
||||
match self {
|
||||
Value::Power(val) => (POWER, val.into_bytes()),
|
||||
Value::Brightness(val) => (BRIGHTNESS, Vec::new()),
|
||||
Value::Brightness(val) => (BRIGHTNESS, (val as u32).to_ne_bytes().to_vec()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn vec_to_f32(value: Vec<u8>) -> f32 {
|
||||
assert!(value.len() == 4);
|
||||
u32::from_ne_bytes([value[0], value[1], value[2], value[3]]) as f32
|
||||
}
|
||||
|
||||
pub struct Command {
|
||||
pub lampname: String,
|
||||
pub command: Value,
|
||||
|
@ -61,6 +55,15 @@ pub struct Update {
|
|||
pub status: Value,
|
||||
}
|
||||
|
||||
impl Update {
|
||||
pub fn new(lampname: &str, status: Value) -> Self {
|
||||
Update {
|
||||
lampname: lampname.to_owned(),
|
||||
status,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Status {
|
||||
Update(Update),
|
||||
New(Light),
|
||||
|
|
22
src/main.rs
22
src/main.rs
|
@ -19,7 +19,7 @@ use std::thread;
|
|||
pub const MQTT_ID: &str = "lifx-mqtt-bridge";
|
||||
|
||||
fn main() {
|
||||
let matches = App::new("lifx-mqtt-bridge")
|
||||
let matches = App::new(MQTT_ID)
|
||||
.version("0.1")
|
||||
.about("Lifx Mqtt Bridge")
|
||||
.author("Buntpfotenkätzchen")
|
||||
|
@ -38,9 +38,9 @@ fn main() {
|
|||
.default_value("1883"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("lifx_secret")
|
||||
Arg::with_name("lifx-secret")
|
||||
.short("s")
|
||||
.long("lifx_secret")
|
||||
.long("lifx-secret")
|
||||
.required(true)
|
||||
.takes_value(true),
|
||||
)
|
||||
|
@ -48,22 +48,22 @@ fn main() {
|
|||
|
||||
let host = matches.value_of("host").unwrap();
|
||||
let port: u16 = matches.value_of("port").unwrap().parse().unwrap();
|
||||
let lifx_secret = matches.value_of("lifx_secret").unwrap();
|
||||
let lifx_secret = matches.value_of("lifx-secret").unwrap();
|
||||
println!("Connecting to {}:{}", host, port);
|
||||
|
||||
let (s_commands, r_commands) = unbounded();
|
||||
let (s_updates, r_updates) = unbounded();
|
||||
|
||||
let (mqtt_commands, mut mqtt_updates) = match mqtt::mqtt_connect(host, port, s_commands, r_updates)
|
||||
{
|
||||
Ok(mqtt) => mqtt,
|
||||
Err(err) => panic!("Error connecting: {}", err),
|
||||
};
|
||||
let (mqtt_commands, mut mqtt_updates) =
|
||||
match mqtt::mqtt_connect(host, port, s_commands, r_updates) {
|
||||
Ok(mqtt) => mqtt,
|
||||
Err(err) => panic!("Error connecting: {}", err),
|
||||
};
|
||||
|
||||
let lifx_client = lifx::Lifx::new(lifx_secret, s_updates, r_commands);
|
||||
let mut lifx_client = lifx::Lifx::new(lifx_secret, s_updates, r_commands);
|
||||
|
||||
thread::spawn(move || mqtt_commands.listen());
|
||||
thread::spawn(move || mqtt_updates.listen());
|
||||
|
||||
loop {}
|
||||
lifx_client.listen();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::light::Status;
|
||||
use crate::light::{Status, Update, Value, BRIGHTNESS, POWER};
|
||||
use rumqtt;
|
||||
use rumqtt::{MqttClient, QoS};
|
||||
|
||||
|
@ -18,40 +18,54 @@ impl MqttUpdates {
|
|||
false,
|
||||
format!("{}:{}", id, lampname),
|
||||
)?;
|
||||
let base_url = format!("{}/{}/", crate::MQTT_ID, lampname);
|
||||
self.client.publish(
|
||||
format!("{}/{}/status/connected", crate::MQTT_ID, lampname),
|
||||
base_url.clone() + "status/connected",
|
||||
QoS::AtLeastOnce,
|
||||
false,
|
||||
true,
|
||||
"true",
|
||||
)?;
|
||||
self.client.subscribe(
|
||||
format!("{}/{}/command/power", crate::MQTT_ID, lampname),
|
||||
QoS::AtLeastOnce,
|
||||
)?;
|
||||
self.client.subscribe(
|
||||
format!("{}/{}/command/brightness", crate::MQTT_ID, lampname),
|
||||
QoS::AtLeastOnce,
|
||||
)?;
|
||||
self.client
|
||||
.subscribe(base_url.clone() + "command/" + POWER, QoS::AtLeastOnce)?;
|
||||
self.client
|
||||
.subscribe(base_url.clone() + "command/" + BRIGHTNESS, QoS::AtLeastOnce)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn listen(&mut self) {
|
||||
while let Ok(status) = self.updates.recv() {
|
||||
match status {
|
||||
Status::New(light) => self.add_light(&light.id, &light.label).unwrap(),
|
||||
Status::Remove(_name) => unimplemented!(),
|
||||
Status::Update(update) => {
|
||||
let (detail, value) = update.status.unravel();
|
||||
self.client
|
||||
.publish(
|
||||
format!("{}/{}/status/{}", crate::MQTT_ID, update.lampname, detail),
|
||||
QoS::AtLeastOnce,
|
||||
true,
|
||||
value,
|
||||
)
|
||||
.unwrap();
|
||||
Status::New(light) => {
|
||||
self.add_light(&light.id, &light.label).unwrap();
|
||||
self.handle_update(Update::new(&light.label, Value::Power(light.power)));
|
||||
self.handle_update(Update::new(
|
||||
&light.label,
|
||||
Value::Brightness(light.brightness),
|
||||
));
|
||||
}
|
||||
Status::Remove(_name) => self
|
||||
.client
|
||||
.publish(
|
||||
format!("{}/{}/status/connected", crate::MQTT_ID, _name),
|
||||
QoS::AtLeastOnce,
|
||||
true,
|
||||
"false",
|
||||
)
|
||||
.unwrap(),
|
||||
Status::Update(update) => self.handle_update(update),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_update(&mut self, update: Update) {
|
||||
let (detail, value) = update.status.unravel();
|
||||
self.client
|
||||
.publish(
|
||||
format!("{}/{}/status/{}", crate::MQTT_ID, update.lampname, detail),
|
||||
QoS::AtLeastOnce,
|
||||
true,
|
||||
value,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue