Skip to content

Commit

Permalink
Merge pull request #189 from acoster/open-window
Browse files Browse the repository at this point in the history
Add gauge for open window sensor reading
  • Loading branch information
eko committed Nov 20, 2022
2 parents e02edc2 + 56c8407 commit d50bc22
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 11 deletions.
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

0 comments on commit d50bc22

Please sign in to comment.