Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add gauge for open window sensor reading #189

Merged
merged 4 commits into from
Nov 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 142 additions & 1 deletion src/tado/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,11 +202,16 @@ impl Client {
}

#[cfg(test)]

mod tests {
use super::*;

use crate::tado::model::{
WeatherOutsideTemperatureApiResponse, WeatherSolarIntensityApiResponse,
ActivityDataPointsHeatingPowerApiResponse, SensorDataPointsHumidityApiResponse,
SensorDataPointsInsideTemperatureApiResponse, WeatherOutsideTemperatureApiResponse,
WeatherSolarIntensityApiResponse, ZoneStateActivityDataPointsApiResponse,
ZoneStateApiResponse, ZoneStateSensorDataPointsApiResponse, ZoneStateSettingApiResponse,
ZoneStateSettingTemperatureApiResponse,
};

use rstest::*;
Expand Down Expand Up @@ -306,4 +311,140 @@ mod tests {
// THEN
assert_eq!(actual, expected);
}

#[rstest(response_str, expected,
case(
r#"{
"setting":{
"type":"tado",
"temperature":{
"celsius":21.53,
"fahrenheit":70.75
}
},
"activityDataPoints":{
"heatingPower":{
"percentage":0.0
},
"acPower":null
},
"openWindowDetected":true,
"sensorDataPoints":{
"insideTemperature":{
"celsius":25.0,
"fahrenheit":77.0
},
"humidity":{
"percentage":75.0
}
}
}"#,
ZoneStateApiResponse {
setting : ZoneStateSettingApiResponse {
deviceType: "tado".to_string(),
temperature: Some(ZoneStateSettingTemperatureApiResponse {
celsius: 21.53,
fahrenheit: 70.75
})
},
activityDataPoints : ZoneStateActivityDataPointsApiResponse {
heatingPower : Some(ActivityDataPointsHeatingPowerApiResponse {
percentage: 0.0
}),
acPower : None
},
openWindowDetected: Some(true),
sensorDataPoints: ZoneStateSensorDataPointsApiResponse {
insideTemperature : Some(SensorDataPointsInsideTemperatureApiResponse {
celsius: 25.0,
fahrenheit: 77.0
}),
humidity : Some(SensorDataPointsHumidityApiResponse {
percentage: 75.0
})
}
}
),
case(
r#"{
"setting":{
"type":"tado",
"temperature":{
"celsius":21.53,
"fahrenheit":70.75
}
},
"activityDataPoints":{
"heatingPower":{
"percentage":0.0
},
"acPower":null
},
"sensorDataPoints":{
"insideTemperature":{
"celsius":25.0,
"fahrenheit":77.0
},
"humidity":{
"percentage":75.0
}
}
}"#,
ZoneStateApiResponse {
setting : ZoneStateSettingApiResponse {
deviceType: "tado".to_string(),
temperature: Some(ZoneStateSettingTemperatureApiResponse {
celsius: 21.53,
fahrenheit: 70.75
})
},
activityDataPoints : ZoneStateActivityDataPointsApiResponse {
heatingPower : Some(ActivityDataPointsHeatingPowerApiResponse {
percentage: 0.0
}),
acPower : None
},
openWindowDetected: None,
sensorDataPoints: ZoneStateSensorDataPointsApiResponse {
insideTemperature : Some(SensorDataPointsInsideTemperatureApiResponse {
celsius: 25.0,
fahrenheit: 77.0
}),
humidity : Some(SensorDataPointsHumidityApiResponse {
percentage: 75.0
})
}
}
)
)]
#[actix_rt::test]
async fn test_zone_state(response_str: &str, expected: ZoneStateApiResponse) {
/*
GIVEN an OSM client
WHEN calling the zone_state() function
THEN returns the zone state
*/

// GIVEN
let mock_server = MockServer::start().await;

Mock::given(method("GET"))
.and(path("api/v2/homes/0/zones/0/state"))
.respond_with(ResponseTemplate::new(200).set_body_raw(response_str, "application/json"))
.mount(&mock_server)
.await;

let mut client = Client::with_base_url(
mock_server.uri().parse().unwrap(),
"username".to_string(),
"passwored".to_string(),
"client_secret".to_string(),
);

// WHEN
let actual = client.zone_state(0).await.unwrap();

// THEN
assert_eq!(actual, expected);
}
}
29 changes: 29 additions & 0 deletions src/tado/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ lazy_static! {
&["unit"]
)
.unwrap();
pub static ref SENSOR_WINDOW_OPENED: GaugeVec = register_gauge_vec!(
"tado_sensor_window_opened",
"1 if the sensor detected a window is open, 0 otherwise.",
&["zone", "type"]
)
.unwrap();
}

