181 lines
5.1 KiB
Rust
181 lines
5.1 KiB
Rust
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<String>,
|
|
lights_state: Lights,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct LightsData {
|
|
on: bool,
|
|
brightness: Option<u8>,
|
|
}
|
|
|
|
const INDEX_HTML: &'static str = r#"
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Licht 24 - Licht hier jetzt!</title>
|
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<link rel="stylesheet" href="">
|
|
<script>
|
|
function post_brightness() {
|
|
fetch("/lights", {
|
|
method: "POST",
|
|
body: JSON.stringify({
|
|
on: true,
|
|
brightness: parseInt(document.getElementById("brightness").value)
|
|
}),
|
|
headers: {
|
|
"Content-type": "application/json; charset=UTF-8"
|
|
}
|
|
})
|
|
}
|
|
</script>
|
|
<style>
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div>
|
|
<h3>Helligkeit</h3>
|
|
<input type="range" id="brightness" name="brightness" min="1" max="255" onmouseup="post_brightness()" ontouchend="post_brightness()" />
|
|
</div>
|
|
|
|
</body>
|
|
</html>"#;
|
|
|
|
#[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<Arc<AppState>>,
|
|
Json(body): Json<LightsData>,
|
|
) -> Json<LightsData> {
|
|
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)
|
|
}
|