geostar-symphony 0.1.3

An async wrapper around the Geostar Symphony cloud service API
Documentation
use std::str::FromStr;

use compact_str::CompactString;
use serde::{Deserialize, Serialize};
use serde_with::{DeserializeFromStr, DisplayFromStr, serde_as};

use crate::error::InvalidGatewayKind;

#[derive(Debug, Clone, Copy)]
pub enum TemperatureUnit {
	Fahrenheit,
	Celsius,
}

impl TemperatureUnit {
	pub(crate) fn as_cookie_str(self) -> &'static str {
		match self {
			Self::Fahrenheit => "temp_unit=f",
			Self::Celsius => "temp_unit=c",
		}
	}
}

impl FromStr for TemperatureUnit {
	type Err = &'static str;

	fn from_str(s: &str) -> Result<Self, Self::Err> {
		match s {
			"F" | "f" => Ok(Self::Fahrenheit),
			"C" | "c" => Ok(Self::Celsius),
			_ => Err("Invalid tempearture unit; must be F or C"),
		}
	}
}

#[derive(Debug, Serialize)]
pub(crate) struct LoginRequest<'a> {
	op: &'static str,
	redirect: &'static str,
	emailaddress: &'a str,
	password: &'a str,
}

impl<'a> LoginRequest<'a> {
	#[inline]
	pub(crate) fn new(email: &'a str, password: &'a str) -> Self {
		Self { op: "login", redirect: "/", emailaddress: email, password }
	}
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct User {
	pub emailaddress: CompactString,
	//username
	//firstname
	//lastname
	pub name: CompactString,
	pub awl_user_key: u32,
	pub contact_user_key: u16,
	pub custkey: CompactString,
	pub has_portal_permissions: bool,
	pub customer_mode: bool,
	//portal_permissions
	pub is_dealer: bool,
	//portal_dist_permissions
	pub is_distributor: bool,
	//current_location
	//current_gwid
	//current_gwid_desc
	//current_gateway
	pub has_devices: bool,
	#[serde(rename = "type")]
	pub kind: u8,
	pub is_registered: bool,
	pub country: isocountry::CountryCode,
}

#[derive(Debug, Serialize)]
pub(crate) struct GetLocationRequest {
	pub(crate) awluserkey: u32,
}

#[derive(Debug, Deserialize)]
pub struct Gateway {
	pub gwid: CompactString,
	pub description: CompactString,
	#[serde(rename = "type")]
	pub kind: GatewayKind,
	pub gwkey: u32,
}

#[derive(Debug, Clone, Copy, DeserializeFromStr)]
pub enum GatewayKind {
	AuroraWebLink,
}

impl FromStr for GatewayKind {
	type Err = InvalidGatewayKind;

	#[inline]
	fn from_str(s: &str) -> Result<Self, Self::Err> {
		match s {
			"AWL" => Ok(Self::AuroraWebLink),
			_ => Err(InvalidGatewayKind),
		}
	}
}

#[serde_as]
#[derive(Debug, Deserialize)]
pub struct Location {
	pub description: CompactString,
	pub locationkey: u32,
	#[serde_as(as = "DisplayFromStr")]
	pub latitude: f64,
	#[serde_as(as = "DisplayFromStr")]
	pub longitude: f64,
	pub custkey: CompactString,
	pub gateways: Vec<Gateway>,
}

#[derive(Debug, Deserialize)]
pub(crate) struct LocationResponse {
	pub(crate) data: Vec<Location>,
}

#[derive(Debug, Serialize)]
pub(crate) struct WsLoginRequest<'a> {
	pub(crate) cmd: &'static str,
	pub(crate) tid: u8,
	pub(crate) source: &'static str,
	pub(crate) sessionid: &'a str,
}

#[derive(Debug, Serialize)]
pub(crate) struct DataRequest<'a> {
	pub(crate) cmd: &'static str,
	pub(crate) tid: u8,
	pub(crate) awlid: &'a str,
	pub(crate) zone: u8,
	pub(crate) rlist: &'static [&'static str],
	pub(crate) source: &'static str,
}

#[derive(Debug, Deserialize)]
pub(crate) struct DataResponse {
	rsp: CompactString,
	awlid: CompactString,
	tid: u8,
	err: CompactString,
	compressorpower: u16,
	zone: u8,
	fanpower: u16,
	auxpower: u16,
	looppumppower: u16,
	totalunitpower: u16,
	awlabctype: u8,
	modeofoperation: u8, // heat stg1=5; heat stg2=6; emheat=7
	actualcompressorspeed: u8,
	airflowcurrentspeed: u8, // Fan motor speed "stage"
	auroraoutputeh1: u8,
	auroraoutputeh2: u8,
	auroraoutputcc: u8,  // compressor stage? cool stg1=1; heat stg1=1; heat stg2=1
	auroraoutputcc2: u8, // heat stg2=1
	tstatdehumidsetpoint: u8,
	tstatrelativehumidity: u8,
	leavingairtemp: f64,    // Supply air
	tstatroomtemp: f64,     // Return air
	enteringwatertemp: f64, // Loop water
	aocenteringwatertemp: f64,
	auroraoutputrv: u8,
	awltstattype: u8,
	humidity_offset_settings: HumidityOffsetSettings,
	iz2_humidity_offset_settings: HumidityOffsetSettings,
	dehumid_humid_sp: DehumidificationSettings,
	iz2_dehumid_humid_sp: DehumidificationSettings,
	lockoutstatus: LockoutStatus,
	lastfault: u8,
	lastlockout: LastLockout,
	homeautomationalarm1: u8,
	homeautomationalarm2: u8,
	roomtemp: u8,
	activesettings: ActiveSettings,
	tstatactivesetpoint: u8,
	tstatmode: u8,
	tstatheatingsetpoint: u8,
	tstatcoolingsetpoint: u8,
}

