From aa8963bd4a503bbcee107d6794de4a83fa24d62e Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Thu, 19 Jan 2023 16:35:35 +0100 Subject: [PATCH] Directly send wol packet instead of using the webhook --- .drone.yml | 5 +++-- Cargo.lock | 23 +++++++++++++++++++++++ Cargo.toml | 1 + config/config.toml | 1 + src/config.rs | 10 ++++++++-- src/devices/wake_on_lan.rs | 36 ++++++++++++++---------------------- 6 files changed, 50 insertions(+), 26 deletions(-) diff --git a/.drone.yml b/.drone.yml index 7090107..a71bbdb 100644 --- a/.drone.yml +++ b/.drone.yml @@ -30,8 +30,9 @@ steps: - docker rm automation_rs || true - - docker create -e RUST_LOG=$RUST_LOG -e MQTT_PASSWORD=$MQTT_PASSWORD -e HUE_TOKEN=$HUE_TOKEN -e NTFY_TOPIC=$NTFY_TOPIC --name automation_rs automation_rs - - docker network connect mqtt automation_rs + # Networks need to be setup to to allow broadcasts: https://www.devwithimagination.com/2020/06/15/homebridge-docker-and-wake-on-lan/ https://github.com/dhutchison/container-images/blob/0c2d7d96bab751fb0a008cc91ba2990724bbd11f/homebridge/configure_docker_networks_for_wol.sh + # Needs to be done for ALL networks, because we can't seem to control which interface gets used to send the broadcast + - docker create -e RUST_LOG=$RUST_LOG -e MQTT_PASSWORD=$MQTT_PASSWORD -e HUE_TOKEN=$HUE_TOKEN -e NTFY_TOPIC=$NTFY_TOPIC --network mqtt --restart unless-stopped --name automation_rs automation_rs - docker network connect web automation_rs - docker start automation_rs diff --git a/Cargo.lock b/Cargo.lock index 483407b..31299c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,12 @@ version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + [[package]] name = "async-recursion" version = "1.0.0" @@ -72,6 +78,7 @@ dependencies = [ "toml", "tracing", "tracing-subscriber", + "wakey", ] [[package]] @@ -375,6 +382,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "http" version = "0.2.8" @@ -1282,6 +1295,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "wakey" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dedab5a691c0d33bcfb5c1ed6bb17265e531ed3392282eed9b20063a0f23e9f5" +dependencies = [ + "arrayvec", + "hex", +] + [[package]] name = "want" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index b9d7a4f..f44cd79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ futures = "0.3.25" eui48 = { version = "1.1.0", default-features = false, features = ["disp_hexstring", "serde"] } thiserror = "1.0.38" anyhow = "1.0.68" +wakey = "0.3.0" [profile.release] lto=true diff --git a/config/config.toml b/config/config.toml index 125bb9f..b5d7437 100644 --- a/config/config.toml +++ b/config/config.toml @@ -46,6 +46,7 @@ name = "Zeus" room = "Living Room" topic = "automation/appliance/living_room/zeus" mac_address = "30:9c:23:60:9c:13" +broadcast_ip = "10.0.0.255" [devices.living_audio] type = "AudioSetup" diff --git a/src/config.rs b/src/config.rs index 2fa96e8..0b3e50d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -165,6 +165,8 @@ pub enum Device { #[serde(flatten)] mqtt: MqttDeviceConfig, mac_address: MacAddress, + #[serde(default = "default_broadcast_ip")] + broadcast_ip: Ipv4Addr, }, KasaOutlet { ip: Ipv4Addr, @@ -182,6 +184,10 @@ pub enum Device { } } +fn default_broadcast_ip() -> Ipv4Addr { + Ipv4Addr::new(255, 255, 255, 255) +} + impl Config { pub fn parse_file(filename: &str) -> Result { debug!("Loading config: {filename}"); @@ -227,9 +233,9 @@ impl Device { IkeaOutlet::build(&identifier, info, mqtt, kettle, client).await .map(device_box)? }, - Device::WakeOnLAN { info, mqtt, mac_address } => { + Device::WakeOnLAN { info, mqtt, mac_address, broadcast_ip } => { trace!(id = identifier, "WakeOnLan [{} in {:?}]", info.name, info.room); - WakeOnLAN::build(&identifier, info, mqtt, mac_address, client).await + WakeOnLAN::build(&identifier, info, mqtt, mac_address, broadcast_ip, client).await .map(device_box)? }, Device::KasaOutlet { ip } => { diff --git a/src/devices/wake_on_lan.rs b/src/devices/wake_on_lan.rs index 3bef4ce..313b24c 100644 --- a/src/devices/wake_on_lan.rs +++ b/src/devices/wake_on_lan.rs @@ -1,8 +1,9 @@ +use std::net::Ipv4Addr; + use async_trait::async_trait; use google_home::{GoogleHomeDevice, types::Type, device, traits::{self, Scene}, errors::ErrorCode}; use tracing::{debug, error}; use rumqttc::{AsyncClient, Publish, matches}; -use pollster::FutureExt as _; use eui48::MacAddress; use crate::{config::{InfoConfig, MqttDeviceConfig}, mqtt::{OnMqtt, ActivateMessage}, error::DeviceError}; @@ -15,14 +16,15 @@ pub struct WakeOnLAN { info: InfoConfig, mqtt: MqttDeviceConfig, mac_address: MacAddress, + broadcast_ip: Ipv4Addr, } impl WakeOnLAN { - pub async fn build(identifier: &str, info: InfoConfig, mqtt: MqttDeviceConfig, mac_address: MacAddress, client: AsyncClient) -> Result { + pub async fn build(identifier: &str, info: InfoConfig, mqtt: MqttDeviceConfig, mac_address: MacAddress, broadcast_ip: Ipv4Addr, client: AsyncClient) -> Result { // @TODO Handle potential errors here client.subscribe(mqtt.topic.clone(), rumqttc::QoS::AtLeastOnce).await?; - Ok(Self { identifier: identifier.to_owned(), info, mqtt, mac_address }) + Ok(Self { identifier: identifier.to_owned(), info, mqtt, mac_address, broadcast_ip }) } } @@ -79,26 +81,16 @@ impl GoogleHomeDevice for WakeOnLAN { impl traits::Scene for WakeOnLAN { fn set_active(&self, activate: bool) -> Result<(), ErrorCode> { if activate { - // @TODO In the future send the wake on lan package directly, this is kind of annoying - // if we are inside of docker, so for now just call a webhook that does it for us - let mac_address = self.mac_address.clone(); - let id = self.identifier.clone(); + debug!(id = self.identifier, "Activating Computer: {} (Sending to {})", self.mac_address, self.broadcast_ip); + let wol = wakey::WolPacket::from_bytes(&self.mac_address.to_array()).map_err(|err| { + error!(id = self.identifier, "invalid mac address: {err}"); + google_home::errors::DeviceError::TransientError + })?; - debug!(id, "Activating Computer: {}", mac_address); - let res = match reqwest::get(format!("http://10.0.0.2:9000/start-pc?mac={mac_address}")).block_on() { - Ok(res) => res, - Err(err) => { - error!(id, "Failed to call webhook: {err}"); - return Err(google_home::errors::DeviceError::TransientError.into()); - } - }; - - let status = res.status(); - if !status.is_success() { - error!(id, "Failed to call webhook: {}", status); - } - - Ok(()) + wol.send_magic_to((Ipv4Addr::new(0, 0, 0, 0), 0), (self.broadcast_ip, 9)).map_err(|err| { + error!(id = self.identifier, "Failed to activate computer: {err}"); + google_home::errors::DeviceError::TransientError.into() + }).map(|_| debug!(id = self.identifier, "Success!")) } else { debug!(id = self.identifier, "Trying to deactive computer, this is not currently supported"); // We do not support deactivating this scene