feat: Added low battery notification and made mqtt message parsing more robust

Resolves: #1
This commit is contained in:
2025-09-02 01:04:26 +02:00
parent 1b8566e593
commit 8109dcf2f5
5 changed files with 129 additions and 59 deletions

View File

@@ -36,6 +36,9 @@ pub struct Config {
#[device_config(from_lua, default)]
pub callback: ActionCallback<ContactSensor, bool>,
#[device_config(from_lua, default)]
pub battery_callback: ActionCallback<ContactSensor, f32>,
#[device_config(from_lua)]
pub client: WrappedAsyncClient,
}
@@ -149,21 +152,27 @@ impl OnMqtt for ContactSensor {
return;
}
let is_closed = match ContactMessage::try_from(message) {
Ok(state) => state.is_closed(),
let message = match ContactMessage::try_from(message) {
Ok(message) => message,
Err(err) => {
error!(id = self.get_id(), "Failed to parse message: {err}");
return;
}
};
if is_closed == self.state().await.is_closed {
return;
if let Some(is_closed) = message.contact {
if is_closed == self.state().await.is_closed {
return;
}
self.config.callback.call(self, &!is_closed).await;
debug!(id = self.get_id(), "Updating state to {is_closed}");
self.state_mut().await.is_closed = is_closed;
}
self.config.callback.call(self, &!is_closed).await;
debug!(id = self.get_id(), "Updating state to {is_closed}");
self.state_mut().await.is_closed = is_closed;
if let Some(battery) = message.battery {
self.config.battery_callback.call(self, &battery).await;
}
}
}

View File

@@ -31,9 +31,12 @@ pub struct Config {
#[device_config(from_lua, default)]
pub right_hold_callback: ActionCallback<HueSwitch, ()>,
#[device_config(from_lua, default)]
pub battery_callback: ActionCallback<HueSwitch, f32>,
}
#[derive(Debug, Clone, Deserialize)]
#[derive(Debug, Copy, Clone, Deserialize)]
#[serde(rename_all = "snake_case")]
enum Action {
LeftPress,
@@ -48,7 +51,8 @@ enum Action {
#[derive(Debug, Clone, Deserialize)]
struct State {
action: Action,
action: Option<Action>,
battery: Option<f32>,
}
#[derive(Debug, Clone, LuaDevice)]
@@ -84,32 +88,43 @@ impl OnMqtt for HueSwitch {
async fn on_mqtt(&self, message: Publish) {
// Check if the message is from the device itself or from a remote
if matches(&message.topic, &self.config.mqtt.topic) {
let action = match serde_json::from_slice::<State>(&message.payload) {
Ok(message) => message.action,
let message = match serde_json::from_slice::<State>(&message.payload) {
Ok(message) => message,
Err(err) => {
warn!(id = Device::get_id(self), "Failed to parse message: {err}");
return;
}
};
debug!(id = Device::get_id(self), "Remote action = {:?}", action);
match action {
Action::LeftPressRelease => self.config.left_callback.call(self, &()).await,
Action::RightPressRelease => self.config.right_callback.call(self, &()).await,
Action::LeftHold => self.config.left_hold_callback.call(self, &()).await,
Action::RightHold => self.config.right_hold_callback.call(self, &()).await,
// If there is no hold action, the switch will act like a normal release
Action::RightHoldRelease => {
if !self.config.right_hold_callback.is_set() {
self.config.right_callback.call(self, &()).await
if let Some(action) = message.action {
debug!(
id = Device::get_id(self),
?message.action,
"Action received",
);
match action {
Action::LeftPressRelease => self.config.left_callback.call(self, &()).await,
Action::RightPressRelease => self.config.right_callback.call(self, &()).await,
Action::LeftHold => self.config.left_hold_callback.call(self, &()).await,
Action::RightHold => self.config.right_hold_callback.call(self, &()).await,
// If there is no hold action, the switch will act like a normal release
Action::RightHoldRelease => {
if !self.config.right_hold_callback.is_set() {
self.config.right_callback.call(self, &()).await
}
}
}
Action::LeftHoldRelease => {
if !self.config.left_hold_callback.is_set() {
self.config.left_callback.call(self, &()).await
Action::LeftHoldRelease => {
if !self.config.left_hold_callback.is_set() {
self.config.left_callback.call(self, &()).await
}
}
_ => {}
}
_ => {}
}
if let Some(battery) = message.battery {
self.config.battery_callback.call(self, &battery).await;
}
}
}

View File

@@ -25,6 +25,8 @@ pub struct Config {
#[device_config(from_lua)]
pub callback: ActionCallback<IkeaRemote, bool>,
#[device_config(from_lua, default)]
pub battery_callback: ActionCallback<IkeaRemote, f32>,
}
#[derive(Debug, Clone, LuaDevice)]
@@ -60,31 +62,38 @@ impl OnMqtt for IkeaRemote {
async fn on_mqtt(&self, message: Publish) {
// Check if the message is from the deviec itself or from a remote
if matches(&message.topic, &self.config.mqtt.topic) {
let action = match RemoteMessage::try_from(message) {
Ok(message) => message.action(),
let message = match RemoteMessage::try_from(message) {
Ok(message) => message,
Err(err) => {
error!(id = Device::get_id(self), "Failed to parse message: {err}");
return;
}
};
debug!(id = Device::get_id(self), "Remote action = {:?}", action);
let on = if self.config.single_button {
match action {
RemoteAction::On => Some(true),
RemoteAction::BrightnessMoveUp => Some(false),
_ => None,
}
} else {
match action {
RemoteAction::On => Some(true),
RemoteAction::Off => Some(false),
_ => None,
}
};
if let Some(action) = message.action {
debug!(id = Device::get_id(self), "Remote action = {:?}", action);
if let Some(on) = on {
self.config.callback.call(self, &on).await;
let on = if self.config.single_button {
match action {
RemoteAction::On => Some(true),
RemoteAction::BrightnessMoveUp => Some(false),
_ => None,
}
} else {
match action {
RemoteAction::On => Some(true),
RemoteAction::Off => Some(false),
_ => None,
}
};
if let Some(on) = on {
self.config.callback.call(self, &on).await;
}
}
if let Some(battery) = message.battery {
self.config.battery_callback.call(self, &battery).await;
}
}
}