diff --git a/src/main.rs b/src/main.rs index 95368b0..225f0bd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,6 +22,7 @@ use std::env; use std::fs::File; use std::io::prelude::*; use std::sync::mpsc; +use std::sync::{Arc, Barrier}; use std::thread; use crate::filter::*; @@ -73,7 +74,7 @@ fn main() { simplelog::TermLogger::init( level, simplelog::Config { - time: Some(simplelog::Level::Debug), + time: None, level: Some(simplelog::Level::Error), target: Some(simplelog::Level::Info), location: Some(simplelog::Level::Debug), @@ -93,10 +94,14 @@ fn main() { let config: AppConfig = serde_yaml::from_str(&config_content).expect("Invalid config file content"); + let thread_count = config.monitors.len() + config.outputs.len(); + let barrier = Arc::new(Barrier::new(thread_count + 1)); + // Monitors -> dispatcher channel let (mon_sender, mon_receiver) = mpsc::channel(); // Instantiate monitor threads and structs + let mut mon_threads = vec![]; for mon_config in config.monitors { let mon_type = mon_config .get("type") @@ -106,21 +111,23 @@ fn main() { .to_owned(); let snd = mon_sender.clone(); + let bar = barrier.clone(); - thread::spawn(move || { + mon_threads.push(thread::spawn(move || { log::info!("+> monitor: {}", mon_type); match monitors::factory(&mon_type, &mon_config) { Ok(mut mon) => loop { + bar.wait(); mon.run(&snd); }, Err(e) => log::error!("Cannot instantiate monitor {}: {}", mon_type, e), } - }); + })); } // Instantiate output threads and structs - // let mut outputs: Vec<(Box, Filter)> = vec![]; let mut output_senders: Vec> = vec![]; + let mut output_threads = vec![]; for out_config in config.outputs { let out_type = out_config .get("type") @@ -133,11 +140,13 @@ fn main() { output_senders.push(out_sender); let filter = Filter::new(&out_config); + let bar = barrier.clone(); - thread::spawn(move || { + output_threads.push(thread::spawn(move || { log::info!("+> output: {}", out_type); match outputs::factory(&out_type, &out_config) { Ok(mut output) => loop { + bar.wait(); let message = out_receiver.recv().unwrap(); if filter.is_message_allowed(&message) { output.process_message(message); @@ -145,10 +154,20 @@ fn main() { }, Err(e) => log::error!("Cannot instantiate output {}: {}", out_type, e), } - }); + })); } let output_senders = output_senders; + // Start all the threads + barrier.wait(); + log::info!("Started rnetmon"); + for t in &mon_threads { + t.thread().unpark(); + } + for t in &output_threads { + t.thread().unpark(); + } + // Dispatch messages loop { let message = mon_receiver.recv().unwrap(); diff --git a/src/monitors/dhcp_leases.rs b/src/monitors/dhcp_leases.rs index b6b8b48..52761e8 100644 --- a/src/monitors/dhcp_leases.rs +++ b/src/monitors/dhcp_leases.rs @@ -31,14 +31,15 @@ impl Monitor for DHCPLeases { .get("config") .unwrap_or(&serde_yaml::Mapping::new().into()) .clone(); - let config = serde_yaml::from_value(config_node).expect("Invalid config for dhcp_leases"); + let config = serde_yaml::from_value(config_node)?; // Regex compilation let rgx_lease = Regex::new(r"(?s)lease\s+(\d+(?:\.\d+){3})\s*\{\n?(.*?)\}").unwrap(); let rgx_mac = - Regex::new(r"^\s*hardware\s+ethernet\s([a-f0-9]{2}(?::[a-f0-9]{2}){5})\s*;").unwrap(); - let rgx_date_start = Regex::new(r"^\s*starts\s+(.*?)\s*;").unwrap(); - let rgx_date_ends = Regex::new(r"^\s*ends\s+(.*?)\s*;").unwrap(); + Regex::new(r"(?m)^\s*hardware\s+ethernet\s([a-f0-9]{2}(?::[a-f0-9]{2}){5})\s*;") + .unwrap(); + let rgx_date_start = Regex::new(r"(?m)^\s*starts\s+\d+\s+(.*?)\s*;").unwrap(); + let rgx_date_ends = Regex::new(r"(?m)^\s*ends\s+\d+\s+(.*?)\s*;").unwrap(); Ok(DHCPLeases { config: config, @@ -66,36 +67,38 @@ impl Monitor for DHCPLeases { let mut unauthorized_macs: Vec = vec![]; for cap in self.rgx_lease.captures_iter(&config_content) { - let ip = cap.get(1).unwrap().as_str(); + let _ip = cap.get(1).unwrap().as_str(); let content = cap.get(2).unwrap().as_str(); let mac = self .rgx_mac .captures(content) - .expect("No 'hardware ethernet' field found for MAC address") - .get(0) + .expect(&format!( + "No 'hardware ethernet' field found for MAC address in {}", + content + )) + .get(1) .unwrap() .as_str(); let starts_str = self .rgx_date_start .captures(content) .expect("No 'starts' field found in lease") - .get(0) + .get(1) .unwrap() .as_str(); let starts = - chrono::naive::NaiveDateTime::parse_from_str(starts_str, "%U %Y/%m/%d %H:%M:%S") - .expect("Bad date format"); + chrono::naive::NaiveDateTime::parse_from_str(starts_str, "%Y/%m/%d %H:%M:%S") + .expect(&format!("Bad date format: '{}'", starts_str)); let ends_str = self .rgx_date_ends .captures(content) .expect("No 'ends' field found in lease") - .get(0) + .get(1) .unwrap() .as_str(); - let ends = - chrono::naive::NaiveDateTime::parse_from_str(ends_str, "%U %Y/%m/%d %H:%M:%S") - .expect("Bad date format"); + let ends = chrono::naive::NaiveDateTime::parse_from_str(ends_str, "%Y/%m/%d %H:%M:%S") + .expect(&format!("Bad date format: '{}'", ends_str)); let now = Local::now().naive_local(); if starts <= now && now < ends { diff --git a/src/outputs/light_beewi_bbl227.rs b/src/outputs/light_beewi_bbl227.rs index bb498d2..ceabe32 100644 --- a/src/outputs/light_beewi_bbl227.rs +++ b/src/outputs/light_beewi_bbl227.rs @@ -2,6 +2,7 @@ extern crate blurz; use blurz::bluetooth_adapter::BluetoothAdapter; use blurz::bluetooth_device::BluetoothDevice; +use blurz::bluetooth_discovery_session::BluetoothDiscoverySession; use blurz::bluetooth_gatt_characteristic::BluetoothGATTCharacteristic; use blurz::bluetooth_gatt_service::BluetoothGATTService; use blurz::bluetooth_session::BluetoothSession; @@ -63,13 +64,19 @@ impl LightConfig { } } impl BluetoothLightbulb { - fn set_color(&self, characs: &Vec, r: f64, g: f64, b: f64) { + fn set_color( + &self, + targets: &Vec<(BluetoothDevice, BluetoothGATTCharacteristic)>, + r: f64, + g: f64, + b: f64, + ) { let r = (r * 255.0) as u8; let g = (g * 255.0) as u8; let b = (b * 255.0) as u8; // println!("r={} g={} b={}", r, g, b); - for charac in characs { + for (_, charac) in targets { charac .write_value(vec![0x55, 0x13, r, g, b, '\r' as u8, '\n' as u8], None) .unwrap(); @@ -78,10 +85,15 @@ impl BluetoothLightbulb { fn initiate_scan(&self, adapter: &BluetoothAdapter) { if !adapter.is_discovering().unwrap() { - log::info!("bluetooth adapter {}: Set scan ON", adapter.get_id()); - adapter + log::info!("{}: Searching for new devices...", adapter.get_id()); + + let disc_session = + BluetoothDiscoverySession::create_session(&self.session, adapter.get_id()) + .expect("Could not create discovery session"); + + disc_session .start_discovery() - .expect("Cannot activate scanning on bluetooth device"); + .expect("Could not start discovery session"); } } } @@ -94,8 +106,7 @@ impl Output for BluetoothLightbulb { .get("config") .unwrap_or(&serde_yaml::Mapping::new().into()) .clone(); - let config: BluetoothLightbulbConfig = - serde_yaml::from_value(config_node).expect("Invalid config for light_beewi_bbl227"); + let config: BluetoothLightbulbConfig = serde_yaml::from_value(config_node)?; let msg_types = config .msg_types @@ -115,8 +126,7 @@ impl Output for BluetoothLightbulb { .map(|(k, v)| (k.clone(), v.clone())) .collect(); - let session = - BluetoothSession::create_session(None).expect("Could not initialize bluetooth session"); + let session = BluetoothSession::create_session(None)?; let ret = BluetoothLightbulb { config: config, @@ -125,9 +135,8 @@ impl Output for BluetoothLightbulb { session: session, }; - // Initialize bluetooth device - let adapter = - BluetoothAdapter::init(&ret.session).expect("Could not initialize bluetooth adapter"); + // Initialize bluetooth adapter + let adapter = BluetoothAdapter::init(&ret.session)?; // Power on if !adapter.is_powered().unwrap() { log::info!("bluetooth adapter {}: Set power ON", adapter.get_id()); @@ -135,8 +144,6 @@ impl Output for BluetoothLightbulb { .set_powered(true) .expect("Cannot power on bluetooth device"); } - // Start scan - ret.initiate_scan(&adapter); Ok(ret) } @@ -144,7 +151,7 @@ impl Output for BluetoothLightbulb { let adapter = BluetoothAdapter::init(&self.session).expect("Could not initialize bluetooth adapter"); - let mut characs = vec![]; + let mut targets = vec![]; let mut found_macs: HashMap = HashMap::new(); for mac in self.config.mac.keys() { found_macs.insert(mac.to_owned(), false); @@ -157,13 +164,19 @@ impl Output for BluetoothLightbulb { if self.config.mac.contains_key(&addr) { if !device.is_connected().unwrap() { log::info!("Connecting to {}", addr); - match device.connect(5000) { + match device.connect(30000) { Ok(_) => { - let power = device.get_tx_power().expect("Could not request tx power"); + let power = match device.get_tx_power() { + Ok(p) => p.to_string(), + Err(_) => "?".to_owned(), + }; log::info!("Connected to {}, tx power={}", addr, power); + + // GATT characteristics can take a second to correctly load once connected + std::thread::sleep(std::time::Duration::from_secs(1)); } Err(e) => { - log::error!("Could not connect to {}: {}", addr, e); + log::error!("Could not connect to {}: {:?}", addr, e); continue; } } @@ -182,7 +195,7 @@ impl Output for BluetoothLightbulb { let charac_uuid = charac.get_uuid().unwrap(); if charac_uuid == "a8b3fff1-4834-4051-89d0-3de95cddd318" { - characs.push(charac); + targets.push((device.clone(), charac)); found_macs.insert(addr.clone(), true); } } @@ -191,11 +204,11 @@ impl Output for BluetoothLightbulb { } } + // List devices not found let macs_not_found: HashMap = found_macs .into_iter() .filter(|(_, v)| *v == false) .collect(); - if macs_not_found.len() > 0 { log::warn!( "Could not find configured lightbulbs: {:?}", @@ -205,7 +218,7 @@ impl Output for BluetoothLightbulb { return; } - // find out color & pattern to play + // Merge LightConfig properties let mut cfg = LightConfig { color: Some(0xffffff), animation: Some(Animation::Smooth), @@ -222,6 +235,7 @@ impl Output for BluetoothLightbulb { cfg.merge(level_cfg); } + // Aimation properties let color = cfg.color.unwrap(); let r = ((color >> 16) & 0xff) as f64 / 255.0; let g = ((color >> 8) & 0xff) as f64 / 255.0; @@ -235,16 +249,16 @@ impl Output for BluetoothLightbulb { for _ in 0..cfg.repeat.unwrap() { match anim { Animation::None => { - self.set_color(&characs, r, g, b); + self.set_color(&targets, r, g, b); std::thread::sleep(std::time::Duration::from_millis((2000.0 / speed) as u64)); } Animation::Bounce => { for _ in 0..4 { - self.set_color(&characs, r, g, b); + self.set_color(&targets, r, g, b); std::thread::sleep(std::time::Duration::from_millis( (250.0 / speed) as u64, )); - self.set_color(&characs, r * 0.5, g * 0.5, b * 0.5); + self.set_color(&targets, r * 0.5, g * 0.5, b * 0.5); std::thread::sleep(std::time::Duration::from_millis( (250.0 / speed) as u64, )); @@ -253,11 +267,11 @@ impl Output for BluetoothLightbulb { Animation::Smooth => { for i in 0..10 { let mult = i as f64 / 10.0; - self.set_color(&characs, r * mult, g * mult, b * mult); + self.set_color(&targets, r * mult, g * mult, b * mult); } for i in 0..10 { let mult = 1.0 - (i as f64 / 10.0); - self.set_color(&characs, r * mult, g * mult, b * mult); + self.set_color(&targets, r * mult, g * mult, b * mult); } } Animation::Blink => { @@ -266,7 +280,7 @@ impl Output for BluetoothLightbulb { 0 => (r, g, b), _ => (0.0, 0.0, 0.0), }; - self.set_color(&characs, r, g, b); + self.set_color(&targets, r, g, b); std::thread::sleep(std::time::Duration::from_millis( (400.0 / speed) as u64, )); @@ -275,24 +289,24 @@ impl Output for BluetoothLightbulb { Animation::RampUp => { for i in 0..20 { let mult = i as f64 / 20.0; - self.set_color(&characs, r * mult, g * mult, b * mult); + self.set_color(&targets, r * mult, g * mult, b * mult); } std::thread::sleep(std::time::Duration::from_millis(100)); - self.set_color(&characs, 0.0, 0.0, 0.0); + self.set_color(&targets, 0.0, 0.0, 0.0); std::thread::sleep(std::time::Duration::from_millis(100)); } Animation::RampDown => { - self.set_color(&characs, r, g, b); + self.set_color(&targets, r, g, b); std::thread::sleep(std::time::Duration::from_millis(100)); for i in 1..20 { let mult = 1.0 - (i as f64 / 20.0); - self.set_color(&characs, r * mult, g * mult, b * mult); + self.set_color(&targets, r * mult, g * mult, b * mult); } - self.set_color(&characs, 0.0, 0.0, 0.0); + self.set_color(&targets, 0.0, 0.0, 0.0); std::thread::sleep(std::time::Duration::from_millis(100)); } } } - self.set_color(&characs, 0.0, 0.0, 0.0); + self.set_color(&targets, 0.0, 0.0, 0.0); } }