Files
smart-home-in-rust-with-hom…/entrypoint/src/lib.rs

212 lines
7.8 KiB
Rust

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<PathBuf>,
#[arg(env)]
tracing_directory: Option<PathBuf>,
#[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<Context<()>> = None;
// let target: Option<()> = None;
// let turn_off_result: Result<Py<PyAny>, _> = 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<Context<()>> = None;
// let target: Option<()> = None;
// let alarms_only_result: Result<Py<PyAny>, _> = 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<Context<()>> = None;
// let target: Option<()> = None;
// let priority_only_result: Result<Py<PyAny>, _> = 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<Context<()>> = None;
// let target: Option<()> = None;
// let total_silence_result: Result<Py<PyAny>, _> = 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<Bound<'py, PyAny>> {
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(())
}