lifx-mqtt-bridge/src/lifx.rs

184 lines
5.5 KiB
Rust

use crate::light::{Command, Light, Status, Update, Value};
use crossbeam_channel::RecvTimeoutError;
use lifxi::http::prelude::*;
use log::{debug, info, trace, warn};
use std::time::Duration;
pub struct Lifx {
client: Client,
updates: crossbeam_channel::Sender<Status>,
commands: crossbeam_channel::Receiver<Command>,
lights: Vec<Light>,
}
impl Lifx {
pub fn new<S: ToString>(
secret: S,
updates: crossbeam_channel::Sender<Status>,
commands: crossbeam_channel::Receiver<Command>,
) -> Self {
Lifx {
client: Client::new(secret),
updates,
commands,
lights: vec![],
}
}
pub fn get_lights(&self) -> Option<Vec<Light>> {
let response = self.client.select(Selector::All).list().send();
match response {
Ok(mut json_response) => match json_response.json() {
Ok(light) => {
trace!("{:#?}", light);
light
}
Err(err) => {
warn!("{}", err);
None
}
},
Err(err) => {
warn!("{}", err);
None
}
}
}
pub fn listen(&mut self) {
info!("Listening for lifx commands and updates");
loop {
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();
}
}
fn update_lights(&mut self) {
if let Some(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 {
if let Err(err) = self.updates.send(Status::New(new_light.clone())) {
warn!("{}", err);
}
}
}
// find removed lights
self.lights
.iter()
.filter(|o| new_lights.iter().find(|n| n.id == o.id).is_none())
.for_each(|l| {
if let Err(err) = self.updates.send(Status::Remove(l.label.clone())) {
warn!("{}", err);
}
});
self.lights = new_lights;
}
}
fn find_diffs(&self, old_light: &Light, new_light: &Light) {
if old_light.power != new_light.power {
if let Err(err) = self.updates.send(Status::Update(Update::new(
&new_light.label,
Value::Power(new_light.power.clone()),
))) {
warn!("{}", err);
}
}
if (old_light.brightness - new_light.brightness).abs() < 0.01 {
if let Err(err) = self.updates.send(Status::Update(Update::new(
&new_light.label,
Value::Brightness(new_light.brightness),
))) {
warn!("{}", err);
}
}
}
fn handle_command(&self, command: Command) {
match command.command {
Value::Power(val) => {
if let Err(err) = self.set_power(command.lightname, val == "on") {
warn!("{}", err);
}
}
Value::Brightness(val) => {
if let Err(err) = self.set_brightness(command.lightname, val) {
warn!("{}", err);
}
}
Value::Hue(val) => {
if let Err(err) = self.set_hue(command.lightname, val) {
warn!("{}", err);
}
}
Value::Saturation(val) => {
if let Err(err) = self.set_saturation(command.lightname, val) {
warn!("{}", err);
}
}
Value::Kelvin(val) => {
if let Err(err) = self.set_kelvin(command.lightname, val) {
warn!("{}", err);
}
}
};
}
fn set_power(&self, name: String, value: bool) -> Result<(), lifxi::http::Error> {
self.client
.select(Selector::Label(name))
.change_state()
.power(value)
.send()
.and(Ok(()))
}
fn set_brightness(&self, name: String, value: f32) -> Result<(), lifxi::http::Error> {
self.client
.select(Selector::Label(name))
.change_state()
.brightness(value)
.send()
.and(Ok(()))
}
fn set_hue(&self, name: String, value: f32) -> Result<(), lifxi::http::Error> {
self.client
.select(Selector::Label(name))
.change_state()
.hue(value as i16)
.send()
.and(Ok(()))
}
fn set_saturation(&self, name: String, value: f32) -> Result<(), lifxi::http::Error> {
self.client
.select(Selector::Label(name))
.change_state()
.saturation(value)
.send()
.and(Ok(()))
}
fn set_kelvin(&self, name: String, value: i16) -> Result<(), lifxi::http::Error> {
self.client
.select(Selector::Label(name))
.change_state()
.kelvin(value)
.send()
.and(Ok(()))
}
}