use std::{num::NonZeroUsize, path::PathBuf, str::FromStr, time::Duration}; use clap::Parser; use driver_kasa::connection::LB130USHandle; use futures::stream::{FuturesUnordered, StreamExt}; use home_assistant::{ event::context::context::Context, home_assistant::HomeAssistant, light::HomeAssistantLight, notify::service::mobile_app::StandardNotification, object_id::ObjectId, }; use persisted::{persisted, Config, Keyspace, Partition, PartitionCreateOptions}; use protocol::light::{IsOff, IsOn, Kelvin, TurnToTemperature}; use pyo3::prelude::*; use shadow_rs::shadow; use tokio::{ task::{spawn_blocking, JoinSet}, time::{interval, MissedTickBehavior}, }; use tracing::{level_filters::LevelFilter, Level}; use tracing_appender::rolling::{self, RollingFileAppender}; use tracing_subscriber::{ fmt::{self, fmt, format::FmtSpan}, layer::SubscriberExt, registry, util::SubscriberInitExt, Layer, }; use tracing_to_home_assistant::TracingToHomeAssistant; mod tracing_to_home_assistant; shadow!(build_info); #[derive(Debug, Parser)] struct Args { #[arg(env)] persistence_directory: Option, #[arg(env)] tracing_directory: Option, #[arg(env, default_value = "")] tracing_file_name_prefix: String, #[arg(env, default_value = "log")] tracing_file_name_suffix: String, #[arg(env, default_value_t = 64)] tracing_max_log_files: usize, } async fn real_main( Args { persistence_directory, tracing_directory, tracing_file_name_prefix, tracing_file_name_suffix, tracing_max_log_files, }: Args, home_assistant: HomeAssistant, ) -> ! { let tracing_to_directory_res = tracing_directory .map(|tracing_directory| { tracing_appender::rolling::Builder::new() .filename_prefix(tracing_file_name_prefix) .filename_suffix(tracing_file_name_suffix) .max_log_files(tracing_max_log_files) .build(tracing_directory) .map(tracing_appender::non_blocking) }) .transpose(); let (tracing_to_directory, _guard, tracing_to_directory_initialization_error) = match tracing_to_directory_res { Ok(tracing_to_directory) => match tracing_to_directory { Some((tracing_to_directory, guard)) => { (Some(tracing_to_directory), Some(guard), None) } None => (None, None, None), }, Err(error) => (None, None, Some(error)), }; registry() .with( fmt::layer() .pretty() .with_span_events(FmtSpan::ACTIVE) .with_filter(LevelFilter::from_level(Level::TRACE)), ) .with(TracingToHomeAssistant) .with(tracing_to_directory.map(|writer| { fmt::layer() .pretty() .with_span_events(FmtSpan::ACTIVE) .with_writer(writer) .with_filter(LevelFilter::from_level(Level::TRACE)) })) .init(); if let Some(error) = tracing_to_directory_initialization_error { tracing::error!(?error, "cannot trace to directory"); } let built_at = build_info::BUILD_TIME; tracing::info!(built_at); tokio::join!( async { let mut bathroom_mirror_lights = [ LB130USHandle::new( ([10, 0, 3, 80], 9999).into(), Duration::from_secs(10), (64).try_into().unwrap(), ), LB130USHandle::new( ([10, 0, 3, 82], 9999).into(), Duration::from_secs(10), (64).try_into().unwrap(), ), ]; let mut interval = interval(Duration::from_secs(15)); interval.set_missed_tick_behavior(MissedTickBehavior::Delay); let mut temperature = Kelvin::MIN; loop { let instant = interval.tick().await; // temperature = temperature.wrapping_add(5173); tracing::info!(?temperature); let tasks = bathroom_mirror_lights .iter_mut() .map(|bathroom_mirror_light| { bathroom_mirror_light.turn_to_temperature(temperature) }); let mut tasks = FuturesUnordered::from_iter(tasks); while let Some(result) = tasks.next().await { match result { Ok(()) => {} Err(error_turning_to_temperature) => { tracing::error!(?error_turning_to_temperature) } } } } }, async { let jacob_phone_id = "galaxy_s21_ultra_1"; let jacob_phone_object_id = ObjectId::from_str(jacob_phone_id).unwrap(); let services = Python::attach(|py| home_assistant.services(py)).unwrap(); let mut interval = interval(Duration::from_secs(15)); interval.set_missed_tick_behavior(MissedTickBehavior::Delay); use home_assistant::notify::service::mobile_app::command; // loop { // let instant = interval.tick().await; // let context: Option> = None; // let target: Option<()> = None; // let turn_off_result: Result, _> = services.call_service(command::DoNotDisturb { // object_id: jacob_phone_object_id.clone(), // filter: command::DoNotDisturbFilter::Off, // }, context, target, false).await; // dbg!(turn_off_result); // let instant = interval.tick().await; // let context: Option> = None; // let target: Option<()> = None; // let alarms_only_result: Result, _> = services.call_service(command::DoNotDisturb { // object_id: jacob_phone_object_id.clone(), // filter: command::DoNotDisturbFilter::AlarmsOnly, // }, context, target, false).await; // dbg!(alarms_only_result); // let instant = interval.tick().await; // let context: Option> = None; // let target: Option<()> = None; // let priority_only_result: Result, _> = services.call_service(command::DoNotDisturb { // object_id: jacob_phone_object_id.clone(), // filter: command::DoNotDisturbFilter::PriorityOnly, // }, context, target, false).await; // dbg!(priority_only_result); // let instant = interval.tick().await; // let context: Option> = None; // let target: Option<()> = None; // let total_silence_result: Result, _> = services.call_service(command::DoNotDisturb { // object_id: jacob_phone_object_id.clone(), // filter: command::DoNotDisturbFilter::TotalSilence, // }, context, target, false).await; // dbg!(total_silence_result); // } } ).0 } #[pyfunction] fn main<'py>(py: Python<'py>, home_assistant: HomeAssistant) -> PyResult> { let args = Args::parse(); pyo3_async_runtimes::tokio::future_into_py::<_, ()>(py, async move { real_main(args, home_assistant).await; }) } /// A Python module implemented in Rust. #[pymodule] fn smart_home_in_rust_with_home_assistant(module: &Bound<'_, PyModule>) -> PyResult<()> { module.add_function(wrap_pyfunction!(main, module)?)?; Ok(()) }