diff --git a/homekit-controller/src/homekit_http.rs b/homekit-controller/src/homekit_http.rs index 7e15448..5841277 100644 --- a/homekit-controller/src/homekit_http.rs +++ b/homekit-controller/src/homekit_http.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use chacha20poly1305::{ aead::generic_array::GenericArray, AeadInPlace, ChaCha20Poly1305, KeyInit, Nonce, }; @@ -8,7 +10,7 @@ use tokio::{ }; use crate::{ - pairing_data::{Accessory, ServiceCharacteristic}, + pairing_data::{AccessoryService, ServiceCharacteristic}, tlv8::TlvEncode, HomekitError, }; @@ -81,7 +83,9 @@ impl AccessorySocket { .await } - pub async fn get_accessories(&mut self) -> Result, HomekitError> { + pub async fn get_accessories( + &mut self, + ) -> Result>, HomekitError> { let response = self.req("/accessories", Method::GET, None, &[]).await?; let accessories: crate::pairing_data::Accessories = serde_json::from_slice(&response)?; Ok(accessories.accessories) @@ -90,11 +94,12 @@ impl AccessorySocket { pub async fn get_characteristics( &mut self, characteristics: &[String], - ) -> Result, HomekitError> { + ) -> Result, HomekitError> { let characteristic_request = characteristics.join(","); let url = format!("/characteristics?id={characteristic_request}&meta=1&perms=1&type=1&ev=1"); let response = self.req(&url, Method::GET, None, &[]).await?; + log::info!("got: {}", std::str::from_utf8(&response).unwrap()); let characteristics: crate::pairing_data::Characteristics = serde_json::from_slice(&response)?; diff --git a/homekit-controller/src/pairing_data/characteristics.rs b/homekit-controller/src/pairing_data/characteristics.rs index d1572dd..015208e 100644 --- a/homekit-controller/src/pairing_data/characteristics.rs +++ b/homekit-controller/src/pairing_data/characteristics.rs @@ -11,7 +11,7 @@ where CharacteristicTypeInner::deserialize(deserializer).map(|v| v.into()) } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub enum CharacteristicType { AccessControlLevel, @@ -1228,7 +1228,7 @@ where ServiceTypeInner::deserialize(deserializer).map(|v| v.into()) } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] pub enum ServiceType { AccessControl, AccessoryInformation, diff --git a/homekit-controller/src/pairing_data/mod.rs b/homekit-controller/src/pairing_data/mod.rs index e531572..bb113da 100644 --- a/homekit-controller/src/pairing_data/mod.rs +++ b/homekit-controller/src/pairing_data/mod.rs @@ -1,11 +1,13 @@ -use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +use serde::{Deserialize, Deserializer, Serialize}; use characteristics::{deserialize_characteristic_type, deserialize_service_type}; pub use characteristics::{CharacteristicType, ServiceType}; mod characteristics; -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone)] pub struct DevicePairingData { #[serde(rename = "AccessoryPairingID")] pub accessory_pairing_id: String, @@ -23,88 +25,227 @@ pub struct DevicePairingData { pub accessory_port: usize, #[serde(rename = "Connection")] pub connection: ConnectionType, - #[serde(rename = "accessories")] - pub accessories: Option>, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct Accessory { - #[serde(rename = "aid")] - pub id: usize, - pub services: Vec, + #[serde(default, deserialize_with = "deserialize_accessories")] + pub accessories: HashMap>, } #[derive(Deserialize)] pub struct Accessories { - pub accessories: Vec, + #[serde(default, deserialize_with = "deserialize_accessories")] + pub accessories: HashMap>, } -impl Accessory { - pub fn get_service_ids(&self) -> Vec { - self.services +fn deserialize_accessories<'de, D>( + deserializer: D, +) -> Result>, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + struct Accessory { + aid: usize, + #[serde(deserialize_with = "deserialize_accessory_service")] + services: HashMap, + } + Vec::::deserialize(deserializer).map(|v| { + v.into_iter().fold(HashMap::new(), |mut map, accessory| { + map.insert(accessory.aid, accessory.services); + map + }) + }) +} + +#[derive(Debug, Clone)] +pub struct AccessoryService { + pub name: Option, + pub service_type: ServiceType, + pub primary: bool, + pub hidden: bool, + pub characteristics: HashMap, +} + +fn deserialize_accessory_service<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + struct AccessoryServiceInner { + #[serde(skip)] + name: Option, + iid: usize, + #[serde(rename = "type", deserialize_with = "deserialize_service_type")] + service_type: ServiceType, + primary: bool, + hidden: bool, + #[serde(deserialize_with = "deserialize_service_characteristics")] + characteristics: HashMap, + } + impl From for AccessoryService { + fn from(val: AccessoryServiceInner) -> Self { + Self { + name: val.name, + service_type: val.service_type, + primary: val.primary, + hidden: val.hidden, + characteristics: val.characteristics, + } + } + } + let mut services = Vec::::deserialize(deserializer)?; + for service in &mut services { + if let Some((name_key, Some(Data::String(name)))) = service + .characteristics .iter() - .flat_map(|service| { - service - .characteristics - .iter() - .map(|c| format!("{}.{}", self.id, c.id)) - }) - .collect() + .find(|(_, v)| v.characteristic_type == CharacteristicType::Name) + .map(|(k, v)| (*k, v.value.clone())) + { + service.name = Some(name); + service.characteristics.remove(&name_key); + } + } + + Ok(services + .into_iter() + .fold(HashMap::new(), |mut map, service| { + map.insert(service.iid, service.into()); + map + })) +} + +#[derive(Debug, Clone)] +pub struct ServiceCharacteristic { + pub characteristic_type: CharacteristicType, + pub perms: Vec, + pub value: Option, + pub event_notifications_enabled: Option, + pub enc: Option, + pub max_data_len: Option, + pub min_value: Option, + pub max_value: Option, + pub min_step: Option, + pub unit: Option, +} + +impl ServiceCharacteristic { + pub fn update_from(&mut self, other: &ServiceCharacteristic) { + self.value = other.value.clone(); + if other.event_notifications_enabled.is_some() { + self.event_notifications_enabled = other.event_notifications_enabled; + } + if other.enc.is_some() { + self.enc = other.enc; + } + if other.max_data_len.is_some() { + self.max_data_len = other.max_data_len; + } + if other.min_value.is_some() { + self.min_value = other.min_value; + } + if other.max_value.is_some() { + self.max_value = other.max_value; + } + if other.min_step.is_some() { + self.min_step = other.min_step; + } + if other.unit.is_some() { + self.unit = other.unit; + } } } -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct AccessoryService { - #[serde(rename = "iid")] - pub id: usize, - #[serde(rename = "type", deserialize_with = "deserialize_service_type")] - service_type: ServiceType, - pub primary: bool, - pub hidden: bool, - pub characteristics: Vec, +#[derive(Deserialize, Debug, Clone)] +pub enum CharacteristicPermissions { + #[serde(rename = "pr")] + PairedRead, + #[serde(rename = "pw")] + PairedWrite, + #[serde(rename = "ev")] + Events, + #[serde(rename = "aa")] + AdditionalAuthorization, + #[serde(rename = "tw")] + TimedWrite, + #[serde(rename = "hd")] + Hidden, + #[serde(rename = "wr")] + WriteResponse, } -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct ServiceCharacteristic { - #[serde(rename = "iid")] - pub id: usize, - #[serde(rename = "type", deserialize_with = "deserialize_characteristic_type")] - pub characteristic_type: CharacteristicType, - pub perms: Vec, - #[serde(flatten)] - pub value: Option, - pub ev: Option, - pub enc: Option, - #[serde(rename = "maxDataLen")] - pub max_data_len: Option, - #[serde(rename = "minValue")] - pub min_value: Option, - #[serde(rename = "maxValue")] - pub max_value: Option, - #[serde(rename = "minStep")] - pub min_step: Option, - pub unit: Option, +fn deserialize_service_characteristics<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + struct ServiceCharacteristicInner { + #[serde(rename = "iid")] + iid: usize, + #[serde(rename = "type", deserialize_with = "deserialize_characteristic_type")] + characteristic_type: CharacteristicType, + perms: Vec, + #[serde(flatten)] + value: Option, + #[serde(rename = "ev")] + event_notifications_enabled: Option, + enc: Option, + #[serde(rename = "maxDataLen")] + max_data_len: Option, + #[serde(rename = "minValue")] + min_value: Option, + #[serde(rename = "maxValue")] + max_value: Option, + #[serde(rename = "minStep")] + min_step: Option, + unit: Option, + } + impl From for ServiceCharacteristic { + fn from(value: ServiceCharacteristicInner) -> Self { + Self { + characteristic_type: value.characteristic_type, + perms: value.perms, + value: value.value, + event_notifications_enabled: value.event_notifications_enabled, + enc: value.enc, + max_data_len: value.max_data_len, + min_value: value.min_value, + max_value: value.max_value, + min_step: value.min_step, + unit: value.unit, + } + } + } + Vec::::deserialize(deserializer).map(|v| { + v.into_iter() + .fold(HashMap::new(), |mut map, characteristic| { + map.insert(characteristic.iid, characteristic.into()); + map + }) + }) } #[derive(Deserialize)] pub struct Characteristics { - pub characteristics: Vec, + #[serde(deserialize_with = "deserialize_service_characteristics")] + pub characteristics: HashMap, } #[allow(clippy::enum_variant_names)] #[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(tag = "format", content = "value")] +#[serde(tag = "format", content = "value", rename_all = "lowercase")] pub enum Data { - #[serde(rename = "string")] - String(String), - #[serde(rename = "bool")] Bool(bool), - #[serde(rename = "data")] - Data(String), - #[serde(rename = "uint32")] - Uint(u32), - #[serde(rename = "float")] + Uint8(u8), + Uint16(u16), + Uint32(u32), + Uint64(u64), + Int(i32), Float(f64), + String(String), + Tlv8(String), + Data(String), } #[derive(Serialize, Deserialize, Debug, Clone, Copy)] @@ -116,3 +257,13 @@ pub enum ConnectionType { #[serde(rename = "ADDITIONAL_PAIRING")] AdditionalPairing, } + +#[derive(Serialize, Deserialize, Debug, Clone, Copy)] +#[serde(rename_all = "lowercase")] +pub enum Unit { + Celsius, + Percentage, + ArcDegrees, + Lux, + Seconds, +}