use log::{info, warn}; use std::{ sync::{ Arc, atomic::{AtomicBool, AtomicU8, Ordering}, }, time::Duration, }; use axum::{ Json, Router, extract::State, response::Html, routing::{get, post}, }; use rumqttc::{AsyncClient, MqttOptions, QoS}; use serde::{Deserialize, Serialize}; use tokio::task; struct Lights { on: AtomicBool, brightness: AtomicU8, } struct AppState { mqtt_client: AsyncClient, topics: Vec, lights_state: Lights, } #[derive(Serialize, Deserialize)] struct LightsData { on: bool, brightness: Option, } const INDEX_HTML: &'static str = r#" Licht 24 - Licht hier jetzt!

Helligkeit

"#; #[tokio::main] async fn main() { let mut mqttoptions = MqttOptions::new("licht24", "192.168.3.145", 1883); mqttoptions.set_keep_alive(Duration::from_secs(5)); let (client, mut eventloop) = AsyncClient::new(mqttoptions, 10); let shared_state = Arc::new(AppState { mqtt_client: client, topics: vec![ String::from("zigbee2mqtt/sofaecke/decke_1/set"), String::from("zigbee2mqtt/sofaecke/decke_2/set"), String::from("zigbee2mqtt/sofaecke/decke_3/set"), String::from("zigbee2mqtt/sitzecke/decke_1/set"), String::from("zigbee2mqtt/sitzecke/decke_2/set"), String::from("zigbee2mqtt/trinkregal/n_1/set"), String::from("zigbee2mqtt/trinkregal/n_2/set"), String::from("zigbee2mqtt/elektro_ecke/decke_1/set"), String::from("zigbee2mqtt/elektro_ecke/decke_2/set"), String::from("zigbee2mqtt/spind/set"), String::from("zigbee2mqtt/3ddruck/wand/set"), ], lights_state: Lights { on: AtomicBool::new(true), brightness: AtomicU8::new(100), }, }); task::spawn(async move { loop { let notification = eventloop .poll() .await .expect("Couldn't poll MQTT eventloop."); println!("Received = {:?}", notification); } }); let app = Router::new() .route("/", get(|| async { Html(INDEX_HTML) })) .route("/lights", post(set_lights)) .with_state(shared_state); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000") .await .expect("Unable to bind to port 3000."); axum::serve(listener, app).await.unwrap(); } async fn set_lights( State(state): State>, Json(body): Json, ) -> Json { if let Some(brightness) = body.brightness { for topic in &state.topics { match state .mqtt_client .publish( topic, QoS::AtMostOnce, false, format!("{{\"state\": \"ON\", \"brightness\": {0}}}", brightness), ) .await { Ok(_) => { state .lights_state .brightness .store(brightness, Ordering::Relaxed); state.lights_state.on.store(true, Ordering::Relaxed); info!( "Successfully published brightness level: {0} to topic: {topic}", brightness ) } Err(err) => warn!("Unable to publish to topic: {topic} with error: {err}"), } } } else { for topic in &state.topics { match state .mqtt_client .publish( topic, QoS::AtMostOnce, false, match body.on { true => format!("{{\"state\": \"ON\"}}"), false => format!("{{\"state\": \"OFF\"}}"), }, ) .await { Ok(_) => { state.lights_state.on.store(body.on, Ordering::Relaxed); info!( "Successfully published brightness state: {0} to topic: {topic}", match body.on { true => "ON", false => "OFF", }, ) } Err(err) => warn!("Unable to publish to topic: {topic} with error: {err}"), } } } Json(body) }