Compare commits

..

No commits in common. "401fd144cd666b595cd343cfc7cb45940c1eae08" and "e4ffd63a8aa68d3e56850821a02497cc044bde7f" have entirely different histories.

9 changed files with 53 additions and 157 deletions

2
Cargo.lock generated
View file

@ -1147,10 +1147,8 @@ version = "0.2.0"
dependencies = [ dependencies = [
"clap", "clap",
"env_logger", "env_logger",
"futures-util",
"homekit-controller", "homekit-controller",
"log", "log",
"mdns",
"rocket", "rocket",
"tokio", "tokio",
] ]

View file

@ -1,8 +1,4 @@
use std::{ use std::{collections::HashMap, time::Duration};
collections::{HashMap, VecDeque},
mem,
time::Duration,
};
use chacha20poly1305::{ use chacha20poly1305::{
aead::generic_array::GenericArray, AeadInPlace, ChaCha20Poly1305, KeyInit, Nonce, aead::generic_array::GenericArray, AeadInPlace, ChaCha20Poly1305, KeyInit, Nonce,
@ -232,30 +228,30 @@ impl AccessorySocket {
{ {
if transfer_encoding.value == b"chunked" { if transfer_encoding.value == b"chunked" {
loop { loop {
let chunked = ChunkedTransfer::clone_from(&packet).collect::<Vec<Vec<u8>>>(); packet.append(&mut self.get_next().await?);
if let Some(last) = chunked.chunks(2).last() { let utf8_decoded = String::from_utf8(packet.clone())?;
if last.len() == 2 && last[0] == b"0" {
break;
}
}
match tokio::time::timeout(Duration::from_secs(2), self.get_next()).await { let split = utf8_decoded
Ok(next) => { .split_terminator("\r\n")
packet.append(&mut next?); .map(String::from)
} .collect::<Vec<_>>();
Err(_) => { if let Some(last) = split.chunks(2).last() {
log::error!("timed out"); if last.len() == 2 && last[0] == "0" {
break; break;
} }
} }
} }
let mut chunked = let utf8_decoded = String::from_utf8(std::mem::take(&mut packet))?;
ChunkedTransfer::from(std::mem::take(&mut packet)).collect::<Vec<Vec<u8>>>();
for chunk in chunked.chunks_exact_mut(2) { let split = utf8_decoded
packet.append(&mut chunk[1]); .split_terminator("\r\n")
.map(String::from)
.collect::<Vec<_>>();
for chunk in split.chunks_exact(2) {
packet.extend_from_slice(chunk[1].as_bytes())
} }
} }
} }
@ -333,52 +329,3 @@ pub enum DiscoveryError {
#[error("not found")] #[error("not found")]
NotFound, NotFound,
} }
struct ChunkedTransfer {
data: Vec<u8>,
}
impl From<Vec<u8>> for ChunkedTransfer {
fn from(value: Vec<u8>) -> Self {
Self { data: value }
}
}
impl ChunkedTransfer {
fn clone_from(value: &[u8]) -> Self {
Self {
data: value.to_owned(),
}
}
}
impl Iterator for ChunkedTransfer {
type Item = Vec<u8>;
fn next(&mut self) -> Option<Self::Item> {
if self.data.is_empty() {
None
} else {
let mut crlf_index = None;
for (i, val) in self.data.iter().enumerate() {
if let Some(next) = self.data.get(i + 1) {
if *val == b'\r' && *next == b'\n' {
crlf_index = Some(i);
break;
}
}
}
if let Some(i) = crlf_index {
let remainder = self.data.split_off(i);
let mut remainder = VecDeque::from(remainder);
let _ = remainder.pop_front().map(|v| v as char);
let _ = remainder.pop_front().map(|v| v as char);
Some(mem::replace(&mut self.data, Vec::from(remainder)))
} else {
Some(mem::take(&mut self.data))
}
}
}
}

View file

@ -60,10 +60,6 @@ impl PythonPairingData {
} }
} }
// (id, (ip, port))
// id - from TXT record
// ip - from A record
// port - from SRV record
pub type MdnsDiscoveredList = Arc<RwLock<HashMap<String, (String, u16)>>>; pub type MdnsDiscoveredList = Arc<RwLock<HashMap<String, (String, u16)>>>;
pub fn spawn_discover_thread() -> Result<MdnsDiscoveredList, DiscoveryError> { pub fn spawn_discover_thread() -> Result<MdnsDiscoveredList, DiscoveryError> {
@ -111,18 +107,11 @@ pub fn spawn_discover_thread() -> Result<MdnsDiscoveredList, DiscoveryError> {
None None
}) { }) {
let mut connections = discovered.write().await; let mut connections = discovered.write().await;
if !connections.get(&id).is_some_and(|(old_ip, old_port)| {
*old_ip == ip.to_string() && *old_port == port
}) {
log::info!(
"mdns: discovered {name} - id: {id}, ip: {ip}, port: {port}"
);
connections.insert(id, (ip.to_string(), port)); connections.insert(id, (ip.to_string(), port));
} }
} }
} }
} }
}
tokio::time::sleep(Duration::from_secs(5 * 60)).await; tokio::time::sleep(Duration::from_secs(5 * 60)).await;
} }
}); });

