use serde::{Deserialize, Deserializer, Serialize};
use crate::types::{SetPointStatus, QuickAction};
fn deserialize_null_as_empty<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de>,
{
let opt = Option::deserialize(deserializer)?;
Ok(opt.unwrap_or_default())
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Zone {
pub id: String,
pub name: Option<String>,
pub temperature: f64,
pub target_heat_temperature: f64,
pub min_heat_setpoint: f64,
pub max_heat_setpoint: f64,
pub is_alive: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub device_id: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mac_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thermostat_model_type: Option<String>,
#[serde(default)]
pub has_alerts: bool,
#[serde(default)]
pub has_comm_lost_alert: bool,
#[serde(default)]
pub has_battery_low_alert: bool,
#[serde(default)]
pub has_sensor_failure_alert: bool,
#[serde(default)]
pub override_active: bool,
#[serde(default)]
pub hold_temperature_permanently: bool,
#[serde(default)]
pub set_point_status: u8,
#[serde(default)]
pub thermostat_units: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thermostat_version: Option<String>,
}
impl Zone {
pub fn status(&self) -> SetPointStatus {
self.set_point_status.into()
}
pub fn is_following_schedule(&self) -> bool {
self.status() == SetPointStatus::FollowingSchedule
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Gateway {
pub id: String,
pub mac_id: Option<String>,
pub crc: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Location {
pub id: String,
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub city: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub country: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub postcode: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub street_address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub owner_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub time_zone_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub time_zone_display_name: Option<String>,
#[serde(default = "default_heating_system_type")]
pub heating_system_type: u8,
#[serde(default, deserialize_with = "deserialize_null_as_empty")]
pub zones: Vec<Zone>,
#[serde(default, deserialize_with = "deserialize_null_as_empty")]
pub gateways: Vec<Gateway>,
#[serde(default, deserialize_with = "deserialize_null_as_empty")]
pub notification_emails: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub system_mode_status: Option<SystemModeStatus>,
}
fn default_heating_system_type() -> u8 {
1
}
impl Location {
pub fn get_zone_by_id(&self, zone_id: &str) -> Option<&Zone> {
self.zones.iter().find(|z| z.id == zone_id)
}
pub fn get_zone_by_name(&self, name: &str) -> Option<&Zone> {
let name_lower = name.to_lowercase();
self.zones.iter().find(|z| z.name.as_deref().map(|n| n.to_lowercase()) == Some(name_lower.clone()))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct UserInfo {
pub username: String,
pub first_name: Option<String>,
pub last_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub street_address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub city: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub postcode: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub country_id: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub country_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub user_language: Option<i32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct SystemModeStatus {
pub mode: u8,
pub is_permanent: bool,
}
impl SystemModeStatus {
pub fn action(&self) -> QuickAction {
self.mode.into()
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub(crate) struct ApiResponse<T> {
pub content: Option<T>,
pub errors: Option<Vec<ApiError>>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub(crate) struct ApiError {
pub message: String,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "PascalCase")]
pub(crate) struct LoginRequest {
pub email_address: String,
pub password: String,
pub is_service_status_returned: bool,
pub api_active: bool,
pub api_down: bool,
pub redirect_url: String,
pub events: Vec<String>,
pub form_errors: Vec<String>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct LoginResponse {
pub user_id: String,
pub display_name: Option<String>,
pub user_name: Option<String>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub(crate) struct LocationsContent {
pub locations: Vec<Location>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub(crate) struct LocationDetailContent {
pub location: Location,
#[serde(default, deserialize_with = "deserialize_null_as_empty")]
pub gateways: Vec<Gateway>,
#[serde(default, deserialize_with = "deserialize_null_as_empty")]
pub notification_emails: Vec<String>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub(crate) struct LocationSystemContent {
pub location_model: Location,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub(crate) struct AccountInfoContent {
pub user_info: UserInfo,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct SetTemperatureRequest {
pub zone_id: String,
pub heat_temperature: String,
pub hot_water_state_is_on: bool,
pub is_permanent: bool,
pub set_until_hours: String,
pub set_until_minutes: String,
pub location_time_offset_minutes: i32,
pub is_following_schedule: bool,
}