1786095613
This commit is contained in:
188
src/main.rs
188
src/main.rs
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user