almost auths
This commit is contained in:
parent
0aef7feb32
commit
371937bf1f
1098
Cargo.lock
generated
1098
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -10,3 +10,10 @@ serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
reqwest = "0.11"
|
||||||
|
x25519-dalek = { version = "2", features = ["getrandom"] }
|
||||||
|
ed25519-dalek = { version = "2" }
|
||||||
|
chacha20poly1305 = "0.10.1"
|
||||||
|
hkdf = "0.12.4"
|
||||||
|
sha2 = "0.10"
|
||||||
|
hex = { version = "0.4", features = ["serde"] }
|
||||||
|
|
|
@ -1,15 +1,23 @@
|
||||||
|
use chacha20poly1305::{aead::generic_array::GenericArray, AeadInPlace, ChaCha20Poly1305, KeyInit};
|
||||||
|
use ed25519_dalek::{Signer, Verifier};
|
||||||
|
use hkdf::Hkdf;
|
||||||
|
use reqwest::{Client, Method};
|
||||||
|
use sha2::Sha512;
|
||||||
use std::{collections::HashMap, path::PathBuf};
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
|
||||||
use characteristics::{
|
|
||||||
deserialize_characteristic_type, deserialize_service_type, CharacteristicType, ServiceType,
|
|
||||||
};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
use tlv8::{TlvEncode, TlvType};
|
||||||
|
use x25519_dalek::{EphemeralSecret, PublicKey};
|
||||||
|
|
||||||
mod characteristics;
|
use pairing_data::DevicePairingData;
|
||||||
|
|
||||||
|
use crate::tlv8::{decode, TlvEncodableData};
|
||||||
|
|
||||||
|
mod pairing_data;
|
||||||
|
mod tlv8;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct HomekitController {
|
pub struct HomekitController {
|
||||||
|
client: Client,
|
||||||
devices: HashMap<String, DevicePairingData>,
|
devices: HashMap<String, DevicePairingData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,96 +25,257 @@ impl HomekitController {
|
||||||
pub fn load(pairing_data: PathBuf) -> Result<Self, HomekitError> {
|
pub fn load(pairing_data: PathBuf) -> Result<Self, HomekitError> {
|
||||||
let devices: HashMap<String, DevicePairingData> =
|
let devices: HashMap<String, DevicePairingData> =
|
||||||
serde_json::from_str(&std::fs::read_to_string(pairing_data)?)?;
|
serde_json::from_str(&std::fs::read_to_string(pairing_data)?)?;
|
||||||
|
let client = Client::builder().build()?;
|
||||||
|
Ok(Self { client, devices })
|
||||||
|
}
|
||||||
|
|
||||||
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(&self.client).await
|
||||||
|
} else {
|
||||||
|
Err(HomekitError::NoPairingData)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
impl DevicePairingData {
|
||||||
pub struct DevicePairingData {
|
pub async fn connect(&self, client: &Client) -> Result<(), HomekitError> {
|
||||||
#[serde(rename = "AccessoryPairingID")]
|
let key = EphemeralSecret::random();
|
||||||
accessory_pairing_id: String,
|
let pubkey = PublicKey::from(&key);
|
||||||
#[serde(rename = "AccessoryLTPK")]
|
let step1_response = verify_request(
|
||||||
accessory_ltpk: String,
|
&self.accessory_ip,
|
||||||
#[serde(rename = "iOSPairingId")]
|
self.accessory_port,
|
||||||
ios_pairing_id: String,
|
&[
|
||||||
#[serde(rename = "iOSDeviceLTSK")]
|
(
|
||||||
ios_device_ltsk: String,
|
TlvType::State.into(),
|
||||||
#[serde(rename = "iOSDeviceLTPK")]
|
(HomekitState::M1 as u8).encode_value(),
|
||||||
ios_device_ltpk: String,
|
),
|
||||||
#[serde(rename = "AccessoryIP")]
|
(TlvType::PublicKey.into(), pubkey.as_bytes().encode_value()),
|
||||||
accessory_ip: String,
|
],
|
||||||
#[serde(rename = "AccessoryPort")]
|
client,
|
||||||
accessory_port: usize,
|
)
|
||||||
#[serde(rename = "Connection")]
|
.await?;
|
||||||
connection: ConnectionType,
|
|
||||||
#[serde(rename = "accessories")]
|
if step1_response
|
||||||
accessories: Vec<Accessory>,
|
.get(&TlvType::State.into())
|
||||||
|
.ok_or(HomekitError::TlvNotFound)?[0]
|
||||||
|
!= (HomekitState::M2 as u8)
|
||||||
|
{
|
||||||
|
return Err(HomekitError::StateMismatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
let response_pubkey = step1_response
|
||||||
|
.get(&TlvType::PublicKey.into())
|
||||||
|
.ok_or(HomekitError::TlvNotFound)?;
|
||||||
|
|
||||||
|
let response_encrypted_data = step1_response
|
||||||
|
.get(&TlvType::EncryptedData.into())
|
||||||
|
.ok_or(HomekitError::TlvNotFound)?;
|
||||||
|
|
||||||
|
let accessory_pubkey_bytes: Box<[u8; 32]> =
|
||||||
|
response_pubkey.clone().into_boxed_slice().try_into()?;
|
||||||
|
let accessory_pubkey = x25519_dalek::PublicKey::from(*accessory_pubkey_bytes);
|
||||||
|
let shared_secret = key.diffie_hellman(&accessory_pubkey);
|
||||||
|
|
||||||
|
let hk: Hkdf<Sha512> = Hkdf::<Sha512>::new(
|
||||||
|
Some("Pair-Verify-Encrypt-Salt".as_bytes()),
|
||||||
|
shared_secret.as_bytes(),
|
||||||
|
);
|
||||||
|
let mut session_key = [0; 32];
|
||||||
|
hk.expand("Pair-Verify-Encrypt-Info".as_bytes(), &mut session_key)?;
|
||||||
|
|
||||||
|
let (encrypted, authtag) =
|
||||||
|
response_encrypted_data.split_at((response_encrypted_data.len()) - 16);
|
||||||
|
let mut encrypted = encrypted.to_vec();
|
||||||
|
|
||||||
|
let chacha = ChaCha20Poly1305::new_from_slice(&session_key)?;
|
||||||
|
let nonce: [u8; 12] = [0; 4]
|
||||||
|
.iter()
|
||||||
|
.chain("PV-Msg02".as_bytes())
|
||||||
|
.copied()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.try_into()?;
|
||||||
|
|
||||||
|
chacha.decrypt_in_place_detached(
|
||||||
|
GenericArray::from_slice(&nonce),
|
||||||
|
&[],
|
||||||
|
&mut encrypted,
|
||||||
|
authtag.into(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let decrypted = decode(&encrypted)?;
|
||||||
|
|
||||||
|
let accessory_identifier = decrypted
|
||||||
|
.get(&TlvType::Identifier.into())
|
||||||
|
.ok_or(HomekitError::TlvNotFound)?;
|
||||||
|
let accessory_signature = decrypted
|
||||||
|
.get(&TlvType::Signature.into())
|
||||||
|
.ok_or(HomekitError::TlvNotFound)?;
|
||||||
|
|
||||||
|
if accessory_identifier != self.accessory_pairing_id.as_bytes() {
|
||||||
|
return Err(HomekitError::Auth);
|
||||||
|
}
|
||||||
|
|
||||||
|
let accessory_ltpk = ed25519_dalek::VerifyingKey::from_bytes(&self.accessory_ltpk)?;
|
||||||
|
let mut accessory_info = accessory_pubkey_bytes.to_vec();
|
||||||
|
accessory_info.extend_from_slice(accessory_identifier);
|
||||||
|
accessory_info.extend_from_slice(pubkey.as_bytes());
|
||||||
|
log::info!("info len: {}", accessory_info.len());
|
||||||
|
|
||||||
|
let accessory_signature_bytes: Box<[u8; 64]> =
|
||||||
|
accessory_signature.clone().into_boxed_slice().try_into()?;
|
||||||
|
accessory_ltpk.verify(
|
||||||
|
&accessory_info,
|
||||||
|
&ed25519_dalek::Signature::from_bytes(&accessory_signature_bytes),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let ios_device_info = {
|
||||||
|
let mut buf = pubkey.as_bytes().to_vec();
|
||||||
|
buf.extend_from_slice(self.ios_pairing_id.as_bytes());
|
||||||
|
buf.extend_from_slice(pubkey.as_bytes());
|
||||||
|
buf
|
||||||
|
};
|
||||||
|
let signing_key = ed25519_dalek::SigningKey::from_bytes(&self.ios_device_ltsk);
|
||||||
|
let signature = signing_key.sign(&ios_device_info);
|
||||||
|
|
||||||
|
let mut encrypted_tlv = //tlv8::encode(&);
|
||||||
|
([
|
||||||
|
(
|
||||||
|
TlvType::Identifier.into(),
|
||||||
|
self.ios_pairing_id.encode_value(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
TlvType::Signature.into(),
|
||||||
|
(&signature.to_bytes()).encode_value(),
|
||||||
|
),
|
||||||
|
]).encode();
|
||||||
|
|
||||||
|
let nonce: [u8; 12] = [0; 4]
|
||||||
|
.iter()
|
||||||
|
.chain("PV-Msg03".as_bytes())
|
||||||
|
.copied()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.try_into()?;
|
||||||
|
chacha.encrypt_in_place_detached(
|
||||||
|
GenericArray::from_slice(&nonce),
|
||||||
|
&[],
|
||||||
|
&mut encrypted_tlv,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let step3_response = verify_request(
|
||||||
|
&self.accessory_ip,
|
||||||
|
self.accessory_port,
|
||||||
|
&[
|
||||||
|
(
|
||||||
|
TlvType::State.into(),
|
||||||
|
(HomekitState::M3 as u8).encode_value(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
TlvType::EncryptedData.into(),
|
||||||
|
(&encrypted_tlv as &[u8]).encode_value(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
client,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if step3_response
|
||||||
|
.get(&TlvType::State.into())
|
||||||
|
.ok_or(HomekitError::TlvNotFound)?[0]
|
||||||
|
!= (HomekitState::M4 as u8)
|
||||||
|
{
|
||||||
|
return Err(HomekitError::StateMismatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
let hk: Hkdf<Sha512> =
|
||||||
|
Hkdf::<Sha512>::new(Some("Control-Salt".as_bytes()), shared_secret.as_bytes());
|
||||||
|
let mut controller_to_accessory_key = [0; 32];
|
||||||
|
hk.expand(
|
||||||
|
"Control-Write-Encryption-Key".as_bytes(),
|
||||||
|
&mut controller_to_accessory_key,
|
||||||
|
)?;
|
||||||
|
let hk: Hkdf<Sha512> =
|
||||||
|
Hkdf::<Sha512>::new(Some("Control-Salt".as_bytes()), shared_secret.as_bytes());
|
||||||
|
let mut accessory_to_controller_key = [0; 32];
|
||||||
|
hk.expand(
|
||||||
|
"Control-Read-Encryption-Key".as_bytes(),
|
||||||
|
&mut accessory_to_controller_key,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// now get characteristics
|
||||||
|
get_characteristics(
|
||||||
|
&self.accessory_ip,
|
||||||
|
self.accessory_port,
|
||||||
|
&self
|
||||||
|
.accessories
|
||||||
|
.iter()
|
||||||
|
.flat_map(|v| v.get_service_ids())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
client,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
async fn verify_request(
|
||||||
pub struct Accessory {
|
ip: &str,
|
||||||
#[serde(rename = "aid")]
|
port: usize,
|
||||||
id: usize,
|
data: &[(u8, Vec<u8>)],
|
||||||
services: Vec<AccessoryService>,
|
client: &Client,
|
||||||
|
) -> Result<HashMap<u8, Vec<u8>>, HomekitError> {
|
||||||
|
let uri = format!("http://{ip}:{port}/pair-verify");
|
||||||
|
log::info!("request uri: {uri}");
|
||||||
|
let encoded = data.encode();
|
||||||
|
let request = client
|
||||||
|
.post(uri)
|
||||||
|
.header("Content-Type", "application/pairing+tlv8")
|
||||||
|
.header("Content-Length", encoded.len())
|
||||||
|
.body(encoded)
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
match client.execute(request).await {
|
||||||
|
Ok(val) => Ok(tlv8::decode(val.bytes().await?.as_ref())?),
|
||||||
|
Err(e) => Err(e.into()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
async fn get_characteristics(
|
||||||
pub struct AccessoryService {
|
ip: &str,
|
||||||
#[serde(rename = "iid")]
|
port: usize,
|
||||||
id: usize,
|
characteristics: &[String],
|
||||||
#[serde(rename = "type", deserialize_with = "deserialize_service_type")]
|
client: &Client,
|
||||||
service_type: ServiceType,
|
) -> Result<(), HomekitError> {
|
||||||
primary: bool,
|
let characteristic_request = characteristics.join(",");
|
||||||
hidden: bool,
|
let uri = format!(
|
||||||
characteristics: Vec<ServiceCharacteristic>,
|
"http://{ip}:{port}/characteristics?id={characteristic_request}&meta=1&perms=1&type=1&ev=1"
|
||||||
|
);
|
||||||
|
let request = client.request(Method::GET, uri).build()?;
|
||||||
|
|
||||||
|
log::info!("request: {request:?}");
|
||||||
|
match client.execute(request).await {
|
||||||
|
Ok(val) => {
|
||||||
|
log::info!("characteristic get request successful:\n{val:#?}");
|
||||||
|
let bytes = val.bytes().await?.to_vec();
|
||||||
|
log::info!(
|
||||||
|
"str: {:?}, bytes: {bytes:?}",
|
||||||
|
String::from_utf8(bytes.clone())
|
||||||
|
);
|
||||||
|
log::info!("parsed: {:#?}", tlv8::decode(&bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(e) => log::error!("characteristic get request error {e:?}"),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
pub enum HomekitState {
|
||||||
pub struct ServiceCharacteristic {
|
M1 = 1,
|
||||||
#[serde(rename = "iid")]
|
M2 = 2,
|
||||||
id: usize,
|
M3 = 3,
|
||||||
#[serde(rename = "type", deserialize_with = "deserialize_characteristic_type")]
|
M4 = 4,
|
||||||
characteristic_type: CharacteristicType,
|
M5 = 5,
|
||||||
perms: Vec<String>,
|
M6 = 6,
|
||||||
#[serde(flatten)]
|
|
||||||
value: Option<Data>,
|
|
||||||
ev: Option<bool>,
|
|
||||||
enc: Option<bool>,
|
|
||||||
#[serde(rename = "maxDataLen")]
|
|
||||||
max_data_len: Option<usize>,
|
|
||||||
#[serde(rename = "minValue")]
|
|
||||||
min_value: Option<f64>,
|
|
||||||
#[serde(rename = "maxValue")]
|
|
||||||
max_value: Option<f64>,
|
|
||||||
#[serde(rename = "minStep")]
|
|
||||||
min_step: Option<f64>,
|
|
||||||
unit: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
#[serde(tag = "format", content = "value")]
|
|
||||||
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")]
|
|
||||||
Float(f64),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
|
||||||
pub enum ConnectionType {
|
|
||||||
#[serde(rename = "IP")]
|
|
||||||
Ip,
|
|
||||||
#[serde(rename = "BLE")]
|
|
||||||
Ble,
|
|
||||||
#[serde(rename = "ADDITIONAL_PAIRING")]
|
|
||||||
AdditionalPairing,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
@ -115,4 +284,52 @@ pub enum HomekitError {
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
#[error("serde_json")]
|
#[error("serde_json")]
|
||||||
SerdeJson(#[from] serde_json::Error),
|
SerdeJson(#[from] serde_json::Error),
|
||||||
|
#[error("no device pairing data stored")]
|
||||||
|
NoPairingData,
|
||||||
|
#[error("http transport error")]
|
||||||
|
Http(#[from] reqwest::Error),
|
||||||
|
#[error("tlv error")]
|
||||||
|
Tlv(#[from] tlv8::TlvError),
|
||||||
|
#[error("mismatch with state response")]
|
||||||
|
StateMismatch,
|
||||||
|
#[error("Tlv not found")]
|
||||||
|
TlvNotFound,
|
||||||
|
#[error("length mismatch")]
|
||||||
|
LengthMismatch,
|
||||||
|
#[error("hkdf error")]
|
||||||
|
Hkdf,
|
||||||
|
#[error("invalid length")]
|
||||||
|
InvalidLength(#[from] sha2::digest::InvalidLength),
|
||||||
|
#[error("slice length error")]
|
||||||
|
SliceLength(#[from] std::array::TryFromSliceError),
|
||||||
|
#[error("chacha")]
|
||||||
|
Chacha,
|
||||||
|
#[error("auth mismatch")]
|
||||||
|
Auth,
|
||||||
|
#[error("ed25519")]
|
||||||
|
Ed25519(#[from] ed25519_dalek::ed25519::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<chacha20poly1305::Error> for HomekitError {
|
||||||
|
fn from(_: chacha20poly1305::Error) -> Self {
|
||||||
|
Self::Chacha
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<hkdf::InvalidLength> for HomekitError {
|
||||||
|
fn from(_: hkdf::InvalidLength) -> Self {
|
||||||
|
Self::Hkdf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Box<[u8]>> for HomekitError {
|
||||||
|
fn from(_: Box<[u8]>) -> Self {
|
||||||
|
Self::LengthMismatch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<u8>> for HomekitError {
|
||||||
|
fn from(_: Vec<u8>) -> Self {
|
||||||
|
Self::LengthMismatch
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -239,6 +239,241 @@ pub enum CharacteristicType {
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for CharacteristicType {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::AccessControlLevel => "000000E5-0000-1000-8000-0026BB765291",
|
||||||
|
Self::AccessoryFlags => "000000A6-0000-1000-8000-0026BB765291",
|
||||||
|
Self::ProductData => "00000220-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Active => "000000B0-0000-1000-8000-0026BB765291",
|
||||||
|
Self::AdministratorOnlyAccess => "00000001-0000-1000-8000-0026BB765291",
|
||||||
|
Self::AirParticulateDensity => "00000064-0000-1000-8000-0026BB765291",
|
||||||
|
Self::AirParticulateSize => "00000065-0000-1000-8000-0026BB765291",
|
||||||
|
Self::AirQuality => "00000095-0000-1000-8000-0026BB765291",
|
||||||
|
Self::AudioFeedback => "00000005-0000-1000-8000-0026BB765291",
|
||||||
|
Self::BatteryLevel => "00000068-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Brightness => "00000008-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CarbonDioxideDetected => "00000092-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CarbonDioxideLevel => "00000093-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CarbonDioxidePeakLevel => "00000094-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CarbonMonoxideDetected => "00000069-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CarbonMonoxideLevel => "00000090-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CarbonMonoxidePeakLevel => "00000091-0000-1000-8000-0026BB765291",
|
||||||
|
Self::ChargingState => "0000008F-0000-1000-8000-0026BB765291",
|
||||||
|
Self::ColorTemperature => "000000CE-0000-1000-8000-0026BB765291",
|
||||||
|
Self::ContactSensorState => "0000006A-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CoolingThresholdTemperature => "0000000D-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CurrentAirPurifierState => "000000A9-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CurrentAmbientLightLevel => "0000006B-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CurrentDoorState => "0000000E-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CurrentFanState => "000000AF-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CurrentHeaterCoolerState => "000000B1-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CurrentHeatingCoolingState => "0000000F-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CurrentHorizontalTiltAngle => "0000006C-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CurrentHumidifierDehumidifierState => "000000B3-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CurrentPosition => "0000006D-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CurrentRelativeHumidity => "00000010-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CurrentSlatState => "000000AA-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CurrentTemperature => "00000011-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CurrentTiltAngle => "000000C1-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CurrentVerticalTiltAngle => "0000006E-0000-1000-8000-0026BB765291",
|
||||||
|
Self::DigitalZoom => "0000011D-0000-1000-8000-0026BB765291",
|
||||||
|
Self::FilterChangeIndication => "000000AC-0000-1000-8000-0026BB765291",
|
||||||
|
Self::FilterLifeLevel => "000000AB-0000-1000-8000-0026BB765291",
|
||||||
|
Self::FirmwareRevision => "00000052-0000-1000-8000-0026BB765291",
|
||||||
|
Self::HardwareRevision => "00000053-0000-1000-8000-0026BB765291",
|
||||||
|
Self::HeatingThresholdTemperature => "00000012-0000-1000-8000-0026BB765291",
|
||||||
|
Self::HoldPosition => "0000006F-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Hue => "00000013-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Identify => "00000014-0000-1000-8000-0026BB765291",
|
||||||
|
Self::ImageMirroring => "0000011F-0000-1000-8000-0026BB765291",
|
||||||
|
Self::ImageRotation => "0000011E-0000-1000-8000-0026BB765291",
|
||||||
|
Self::InUse => "000000D2-0000-1000-8000-0026BB765291",
|
||||||
|
Self::IsConfigured => "000000D6-0000-1000-8000-0026BB765291",
|
||||||
|
Self::LeakDetected => "00000070-0000-1000-8000-0026BB765291",
|
||||||
|
Self::LockControlPoint => "00000019-0000-1000-8000-0026BB765291",
|
||||||
|
Self::LockCurrentState => "0000001D-0000-1000-8000-0026BB765291",
|
||||||
|
Self::LockLastKnownAction => "0000001C-0000-1000-8000-0026BB765291",
|
||||||
|
Self::LockManagementAutoSecurityTimeout => "0000001A-0000-1000-8000-0026BB765291",
|
||||||
|
Self::LockPhysicalControls => "000000A7-0000-1000-8000-0026BB765291",
|
||||||
|
Self::LockTargetState => "0000001E-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Logs => "0000001F-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Manufacturer => "00000020-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Model => "00000021-0000-1000-8000-0026BB765291",
|
||||||
|
Self::MotionDetected => "00000022-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Mute => "0000011A-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Name => "00000023-0000-1000-8000-0026BB765291",
|
||||||
|
Self::NightVision => "0000011B-0000-1000-8000-0026BB765291",
|
||||||
|
Self::NitrogenDioxideDensity => "000000C4-0000-1000-8000-0026BB765291",
|
||||||
|
Self::ObstructionDetected => "00000024-0000-1000-8000-0026BB765291",
|
||||||
|
Self::OccupancyDetected => "00000071-0000-1000-8000-0026BB765291",
|
||||||
|
Self::On => "00000025-0000-1000-8000-0026BB765291",
|
||||||
|
Self::OpticalZoom => "0000011C-0000-1000-8000-0026BB765291",
|
||||||
|
Self::OutletInUse => "00000026-0000-1000-8000-0026BB765291",
|
||||||
|
Self::OzoneDensity => "000000C3-0000-1000-8000-0026BB765291",
|
||||||
|
Self::PairSetup => "0000004C-0000-1000-8000-0026BB765291",
|
||||||
|
Self::PairVerify => "0000004E-0000-1000-8000-0026BB765291",
|
||||||
|
Self::PairingFeatures => "0000004F-0000-1000-8000-0026BB765291",
|
||||||
|
Self::PairingPairings => "00000050-0000-1000-8000-0026BB765291",
|
||||||
|
Self::PasswordSetting => "000000E4-0000-1000-8000-0026BB765291",
|
||||||
|
Self::PM10Density => "000000C7-0000-1000-8000-0026BB765291",
|
||||||
|
Self::PM2_5Density => "000000C6-0000-1000-8000-0026BB765291",
|
||||||
|
Self::PositionState => "00000072-0000-1000-8000-0026BB765291",
|
||||||
|
Self::ProgramMode => "000000D1-0000-1000-8000-0026BB765291",
|
||||||
|
Self::ProgrammableSwitchEvent => "00000073-0000-1000-8000-0026BB765291",
|
||||||
|
Self::RelativeHumidityDehumidifierThreshold => "000000C9-0000-1000-8000-0026BB765291",
|
||||||
|
Self::RelativeHumidityHumidifierThreshold => "000000CA-0000-1000-8000-0026BB765291",
|
||||||
|
Self::RemainingDuration => "000000D4-0000-1000-8000-0026BB765291",
|
||||||
|
Self::ResetFilterIndication => "000000AD-0000-1000-8000-0026BB765291",
|
||||||
|
Self::RotationDirection => "00000028-0000-1000-8000-0026BB765291",
|
||||||
|
Self::RotationSpeed => "00000029-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Saturation => "0000002F-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SecuritySystemAlarmType => "0000008E-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SecuritySystemCurrentState => "00000066-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SecuritySystemTargetState => "00000067-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SelectedRTPStreamConfiguration => "00000117-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SerialNumber => "00000030-0000-1000-8000-0026BB765291",
|
||||||
|
Self::ServiceLabelIndex => "000000CB-0000-1000-8000-0026BB765291",
|
||||||
|
Self::ServiceLabelNamespace => "000000CD-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SetDuration => "000000D3-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SetupEndpoints => "00000118-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SlatType => "000000C0-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SmokeDetected => "00000076-0000-1000-8000-0026BB765291",
|
||||||
|
Self::StatusActive => "00000075-0000-1000-8000-0026BB765291",
|
||||||
|
Self::StatusFault => "00000077-0000-1000-8000-0026BB765291",
|
||||||
|
Self::StatusJammed => "00000078-0000-1000-8000-0026BB765291",
|
||||||
|
Self::StatusLowBattery => "00000079-0000-1000-8000-0026BB765291",
|
||||||
|
Self::StatusTampered => "0000007A-0000-1000-8000-0026BB765291",
|
||||||
|
Self::StreamingStatus => "00000120-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SulphurDioxideDensity => "000000C5-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SupportedAudioStreamConfiguration => "00000115-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SupportedRTPConfiguration => "00000116-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SupportedVideoStreamConfiguration => "00000114-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SwingMode => "000000B6-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TargetAirPurifierState => "000000A8-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TargetAirQuality => "000000AE-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TargetDoorState => "00000032-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TargetFanState => "000000BF-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TargetHeaterCoolerState => "000000B2-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TargetHeatingCoolingState => "00000033-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TargetHorizontalTiltAngle => "0000007B-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TargetHumidifierDehumidifierState => "000000B4-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TargetPosition => "0000007C-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TargetRelativeHumidity => "00000034-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TargetSlatState => "000000BE-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TargetTemperature => "00000035-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TargetTiltAngle => "000000C2-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TargetVerticalTiltAngle => "0000007D-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TemperatureDisplayUnits => "00000036-0000-1000-8000-0026BB765291",
|
||||||
|
Self::ValveType => "000000D5-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Version => "00000037-0000-1000-8000-0026BB765291",
|
||||||
|
Self::VOCDensity => "000000C8-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Volume => "00000119-0000-1000-8000-0026BB765291",
|
||||||
|
Self::WaterLevel => "000000B5-0000-1000-8000-0026BB765291",
|
||||||
|
Self::RecordingAudioActive => "00000226-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SupportedCameraRecordingConfiguration => "00000205-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SupportedVideoRecordingConfiguration => "00000206-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SupportedAudioRecordingConfiguration => "00000207-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SelectedCameraRecordingConfiguration => "00000209-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CameraOperatingModeIndicator => "0000021D-0000-1000-8000-0026BB765291",
|
||||||
|
Self::EventSnapshotsActive => "00000223-0000-1000-8000-0026BB765291",
|
||||||
|
Self::DiagonalFieldOfView => "00000224-0000-1000-8000-0026BB765291",
|
||||||
|
Self::HomeKitCameraActive => "0000021B-0000-1000-8000-0026BB765291",
|
||||||
|
Self::ManuallyDisabled => "00000227-0000-1000-8000-0026BB765291",
|
||||||
|
Self::ThirdPartyCameraActive => "0000021C-0000-1000-8000-0026BB765291",
|
||||||
|
Self::PeriodicSnapshotsActive => "00000225-0000-1000-8000-0026BB765291",
|
||||||
|
Self::NetworkClientProfileControl => "0000020C-0000-1000-8000-0026BB765291",
|
||||||
|
Self::NetworkClientStatusControl => "0000020D-0000-1000-8000-0026BB765291",
|
||||||
|
Self::RouterStatus => "0000020E-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SupportedRouterConfiguration => "00000210-0000-1000-8000-0026BB765291",
|
||||||
|
Self::WANConfigurationList => "00000211-0000-1000-8000-0026BB765291",
|
||||||
|
Self::WANStatusList => "00000212-0000-1000-8000-0026BB765291",
|
||||||
|
Self::ManagedNetworkEnable => "00000215-0000-1000-8000-0026BB765291",
|
||||||
|
Self::NetworkAccessViolationControl => "0000021F-0000-1000-8000-0026BB765291",
|
||||||
|
Self::WiFiSatelliteStatus => "0000021E-0000-1000-8000-0026BB765291",
|
||||||
|
Self::WakeConfiguration => "00000222-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SupportedTransferTransportConfiguration => "00000202-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SetupTransferTransport => "00000201-0000-1000-8000-0026BB765291",
|
||||||
|
Self::ActivityInterval => "0000021E-0000-1000-8000-0000023B",
|
||||||
|
Self::CCAEnergyDetectThreshold => "0000021E-0000-1000-8000-00000246",
|
||||||
|
Self::CCASignalDetectThreshold => "0000021E-0000-1000-8000-00000245",
|
||||||
|
Self::CharacteristicValueTransitionControl => "0000021E-0000-1000-8000-00000143",
|
||||||
|
Self::SupportedCharacteristicValueTransitionConfiguration => {
|
||||||
|
"0000021E-0000-1000-8000-00000144"
|
||||||
|
}
|
||||||
|
Self::CurrentTransport => "0000021E-0000-1000-8000-0000022B",
|
||||||
|
Self::DataStreamHAPTransport => "0000021E-0000-1000-8000-00000138",
|
||||||
|
Self::DataStreamHAPTransportInterrupt => "0000021E-0000-1000-8000-00000139",
|
||||||
|
Self::EventRetransmissionMaximum => "0000021E-0000-1000-8000-0000023D",
|
||||||
|
Self::EventTransmissionCounters => "0000021E-0000-1000-8000-0000023E",
|
||||||
|
Self::HeartBeat => "0000021E-0000-1000-8000-0000024A",
|
||||||
|
Self::MACRetransmissionMaximum => "0000021E-0000-1000-8000-00000247",
|
||||||
|
Self::MACTransmissionCounters => "0000021E-0000-1000-8000-00000248",
|
||||||
|
Self::OperatingStateResponse => "0000021E-0000-1000-8000-00000232",
|
||||||
|
Self::Ping => "0000021E-0000-1000-8000-0000023C",
|
||||||
|
Self::ReceiverSensitivity => "0000021E-0000-1000-8000-00000244",
|
||||||
|
Self::ReceivedSignalStrengthIndication => "0000021E-0000-1000-8000-0000023F",
|
||||||
|
Self::SleepInterval => "0000021E-0000-1000-8000-0000023A",
|
||||||
|
Self::SignalToNoiseRatio => "0000021E-0000-1000-8000-00000241",
|
||||||
|
Self::SupportedDiagnosticsSnapshot => "0000021E-0000-1000-8000-00000238",
|
||||||
|
Self::TransmitPower => "0000021E-0000-1000-8000-00000242",
|
||||||
|
Self::TransmitPowerMaximum => "0000021E-0000-1000-8000-00000243",
|
||||||
|
Self::VideoAnalysisActive => "0000021E-0000-1000-8000-00000229",
|
||||||
|
Self::WiFiCapabilities => "0000021E-0000-1000-8000-0000022C",
|
||||||
|
Self::WiFiConfigurationControl => "0000021E-0000-1000-8000-0000022D",
|
||||||
|
Self::AppMatchingIdentifier => "000000A4-0000-1000-8000-0026BB765291",
|
||||||
|
Self::ProgrammableSwitchOutputState => "00000074-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SoftwareRevision => "00000054-0000-1000-8000-0026BB765291",
|
||||||
|
Self::AccessoryIdentifier => "00000057-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Category => "000000A3-0000-1000-8000-0026BB765291",
|
||||||
|
Self::ConfigureBridgedAccessory => "000000A0-0000-1000-8000-0026BB765291",
|
||||||
|
Self::ConfigureBridgedAccessoryStatus => "0000009D-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CurrentTime => "0000009B-0000-1000-8000-0026BB765291",
|
||||||
|
Self::DayoftheWeek => "00000098-0000-1000-8000-0026BB765291",
|
||||||
|
Self::DiscoverBridgedAccessories => "0000009E-0000-1000-8000-0026BB765291",
|
||||||
|
Self::DiscoveredBridgedAccessories => "0000009F-0000-1000-8000-0026BB765291",
|
||||||
|
Self::LinkQuality => "0000009C-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Reachable => "00000063-0000-1000-8000-0026BB765291",
|
||||||
|
Self::RelayControlPoint => "0000005E-0000-1000-8000-0026BB765291",
|
||||||
|
Self::RelayEnabled => "0000005B-0000-1000-8000-0026BB765291",
|
||||||
|
Self::RelayState => "0000005C-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TimeUpdate => "0000009A-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TunnelConnectionTimeout => "00000061-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TunneledAccessoryAdvertising => "00000060-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TunneledAccessoryConnected => "00000059-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TunneledAccessoryStateNumber => "00000058-0000-1000-8000-0026BB765291",
|
||||||
|
Self::ActiveIdentifier => "000000E7-0000-1000-8000-0026BB765291",
|
||||||
|
Self::ConfiguredName => "000000E3-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SleepDiscoveryMode => "000000E8-0000-1000-8000-0026BB765291",
|
||||||
|
Self::ClosedCaptions => "000000DD-0000-1000-8000-0026BB765291",
|
||||||
|
Self::DisplayOrder => "00000136-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CurrentMediaState => "000000E0-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TargetMediaState => "00000137-0000-1000-8000-0026BB765291",
|
||||||
|
Self::PictureMode => "000000E2-0000-1000-8000-0026BB765291",
|
||||||
|
Self::PowerModeSelection => "000000DF-0000-1000-8000-0026BB765291",
|
||||||
|
Self::RemoteKey => "000000E1-0000-1000-8000-0026BB765291",
|
||||||
|
Self::InputSourceType => "000000DB-0000-1000-8000-0026BB765291",
|
||||||
|
Self::InputDeviceType => "000000DC-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Identifier => "000000E6-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CurrentVisibilityState => "00000135-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TargetVisibilityState => "00000134-0000-1000-8000-0026BB765291",
|
||||||
|
Self::VolumeControlType => "000000E9-0000-1000-8000-0026BB765291",
|
||||||
|
Self::VolumeSelector => "000000EA-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TargetControlSupportedConfiguration => "00000123-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TargetControlList => "00000124-0000-1000-8000-0026BB765291",
|
||||||
|
Self::ButtonEvent => "00000126-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SelectedAudioStreamConfiguration => "00000128-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SiriInputType => "00000132-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SupportedDataStreamTransportConfiguration => {
|
||||||
|
"00000130-0000-1000-8000-0026BB765291"
|
||||||
|
}
|
||||||
|
Self::SetupDataStreamTransport => "00000131-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Unknown => "",
|
||||||
|
}
|
||||||
|
.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(field_identifier)]
|
#[serde(field_identifier)]
|
||||||
enum CharacteristicTypeInner {
|
enum CharacteristicTypeInner {
|
||||||
|
@ -1057,6 +1292,7 @@ pub enum ServiceType {
|
||||||
TunneledBTLEAccessoryService,
|
TunneledBTLEAccessoryService,
|
||||||
Television,
|
Television,
|
||||||
InputSource,
|
InputSource,
|
||||||
|
TelevisionSpeaker,
|
||||||
TargetControlManagement,
|
TargetControlManagement,
|
||||||
TargetControl,
|
TargetControl,
|
||||||
AudioStreamManagement,
|
AudioStreamManagement,
|
||||||
|
@ -1065,6 +1301,83 @@ pub enum ServiceType {
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ServiceType {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::AccessControl => "000000DA-0000-1000-8000-0026BB765291",
|
||||||
|
Self::AccessoryInformation => "0000003E-0000-1000-8000-0026BB765291",
|
||||||
|
Self::AirPurifier => "000000BB-0000-1000-8000-0026BB765291",
|
||||||
|
Self::AirQualitySensor => "0000008D-0000-1000-8000-0026BB765291",
|
||||||
|
Self::BatteryService => "00000096-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CameraRTPStreamManagement => "00000110-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CarbonDioxideSensor => "00000097-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CarbonMonoxideSensor => "0000007F-0000-1000-8000-0026BB765291",
|
||||||
|
Self::ContactSensor => "00000080-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Door => "00000081-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Doorbell => "00000121-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Fan => "00000040-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Fanv2 => "000000B7-0000-1000-8000-0026BB765291",
|
||||||
|
Self::FilterMaintenance => "000000BA-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Faucet => "000000D7-0000-1000-8000-0026BB765291",
|
||||||
|
Self::GarageDoorOpener => "00000041-0000-1000-8000-0026BB765291",
|
||||||
|
Self::HeaterCooler => "000000BC-0000-1000-8000-0026BB765291",
|
||||||
|
Self::HumidifierDehumidifier => "000000BD-0000-1000-8000-0026BB765291",
|
||||||
|
Self::HumiditySensor => "00000082-0000-1000-8000-0026BB765291",
|
||||||
|
Self::IrrigationSystem => "000000CF-0000-1000-8000-0026BB765291",
|
||||||
|
Self::LeakSensor => "00000083-0000-1000-8000-0026BB765291",
|
||||||
|
Self::LightSensor => "00000084-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Lightbulb => "00000043-0000-1000-8000-0026BB765291",
|
||||||
|
Self::LockManagement => "00000044-0000-1000-8000-0026BB765291",
|
||||||
|
Self::LockMechanism => "00000045-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Microphone => "00000112-0000-1000-8000-0026BB765291",
|
||||||
|
Self::MotionSensor => "00000085-0000-1000-8000-0026BB765291",
|
||||||
|
Self::OccupancySensor => "00000086-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Outlet => "00000047-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SecuritySystem => "0000007E-0000-1000-8000-0026BB765291",
|
||||||
|
Self::ServiceLabel => "000000CC-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Slat => "000000B9-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SmokeSensor => "00000087-0000-1000-8000-0026BB765291",
|
||||||
|
Self::SmartSpeaker => "00000228-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Speaker => "00000113-0000-1000-8000-0026BB765291",
|
||||||
|
Self::StatelessProgrammableSwitch => "00000089-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Switch => "00000049-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TemperatureSensor => "0000008A-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Thermostat => "0000004A-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Valve => "000000D0-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Window => "0000008B-0000-1000-8000-0026BB765291",
|
||||||
|
Self::WindowCovering => "0000008C-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CameraOperatingMode => "0000021A-0000-1000-8000-0026BB765291",
|
||||||
|
Self::CameraEventRecordingManagement => "00000204-0000-1000-8000-0026BB765291",
|
||||||
|
Self::WiFiRouter => "0000020A-0000-1000-8000-0026BB765291",
|
||||||
|
Self::WiFiSatellite => "0000020F-0000-1000-8000-0026BB765291",
|
||||||
|
Self::PowerManagement => "00000221-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TransferTransportManagement => "00000203-0000-1000-8000-0026BB765291",
|
||||||
|
Self::AccessoryRuntimeInformation => "00000203-0000-1000-8000-00000239",
|
||||||
|
Self::Diagnostics => "00000203-0000-1000-8000-00000237",
|
||||||
|
Self::WiFiTransport => "00000203-0000-1000-8000-0000022A",
|
||||||
|
Self::CameraControl => "00000111-0000-1000-8000-0026BB765291",
|
||||||
|
Self::StatefulProgrammableSwitch => "00000088-0000-1000-8000-0026BB765291",
|
||||||
|
Self::BridgeConfiguration => "000000A1-0000-1000-8000-0026BB765291",
|
||||||
|
Self::BridgingState => "00000062-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Pairing => "00000055-0000-1000-8000-0026BB765291",
|
||||||
|
Self::ProtocolInformation => "000000A2-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Relay => "0000005A-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TimeInformation => "00000099-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TunneledBTLEAccessoryService => "00000056-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Television => "000000D8-0000-1000-8000-0026BB765291",
|
||||||
|
Self::InputSource => "000000D9-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TelevisionSpeaker => "00000113-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TargetControlManagement => "00000122-0000-1000-8000-0026BB765291",
|
||||||
|
Self::TargetControl => "00000125-0000-1000-8000-0026BB765291",
|
||||||
|
Self::AudioStreamManagement => "00000127-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Siri => "00000133-0000-1000-8000-0026BB765291",
|
||||||
|
Self::DataStreamTransportManagement => "00000129-0000-1000-8000-0026BB765291",
|
||||||
|
Self::Unknown => "",
|
||||||
|
}
|
||||||
|
.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(field_identifier)]
|
#[serde(field_identifier)]
|
||||||
pub enum ServiceTypeInner {
|
pub enum ServiceTypeInner {
|
||||||
|
@ -1192,6 +1505,8 @@ pub enum ServiceTypeInner {
|
||||||
Television,
|
Television,
|
||||||
#[serde(rename = "000000D9-0000-1000-8000-0026BB765291")]
|
#[serde(rename = "000000D9-0000-1000-8000-0026BB765291")]
|
||||||
InputSource,
|
InputSource,
|
||||||
|
#[serde(rename = "00000113-0000-1000-8000-0026BB765291")]
|
||||||
|
TelevisionSpeaker,
|
||||||
#[serde(rename = "00000122-0000-1000-8000-0026BB765291")]
|
#[serde(rename = "00000122-0000-1000-8000-0026BB765291")]
|
||||||
TargetControlManagement,
|
TargetControlManagement,
|
||||||
#[serde(rename = "00000125-0000-1000-8000-0026BB765291")]
|
#[serde(rename = "00000125-0000-1000-8000-0026BB765291")]
|
||||||
|
@ -1276,6 +1591,7 @@ impl From<ServiceTypeInner> for ServiceType {
|
||||||
ServiceTypeInner::TunneledBTLEAccessoryService => Self::TunneledBTLEAccessoryService,
|
ServiceTypeInner::TunneledBTLEAccessoryService => Self::TunneledBTLEAccessoryService,
|
||||||
ServiceTypeInner::Television => Self::Television,
|
ServiceTypeInner::Television => Self::Television,
|
||||||
ServiceTypeInner::InputSource => Self::InputSource,
|
ServiceTypeInner::InputSource => Self::InputSource,
|
||||||
|
ServiceTypeInner::TelevisionSpeaker => Self::TelevisionSpeaker,
|
||||||
ServiceTypeInner::TargetControlManagement => Self::TargetControlManagement,
|
ServiceTypeInner::TargetControlManagement => Self::TargetControlManagement,
|
||||||
ServiceTypeInner::TargetControl => Self::TargetControl,
|
ServiceTypeInner::TargetControl => Self::TargetControl,
|
||||||
ServiceTypeInner::AudioStreamManagement => Self::AudioStreamManagement,
|
ServiceTypeInner::AudioStreamManagement => Self::AudioStreamManagement,
|
103
homekit-controller/src/pairing_data/mod.rs
Normal file
103
homekit-controller/src/pairing_data/mod.rs
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use characteristics::{deserialize_characteristic_type, deserialize_service_type};
|
||||||
|
pub use characteristics::{CharacteristicType, ServiceType};
|
||||||
|
|
||||||
|
mod characteristics;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct DevicePairingData {
|
||||||
|
#[serde(rename = "AccessoryPairingID")]
|
||||||
|
pub accessory_pairing_id: String,
|
||||||
|
#[serde(with = "hex::serde", rename = "AccessoryLTPK")]
|
||||||
|
pub accessory_ltpk: [u8; 32],
|
||||||
|
#[serde(rename = "iOSPairingId")]
|
||||||
|
pub ios_pairing_id: String,
|
||||||
|
#[serde(with = "hex::serde", rename = "iOSDeviceLTSK")]
|
||||||
|
pub ios_device_ltsk: [u8; 32],
|
||||||
|
#[serde(with = "hex::serde", rename = "iOSDeviceLTPK")]
|
||||||
|
pub ios_device_ltpk: [u8; 32],
|
||||||
|
#[serde(rename = "AccessoryIP")]
|
||||||
|
pub accessory_ip: String,
|
||||||
|
#[serde(rename = "AccessoryPort")]
|
||||||
|
pub accessory_port: usize,
|
||||||
|
#[serde(rename = "Connection")]
|
||||||
|
pub connection: ConnectionType,
|
||||||
|
#[serde(rename = "accessories")]
|
||||||
|
pub accessories: Vec<Accessory>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct Accessory {
|
||||||
|
#[serde(rename = "aid")]
|
||||||
|
pub id: usize,
|
||||||
|
pub services: Vec<AccessoryService>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Accessory {
|
||||||
|
pub fn get_service_ids(&self) -> Vec<String> {
|
||||||
|
self.services
|
||||||
|
.iter()
|
||||||
|
.map(|service| format!("{}.{}", self.id, service.id))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<ServiceCharacteristic>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<String>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub value: Option<Data>,
|
||||||
|
pub ev: Option<bool>,
|
||||||
|
pub enc: Option<bool>,
|
||||||
|
#[serde(rename = "maxDataLen")]
|
||||||
|
pub max_data_len: Option<usize>,
|
||||||
|
#[serde(rename = "minValue")]
|
||||||
|
pub min_value: Option<f64>,
|
||||||
|
#[serde(rename = "maxValue")]
|
||||||
|
pub max_value: Option<f64>,
|
||||||
|
#[serde(rename = "minStep")]
|
||||||
|
pub min_step: Option<f64>,
|
||||||
|
pub unit: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::enum_variant_names)]
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
#[serde(tag = "format", content = "value")]
|
||||||
|
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")]
|
||||||
|
Float(f64),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||||
|
pub enum ConnectionType {
|
||||||
|
#[serde(rename = "IP")]
|
||||||
|
Ip,
|
||||||
|
#[serde(rename = "BLE")]
|
||||||
|
Ble,
|
||||||
|
#[serde(rename = "ADDITIONAL_PAIRING")]
|
||||||
|
AdditionalPairing,
|
||||||
|
}
|
|
@ -1,5 +1,3 @@
|
||||||
use super::Tlv;
|
|
||||||
|
|
||||||
pub enum TlvType {
|
pub enum TlvType {
|
||||||
Method,
|
Method,
|
||||||
Identifier,
|
Identifier,
|
||||||
|
@ -43,109 +41,3 @@ impl From<TlvType> for u8 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum TlvData {
|
|
||||||
Bytes(Vec<u8>),
|
|
||||||
Tlv8(Vec<Tlv>),
|
|
||||||
Integer(TlvInteger),
|
|
||||||
Float(f64),
|
|
||||||
String(String),
|
|
||||||
Uint(TlvUnsignedInteger),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum TlvInteger {
|
|
||||||
OneByte(i8),
|
|
||||||
TwoBytes(i16),
|
|
||||||
FourBytes(i32),
|
|
||||||
EightBytes(i64),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TlvInteger {
|
|
||||||
pub fn to_le_bytes(&self) -> Vec<u8> {
|
|
||||||
match self {
|
|
||||||
TlvInteger::OneByte(v) => v.to_le_bytes().to_vec(),
|
|
||||||
TlvInteger::TwoBytes(v) => v.to_le_bytes().to_vec(),
|
|
||||||
TlvInteger::FourBytes(v) => v.to_le_bytes().to_vec(),
|
|
||||||
TlvInteger::EightBytes(v) => v.to_le_bytes().to_vec(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> From<T> for TlvData
|
|
||||||
where
|
|
||||||
T: Into<TlvInteger>,
|
|
||||||
{
|
|
||||||
fn from(value: T) -> Self {
|
|
||||||
Self::Integer(value.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<i8> for TlvInteger {
|
|
||||||
fn from(value: i8) -> Self {
|
|
||||||
Self::OneByte(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<i16> for TlvInteger {
|
|
||||||
fn from(value: i16) -> Self {
|
|
||||||
Self::TwoBytes(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<i32> for TlvInteger {
|
|
||||||
fn from(value: i32) -> Self {
|
|
||||||
Self::FourBytes(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<i64> for TlvInteger {
|
|
||||||
fn from(value: i64) -> Self {
|
|
||||||
Self::EightBytes(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum TlvUnsignedInteger {
|
|
||||||
OneByte(u8),
|
|
||||||
TwoBytes(u16),
|
|
||||||
FourBytes(u32),
|
|
||||||
EightBytes(u64),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TlvUnsignedInteger {
|
|
||||||
pub fn to_le_bytes(&self) -> Vec<u8> {
|
|
||||||
match self {
|
|
||||||
TlvUnsignedInteger::OneByte(v) => v.to_le_bytes().to_vec(),
|
|
||||||
TlvUnsignedInteger::TwoBytes(v) => v.to_le_bytes().to_vec(),
|
|
||||||
TlvUnsignedInteger::FourBytes(v) => v.to_le_bytes().to_vec(),
|
|
||||||
TlvUnsignedInteger::EightBytes(v) => v.to_le_bytes().to_vec(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<u8> for TlvUnsignedInteger {
|
|
||||||
fn from(value: u8) -> Self {
|
|
||||||
Self::OneByte(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<u16> for TlvUnsignedInteger {
|
|
||||||
fn from(value: u16) -> Self {
|
|
||||||
Self::TwoBytes(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<u32> for TlvUnsignedInteger {
|
|
||||||
fn from(value: u32) -> Self {
|
|
||||||
Self::FourBytes(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<u64> for TlvUnsignedInteger {
|
|
||||||
fn from(value: u64) -> Self {
|
|
||||||
Self::EightBytes(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,8 +2,7 @@ mod data_types;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[allow(unused)]
|
pub use data_types::TlvType;
|
||||||
pub use data_types::{TlvData, TlvInteger, TlvType, TlvUnsignedInteger};
|
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
@ -11,33 +10,34 @@ pub fn decode(data: &[u8]) -> Result<HashMap<u8, Vec<u8>>, TlvError> {
|
||||||
let mut tlvs = HashMap::new();
|
let mut tlvs = HashMap::new();
|
||||||
let mut data = data;
|
let mut data = data;
|
||||||
|
|
||||||
while data.len() > 0 {
|
while !data.is_empty() {
|
||||||
let tlv_len = (data[1] as usize) + 2;
|
let tlv_len = (data[1] as usize) + 2;
|
||||||
let current;
|
let current;
|
||||||
|
if tlv_len > data.len() {
|
||||||
|
return Err(TlvError::WrongLength);
|
||||||
|
}
|
||||||
|
|
||||||
(current, data) = data.split_at(tlv_len);
|
(current, data) = data.split_at(tlv_len);
|
||||||
|
|
||||||
if data.len() < 3 {
|
if current.len() < 2 {
|
||||||
return Err(TlvError::TooShort);
|
return Err(TlvError::TooShort);
|
||||||
}
|
}
|
||||||
|
|
||||||
if data.len() < tlv_len {
|
if current.len() < tlv_len {
|
||||||
return Err(TlvError::WrongLength);
|
return Err(TlvError::WrongLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
let tlv_type = current[0];
|
let tlv_type = current[0];
|
||||||
let tlv_data = current[2..].to_vec();
|
let tlv_data = if tlv_len == 0 {
|
||||||
|
vec![]
|
||||||
|
} else {
|
||||||
|
current[2..].to_vec()
|
||||||
|
};
|
||||||
tlvs.insert(tlv_type, tlv_data);
|
tlvs.insert(tlv_type, tlv_data);
|
||||||
}
|
}
|
||||||
Ok(tlvs)
|
Ok(tlvs)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_single(data: &[u8]) -> Result<(u8, Vec<u8>), TlvError> {
|
|
||||||
let tlv_type = data[0];
|
|
||||||
let tlv_len = data[1];
|
|
||||||
let data = &data[2..];
|
|
||||||
Ok((tlv_type, if tlv_len == 0 { vec![] } else { data.to_vec() }))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum TlvError {
|
pub enum TlvError {
|
||||||
#[error("too short")]
|
#[error("too short")]
|
||||||
|
@ -88,81 +88,6 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Tlv(u8, TlvData);
|
|
||||||
|
|
||||||
impl Tlv {
|
|
||||||
pub fn new<A: Into<u8>>(tlv_type: A, tlv_data: TlvData) -> Self {
|
|
||||||
Self(tlv_type.into(), tlv_data)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn encode(&self) -> Vec<u8> {
|
|
||||||
let Tlv(tlv_type, data) = self;
|
|
||||||
let bytes = match data {
|
|
||||||
TlvData::Bytes(v) => v.clone(),
|
|
||||||
TlvData::Tlv8(v) => todo!(),
|
|
||||||
TlvData::Integer(v) => v.to_le_bytes(),
|
|
||||||
TlvData::Float(v) => v.to_le_bytes().to_vec(),
|
|
||||||
TlvData::String(v) => v.as_bytes().to_vec(),
|
|
||||||
TlvData::Uint(v) => v.to_le_bytes(),
|
|
||||||
};
|
|
||||||
let length = bytes.len() as u32;
|
|
||||||
let packets = bytes
|
|
||||||
.chunks(255)
|
|
||||||
.enumerate()
|
|
||||||
.flat_map(|(packet_num, data)| {
|
|
||||||
let mut packet = vec![*tlv_type];
|
|
||||||
packet.push((length - 255 * (packet_num as u32)) as u8);
|
|
||||||
packet.extend_from_slice(data);
|
|
||||||
packet
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
if packets.is_empty() {
|
|
||||||
let mut first_packet = vec![*tlv_type];
|
|
||||||
first_packet.extend_from_slice(&length.to_le_bytes());
|
|
||||||
first_packet.push(0);
|
|
||||||
first_packet
|
|
||||||
} else {
|
|
||||||
packets
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn decode_single(data: &[u8]) -> Result<Self, TlvError> {
|
|
||||||
let tlv_type = data[0];
|
|
||||||
let tlv_len = data[1];
|
|
||||||
let data = &data[2..];
|
|
||||||
Ok(Self(
|
|
||||||
tlv_type,
|
|
||||||
TlvData::Bytes(if tlv_len == 0 { vec![] } else { data.to_vec() }),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tlv_type(&self) -> u8 {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tlv_data(&self) -> &TlvData {
|
|
||||||
&self.1
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_type<T: Into<u8>>(&self, value: T) -> bool {
|
|
||||||
self.0 == value.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TlvData {
|
|
||||||
pub fn as_bytes(&self) -> Vec<u8> {
|
|
||||||
match self {
|
|
||||||
TlvData::Bytes(val) => val.clone(),
|
|
||||||
TlvData::Tlv8(_val) => todo!(),
|
|
||||||
TlvData::Integer(val) => val.to_le_bytes(),
|
|
||||||
TlvData::Float(_val) => todo!(),
|
|
||||||
TlvData::String(val) => val.as_bytes().to_vec(),
|
|
||||||
TlvData::Uint(val) => val.to_le_bytes(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait TlvEncodableData {
|
pub trait TlvEncodableData {
|
||||||
fn encode_value(&self) -> Vec<u8>;
|
fn encode_value(&self) -> Vec<u8>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,8 @@ struct Args {
|
||||||
command: Commands,
|
command: Commands,
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pairing_data: PathBuf,
|
pairing_data: PathBuf,
|
||||||
|
#[clap(long)]
|
||||||
|
device_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand, Debug, Clone)]
|
#[derive(Subcommand, Debug, Clone)]
|
||||||
|
@ -26,6 +28,8 @@ 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 controller = HomekitController::load(args.pairing_data)?;
|
||||||
|
// println!("{controller:#?}");
|
||||||
|
controller.connect_to(&args.device_name).await?;
|
||||||
} 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