accessory names, better parsing etc
This commit is contained in:
parent
65c976c851
commit
6ddabfbf11
|
@ -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),
|
||||||
|
|
|
@ -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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// now get characteristics
|
pub struct ConnectedDevice {
|
||||||
let characteristics = socket
|
pub accessories: HashMap<usize, Accessory>,
|
||||||
.get_characteristics(
|
socket: AccessorySocket,
|
||||||
&accessories
|
}
|
||||||
.iter()
|
|
||||||
.flat_map(|v| v.get_service_ids())
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
log::info!("characteristics:\n{characteristics:#?}");
|
impl ConnectedDevice {
|
||||||
|
pub async fn update_characteristics(&mut self) -> Result<(), HomekitError> {
|
||||||
|
self.characteristics_request(true).await
|
||||||
|
}
|
||||||
|
|
||||||
|
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?;
|
||||||
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue