Compare commits

...

2 commits

Author SHA1 Message Date
Alex Janka dca67cb66e version bumpe 2024-03-07 09:45:32 +11:00
Alex Janka 8d5c8e85c6 lil reformatting + log changes 2024-03-07 09:44:34 +11:00
9 changed files with 102 additions and 94 deletions

4
Cargo.lock generated
View file

@ -1120,7 +1120,7 @@ dependencies = [
[[package]] [[package]]
name = "homekit-controller" name = "homekit-controller"
version = "0.1.0" version = "0.3.0"
dependencies = [ dependencies = [
"chacha20poly1305", "chacha20poly1305",
"ed25519-dalek", "ed25519-dalek",
@ -1143,7 +1143,7 @@ dependencies = [
[[package]] [[package]]
name = "homekit-exporter" name = "homekit-exporter"
version = "0.2.0" version = "0.3.0"
dependencies = [ dependencies = [
"clap", "clap",
"env_logger", "env_logger",

View file

@ -1,6 +1,6 @@
[package] [package]
name = "homekit-controller" name = "homekit-controller"
version = "0.1.0" version = "0.3.0"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View file

@ -0,0 +1,74 @@
use crate::homekit_http::DiscoveryError;
use futures_util::{pin_mut, StreamExt};
use mdns::RecordKind;
use std::{collections::HashMap, sync::Arc, time::Duration};
use tokio::sync::RwLock;
// (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 fn spawn_discover_thread() -> Result<MdnsDiscoveredList, DiscoveryError> {
let discovered = Arc::new(RwLock::new(HashMap::new()));
let r_discovered = discovered.clone();
let stream = mdns::discover::all("_hap._tcp.local", Duration::from_secs(1))?.listen();
tokio::task::spawn(async move {
pin_mut!(stream);
loop {
while let Some(Ok(response)) = stream.next().await {
let all = response
.answers
.iter()
.chain(response.additional.iter())
.collect::<Vec<_>>();
if let Some((name, id)) = all.iter().find_map(|record| {
if let RecordKind::TXT(v) = &record.kind {
if let Some(id_string) = v.iter().find(|v| v.contains("id=")) {
let id = id_string[3..].to_string();
return Some((record.name.clone(), id));
}
}
None
}) {
if let Some((target, port)) = all.iter().find_map(|record| {
if record.name == name {
if let RecordKind::SRV {
priority: _,
weight: _,
port,
target,
} = &record.kind
{
return Some((target.clone(), *port));
}
}
None
}) {
if let Some(ip) = all.iter().find_map(|record| {
if record.name == target {
if let RecordKind::A(ip) = record.kind {
return Some(ip);
}
}
None
}) {
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));
}
}
}
}
}
tokio::time::sleep(Duration::from_secs(5 * 60)).await;
}
});
Ok(r_discovered)
}

View file

@ -15,9 +15,10 @@ use tokio::{
}; };
use crate::{ use crate::{
discovery::MdnsDiscoveredList,
pairing_data::{Accessory, ServiceCharacteristic}, pairing_data::{Accessory, ServiceCharacteristic},
tlv8::TlvEncode, tlv8::TlvEncode,
ConnectionError, HomekitError, MdnsDiscoveredList, ConnectionError, HomekitError,
}; };
pub(super) struct AccessorySocket { pub(super) struct AccessorySocket {

View file

@ -1,26 +1,26 @@
use chacha20poly1305::{ use chacha20poly1305::{
aead::generic_array::GenericArray, AeadInPlace, ChaCha20Poly1305, KeyInit, Nonce, aead::generic_array::GenericArray, AeadInPlace, ChaCha20Poly1305, KeyInit, Nonce,
}; };
use ed25519_dalek::{Signer, Verifier}; use ed25519_dalek::{Signer, Verifier};
use futures_util::{pin_mut, StreamExt};
use hkdf::Hkdf; use hkdf::Hkdf;
use homekit_http::DiscoveryError;
use mdns::RecordKind;
use sha2::Sha512; use sha2::Sha512;
use std::{collections::HashMap, path::PathBuf, sync::Arc, time::Duration}; use std::{collections::HashMap, path::PathBuf};
use thiserror::Error; use thiserror::Error;
use tlv8::{HomekitState, TlvEncode, TlvError, TlvType};
use tokio::sync::RwLock;
use x25519_dalek::{EphemeralSecret, PublicKey}; use x25519_dalek::{EphemeralSecret, PublicKey};
use pairing_data::{Accessory, PythonPairingData}; pub use crate::{
discovery::{spawn_discover_thread, MdnsDiscoveredList},
pub use crate::pairing_data::{CharacteristicType, Data, ServiceType}; pairing_data::{CharacteristicType, Data, ServiceType},
};
use crate::{ use crate::{
homekit_http::AccessorySocket, homekit_http::{AccessorySocket, DiscoveryError},
tlv8::{decode, TlvEncodableData}, pairing_data::{Accessory, PythonPairingData},
tlv8::{decode, HomekitState, TlvEncodableData, TlvEncode, TlvError, TlvType},
}; };
mod discovery;
mod homekit_http; mod homekit_http;
mod pairing_data; mod pairing_data;
mod tlv8; mod tlv8;
@ -60,75 +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 fn spawn_discover_thread() -> Result<MdnsDiscoveredList, DiscoveryError> {
let discovered = Arc::new(RwLock::new(HashMap::new()));
let r_discovered = discovered.clone();
let stream = mdns::discover::all("_hap._tcp.local", Duration::from_secs(1))?.listen();
tokio::task::spawn(async move {
pin_mut!(stream);
loop {
while let Some(Ok(response)) = stream.next().await {
let all = response
.answers
.iter()
.chain(response.additional.iter())
.collect::<Vec<_>>();
if let Some((name, id)) = all.iter().find_map(|record| {
if let RecordKind::TXT(v) = &record.kind {
if let Some(id_string) = v.iter().find(|v| v.contains("id=")) {
let id = id_string[3..].to_string();
return Some((record.name.clone(), id));
}
}
None
}) {
if let Some((target, port)) = all.iter().find_map(|record| {
if record.name == name {
if let RecordKind::SRV {
priority: _,
weight: _,
port,
target,
} = &record.kind
{
return Some((target.clone(), *port));
}
}
None
}) {
if let Some(ip) = all.iter().find_map(|record| {
if record.name == target {
if let RecordKind::A(ip) = record.kind {
return Some(ip);
}
}
None
}) {
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));
}
}
}
}
}
tokio::time::sleep(Duration::from_secs(5 * 60)).await;
}
});
Ok(r_discovered)
}
struct DevicePairingData { struct DevicePairingData {
accessory_pairing_id: String, accessory_pairing_id: String,
accessory_ip: String, accessory_ip: String,

View file

@ -1,6 +1,6 @@
[package] [package]
name = "homekit-exporter" name = "homekit-exporter"
version = "0.2.0" version = "0.3.0"
edition = "2021" edition = "2021"
license = "Apache-2.0" license = "Apache-2.0"
description = "Prometheus exporter for HomeKit sensors" description = "Prometheus exporter for HomeKit sensors"

View file

@ -1,7 +1,7 @@
# Maintainer: Alex Janka <alex@alexjanka.com> # Maintainer: Alex Janka <alex@alexjanka.com>
pkgname=homekit-logger pkgname=homekit-logger
pkgver=0.2.0 pkgver=0.3.0
pkgrel=1 pkgrel=1
pkgdesc="Prometheus exporter for HomeKit sensors" pkgdesc="Prometheus exporter for HomeKit sensors"
arch=('x86_64' 'aarch64') arch=('x86_64' 'aarch64')

View file

@ -7,7 +7,7 @@ StartLimitIntervalSec=0
Type=simple Type=simple
Restart=always Restart=always
RestartSec=10s RestartSec=10s
Environment="RUST_LOG=error,warn" Environment="RUST_LOG=error,warn,mdns=off"
Environment="LOG_TIMESTAMP=false" Environment="LOG_TIMESTAMP=false"
ExecStart=/usr/bin/homekit-exporter watch ExecStart=/usr/bin/homekit-exporter watch

View file

@ -75,7 +75,7 @@ async fn discover() -> Result<(), mdns::Error> {
async fn init(pairing_data: PathBuf) -> Result<HashMap<String, DeviceConnection>, HomekitError> { async fn init(pairing_data: PathBuf) -> Result<HashMap<String, DeviceConnection>, HomekitError> {
let discovered = spawn_discover_thread()?; let discovered = spawn_discover_thread()?;
tokio::time::sleep(Duration::from_secs(1)).await; tokio::time::sleep(Duration::from_secs(2)).await;
if pairing_data.is_file() { if pairing_data.is_file() {
let devices = homekit_controller::load(pairing_data)?; let devices = homekit_controller::load(pairing_data)?;
let mut connected_devices = HashMap::new(); let mut connected_devices = HashMap::new();
@ -84,13 +84,15 @@ async fn init(pairing_data: PathBuf) -> Result<HashMap<String, DeviceConnection>
let connected = loop { let connected = loop {
match v.connect(&discovered).await { match v.connect(&discovered).await {
Ok(v) => break Some(v), Ok(v) => break Some(v),
Err(e) => log::error!("error connecting to {k}: {e:#?}"), Err(e) => {
num += 1;
if num > 10 {
log::error!("error connecting to {k}: {e:?}");
break None;
}
}
} }
num += 1; tokio::time::sleep(Duration::from_millis(250)).await;
if num > 10 {
break None;
}
tokio::time::sleep(Duration::from_millis(100)).await;
}; };
connected_devices.insert(k, connected.ok_or(HomekitError::DeviceNotFound)?); connected_devices.insert(k, connected.ok_or(HomekitError::DeviceNotFound)?);
} }