feat: add webhooks with basic json to send to

This commit is contained in:
Jet Pham 2025-06-04 19:25:08 -07:00
parent 18aa3dfaa8
commit 1b0284b00b
No known key found for this signature in database
7 changed files with 1432 additions and 10 deletions

1311
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -4,8 +4,12 @@ version = "0.1.0"
edition = "2021"
[dependencies]
tokio = { version = "1.36", features = ["full"] }
anyhow = "1.0"
tokio = { version = "1.0", features = ["full"] }
tracing = "0.1"
tracing-subscriber = "0.3"
rppal = "0.22.1"
rppal = "0.22"
reqwest = { version = "0.12", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = "0.4"

View file

@ -9,7 +9,7 @@ cargo zigbuild --release --target aarch64-unknown-linux-gnu
echo "Copying to Raspberry Pi..."
scp target/aarch64-unknown-linux-gnu/release/noisebell noisebridge@noisebell.local:~/
echo "Setting permissions and restarting service..."
echo "Setting permissions"
ssh noisebridge@noisebell.local "chmod +x ~/noisebell "
echo "Deployment complete!"

16
endpoints.json Normal file
View file

@ -0,0 +1,16 @@
{
"endpoints": [
{
"url": "http://localhost:8080/webhook",
"description": "Local development endpoint"
},
{
"url": "https://api.example.com/notifications",
"description": "Production notification service"
},
{
"url": "https://webhook.site/your-unique-id",
"description": "Webhook testing service"
}
]
}

View file

@ -1,14 +1,25 @@
use std::time::Duration;
use std::fmt;
use serde::{Serialize, Deserialize};
use anyhow::Result;
use rppal::gpio::{Gpio, InputPin, Level};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum CircuitEvent {
Open,
Closed,
}
impl fmt::Display for CircuitEvent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CircuitEvent::Open => write!(f, "open"),
CircuitEvent::Closed => write!(f, "closed"),
}
}
}
pub struct GpioMonitor {
pin: InputPin,
poll_interval: Duration,

View file

@ -1,4 +1,5 @@
mod gpio;
mod webhook;
use std::time::Duration;
@ -7,24 +8,29 @@ use tracing::{error, info};
#[tokio::main]
async fn main() -> Result<()> {
// Initialize logging
tracing_subscriber::fmt::init();
// Initialize GPIO monitor
const DEFAULT_GPIO_PIN: u8 = 17;
let gpio_pin = std::env::var("GPIO_PIN")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(DEFAULT_GPIO_PIN);
let webhook_notifier = webhook::WebhookNotifier::new()?;
let mut gpio_monitor = gpio::GpioMonitor::new(gpio_pin, Duration::from_millis(100))?;
// Simple callback function that just logs the event
let callback = |event: gpio::CircuitEvent| {
let callback = move |event: gpio::CircuitEvent| {
info!("Circuit state changed: {:?}", event);
// Clone the webhook notifier for the async block
let notifier = webhook_notifier.clone();
// Spawn a new task to send webhooks
tokio::spawn(async move {
notifier.notify_all("circuit_state_change", event).await;
});
};
// Start GPIO monitoring
if let Err(e) = gpio_monitor.monitor(callback).await {
error!("GPIO monitoring error: {}", e);
}

76
src/webhook.rs Normal file
View file

@ -0,0 +1,76 @@
use anyhow::Result;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::fs;
use tracing::{error, info};
use crate::gpio::CircuitEvent;
#[derive(Debug, Deserialize, Clone)]
struct Endpoint {
url: String,
description: String,
}
#[derive(Debug, Deserialize)]
struct EndpointsConfig {
endpoints: Vec<Endpoint>,
}
#[derive(Debug, Serialize)]
struct WebhookPayload {
event_type: String,
timestamp: String,
new_state: String,
}
#[derive(Clone)]
pub struct WebhookNotifier {
client: Client,
endpoints: Vec<Endpoint>,
}
impl WebhookNotifier {
pub fn new() -> Result<Self> {
let config = fs::read_to_string("endpoints.json")?;
let endpoints_config: EndpointsConfig = serde_json::from_str(&config)?;
Ok(Self {
client: Client::new(),
endpoints: endpoints_config.endpoints,
})
}
pub async fn notify_all(&self, event_type: &str, state: CircuitEvent) {
let payload = WebhookPayload {
event_type: event_type.to_string(),
timestamp: chrono::Utc::now().to_rfc3339(),
new_state: state.to_string(),
};
for endpoint in &self.endpoints {
info!("Sending webhook to {}: {}", endpoint.description, serde_json::to_string(&payload).unwrap());
match self.client
.post(&endpoint.url)
.json(&payload)
.send()
.await
{
Ok(response) => {
info!(
"Successfully sent webhook to {}: {}",
endpoint.description,
response.status()
);
}
Err(e) => {
error!(
"Failed to send webhook to {}: {}",
endpoint.description,
e
);
}
}
}
}
}