View file

@ -3,6 +3,15 @@ use strum_macros::Display;
// data from https://github.com/oznu/homebridge-gsh/blob/master/src/hap-types.ts // data from https://github.com/oznu/homebridge-gsh/blob/master/src/hap-types.ts
pub(crate) fn deserialize_characteristic_type<'de, D>(
deserializer: D,
) -> Result<CharacteristicType, D::Error>
where
D: Deserializer<'de>,
{
CharacteristicTypeInner::deserialize(deserializer).map(|v| v.into())
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Display)] #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Display)]
#[strum(serialize_all = "snake_case")] #[strum(serialize_all = "snake_case")]
@ -234,7 +243,7 @@ pub enum CharacteristicType {
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(field_identifier)] #[serde(field_identifier)]
pub(super) enum CharacteristicTypeInner { enum CharacteristicTypeInner {
#[serde(rename = "000000E5-0000-1000-8000-0026BB765291", alias = "E5")] #[serde(rename = "000000E5-0000-1000-8000-0026BB765291", alias = "E5")]
AccessControlLevel, AccessControlLevel,
#[serde(rename = "000000A6-0000-1000-8000-0026BB765291", alias = "A6")] #[serde(rename = "000000A6-0000-1000-8000-0026BB765291", alias = "A6")]

View file

@ -2,11 +2,9 @@ use std::collections::HashMap;
use serde::{Deserialize, Deserializer, Serialize}; use serde::{Deserialize, Deserializer, Serialize};
use characteristics::deserialize_service_type; use characteristics::{deserialize_characteristic_type, deserialize_service_type};
pub use characteristics::{CharacteristicType, ServiceType}; pub use characteristics::{CharacteristicType, ServiceType};
use crate::pairing_data::characteristics::CharacteristicTypeInner;
mod characteristics; mod characteristics;
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
@ -99,8 +97,8 @@ where
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,
primary: Option<bool>, primary: bool,
hidden: Option<bool>, hidden: bool,
#[serde(deserialize_with = "deserialize_service_characteristics")] #[serde(deserialize_with = "deserialize_service_characteristics")]
characteristics: HashMap<usize, ServiceCharacteristic>, characteristics: HashMap<usize, ServiceCharacteristic>,
} }
@ -109,8 +107,8 @@ where
Self { Self {
name: val.name, name: val.name,
service_type: val.service_type, service_type: val.service_type,
primary: val.primary.unwrap_or(false), primary: val.primary,
hidden: val.hidden.unwrap_or(false), hidden: val.hidden,
characteristics: val.characteristics, characteristics: val.characteristics,
name_for_accessory: val.name_for_accessory, name_for_accessory: val.name_for_accessory,
} }
@ -211,9 +209,9 @@ where
struct ServiceCharacteristicInner { struct ServiceCharacteristicInner {
#[serde(rename = "iid")] #[serde(rename = "iid")]
iid: usize, iid: usize,
#[serde(rename = "type")] #[serde(rename = "type", deserialize_with = "deserialize_characteristic_type")]
characteristic_type: Option<CharacteristicTypeInner>, characteristic_type: CharacteristicType,
perms: Option<Vec<CharacteristicPermissions>>, perms: Vec<CharacteristicPermissions>,
#[serde(flatten)] #[serde(flatten)]
value: Option<Data>, value: Option<Data>,
#[serde(rename = "ev")] #[serde(rename = "ev")]
@ -229,13 +227,11 @@ where
min_step: Option<f64>, min_step: Option<f64>,
unit: Option<Unit>, unit: Option<Unit>,
} }
impl ServiceCharacteristic { impl From<ServiceCharacteristicInner> for ServiceCharacteristic {
fn maybe_from(value: ServiceCharacteristicInner) -> Option<Self> { fn from(value: ServiceCharacteristicInner) -> Self {
let characteristic_type = value.characteristic_type.map(|v| v.into())?; Self {
let perms = value.perms?; characteristic_type: value.characteristic_type,
Some(Self { perms: value.perms,
characteristic_type,
perms,
value: value.value, value: value.value,
event_notifications_enabled: value.event_notifications_enabled, event_notifications_enabled: value.event_notifications_enabled,
enc: value.enc, enc: value.enc,
@ -244,16 +240,13 @@ where
max_value: value.max_value, max_value: value.max_value,
min_step: value.min_step, min_step: value.min_step,
unit: value.unit, unit: value.unit,
}) }
} }
} }
Vec::<ServiceCharacteristicInner>::deserialize(deserializer).map(|v| { Vec::<ServiceCharacteristicInner>::deserialize(deserializer).map(|v| {
v.into_iter() v.into_iter()
.fold(HashMap::new(), |mut map, characteristic| { .fold(HashMap::new(), |mut map, characteristic| {
let iid = characteristic.iid; map.insert(characteristic.iid, characteristic.into());
if let Some(c) = ServiceCharacteristic::maybe_from(characteristic) {
map.insert(iid, c);
}
map map
}) })
}) })

