diff --git a/src/main.rs b/src/main.rs index c5067c0..832e08c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,7 +20,7 @@ use hyper::{service::make_service_fn, service::service_fn, Server}; use ticker::Ticker; use config::loader as config_loader; -use tado::metrics::renderer; +use tado::metrics; use tado::client::Client as TadoClient; #[tokio::main] @@ -37,7 +37,7 @@ async fn main() { info!("starting tado° exporter on address: {:?}", addr); let make_svc = make_service_fn(|_conn| async { - Ok::<_, Infallible>(service_fn(renderer)) + Ok::<_, Infallible>(service_fn(metrics::renderer)) }); let server = Server::bind(&addr).serve(make_svc); @@ -51,9 +51,12 @@ async fn main() { fn run_ticker(config: config_loader::Config) { tokio::spawn(async move { let mut tado_client = TadoClient::new(config.username, config.password, config.client_secret); + let ticker = Ticker::new(0.., Duration::from_secs(config.ticker)); for _ in ticker { - tado_client.retrieve().await; + let zones = tado_client.retrieve().await; + + metrics::set(zones); } }); } \ No newline at end of file diff --git a/src/tado/client.rs b/src/tado/client.rs index 3064772..6348508 100644 --- a/src/tado/client.rs +++ b/src/tado/client.rs @@ -1,8 +1,8 @@ extern crate reqwest; use log::{info, error}; -use super::metrics::{TEMPERATURE_GAUGE, HUMIDITY_PERCENTAGE}; -use super::model::{AuthApiResponse, MeApiResponse, ZonesApiResponse, ZoneStateApiResponse}; +use std::vec::Vec; +use super::model::{AuthApiResponse, MeApiResponse, ZonesApiResponse, ZoneStateApiResponse, ZoneStateResponse}; const AUTH_URL: &'static str = "https://auth.tado.com/oauth/token"; @@ -81,13 +81,13 @@ impl Client { Ok(resp.json::().await?) } - pub async fn retrieve(&mut self) { + pub async fn retrieve(&mut self) -> Vec { // retrieve an access token to use the tado API let api_response = match self.authenticate().await { Ok(resp) => resp, Err(e) => { - error!("[tado° client] unable to authenticate: {}", e); - return; + error!("unable to authenticate: {}", e); + return Vec::new(); } }; @@ -98,8 +98,8 @@ impl Client { let me_response = match self.me().await { Ok(resp) => resp, Err(e) => { - error!("[tado° client] unable to retrieve home identifier: {}", e); - return; + error!("unable to retrieve home identifier: {}", e); + return Vec::new(); } }; @@ -110,35 +110,29 @@ impl Client { let zones_response = match self.zones().await { Ok(resp) => resp, Err(e) => { - error!("[tado° client] unable to retrieve home zones: {}", e); - return; + error!("unable to retrieve home zones: {}", e); + return Vec::new(); } }; + let mut response = Vec::::new(); + for zone in zones_response { - info!("[tado° client] retrieving zone details for {}...", zone.name); + info!("retrieving zone details for {}...", zone.name); let zone_state_response = match self.zone_state(zone.id).await { Ok(resp) => resp, Err(e) => { - error!("[tado° client] unable to retrieve home zone '{}' state: {}", zone.name, e); - return; + error!("unable to retrieve home zone '{}' state: {}", zone.name, e); + return Vec::new(); } }; - // temperature: celsius - let temperature_celsius: f64 = zone_state_response.sensorDataPoints.insideTemperature.celsius; - TEMPERATURE_GAUGE.with_label_values(&[zone.name.as_str(), "celsius"]).set(temperature_celsius); - info!("-> temperature (celsius): {}", temperature_celsius); - - // temperature: fahrenheit - let temperature_fahrenheit: f64 = zone_state_response.sensorDataPoints.insideTemperature.fahrenheit; - TEMPERATURE_GAUGE.with_label_values(&[zone.name.as_str(), "fahrenheit"]).set(temperature_celsius); - info!("-> temperature (fahrenheit): {}", temperature_fahrenheit); - - // humidity percentage - let humidity_percentage: f64 = zone_state_response.sensorDataPoints.humidity.percentage; - HUMIDITY_PERCENTAGE.with_label_values(&[zone.name.as_str()]).set(humidity_percentage); - info!("-> humidity: {}%", humidity_percentage); + response.push(ZoneStateResponse{ + name: zone.name, + state_response: zone_state_response, + }); } + + return response; } } diff --git a/src/tado/metrics.rs b/src/tado/metrics.rs index a789de1..48b0c36 100644 --- a/src/tado/metrics.rs +++ b/src/tado/metrics.rs @@ -1,23 +1,69 @@ use std::convert::Infallible; +use super::model::ZoneStateResponse; + use lazy_static::lazy_static; use hyper::{header::CONTENT_TYPE, Body, Request, Response}; use prometheus::{Encoder, GaugeVec, TextEncoder}; lazy_static! { - pub static ref TEMPERATURE_GAUGE: GaugeVec = register_gauge_vec!( - "tado_temperature_degre", + pub static ref ACTIVITY_HEATING_POWER: GaugeVec = register_gauge_vec!( + "tado_activity_heating_power_percentage", + "The % of heating power in a specific zone.", + &["zone"] + ).unwrap(); + + pub static ref SETTING_TEMPERATURE: GaugeVec = register_gauge_vec!( + "tado_setting_temperature_value", + "The temperature of a specific zone in celsius degres.", + &["zone", "unit"] + ).unwrap(); + + pub static ref SENSOR_TEMPERATURE: GaugeVec = register_gauge_vec!( + "tado_sensor_temperature_value", "The temperature of a specific zone in celsius degres.", &["zone", "unit"] ).unwrap(); - pub static ref HUMIDITY_PERCENTAGE: GaugeVec = register_gauge_vec!( - "tado_humidity_percentage", + pub static ref SENSOR_HUMIDITY_PERCENTAGE: GaugeVec = register_gauge_vec!( + "tado_sensor_humidity_percentage", "The % of humidity in a specific zone.", &["zone"] ).unwrap(); } +pub fn set(zones: Vec) { + for zone in zones { + // setting temperature + let value: f64 = zone.state_response.setting.temperature.celsius; + SETTING_TEMPERATURE.with_label_values(&[zone.name.as_str(), "celsius"]).set(value); + info!("-> {} -> setting temperature (celsius): {}", zone.name, value); + + let value: f64 = zone.state_response.setting.temperature.fahrenheit; + SETTING_TEMPERATURE.with_label_values(&[zone.name.as_str(), "fahrenheit"]).set(value); + info!("-> {} -> setting temperature (fahrenheit): {}", zone.name, value); + + // sensor temperature + let value: f64 = zone.state_response.sensorDataPoints.insideTemperature.celsius; + SENSOR_TEMPERATURE.with_label_values(&[zone.name.as_str(), "celsius"]).set(value); + info!("-> {} -> sensor temperature (celsius): {}", zone.name, value); + + let value: f64 = zone.state_response.sensorDataPoints.insideTemperature.fahrenheit; + SENSOR_TEMPERATURE.with_label_values(&[zone.name.as_str(), "fahrenheit"]).set(value); + info!("-> {} -> sensor temperature (fahrenheit): {}", zone.name, value); + + // sensor humidity + let value: f64 = zone.state_response.sensorDataPoints.humidity.percentage; + SENSOR_HUMIDITY_PERCENTAGE.with_label_values(&[zone.name.as_str()]).set(value); + info!("-> {} -> sensor humidity: {}%", zone.name, value); + + // heating power + let value: f64 = zone.state_response.activityDataPoints.heatingPower.percentage; + ACTIVITY_HEATING_POWER.with_label_values(&[zone.name.as_str()]).set(value); + info!("-> {} -> heating power: {}%", zone.name, value); + } +} + pub async fn renderer(_req: Request) -> Result, Infallible> { let metrics = prometheus::gather(); let mut buffer = vec![]; diff --git a/src/tado/model.rs b/src/tado/model.rs index 6e97b58..1a76165 100644 --- a/src/tado/model.rs +++ b/src/tado/model.rs @@ -22,9 +22,34 @@ pub struct ZonesApiResponse { #[derive(Deserialize, Debug)] #[allow(non_snake_case)] pub struct ZoneStateApiResponse { + pub setting: ZoneStateSettingApiResponse, + pub activityDataPoints: ZoneStateActivityDataPointsApiResponse, pub sensorDataPoints: ZoneStateSensorDataPointsApiResponse, } +#[derive(Deserialize, Debug)] +#[allow(non_snake_case)] +pub struct ZoneStateSettingApiResponse { + pub temperature: ZoneStateSettingTemperatureApiResponse, +} + +#[derive(Deserialize, Debug)] +pub struct ZoneStateSettingTemperatureApiResponse { + pub celsius: f64, + pub fahrenheit: f64, +} + +#[derive(Deserialize, Debug)] +#[allow(non_snake_case)] +pub struct ZoneStateActivityDataPointsApiResponse { + pub heatingPower: ActivityDataPointsHeatingPowerApiResponse, +} + +#[derive(Deserialize, Debug)] +pub struct ActivityDataPointsHeatingPowerApiResponse { + pub percentage: f64, +} + #[derive(Deserialize, Debug)] #[allow(non_snake_case)] pub struct ZoneStateSensorDataPointsApiResponse { @@ -42,3 +67,8 @@ pub struct SensorDataPointsInsideTemperatureApiResponse { pub struct SensorDataPointsHumidityApiResponse { pub percentage: f64, } + +pub struct ZoneStateResponse { + pub name: String, + pub state_response: ZoneStateApiResponse, +} \ No newline at end of file