1786095613

This commit is contained in:
2026-08-07 11:40:13 +02:00
parent 7d9bda13e9
commit 7e7a93e87b
6 changed files with 192 additions and 28 deletions

View File

@@ -1,36 +1,192 @@
use hass_rs::{HassClient, HassEntity};
use slint::{ModelRc, VecModel};
use std::{
collections::HashMap,
env::var,
sync::{Arc, Mutex},
thread::sleep,
time::Duration,
};
slint::include_modules!();
mod rgba;
#[cfg(not(feature = "dev"))]
mod trekstor;
slint::include_modules!();
// #[derive(Clone, Debug, PartialEq)]
// struct LightState {
// entity_id: String,
// name: String,
// on: bool,
// available: bool,
// }
struct Database {
client: HassClient,
entities: HashMap<String, HassEntity>,
}
impl Database {
pub async fn update_light(&mut self, light: &str, checked: bool) {
self.client
.call_service(
"light".to_string(),
if checked {
"turn_on".to_string()
} else {
"turn_off".to_string()
},
Some(serde_json::json!({
"entity_id": light.to_string()
})),
)
.await
.ok();
}
pub fn get_lights(&self) -> Vec<LightData> {
let mut hidden = vec![];
let lights = self
.entities
.iter()
.filter_map(|(id, e)| {
if id.starts_with("light.") {
if let Some(children) = e.attributes["entity_id"].as_array() {
for child in children {
hidden.push(child.as_str().unwrap().to_string());
}
}
return Some(LightData {
available: e.state != "unavailable",
entity_id: e.entity_id.clone().into(),
name: e.attributes["friendly_name"].as_str().unwrap().into(),
on: e.state == "on",
});
}
None
})
.collect::<Vec<_>>();
let mut lights = lights
.iter()
.filter(|e| {
!e.entity_id.ends_with("screen") && !hidden.contains(&e.entity_id.to_string())
})
.map(|e| e.to_owned())
.collect::<Vec<_>>();
lights.sort_by(|a, b| a.name.cmp(&b.name));
lights
}
}
type DB = Arc<Mutex<Database>>;
#[tokio::main]
async fn main() -> Result<(), slint::PlatformError> {
dotenvy::dotenv().ok();
#[cfg(not(feature = "dev"))]
slint::platform::set_platform(Box::new(crate::trekstor::Trekstor::new()))
.expect("set platform");
let main_window = MainWindow::new()?;
let lights = vec![LightData {
available: true,
entity_id: "light.living_room".into(),
name: "Living Room Lights".into(),
on: false,
}];
// start home_assistant webscoket
let db = home_assisstant_websocket_client().await;
main_window.set_lights(ModelRc::new(VecModel::from(lights)));
let db_weak = db.clone();
let weak_window = main_window.as_weak();
main_window.on_toggle_light(move |light, checked| {
let db_weaker = db_weak.clone();
slint::spawn_local(async move {
db_weaker
.lock()
.unwrap()
.update_light(&light.entity_id.to_string(), checked)
.await;
})
.ok();
// let weak_window = main_window.as_weak();
// slint::spawn_local(async move {
// sleep(Duration::from_secs(5));
// weak_window.upgrade_in_event_loop(move |ui| {
// ui.set_text("GoodBye!".into());
// })
// })
// .unwrap();
let lights = db_weak.lock().unwrap().get_lights();
weak_window
.upgrade_in_event_loop(move |ui| {
ui.set_lights(ModelRc::new(VecModel::from(lights)));
})
.ok();
// println!("{} {}", entity_id, checked);
});
let weak_window = main_window.as_weak();
tokio::spawn(async move {
loop {
let lights = db.lock().unwrap().get_lights();
weak_window
.upgrade_in_event_loop(move |ui| {
ui.set_lights(ModelRc::new(VecModel::from(lights)));
})
.ok();
sleep(Duration::from_secs(1));
}
});
main_window.run()
}
// todo: add channel to send actions to HA, maybe on the db object? prolly not, right?
async fn home_assisstant_websocket_client() -> DB {
let hass_ws_url = var("HASS_WS_URL").expect("HASS_WS_URL not set in environment");
let hass_token = var("HASS_TOKEN").expect("HA_TOKEN not set in environment");
let mut client = HassClient::new(&hass_ws_url)
.await
.expect("Failed to connect");
client
.auth_with_longlivedtoken(&hass_token)
.await
.expect("Not able to authenticate");
let db = Arc::new(Mutex::new(Database {
client,
entities: Default::default(),
}));
let mut event_receiver = db
.lock()
.unwrap()
.client
.subscribe_event("state_changed")
.await
.expect("Failed to subscribe");
let states = db
.lock()
.unwrap()
.client
.get_states()
.await
.expect("unable to get states");
states.iter().for_each(|entity| {
db.lock()
.expect("Database poisoned")
.entities
.insert(entity.entity_id.clone(), entity.clone());
});
let db_weak = db.clone();
tokio::spawn(async move {
while let Some(message) = event_receiver.recv().await {
if let Some(entity) = &message.event.data.new_state {
db_weak
.lock()
.expect("Database poisoned")
.entities
.insert(entity.entity_id.clone(), entity.clone());
}
}
});
db
}