217 lines
7.1 KiB
Rust
217 lines
7.1 KiB
Rust
slint::include_modules!();
|
|
|
|
use crate::{
|
|
battery::RawBatteryData,
|
|
ha_ext::*,
|
|
home_assistant::CalendarEvent,
|
|
messages::{CommandMessage, EventMessage},
|
|
};
|
|
|
|
use hass_rs::HassEntity;
|
|
use paste::paste;
|
|
use slint::{Model, ModelRc, ToSharedString, VecModel, Weak, language::ColorScheme};
|
|
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
|
|
|
|
macro_rules! apply_model {
|
|
($app:expr, $entities:expr, $name:ident, $ty:ty, $filter:expr) => {
|
|
paste! {
|
|
if $app
|
|
.[<get_ $name>]()
|
|
.as_any()
|
|
.downcast_ref::<VecModel<$ty>>()
|
|
.is_none()
|
|
{
|
|
$app.[<set_ $name>](ModelRc::new(VecModel::<$ty>::default()));
|
|
}
|
|
|
|
let data = $app
|
|
.[<get_ $name>]();
|
|
|
|
if let Some(model) = data
|
|
.as_any()
|
|
.downcast_ref::<VecModel<$ty>>() {
|
|
|
|
for entity in $entities.iter().filter($filter) {
|
|
match model.iter().position(|item| item.id == entity.entity_id) {
|
|
Some(index) => model.set_row_data(index, entity.into()),
|
|
None => model.push(entity.into()),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
pub fn apply_theme(app: &AppWindow, entities: &Vec<HassEntity>) {
|
|
for entity in entities {
|
|
if entity.entity_id == "sensor.diyless_thermostat_3_ambient_light_level"
|
|
&& let Ok(illuminance) = entity.state.parse::<i32>()
|
|
{
|
|
let mut color_scheme = app.get_color_scheme();
|
|
if illuminance < 100 {
|
|
color_scheme = ColorScheme::Dark;
|
|
} else if illuminance > 400 {
|
|
color_scheme = ColorScheme::Light;
|
|
}
|
|
|
|
app.set_color_scheme(color_scheme);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn apply_clock(app: &AppWindow, entities: &Vec<HassEntity>) {
|
|
for entity in entities {
|
|
if entity.entity_id == "sensor.date_time_iso" {
|
|
app.set_date_time(entity.into());
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn apply_weather(app: &AppWindow, entities: &Vec<HassEntity>) {
|
|
for entity in entities {
|
|
if entity.entity_id == "weather.forecast_home" {
|
|
app.set_weather(entity.into());
|
|
}
|
|
|
|
if entity.entity_id == "sun.sun" {
|
|
app.set_is_night(entity.state == "below_horizon");
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn apply_lights(app: &AppWindow, entities: &[HassEntity]) {
|
|
apply_model!(app, entities, lights, LightData, |entity| {
|
|
entity.domain() == "light"
|
|
&& !entity.entity_id.ends_with("screen")
|
|
&& !entity.entity_id.chars().any(char::is_numeric)
|
|
});
|
|
}
|
|
|
|
pub fn apply_thermometers(app: &AppWindow, entities: &[HassEntity]) {
|
|
apply_model!(app, entities, thermometers, ThermometerData, |entity| {
|
|
entity.domain() == "sensor" && entity.entity_id.ends_with("thermometer_temperature")
|
|
});
|
|
}
|
|
|
|
pub fn apply_thermostats(app: &AppWindow, entities: &[HassEntity]) {
|
|
apply_model!(app, entities, thermostats, ThermostatData, |entity| {
|
|
entity.domain() == "climate" && !entity.entity_id.contains("diyless")
|
|
});
|
|
}
|
|
|
|
pub fn apply_calendars(app: &Weak<AppWindow>, entities: Vec<CalendarEvent>) {
|
|
if let Err(err) = app.upgrade_in_event_loop(move |app| {
|
|
let mut entities = entities.clone();
|
|
entities.sort_by_key(|a| a.sort_epoch);
|
|
|
|
let entities: Vec<CalendarEventData> = entities.iter().map(Into::into).collect::<Vec<_>>();
|
|
|
|
if let Some(event) = &entities.first() {
|
|
app.set_next_event_date(slint::format!("NEXT / {}", event.date.to_uppercase(),));
|
|
app.set_next_event_time(event.time.clone());
|
|
app.set_next_event_summary(event.summary.clone());
|
|
} else {
|
|
app.set_next_event_date("NEXT EVENT".into());
|
|
app.set_next_event_time("".into());
|
|
app.set_next_event_summary("NO UPCOMING EVENTS".into());
|
|
}
|
|
|
|
app.set_calendar_events(ModelRc::new(VecModel::<CalendarEventData>::from(entities)));
|
|
}) {
|
|
eprintln!("Failed to upgrade in event loop.\n\t{:?}", err);
|
|
}
|
|
}
|
|
|
|
pub fn apply_battery(app: &Weak<AppWindow>, batt: RawBatteryData) {
|
|
if let Err(err) = app.upgrade_in_event_loop(move |app| {
|
|
app.set_battery(batt.into());
|
|
}) {
|
|
eprintln!("Failed to upgrade in event loop.\n\t{:?}", err);
|
|
}
|
|
}
|
|
|
|
pub fn apply_state(app: &Weak<AppWindow>, entities: Vec<HassEntity>) {
|
|
if let Err(err) = app.upgrade_in_event_loop(move |app| {
|
|
apply_theme(&app, &entities);
|
|
apply_clock(&app, &entities);
|
|
apply_lights(&app, &entities);
|
|
apply_thermometers(&app, &entities);
|
|
apply_weather(&app, &entities);
|
|
apply_thermostats(&app, &entities);
|
|
}) {
|
|
eprintln!("Failed to upgrade in event loop.\n\t{:?}", err);
|
|
}
|
|
}
|
|
|
|
pub fn bind_buttons(app: &AppWindow, c2s_tx: UnboundedSender<CommandMessage>) {
|
|
// Button Bindings
|
|
app.on_toggle_light({
|
|
let c2s_tx = c2s_tx.clone();
|
|
move |entity_id| {
|
|
let id = entity_id.to_string();
|
|
|
|
if let Err(err) = c2s_tx.send(CommandMessage::ToggleLight { entity_id: id }) {
|
|
eprintln!("Failed to send message to server.\n\t{:?}", err);
|
|
}
|
|
}
|
|
});
|
|
|
|
app.on_set_temperature({
|
|
let c2s_tx = c2s_tx.clone();
|
|
move |entity_id, value| {
|
|
if let Err(err) = c2s_tx.send(CommandMessage::SetTemperature {
|
|
entity_id: entity_id.to_string(),
|
|
temperature: value,
|
|
}) {
|
|
eprintln!("Failed to send message to server.\n\t{:?}", err);
|
|
}
|
|
}
|
|
});
|
|
|
|
app.on_toggle_thermostat({
|
|
let c2s_tx = c2s_tx.clone();
|
|
move |entity_id| {
|
|
let id = entity_id.to_string();
|
|
|
|
if let Err(err) = c2s_tx.send(CommandMessage::ToggleThermostat { entity_id: id }) {
|
|
eprintln!("Failed to send message to server.\n\t{:?}", err);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
pub async fn update_listener(
|
|
app_weak: Weak<AppWindow>,
|
|
mut s2c_rx: UnboundedReceiver<EventMessage>,
|
|
) {
|
|
while let Some(message) = s2c_rx.recv().await {
|
|
match message {
|
|
EventMessage::InitialState(items) => {
|
|
apply_state(&app_weak, items);
|
|
}
|
|
EventMessage::EntityUpdated(hass_entity) => {
|
|
apply_state(&app_weak, vec![hass_entity]);
|
|
}
|
|
EventMessage::EntityRemoved(hass_entity) => {
|
|
apply_state(&app_weak, vec![hass_entity]);
|
|
}
|
|
EventMessage::CalendarUpdated(events) => {
|
|
apply_calendars(&app_weak, events);
|
|
}
|
|
EventMessage::Battery(batt) => {
|
|
apply_battery(&app_weak, batt);
|
|
}
|
|
EventMessage::NoBattery => {
|
|
if let Err(err) = app_weak.upgrade_in_event_loop(move |app| {
|
|
app.set_battery(BatteryData {
|
|
state: "No Battery".to_shared_string(),
|
|
percent: 0.,
|
|
});
|
|
}) {
|
|
eprintln!("Failed to upgrade in event loop.\n\t{:?}", err);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|