use std::{ fs, sync::{Arc, Mutex}, time::{Duration, SystemTime, UNIX_EPOCH}, }; use crate::{db::DataBase, midea_helper::MideaDiscovery, web_server::plug_data_range}; mod action; mod data; mod db; mod devices; mod midea_helper; mod task_scheduler; mod tasmota; mod temperature; mod tibber_handler; mod web_server; use actix_cors::Cors; use actix_web::{web::Data, App, HttpServer}; use anyhow::Result; use devices::Devices; use futures::{try_join, Future}; use midea_helper::MideaDishwasher; use task_scheduler::{Scheduler, Task}; use tasmota::Tasmota; use tibber::TimeResolution::Daily; use tibber_handler::TibberHandler; use web_server::*; fn since_epoch() -> Result { Ok(SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs()) } fn handle_error( f: impl Future> + Send + 'static, ) -> impl Future + Unpin + Send + 'static { Box::pin(async move { if let Err(err) = f.await { println!("{err}:?"); } }) } fn setup_tasmota_tasks( scheduler: &Scheduler, tasmota_plugs: Vec, db: Arc>, ) { for plug in tasmota_plugs.into_iter() { let db_clone = db.clone(); let fut = async move { if let Ok(usage) = plug.read_power_usage().await { db_clone .lock() .unwrap() .write(plug.name(), since_epoch()?, "watts", usage)?; } Ok(()) }; scheduler.add_task(Task::looping(Duration::from_secs(3), handle_error(fut))); } } async fn run_web_server( devices: Devices, plugs: Vec, db: Arc>, dishwasher: Vec>, scheduler: Scheduler, ) -> Result<()> { const IP: &str = "0.0.0.0"; const PORT: u16 = 8062; println!("Starting server on http://{IP}:{PORT}"); HttpServer::new(move || { let cors = Cors::default() .allow_any_origin() .allow_any_method() .allow_any_header(); App::new() .wrap(cors) .app_data(Data::new(devices.clone())) .app_data(Data::new(db.clone())) .app_data(Data::new(plugs.clone())) .app_data(Data::new(dishwasher.clone())) .app_data(Data::new(scheduler.clone())) .service(device_query) .service(plug_state) .service(change_plug_state) .service(change_device_name) .service(plug_data) .service(plug_data_range) .service(push_temperature) .service(push_humidity) .service(update_push_action) .service(actions) }) .bind((IP, PORT)) .map_err(|err| anyhow::Error::msg(format!("failed binding to address: {err:#?}")))? .run() .await?; Ok(()) } #[tokio::main] async fn main() -> Result<()> { let db_future = DataBase::new("home_server.db"); let devices_future = Devices::read("devices.conf"); let tibber_future = TibberHandler::new(fs::read_to_string("tibber_token.txt")?); let (db, devices, tibber, midea) = try_join!( db_future, devices_future, tibber_future, MideaDiscovery::discover() )?; let prices_today = tibber.prices_today().await?; let prices_tomorrow = tibber.prices_tomorrow().await?; let consumption = tibber.consumption(Daily, 1).await?; db.register_devices(&devices)?; let shared_db = Arc::new(Mutex::new(db)); let tasmota_plugs: Vec = devices .plugs .iter() .map(|(plug, _)| Tasmota::new(plug)) .collect(); let dishwasher = MideaDishwasher::create(midea, shared_db.clone()) .await? .into_iter() .map(|d| Arc::new(d)) .collect(); let scheduler = Scheduler::default(); setup_tasmota_tasks(&scheduler, tasmota_plugs.clone(), shared_db.clone()); let scheduler_clone = scheduler.clone(); try_join!( scheduler.run(), run_web_server( devices, tasmota_plugs, shared_db, dishwasher, scheduler_clone ) )?; Ok(()) }