View file

@ -80,10 +80,7 @@ pub fn decode(data: &[u8]) -> Result<HashMap<u8, Vec<u8>>, TlvCodecError> {
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() { if tlv_len > data.len() {
return Err(TlvCodecError::WrongLength { return Err(TlvCodecError::WrongLength);
expected: tlv_len,
got: data.len(),
});
} }
(current, data) = data.split_at(tlv_len); (current, data) = data.split_at(tlv_len);
@ -93,10 +90,7 @@ pub fn decode(data: &[u8]) -> Result<HashMap<u8, Vec<u8>>, TlvCodecError> {
} }
if current.len() < tlv_len { if current.len() < tlv_len {
return Err(TlvCodecError::WrongLength { return Err(TlvCodecError::WrongLength);
expected: tlv_len,
got: current.len(),
});
} }
let tlv_type = current[0]; let tlv_type = current[0];
@ -116,7 +110,7 @@ pub enum TlvCodecError {
#[error("too short")] #[error("too short")]
TooShort, TooShort,
#[error("wrong length")] #[error("wrong length")]
WrongLength { expected: usize, got: usize }, WrongLength,
} }
pub trait TlvEncode { pub trait TlvEncode {

View file

@ -19,5 +19,3 @@ log = "0.4"
clap = { version = "4.0", features = ["derive"] } clap = { version = "4.0", features = ["derive"] }
homekit-controller = { path = "../homekit-controller" } homekit-controller = { path = "../homekit-controller" }
rocket = { version = "0.5", features = ["json"] } rocket = { version = "0.5", features = ["json"] }
mdns = "3.0.0"
futures-util = "0.3.30"

View file

@ -9,7 +9,7 @@ Restart=always
RestartSec=10s RestartSec=10s
Environment="RUST_LOG=error,warn" Environment="RUST_LOG=error,warn"
Environment="LOG_TIMESTAMP=false" Environment="LOG_TIMESTAMP=false"
ExecStart=/usr/bin/homekit-exporter watch ExecStart=/usr/bin/homekit-exporter
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View file

@ -3,8 +3,7 @@ extern crate rocket;
use std::{collections::HashMap, path::PathBuf, time::Duration}; use std::{collections::HashMap, path::PathBuf, time::Duration};
use clap::{Parser, Subcommand}; use clap::Parser;
use futures_util::{pin_mut, StreamExt};
use homekit_controller::{spawn_discover_thread, DeviceConnection, HomekitError, ServiceType}; use homekit_controller::{spawn_discover_thread, DeviceConnection, HomekitError, ServiceType};
use server::launch; use server::launch;
@ -13,22 +12,12 @@ mod server;
#[derive(Parser, Debug, Clone)] #[derive(Parser, Debug, Clone)]
#[clap(author, version, about, long_about = None)] #[clap(author, version, about, long_about = None)]
struct Args { struct Args {
#[command(subcommand)]
command: Commands,
#[clap(long, default_value = "/etc/homekit-exporter/pairing.json")] #[clap(long, default_value = "/etc/homekit-exporter/pairing.json")]
pairing_data: PathBuf, pairing_data: PathBuf,
#[clap(long, default_value_t = 6666)] #[clap(long, default_value_t = 6666)]
port: usize, port: usize,
} }
#[derive(Subcommand, Debug, Clone)]
enum Commands {
/// Run logging server
Watch,
/// Discover devices
Discover,
}
const SENSORS: [ServiceType; 11] = [ const SENSORS: [ServiceType; 11] = [
ServiceType::AirQualitySensor, ServiceType::AirQualitySensor,
ServiceType::CarbonDioxideSensor, ServiceType::CarbonDioxideSensor,
@ -47,30 +36,10 @@ const SENSORS: [ServiceType; 11] = [
async fn rocket() -> rocket::Rocket<rocket::Build> { async fn rocket() -> rocket::Rocket<rocket::Build> {
env_logger::init(); env_logger::init();
let args = Args::parse(); let args = Args::parse();
match args.command { match init(args.pairing_data).await {
Commands::Watch => match init(args.pairing_data).await {
Ok(paired) => launch(paired, args.port), Ok(paired) => launch(paired, args.port),
Err(e) => panic!("Error {e:#?}"), Err(e) => panic!("Error {e:#?}"),
},
Commands::Discover => {
println!("discovering homekit devices via mdns");
match discover().await {
Ok(_) => todo!(),
Err(_) => todo!(),
} }
}
}
}
async fn discover() -> Result<(), mdns::Error> {
let stream = mdns::discover::all("_hap._tcp.local", Duration::from_secs(1))?.listen();
pin_mut!(stream);
while let Some(Ok(response)) = stream.next().await {
println!("found device:\n\t{response:#?}");
}
Ok(())
} }
async fn init(pairing_data: PathBuf) -> Result<HashMap<String, DeviceConnection>, HomekitError> { async fn init(pairing_data: PathBuf) -> Result<HashMap<String, DeviceConnection>, HomekitError> {
@ -82,9 +51,8 @@ async fn init(pairing_data: PathBuf) -> Result<HashMap<String, DeviceConnection>
for (k, v) in devices { for (k, v) in devices {
let mut num = 0; let mut num = 0;
let connected = loop { let connected = loop {
match v.connect(&discovered).await { if let Ok(v) = v.connect(&discovered).await {
Ok(v) => break Some(v), break Some(v);
Err(e) => log::error!("error connecting to {k}: {e:#?}"),
} }
num += 1; num += 1;
if num > 10 { if num > 10 {