pub fn set_zones(zones: Vec<ZoneStateResponse>) {
Expand Down Expand Up @@ -95,6 +101,29 @@ pub fn set_zones(zones: Vec<ZoneStateResponse>) {
);
}

// If openWindowDetected is not None, this means that a window is open.
if zone.state_response.openWindowDetected.is_some() {
info!(
"-> {} ({}) -> window opened: {}",
zone.name,
device_type.as_str(),
true
);
SENSOR_WINDOW_OPENED
.with_label_values(&[zone.name.as_str(), device_type.as_str()])
.set(1.0);
} else {
info!(
"-> {} ({}) -> window opened: {}",
zone.name,
device_type.as_str(),
false
);
SENSOR_WINDOW_OPENED
.with_label_values(&[zone.name.as_str(), device_type.as_str()])
.set(0.0);
}

// sensor temperature
if let Some(inside_temperature) = zone.state_response.sensorDataPoints.insideTemperature {
// celsius
Expand Down
20 changes: 10 additions & 10 deletions src/tado/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,61 +21,61 @@ pub struct ZonesApiResponse {
pub name: String,
}

#[derive(Deserialize, Debug)]
#[derive(Deserialize, Debug, PartialEq)]
#[allow(non_snake_case)]
pub struct ZoneStateApiResponse {
pub setting: ZoneStateSettingApiResponse,
pub activityDataPoints: ZoneStateActivityDataPointsApiResponse,
pub sensorDataPoints: ZoneStateSensorDataPointsApiResponse,
// pub openWindow: Option<ZoneStateOpenWindowApiResponse>,
// pub openWindowDetection: Option<?>,
pub openWindowDetected: Option<bool>,
}

#[derive(Deserialize, Debug)]
#[derive(Deserialize, Debug, PartialEq)]
#[allow(non_snake_case)]
pub struct ZoneStateSettingApiResponse {
#[serde(rename = "type")]
pub deviceType: String,
pub temperature: Option<ZoneStateSettingTemperatureApiResponse>,
}

#[derive(Deserialize, Debug)]
#[derive(Deserialize, Debug, PartialEq)]
pub struct ZoneStateSettingTemperatureApiResponse {
pub celsius: f64,
pub fahrenheit: f64,
}

#[derive(Deserialize, Debug)]
#[derive(Deserialize, Debug, PartialEq)]
#[allow(non_snake_case)]
pub struct ZoneStateActivityDataPointsApiResponse {
pub heatingPower: Option<ActivityDataPointsHeatingPowerApiResponse>,
pub acPower: Option<ActivityDataPointsAcPowerApiResponse>,
}

#[derive(Deserialize, Debug)]
#[derive(Deserialize, Debug, PartialEq)]
pub struct ActivityDataPointsHeatingPowerApiResponse {
pub percentage: f64,
}

#[derive(Deserialize, Debug)]
#[derive(Deserialize, Debug, PartialEq, Eq)]
pub struct ActivityDataPointsAcPowerApiResponse {
pub value: String,
}

#[derive(Deserialize, Debug)]
#[derive(Deserialize, Debug, PartialEq)]
#[allow(non_snake_case)]
pub struct ZoneStateSensorDataPointsApiResponse {
pub insideTemperature: Option<SensorDataPointsInsideTemperatureApiResponse>,
pub humidity: Option<SensorDataPointsHumidityApiResponse>,
}

#[derive(Deserialize, Debug)]
#[derive(Deserialize, Debug, PartialEq)]
pub struct SensorDataPointsInsideTemperatureApiResponse {
pub celsius: f64,
pub fahrenheit: f64,
}

#[derive(Deserialize, Debug)]
#[derive(Deserialize, Debug, PartialEq)]
pub struct SensorDataPointsHumidityApiResponse {
pub percentage: f64,
}
Expand Down