feat: add basic rss feat support

This commit is contained in:
Jet 2026-03-23 13:55:52 -07:00
parent 183b2c2c88
commit 452b8b49c3
No known key found for this signature in database
13 changed files with 232 additions and 166 deletions

View file

@ -3,6 +3,9 @@ name = "noisebell"
version = "0.1.0"
edition = "2021"
[lints]
workspace = true
[dependencies]
anyhow = "1.0"
axum = "0.8"

View file

@ -1,4 +1,4 @@
use std::sync::atomic::{AtomicU8, AtomicU64, Ordering};
use std::sync::atomic::{AtomicU64, AtomicU8, Ordering};
use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
@ -42,7 +42,6 @@ impl LocalDoorState {
Self::Closed => DoorStatus::Closed,
}
}
}
struct AppState {
@ -58,10 +57,7 @@ impl AppState {
}
fn unix_timestamp() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs()
}
async fn get_status(
@ -124,11 +120,7 @@ async fn main() -> Result<()> {
.unwrap_or_else(|_| "true".into())
.parse()
.context("NOISEBELL_ACTIVE_LOW must be true or false")?;
let active_level = if active_low {
SignalLevel::Low
} else {
SignalLevel::High
};
let active_level = if active_low { SignalLevel::Low } else { SignalLevel::High };
let inbound_api_key = std::env::var("NOISEBELL_INBOUND_API_KEY")
.context("NOISEBELL_INBOUND_API_KEY is required")?;
@ -137,33 +129,20 @@ async fn main() -> Result<()> {
let chip = Chip::new("gpiochip0").context("failed to open gpiochip0")?;
let bias = if active_level == SignalLevel::Low {
Bias::PullUp
} else {
Bias::PullDown
};
let bias = if active_level == SignalLevel::Low { Bias::PullUp } else { Bias::PullDown };
// Keep the line requested and poll its value. Edge-triggered reads have
// proven unreliable on Raspberry Pi OS even though the raw line level is
// correct, so we debounce from sampled levels instead.
let opts = Options::input([gpio_pin])
.bias(bias)
.consumer("noisebell");
let inputs = chip
.request_lines(opts)
.context(format!("failed to request GPIO line {gpio_pin}"))?;
let opts = Options::input([gpio_pin]).bias(bias).consumer("noisebell");
let inputs =
chip.request_lines(opts).context(format!("failed to request GPIO line {gpio_pin}"))?;
// Read initial value
let initial_values = inputs
.get_values([false])
.context("failed to read initial GPIO value")?;
let initial_values = inputs.get_values([false]).context("failed to read initial GPIO value")?;
// Value is true when line is active. With Active::High (default),
// true means the physical level is high.
let initial_raw_level = if initial_values[0] {
SignalLevel::High
} else {
SignalLevel::Low
};
let initial_raw_level = if initial_values[0] { SignalLevel::High } else { SignalLevel::Low };
let initial_state = LocalDoorState::from_raw_level(initial_raw_level, active_level);
let now = unix_timestamp();
@ -205,11 +184,7 @@ async fn main() -> Result<()> {
}
};
let new_raw_level = if values[0] {
SignalLevel::High
} else {
SignalLevel::Low
};
let new_raw_level = if values[0] { SignalLevel::High } else { SignalLevel::Low };
let new_state = LocalDoorState::from_raw_level(new_raw_level, active_level);
if new_state != pending_state {
@ -218,9 +193,7 @@ async fn main() -> Result<()> {
} else if new_state != current_state && pending_since.elapsed() >= debounce {
current_state = new_state;
let previous_state = LocalDoorState::from_atomic(
state_for_edges
.door_state
.swap(new_state as u8, Ordering::Relaxed),
state_for_edges.door_state.swap(new_state as u8, Ordering::Relaxed),
);
if previous_state == new_state {
@ -229,9 +202,7 @@ async fn main() -> Result<()> {
}
let timestamp = unix_timestamp();
state_for_edges
.last_changed
.store(timestamp, Ordering::Relaxed);
state_for_edges.last_changed.store(timestamp, Ordering::Relaxed);
let _ = edge_tx.send((new_state.as_door_status(), timestamp));
}
@ -252,12 +223,8 @@ async fn main() -> Result<()> {
let payload = WebhookPayload { status, timestamp };
for attempt in 0..=retry_attempts {
let result = client
.post(&endpoint_url)
.bearer_auth(&api_key)
.json(&payload)
.send()
.await;
let result =
client.post(&endpoint_url).bearer_auth(&api_key).json(&payload).send().await;
match result {
Ok(resp) if resp.status().is_success() => break,
_ => {
@ -279,9 +246,7 @@ async fn main() -> Result<()> {
}
});
let app = Router::new()
.route("/", get(get_status))
.with_state(state);
let app = Router::new().route("/", get(get_status)).with_state(state);
let listener = tokio::net::TcpListener::bind((&*bind_address, port))
.await