Skip to content

Commit

Permalink
feat: added new metrics (heating power % and setting temperature)
Browse files Browse the repository at this point in the history
  • Loading branch information
eko committed Feb 28, 2020
1 parent a8970cf commit 5b23f23
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 33 deletions.
9 changes: 6 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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);
Expand All @@ -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);
}
});
}
46 changes: 20 additions & 26 deletions src/tado/client.rs
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -81,13 +81,13 @@ impl Client {
Ok(resp.json::<ZoneStateApiResponse>().await?)
}

pub async fn retrieve(&mut self) {
pub async fn retrieve(&mut self) -> Vec<ZoneStateResponse> {
// 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();
}
};

Expand All @@ -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();
}
};

Expand All @@ -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::<ZoneStateResponse>::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;
}
}
54 changes: 50 additions & 4 deletions src/tado/metrics.rs
Original file line number Diff line number Diff line change
@@ -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<ZoneStateResponse>) {
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<Body>) -> Result<Response<Body>, Infallible> {
let metrics = prometheus::gather();
let mut buffer = vec![];
Expand Down
30 changes: 30 additions & 0 deletions src/tado/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -42,3 +67,8 @@ pub struct SensorDataPointsInsideTemperatureApiResponse {
pub struct SensorDataPointsHumidityApiResponse {
pub percentage: f64,
}

pub struct ZoneStateResponse {
pub name: String,
pub state_response: ZoneStateApiResponse,
}

0 comments on commit 5b23f23

Please sign in to comment.