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

View file

@ -9,42 +9,26 @@ use thiserror::Error;
use tlv8::{HomekitState, TlvEncode, TlvError, TlvType};
use x25519_dalek::{EphemeralSecret, PublicKey};
use pairing_data::DevicePairingData;
use pairing_data::{Accessory, DevicePairingData};
pub use crate::pairing_data::{CharacteristicType, Data};
use crate::{
homekit_http::AccessorySocket,
tlv8::{decode, TlvEncodableData},
};
mod homekit_http;
mod pairing_data;
mod tlv8;
#[derive(Debug, Clone)]
pub struct HomekitController {
devices: HashMap<String, DevicePairingData>,
pub fn load(pairing_data: PathBuf) -> Result<HashMap<String, DevicePairingData>, HomekitError> {
Ok(serde_json::from_str(&std::fs::read_to_string(
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 {
pub async fn connect(&self) -> Result<(), HomekitError> {
pub async fn connect(&self) -> Result<ConnectedDevice, HomekitError> {
let key = EphemeralSecret::random();
let pubkey = PublicKey::from(&key);
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);
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:#?}");
for a in &accessories {
log::info!("{}: service ids: {:#?}", a.id, a.get_service_ids());
Ok(connected_device)
}
}
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
let characteristics = socket
.get_characteristics(
&accessories
.iter()
.flat_map(|v| v.get_service_ids())
.collect::<Vec<_>>(),
)
async fn characteristics_request(&mut self, additional_data: bool) -> Result<(), HomekitError> {
for (aid, data) in &mut self.accessories {
for service in data.services.values_mut() {
let characteristic_ids = service
.characteristics
.keys()
.map(|k| format!("{aid}.{k}"))
.collect::<Vec<_>>();
let characteristics = self
.socket
.get_characteristics(&characteristic_ids, additional_data)
.await?;
log::info!("characteristics:\n{characteristics:#?}");
for (cid, c) in &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(())
}

View file

@ -26,30 +26,47 @@ pub struct DevicePairingData {
#[serde(rename = "Connection")]
pub connection: ConnectionType,
#[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)]
pub struct Accessories {
#[serde(default, deserialize_with = "deserialize_accessories")]
pub accessories: HashMap<usize, HashMap<usize, AccessoryService>>,
pub accessories: HashMap<usize, Accessory>,
}
fn deserialize_accessories<'de, D>(
deserializer: D,
) -> Result<HashMap<usize, HashMap<usize, AccessoryService>>, D::Error>
fn deserialize_accessories<'de, D>(deserializer: D) -> Result<HashMap<usize, Accessory>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Accessory {
struct AccessoryInner {
aid: usize,
#[serde(deserialize_with = "deserialize_accessory_service")]
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| {
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
})
})
@ -62,6 +79,7 @@ pub struct AccessoryService {
pub primary: bool,
pub hidden: bool,
pub characteristics: HashMap<usize, ServiceCharacteristic>,
name_for_accessory: Option<String>,
}
fn deserialize_accessory_service<'de, D>(
@ -74,6 +92,8 @@ where
struct AccessoryServiceInner {
#[serde(skip)]
name: Option<String>,
#[serde(skip)]
name_for_accessory: Option<String>,
iid: usize,
#[serde(rename = "type", deserialize_with = "deserialize_service_type")]
service_type: ServiceType,
@ -90,6 +110,7 @@ where
primary: val.primary,
hidden: val.hidden,
characteristics: val.characteristics,
name_for_accessory: val.name_for_accessory,
}
}
}
@ -101,7 +122,12 @@ where
.find(|(_, v)| v.characteristic_type == CharacteristicType::Name)
.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.characteristics.remove(&name_key);
}
}

View file

@ -1,7 +1,7 @@
use std::path::PathBuf;
use clap::{Parser, Subcommand};
use homekit_controller::{HomekitController, HomekitError};
use homekit_controller::{Data, HomekitError};
#[derive(Parser, Debug, Clone)]
#[clap(author, version, about, long_about = None)]
@ -28,9 +28,41 @@ async fn main() -> Result<(), HomekitError> {
let args = Args::parse();
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 {
log::error!("{:?} is not a file!", args.pairing_data)
}