diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..a0af84a --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'home_server'", + "cargo": { + "args": [ + "build", + "--bin=home_server", + "--package=home_server" + ], + "filter": { + "name": "home_server", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 05d3953..d01a836 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,4 +12,5 @@ reqwest = "0.11.20" serde = { version="1.0", features = ["derive"] } serde_json = "1.0" futures = "0.3.28" -tokio = { version="1.32.0", features=["macros", "rt-multi-thread"] } \ No newline at end of file +tokio = { version="1.32.0", features=["macros", "rt-multi-thread"] } +tibber = "0.5.0" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 4490395..a1e7f74 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ use std::{ + fs, sync::{Arc, Mutex}, thread, time::{Duration, SystemTime, UNIX_EPOCH}, @@ -9,11 +10,14 @@ use crate::db::DataBase; mod db; mod devices; mod tasmota; +mod tibber; +use ::tibber::TimeResolution::Daily; use anyhow::Result; use devices::Devices; use futures::{future::try_join_all, try_join}; use tasmota::Tasmota; +use tibber::TibberHandler; fn since_epoch() -> Result { Ok(SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs()) @@ -23,8 +27,13 @@ fn since_epoch() -> Result { 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) = try_join!(db_future, devices_future)?; + let (db, devices, tibber) = try_join!(db_future, devices_future, tibber_future)?; + + let prices_today = tibber.prices_today().await?; + let prices_tomorrow = tibber.prices_tomorrow().await?; + let consumption = tibber.consumption(Daily, 1).await?; let shared_db = Arc::new(Mutex::new(db)); let tasmota_plugs: Vec = devices diff --git a/src/tibber.rs b/src/tibber.rs new file mode 100644 index 0000000..6d9f972 --- /dev/null +++ b/src/tibber.rs @@ -0,0 +1,172 @@ +use std::sync::Arc; + +use anyhow::{Error, Result}; +use tibber::{Consumption, HomeId, House, PriceInfo, TibberSession, TimeResolution, User}; + +pub struct TibberHandler { + session: Arc, + pub user: User, + pub homes: Vec<(HomeId, House)>, +} + +impl TibberHandler { + pub async fn new(token: impl ToString + Send + 'static) -> Result { + tokio::task::spawn_blocking(move || { + let session = Arc::new(TibberSession::new(token.to_string())); + + let user = session.get_user().map_err(|err| { + Error::msg(format!( + "TibberHandler: failed getting user information: {err:?}" + )) + })?; + + let mut homes = Vec::new(); + + for home_id in user.homes.clone().into_iter() { + let house = session.get_home(&home_id).map_err(|err| { + Error::msg(format!( + "TibberHandler: failed getting house information: {err:?}" + )) + })?; + + homes.push((home_id, house)); + } + + Ok(Self { + homes, + user, + session, + }) + }) + .await? + } + + async fn get_data(&self, f: F) -> Result> + where + F: Fn(&TibberSession, &HomeId) -> Result + Send + Sync + Copy + 'static, + T: Send + Sync + 'static, + { + let mut v = Vec::new(); + + for (home_id, house) in self.homes.iter() { + v.push(( + house.clone(), + tokio::task::spawn_blocking({ + let session = self.session.clone(); + let home_id = home_id.clone(); + + move || f(&session, &home_id) + }) + .await??, + )); + } + + Ok(v) + } + + pub async fn current_prices(&self) -> Result> { + let mut v = Vec::new(); + + for (home_id, house) in self.homes.iter() { + v.push(( + house.clone(), + tokio::task::spawn_blocking(move || { + self.session.get_current_price(home_id).map_err(|err| { + Error::msg(format!( + "TibberHandler: failed getting current price: {err:?}" + )) + }) + }) + .await?, + )); + } + + Ok(v) + } + + pub async fn prices_today(&self) -> Result)>> { + let mut v = Vec::new(); + + for (home_id, house) in self.homes.iter() { + v.push(( + house.clone(), + tokio::task::spawn_blocking(move || { + self.session.get_prices_today(home_id).map_err(|err| { + Error::msg(format!( + "TibberHandler: failed getting prices of today: {err:?}" + )) + }) + }) + .await?, + )); + } + + Ok(v) + } + + pub async fn prices_tomorrow(&self) -> Result)>> { + let mut v = Vec::new(); + + for (home_id, house) in self.homes.iter() { + v.push(( + house.clone(), + tokio::task::spawn_blocking(move || { + self.session.get_prices_tomorrow(home_id).map_err(|err| { + Error::msg(format!( + "TibberHandler: failed getting prices for tomorrow: {err:?}" + )) + }) + }) + .await?, + )); + } + + Ok(v) + } + + pub async fn consumption( + &self, + resolution: TimeResolution, + last: u32, + ) -> Result)>> { + let mut v = Vec::new(); + + for (home_id, house) in self.homes.iter() { + v.push(( + house.clone(), + tokio::task::spawn_blocking({ + let session = self.session.clone(); + let home_id = home_id.clone(); + + move || { + session + .get_consuption(&home_id, resolution, last) + .map_err(|err| { + Error::msg(format!( + "TibberHandler: failed getting consumption: {err:?}" + )) + }) + } + }) + .await??, + )); + } + + Ok(v) + } +} + +#[cfg(test)] +mod test { + use super::TibberHandler; + use anyhow::Result; + + use std::fs; + + #[tokio::test] + async fn test_connection() -> Result<()> { + TibberHandler::new(fs::read_to_string("tibber_token.txt")?).await?; + + Ok(()) + } +} diff --git a/tibber_token.txt b/tibber_token.txt new file mode 100644 index 0000000..6807739 --- /dev/null +++ b/tibber_token.txt @@ -0,0 +1 @@ +dkvF6ax77CP4v9-sxYyjWUD-9NunpzVPRYHfLJ8a9ps \ No newline at end of file