Compare commits

..
4 Commits
Author SHA1 Message Date
mirsal 38f849e323 Implement sensor status debouncing
This introduces a configurable debouncing delay before which no update
is sent to matrix, which avoids flooding the matrix room with updates
when the door is opened and closed in quick succession. It also allows
to quickly open and close the door without sending notifications.

The debouncing window can be configured in config.yml, see example.
2026-10-07 20:17:49 +00:00
mirsal 233a0944c7 Revert "Implement sensor status debouncing"
This reverts commit 0ed1871042.
2026-10-07 13:27:55 +00:00
mirsal 0ed1871042 Implement sensor status debouncing
This introduces a configurable debouncing delay before which no update
is sent to matrix, which avoids flooding the matrix room with updates
when the door is opened and closed in quick succession. It also allows
to quickly open and close the door without sending notifications.

The debouncing window can be configured in config.yml, see example.
2026-10-07 10:52:14 +00:00
mirsal bf6ccc6df1 src: matrix: Read user agent name and version from cargo env 2026-10-07 10:36:38 +00:00
4 changed files with 77 additions and 16 deletions

No files matched your search

+2
View File
@@ -9,6 +9,8 @@ mqtt:
rooms:
- "!iFCnqQcvrVIeehrjQO:chat.labolyon.fr"
debounce_delay: 10 # seconds
# username / password login
# /!\ will create a new device every time the bot starts
username: "login"
+1
View File
@@ -46,6 +46,7 @@ pub enum AuthConfig {
pub struct Config {
pub mqtt: MQTTConfig,
pub rooms: Vec<String>,
pub debounce_delay: u64,
#[serde(flatten)]
pub auth: AuthConfig,
pub homeserver_uri: String
+71 -15
View File
@@ -24,7 +24,8 @@ mod matrix;
mod mqtt;
use tokio::{
sync::{Notify, broadcast},
sync::{Notify, broadcast, mpsc},
time::{sleep, Duration},
task::JoinHandle,
};
@@ -48,11 +49,16 @@ struct Args {
config: String,
}
#[derive(PartialEq)]
enum LolOpenerStatus {
#[derive(PartialEq, Clone, Copy, Debug)]
enum DoorSensorStatus {
Open,
Closed,
}
#[derive(Debug)]
struct LolOpenerState {
sensor_status: Option<DoorSensorStatus>,
pending: Option<(DoorSensorStatus, String)>,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
@@ -80,25 +86,75 @@ async fn main() -> anyhow::Result<()> {
matrix_ready.notified().await;
info!("Matrix is ready");
let current_status = Arc::new(Mutex::new(None));
let current_status = Arc::clone(&current_status);
let current_state = Arc::new(Mutex::new(LolOpenerState {
sensor_status: None,
pending: None
}));
let current_state = Arc::clone(&current_state);
let (debounce_tx, mut debounce_rx) = mpsc::unbounded_channel::<(DoorSensorStatus, String)>();
let debounce_delay = config.debounce_delay;
let rooms = config.rooms.clone();
let handle = tokio::spawn(async move {
while let Some((new_status, msg)) = debounce_rx.recv().await {
let should_wait = {
//XXX: Panic if the lock is poisoned
let mut state = current_state.lock().unwrap();
let should_wait = state.pending.is_none();
state.pending = Some((new_status, msg));
should_wait
};
if should_wait {
let tx = bcast_tx.clone();
let rooms = rooms.clone();
let state = current_state.clone();
tokio::spawn(async move {
sleep(Duration::from_secs(debounce_delay)).await;
let msg = {
//XXX: Panic if the lock is poisoned
let mut state = state.lock().unwrap();
let pending = state.pending.take();
match pending {
Some((status, msg))
if state.sensor_status.as_ref() != Some(&status) => {
state.sensor_status = Some(status);
Some(msg)
}
_ => None,
}
};
if let Some(msg) = msg {
debug!("Sending msg {}", msg);
//XXX: Panic if the matrix module can't get the message
tx.send((msg, rooms)).unwrap();
}
});
}
}
});
let mut mqtt = MQTT::new(config.mqtt.clone()).await;
mqtt.subscribe(move |status: StatusMessage| {
let (new_status, msg) = match status {
StatusMessage { contact: true, .. } => (Some(LolOpenerStatus::Closed), "🔴 Le LOL est fermé :("),
StatusMessage { contact: false, .. } => (Some(LolOpenerStatus::Open), "🟢 Le LOL est ouvert :)"),
StatusMessage { contact: true, .. } => (DoorSensorStatus::Closed, "🔴 Le LOL est fermé :("),
StatusMessage { contact: false, .. } => (DoorSensorStatus::Open, "🟢 Le LOL est ouvert :)"),
};
//XXX: Panic if the lock is poisoned (why would that happen?)
let mut current_status = current_status.lock().unwrap();
if new_status != *current_status {
debug!("Sending msg {}", msg);
//XXX: Panic if the matrix module can't get the message
bcast_tx.send((msg.into(), config.rooms.clone())).unwrap();
*current_status = new_status;
}
//XXX: Panic if the channel is closed
debounce_tx.send((new_status, String::from(msg))).unwrap();
}).await.unwrap(); //XXX: Panic if the mqtt broker fails
handle.abort();
handle.await.unwrap();
})
};
+3 -1
View File
@@ -133,7 +133,9 @@ async fn login(
match auth_config {
AuthConfig::PasswordAuthConfig{username, password} => {
matrix_auth.login_username(username, password)
.initial_device_display_name("bender v0.2.0").await?;
.initial_device_display_name(
format!("{} v{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")).as_str()
).await?;
},
AuthConfig::TokenAuthConfig{user_id, device_id, access_token} => {
matrix_auth.restore_session(