#[derive(Debug, Deserialize)]
struct HumidityOffsetSettings {
	humidity_offset: u8,
	#[serde(rename = "humdity_control_option")]
	humidity_control_option: u8,
	dehumidification_mode: u8,
	humidification_mode: u8,
}

#[derive(Debug, Deserialize)]
struct DehumidificationSettings {
	dehumidification: u8,
	humidification: u8,
}

#[derive(Debug, Deserialize)]
struct LockoutStatus {
	lockoutstatuscode: u8,
	lockedout: u8,
}

#[derive(Debug, Deserialize)]
struct LastLockout {
	lockoutstatuslast: u8,
}

#[derive(Debug, Deserialize)]
struct ActiveSettings {
	temporaryoverride: u8,
	permanenthold: u8,
	vacationhold: u8,
	onpeakhold: u8,
	superboost: u8,
	tstatmode: u8,
	activemode: u8, // heat stg1=3; heat stg2=3; emheat=4
	heatingsp_read: u8,
	coolingsp_read: u8,
	fanmode_read: u8,
	intertimeon_read: u8,
	intertimeoff_read: u8,
}

#[derive(Debug)]
/// Opinionated response to a request for data - has some fields renamed and many removed from the "raw" response
pub struct GatewayData {
	pub zone: u8,
	pub total_power: u16,
	pub compressor_power: u16,
	pub fan_power: u16,
	pub loop_pump_power: u16,
	pub aux_heat_power: u16,
	pub compressor_stage: u8,
	pub aux_heat_stage: u8,
	pub fan_speed_setting: u8,
	pub dehumidify_setpoint: u8,
	pub relative_humidity: u8,
	pub room_temp: u8,
	pub supply_air_temp: f64,
	pub return_air_temp: f64,
	pub return_water_temp: f64,
	pub heating_setpoint: u8,
	pub cooling_setpoint: u8,
	pub active_setpoint: u8,
	pub mode: Option<ThermostatMode>,
	pub fan_mode: Option<FanMode>,
}

impl From<DataResponse> for GatewayData {
	fn from(raw: DataResponse) -> Self {
		Self {
			zone: raw.zone,
			total_power: raw.totalunitpower,
			compressor_power: raw.compressorpower,
			fan_power: raw.fanpower,
			loop_pump_power: raw.looppumppower,
			aux_heat_power: raw.auxpower,
			compressor_stage: raw.auroraoutputcc + raw.auroraoutputcc2,
			aux_heat_stage: raw.auroraoutputeh1 + raw.auroraoutputeh2,
			fan_speed_setting: raw.airflowcurrentspeed,
			dehumidify_setpoint: raw.tstatdehumidsetpoint,
			relative_humidity: raw.tstatrelativehumidity,
			room_temp: raw.roomtemp,
			supply_air_temp: raw.leavingairtemp,
			return_air_temp: raw.tstatroomtemp,
			return_water_temp: raw.enteringwatertemp,
			heating_setpoint: raw.tstatheatingsetpoint,
			cooling_setpoint: raw.tstatcoolingsetpoint,
			active_setpoint: raw.tstatactivesetpoint,
			mode: match raw.activesettings.activemode {
				0 => Some(ThermostatMode::Off),
				1 => Some(ThermostatMode::Auto),
				2 => Some(ThermostatMode::Cool),
				3 => Some(ThermostatMode::Heat),
				4 => Some(ThermostatMode::EmergencyHeat),
				_ => None,
			},
			fan_mode: match raw.activesettings.fanmode_read {
				0 => Some(FanMode::Auto),
				1 => Some(FanMode::Continuous),
				2 => Some(FanMode::Intermittent { off: raw.activesettings.intertimeoff_read, on: raw.activesettings.intertimeon_read }),
				_ => None,
			},
		}
	}
}

#[derive(Debug, Clone, Copy)]
pub enum ThermostatMode {
	Off,           // 0
	Auto,          // 1
	Cool,          // 2
	Heat,          // 3
	EmergencyHeat, // 4
}

impl ThermostatMode {
	pub fn as_u8(self) -> u8 {
		match self {
			Self::Off => 0,
			Self::Auto => 1,
			Self::Cool => 2,
			Self::Heat => 3,
			Self::EmergencyHeat => 4,
		}
	}
}

#[derive(Debug, Clone, Copy)]
pub enum FanMode {
	Auto,                             // 0
	Continuous,                       // 1
	Intermittent { off: u8, on: u8 }, // 2
}

impl FanMode {
	pub fn as_u8(self) -> u8 {
		match self {
			Self::Auto => 0,
			Self::Continuous => 1,
			Self::Intermittent { .. } => 2,
		}
	}
}