accessory names, better parsing etc

This commit is contained in:
Alex Janka 2024-02-23 12:14:56 +11:00
parent 65c976c851
commit 6ddabfbf11
4 changed files with 131 additions and 64 deletions

View file

@ -10,7 +10,7 @@ use tokio::{
}; };
use crate::{ use crate::{
pairing_data::{AccessoryService, ServiceCharacteristic}, pairing_data::{Accessory, ServiceCharacteristic},
tlv8::TlvEncode, tlv8::TlvEncode,
HomekitError, HomekitError,
}; };
@ -83,9 +83,7 @@ impl AccessorySocket {
.await .await
} }
pub async fn get_accessories( pub async fn get_accessories(&mut self) -> Result<HashMap<usize, Accessory>, HomekitError> {
&mut self,
) -> Result<HashMap<usize, HashMap<usize, AccessoryService>>, HomekitError> {
let response = self.req("/accessories", Method::GET, None, &[]).await?; let response = self.req("/accessories", Method::GET, None, &[]).await?;
let accessories: crate::pairing_data::Accessories = serde_json::from_slice(&response)?; let accessories: crate::pairing_data::Accessories = serde_json::from_slice(&response)?;
Ok(accessories.accessories) Ok(accessories.accessories)
@ -94,12 +92,18 @@ impl AccessorySocket {
pub async fn get_characteristics( pub async fn get_characteristics(
&mut self, &mut self,
characteristics: &[String], characteristics: &[String],
additional_data: bool,
) -> Result<HashMap<usize, ServiceCharacteristic>, HomekitError> { ) -> Result<HashMap<usize, ServiceCharacteristic>, HomekitError> {
let characteristic_request = characteristics.join(","); let characteristic_request = characteristics.join(",");
let url = let url = format!(
format!("/characteristics?id={characteristic_request}&meta=1&perms=1&type=1&ev=1"); "/characteristics?id={characteristic_request}{}",
if additional_data {
"&meta=1&perms=1&type=1&ev=1"
} else {
""
}
);
let response = self.req(&url, Method::GET, None, &[]).await?; let response = self.req(&url, Method::GET, None, &[]).await?;
log::info!("got: {}", std::str::from_utf8(&response).unwrap());
let characteristics: crate::pairing_data::Characteristics = let characteristics: crate::pairing_data::Characteristics =
serde_json::from_slice(&response)?; serde_json::from_slice(&response)?;
@ -146,11 +150,8 @@ impl AccessorySocket {
if let Some(encryption) = self.socket_encryption.as_mut() { if let Some(encryption) = self.socket_encryption.as_mut() {
let mut packets = Vec::new(); let mut packets = Vec::new();
let mut i = 0;
for packet in send_data.chunks(1024) { for packet in send_data.chunks(1024) {
i += 1;
let mut nonce = [0; 12]; let mut nonce = [0; 12];
nonce[4..] nonce[4..]
.copy_from_slice(&encryption.controller_to_accessory_counter.to_le_bytes()); .copy_from_slice(&encryption.controller_to_accessory_counter.to_le_bytes());
@ -176,7 +177,6 @@ impl AccessorySocket {
packets.extend_from_slice(&data); packets.extend_from_slice(&data);
} }
send_data = packets; send_data = packets;
log::info!("encrypted {i} chunks!");
} }
self.socket.write_all(&send_data).await?; self.socket.write_all(&send_data).await?;
@ -187,7 +187,6 @@ impl AccessorySocket {
let packet = self.get_next().await?; let packet = self.get_next().await?;
let result = response.parse(&packet)?; let result = response.parse(&packet)?;
log::info!("response:\n{response:#?}");
let header_size = match result { let header_size = match result {
httparse::Status::Complete(header_size) => Ok(header_size), httparse::Status::Complete(header_size) => Ok(header_size),

View file

@ -9,42 +9,26 @@ use thiserror::Error;
use tlv8::{HomekitState, TlvEncode, TlvError, TlvType}; use tlv8::{HomekitState, TlvEncode, TlvError, TlvType};
use x25519_dalek::{EphemeralSecret, PublicKey}; use x25519_dalek::{EphemeralSecret, PublicKey};
use pairing_data::DevicePairingData; use pairing_data::{Accessory, DevicePairingData};
pub use crate::pairing_data::{CharacteristicType, Data};
use crate::{ use crate::{
homekit_http::AccessorySocket, homekit_http::AccessorySocket,
tlv8::{decode, TlvEncodableData}, tlv8::{decode, TlvEncodableData},
}; };
mod homekit_http;
mod pairing_data; mod pairing_data;
mod tlv8; mod tlv8;
#[derive(Debug, Clone)] pub fn load(pairing_data: PathBuf) -> Result<HashMap<String, DevicePairingData>, HomekitError> {
pub struct HomekitController { Ok(serde_json::from_str(&std::fs::read_to_string(
devices: HashMap<String, DevicePairingData>, pairing_data,
)?)?)
} }
impl HomekitController {
pub fn load(pairing_data: PathBuf) -> Result<Self, HomekitError> {
let devices: HashMap<String, DevicePairingData> =
serde_json::from_str(&std::fs::read_to_string(pairing_data)?)?;
Ok(Self { devices })
}
pub async fn connect_to(&self, device_name: &str) -> Result<(), HomekitError> {
if let Some(device) = self.devices.get(device_name) {
device.connect().await
} else {
Err(HomekitError::NoPairingData)
}
}
}
mod homekit_http;
impl DevicePairingData { impl DevicePairingData {
pub async fn connect(&self) -> Result<(), HomekitError> { pub async fn connect(&self) -> Result<ConnectedDevice, HomekitError> {
let key = EphemeralSecret::random(); let key = EphemeralSecret::random();
let pubkey = PublicKey::from(&key); let pubkey = PublicKey::from(&key);
let mut socket = AccessorySocket::new(&self.accessory_ip, self.accessory_port).await?; let mut socket = AccessorySocket::new(&self.accessory_ip, self.accessory_port).await?;
@ -244,24 +228,50 @@ impl DevicePairingData {
socket.set_encryption(controller_to_accessory_key, accessory_to_controller_key); socket.set_encryption(controller_to_accessory_key, accessory_to_controller_key);
let accessories = socket.get_accessories().await?; let mut connected_device = ConnectedDevice {
accessories: socket.get_accessories().await?,
socket,
};
connected_device.characteristics_request(true).await?;
log::info!("accessories: {accessories:#?}"); Ok(connected_device)
for a in &accessories { }
log::info!("{}: service ids: {:#?}", a.id, a.get_service_ids()); }
pub struct ConnectedDevice {
pub accessories: HashMap<usize, Accessory>,
socket: AccessorySocket,
}
impl ConnectedDevice {
pub async fn update_characteristics(&mut self) -> Result<(), HomekitError> {
self.characteristics_request(true).await
} }
// now get characteristics async fn characteristics_request(&mut self, additional_data: bool) -> Result<(), HomekitError> {
let characteristics = socket for (aid, data) in &mut self.accessories {
.get_characteristics( for service in data.services.values_mut() {
&accessories let characteristic_ids = service
.iter() .characteristics
.flat_map(|v| v.get_service_ids()) .keys()
.collect::<Vec<_>>(), .map(|k| format!("{aid}.{k}"))
) .collect::<Vec<_>>();
let characteristics = self
.socket
.get_characteristics(&characteristic_ids, additional_data)
.await?; .await?;
for (cid, c) in &characteristics {
log::info!("characteristics:\n{characteristics:#?}"); if c.characteristic_type == CharacteristicType::Name {
if let Some(Data::String(name)) = &c.value {
service.name = Some(name.clone());
}
}
if let Some(prev) = service.characteristics.get_mut(cid) {
prev.update_from(c);
}
}
}
}
Ok(()) Ok(())
} }

View file

@ -26,30 +26,47 @@ pub struct DevicePairingData {
#[serde(rename = "Connection")] #[serde(rename = "Connection")]
pub connection: ConnectionType, pub connection: ConnectionType,
#[serde(default, deserialize_with = "deserialize_accessories")] #[serde(default, deserialize_with = "deserialize_accessories")]
pub accessories: HashMap<usize, HashMap<usize, AccessoryService>>, pub accessories: HashMap<usize, Accessory>,
}
#[derive(Debug, Clone)]
pub struct Accessory {
pub name: String,
pub services: HashMap<usize, AccessoryService>,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Accessories { pub struct Accessories {
#[serde(default, deserialize_with = "deserialize_accessories")] #[serde(default, deserialize_with = "deserialize_accessories")]
pub accessories: HashMap<usize, HashMap<usize, AccessoryService>>, pub accessories: HashMap<usize, Accessory>,
} }
fn deserialize_accessories<'de, D>( fn deserialize_accessories<'de, D>(deserializer: D) -> Result<HashMap<usize, Accessory>, D::Error>
deserializer: D,
) -> Result<HashMap<usize, HashMap<usize, AccessoryService>>, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
#[derive(Deserialize)] #[derive(Deserialize)]
struct Accessory { struct AccessoryInner {
aid: usize, aid: usize,
#[serde(deserialize_with = "deserialize_accessory_service")] #[serde(deserialize_with = "deserialize_accessory_service")]
services: HashMap<usize, AccessoryService>, services: HashMap<usize, AccessoryService>,
} }
Vec::<Accessory>::deserialize(deserializer).map(|v| {
Vec::<AccessoryInner>::deserialize(deserializer).map(|v| {
v.into_iter().fold(HashMap::new(), |mut map, accessory| { v.into_iter().fold(HashMap::new(), |mut map, accessory| {
map.insert(accessory.aid, accessory.services); let name = accessory
.services
.values()
.find(|v| v.name_for_accessory.is_some())
.and_then(|v| v.name_for_accessory.clone())
.unwrap_or(format!("Accessory ID {}", accessory.aid));
map.insert(
accessory.aid,
Accessory {
name,
services: accessory.services,
},
);
map map
}) })
}) })
@ -62,6 +79,7 @@ pub struct AccessoryService {
pub primary: bool, pub primary: bool,
pub hidden: bool, pub hidden: bool,
pub characteristics: HashMap<usize, ServiceCharacteristic>, pub characteristics: HashMap<usize, ServiceCharacteristic>,
name_for_accessory: Option<String>,
} }
fn deserialize_accessory_service<'de, D>( fn deserialize_accessory_service<'de, D>(
@ -74,6 +92,8 @@ where
struct AccessoryServiceInner { struct AccessoryServiceInner {
#[serde(skip)] #[serde(skip)]
name: Option<String>, name: Option<String>,
#[serde(skip)]
name_for_accessory: Option<String>,
iid: usize, iid: usize,
#[serde(rename = "type", deserialize_with = "deserialize_service_type")] #[serde(rename = "type", deserialize_with = "deserialize_service_type")]
service_type: ServiceType, service_type: ServiceType,
@ -90,6 +110,7 @@ where
primary: val.primary, primary: val.primary,
hidden: val.hidden, hidden: val.hidden,
characteristics: val.characteristics, characteristics: val.characteristics,
name_for_accessory: val.name_for_accessory,
} }
} }
} }
@ -101,7 +122,12 @@ where
.find(|(_, v)| v.characteristic_type == CharacteristicType::Name) .find(|(_, v)| v.characteristic_type == CharacteristicType::Name)
.map(|(k, v)| (*k, v.value.clone())) .map(|(k, v)| (*k, v.value.clone()))
{ {
if service.service_type == ServiceType::AccessoryInformation {
service.name_for_accessory = Some(name);
service.name = Some(String::from("Accessory information"));
} else {
service.name = Some(name); service.name = Some(name);
}
service.characteristics.remove(&name_key); service.characteristics.remove(&name_key);
} }
} }

View file

@ -1,7 +1,7 @@
use std::path::PathBuf; use std::path::PathBuf;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use homekit_controller::{HomekitController, HomekitError}; use homekit_controller::{Data, HomekitError};
#[derive(Parser, Debug, Clone)] #[derive(Parser, Debug, Clone)]
#[clap(author, version, about, long_about = None)] #[clap(author, version, about, long_about = None)]
@ -28,9 +28,41 @@ async fn main() -> Result<(), HomekitError> {
let args = Args::parse(); let args = Args::parse();
if args.pairing_data.is_file() { if args.pairing_data.is_file() {
let controller = HomekitController::load(args.pairing_data)?; let devices = homekit_controller::load(args.pairing_data)?;
controller.connect_to(&args.device_name).await?; if let Some(device) = devices.get(&args.device_name) {
let mut connected = device.connect().await?;
connected.update_characteristics().await?;
for (aid, accessory) in connected.accessories {
println!("{} ({}): [", accessory.name, aid);
for (sid, service) in accessory.services {
println!(
"\t{} ({}): [",
service.name.as_ref().unwrap_or(&String::from("Unknown")),
sid
);
for (cid, characteristic) in &service.characteristics {
println!(
"\t\t{:?} ({}): {}{}",
characteristic.characteristic_type,
cid,
match &characteristic.value {
Some(Data::String(s)) => s.clone(),
Some(other) => format!("{other:?}"),
None => String::from("no data"),
},
characteristic
.unit
.map(|v| format!(" ({v:?})"))
.unwrap_or_default()
);
}
println!("\t]");
}
println!("]");
}
}
} else { } else {
log::error!("{:?} is not a file!", args.pairing_data) log::error!("{:?} is not a file!", args.pairing_data)
} }