WIP
This commit is contained in:
parent
d030beb00b
commit
4f1f1ba88e
|
@ -1,3 +1,5 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "0.6.9"
|
version = "0.6.9"
|
||||||
|
@ -15,6 +17,11 @@ dependencies = [
|
||||||
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.28"
|
version = "1.0.28"
|
||||||
|
@ -216,16 +223,20 @@ dependencies = [
|
||||||
"getopts 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
"getopts 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"globset 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"globset 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libloading 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libloading 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde_derive 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"serde_yaml 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_yaml 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"simplelog 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.85"
|
version = "1.0.85"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
|
@ -248,6 +259,16 @@ dependencies = [
|
||||||
"yaml-rust 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"yaml-rust 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simplelog"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"term 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "0.15.26"
|
version = "0.15.26"
|
||||||
|
@ -258,6 +279,15 @@ dependencies = [
|
||||||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "term"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thread_local"
|
name = "thread_local"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
|
@ -326,6 +356,7 @@ dependencies = [
|
||||||
[metadata]
|
[metadata]
|
||||||
"checksum aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9a933f4e58658d7b12defcf96dc5c720f20832deebe3e0a19efd3b6aaeeb9e"
|
"checksum aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9a933f4e58658d7b12defcf96dc5c720f20832deebe3e0a19efd3b6aaeeb9e"
|
||||||
"checksum blurz 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1bc2df4d0f1d373b324672d9966aca3f5ba1f03d69edad6240144774539ea59"
|
"checksum blurz 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1bc2df4d0f1d373b324672d9966aca3f5ba1f03d69edad6240144774539ea59"
|
||||||
|
"checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb"
|
||||||
"checksum cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4a8b715cb4597106ea87c7c84b2f1d452c7492033765df7f32651e66fcf749"
|
"checksum cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4a8b715cb4597106ea87c7c84b2f1d452c7492033765df7f32651e66fcf749"
|
||||||
"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4"
|
"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4"
|
||||||
"checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878"
|
"checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878"
|
||||||
|
@ -355,7 +386,9 @@ dependencies = [
|
||||||
"checksum serde 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)" = "534b8b91a95e0f71bca3ed5824752d558da048d4248c91af873b63bd60519752"
|
"checksum serde 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)" = "534b8b91a95e0f71bca3ed5824752d558da048d4248c91af873b63bd60519752"
|
||||||
"checksum serde_derive 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)" = "a915306b0f1ac5607797697148c223bedeaa36bcc2e28a01441cd638cc6567b4"
|
"checksum serde_derive 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)" = "a915306b0f1ac5607797697148c223bedeaa36bcc2e28a01441cd638cc6567b4"
|
||||||
"checksum serde_yaml 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0887a8e097a69559b56aa2526bf7aff7c3048cf627dff781f0b56a6001534593"
|
"checksum serde_yaml 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0887a8e097a69559b56aa2526bf7aff7c3048cf627dff781f0b56a6001534593"
|
||||||
|
"checksum simplelog 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e95345f185d5adeb8ec93459d2dc99654e294cc6ccf5b75414d8ea262de9a13"
|
||||||
"checksum syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)" = "f92e629aa1d9c827b2bb8297046c1ccffc57c99b947a680d3ccff1f136a3bee9"
|
"checksum syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)" = "f92e629aa1d9c827b2bb8297046c1ccffc57c99b947a680d3ccff1f136a3bee9"
|
||||||
|
"checksum term 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5e6b677dd1e8214ea1ef4297f85dbcbed8e8cdddb561040cc998ca2551c37561"
|
||||||
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
|
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
|
||||||
"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
|
"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
|
||||||
"checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86"
|
"checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86"
|
||||||
|
|
|
@ -6,11 +6,12 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libloading = "0.5.0"
|
libloading = "0.5.0"
|
||||||
serde = "1.0"
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_yaml = "0.8"
|
serde_yaml = "0.8"
|
||||||
serde_derive = "1.0"
|
|
||||||
getopts = "0.2"
|
getopts = "0.2"
|
||||||
blurz = "0.4.0"
|
blurz = "0.4.0"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
globset = "0.3"
|
globset = "0.3"
|
||||||
|
log = "0.4"
|
||||||
|
simplelog = "0.5"
|
|
@ -1,8 +0,0 @@
|
||||||
use serde_derive::*;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub struct Config {
|
|
||||||
pub monitors: Vec<HashMap<String, serde_yaml::Value>>,
|
|
||||||
pub outputs: Vec<HashMap<String, serde_yaml::Value>>,
|
|
||||||
}
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
use globset::{Glob, GlobMatcher};
|
||||||
|
|
||||||
|
pub use std::collections::HashMap;
|
||||||
|
|
||||||
|
use log::{info, trace, warn};
|
||||||
|
|
||||||
|
use crate::message::*;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Filter {
|
||||||
|
level: Level,
|
||||||
|
include_types: Vec<GlobMatcher>,
|
||||||
|
exclude_types: Vec<GlobMatcher>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Filter {
|
||||||
|
pub fn new(config: &HashMap<String, serde_yaml::Value>) -> Self {
|
||||||
|
let filter_node = config
|
||||||
|
.get("filter")
|
||||||
|
.unwrap_or(&serde_yaml::Mapping::new().into())
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let include_types = match filter_node.get("include_types") {
|
||||||
|
Some(v) => v
|
||||||
|
.as_sequence()
|
||||||
|
.expect("include_types must be a list of strings")
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| {
|
||||||
|
Glob::new(x.as_str().expect("filter must be a list of strings"))
|
||||||
|
.expect("Invalid glob pattern")
|
||||||
|
.compile_matcher()
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
None => vec![],
|
||||||
|
};
|
||||||
|
let exclude_types = match filter_node.get("exclude_types") {
|
||||||
|
Some(v) => v
|
||||||
|
.as_sequence()
|
||||||
|
.expect("exclude_types must be a list of strings")
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| {
|
||||||
|
Glob::new(x.as_str().expect("filter must be a list of strings"))
|
||||||
|
.expect("Invalid glob pattern")
|
||||||
|
.compile_matcher()
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
None => vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let level = match filter_node.get("level") {
|
||||||
|
Some(l) => Level::try_from(l.as_str().expect("Level must be a string"))
|
||||||
|
.expect("Unknown level name"),
|
||||||
|
None => Level::Notice,
|
||||||
|
};
|
||||||
|
|
||||||
|
Filter {
|
||||||
|
level: level,
|
||||||
|
include_types: include_types,
|
||||||
|
exclude_types: exclude_types,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_message_allowed(&self, message: &Message) -> bool {
|
||||||
|
if message.level < self.level {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for exc in &self.exclude_types {
|
||||||
|
if exc.is_match(&message.msg_type) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.include_types.len() > 0 {
|
||||||
|
for exc in &self.include_types {
|
||||||
|
if exc.is_match(&message.msg_type) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
107
src/main.rs
107
src/main.rs
|
@ -1,25 +1,37 @@
|
||||||
mod config;
|
#![feature(unrestricted_attribute_tokens)]
|
||||||
|
#![feature(custom_attribute)]
|
||||||
|
#![feature(try_from)]
|
||||||
|
|
||||||
|
mod filter;
|
||||||
mod message;
|
mod message;
|
||||||
mod monitor;
|
mod monitor;
|
||||||
|
mod monitors;
|
||||||
mod output;
|
mod output;
|
||||||
mod plugins;
|
mod outputs;
|
||||||
|
|
||||||
extern crate getopts;
|
extern crate getopts;
|
||||||
extern crate serde_derive;
|
extern crate globset;
|
||||||
|
extern crate log;
|
||||||
extern crate serde_yaml;
|
extern crate serde_yaml;
|
||||||
|
extern crate simplelog;
|
||||||
|
|
||||||
|
use getopts::Options;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
use getopts::Options;
|
use crate::filter::*;
|
||||||
|
|
||||||
use crate::config::*;
|
|
||||||
use crate::message::*;
|
use crate::message::*;
|
||||||
use crate::monitor::*;
|
|
||||||
use crate::output::*;
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct AppConfig {
|
||||||
|
pub monitors: Vec<HashMap<String, serde_yaml::Value>>,
|
||||||
|
pub outputs: Vec<HashMap<String, serde_yaml::Value>>,
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args: Vec<String> = env::args().collect();
|
let args: Vec<String> = env::args().collect();
|
||||||
|
@ -33,6 +45,8 @@ fn main() {
|
||||||
"config.yaml",
|
"config.yaml",
|
||||||
);
|
);
|
||||||
opts.optflag("h", "help", "print this help menu");
|
opts.optflag("h", "help", "print this help menu");
|
||||||
|
opts.optflag("v", "verbose", "Switch to DEBUG output");
|
||||||
|
opts.optflag("q", "quiet", "Switch to WARNING output");
|
||||||
|
|
||||||
let matches = match opts.parse(&args[1..]) {
|
let matches = match opts.parse(&args[1..]) {
|
||||||
Ok(m) => m,
|
Ok(m) => m,
|
||||||
|
@ -44,6 +58,30 @@ fn main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let level;
|
||||||
|
if matches.opt_present("v") && matches.opt_present("q") {
|
||||||
|
panic!("-v and -q are mutually exclusive");
|
||||||
|
}
|
||||||
|
if matches.opt_present("v") {
|
||||||
|
level = simplelog::LevelFilter::Debug;
|
||||||
|
} else if matches.opt_present("q") {
|
||||||
|
level = simplelog::LevelFilter::Warn;
|
||||||
|
} else {
|
||||||
|
level = simplelog::LevelFilter::Info;
|
||||||
|
}
|
||||||
|
|
||||||
|
simplelog::TermLogger::init(
|
||||||
|
level,
|
||||||
|
simplelog::Config {
|
||||||
|
time: Some(simplelog::Level::Debug),
|
||||||
|
level: Some(simplelog::Level::Error),
|
||||||
|
target: Some(simplelog::Level::Info),
|
||||||
|
location: Some(simplelog::Level::Debug),
|
||||||
|
time_format: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Parse config
|
// Parse config
|
||||||
let config_path = matches.opt_str("c").unwrap_or("./config.yaml".to_owned());
|
let config_path = matches.opt_str("c").unwrap_or("./config.yaml".to_owned());
|
||||||
|
|
||||||
|
@ -52,7 +90,7 @@ fn main() {
|
||||||
config_file
|
config_file
|
||||||
.read_to_string(&mut config_content)
|
.read_to_string(&mut config_content)
|
||||||
.expect("Could not read configuration file");
|
.expect("Could not read configuration file");
|
||||||
let config: Config =
|
let config: AppConfig =
|
||||||
serde_yaml::from_str(&config_content).expect("Invalid config file content");
|
serde_yaml::from_str(&config_content).expect("Invalid config file content");
|
||||||
|
|
||||||
// Monitors -> dispatcher channel
|
// Monitors -> dispatcher channel
|
||||||
|
@ -60,40 +98,53 @@ fn main() {
|
||||||
|
|
||||||
// Instantiate monitor threads and structs
|
// Instantiate monitor threads and structs
|
||||||
for mon_config in config.monitors {
|
for mon_config in config.monitors {
|
||||||
let name = mon_config
|
let mon_type = mon_config
|
||||||
.get("name")
|
.get("type")
|
||||||
.expect("Missing `name` key for monitor")
|
.expect("Missing `type` key for monitor")
|
||||||
.as_str()
|
.as_str()
|
||||||
.expect("Key `name` for monitor is not a string");
|
.expect("Key `type` for monitor is not a string")
|
||||||
|
.to_owned();
|
||||||
println!("Loading monitor: {}", name);
|
|
||||||
|
|
||||||
let snd = mon_sender.clone();
|
let snd = mon_sender.clone();
|
||||||
let mut mon = plugins::monitor::factory(name, &mon_config);
|
|
||||||
thread::spawn(move || loop {
|
thread::spawn(move || {
|
||||||
mon.run(&snd);
|
log::info!("+> monitor: {}", mon_type);
|
||||||
|
match monitors::factory(&mon_type, &mon_config) {
|
||||||
|
Ok(mut mon) => loop {
|
||||||
|
mon.run(&snd);
|
||||||
|
},
|
||||||
|
Err(e) => log::error!("Cannot instantiate monitor {}: {}", mon_type, e),
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instantiate output threads and structs
|
// Instantiate output threads and structs
|
||||||
|
// let mut outputs: Vec<(Box<Output>, Filter)> = vec![];
|
||||||
let mut output_senders: Vec<mpsc::Sender<Message>> = vec![];
|
let mut output_senders: Vec<mpsc::Sender<Message>> = vec![];
|
||||||
for out_config in config.outputs {
|
for out_config in config.outputs {
|
||||||
let name = out_config
|
let out_type = out_config
|
||||||
.get("name")
|
.get("type")
|
||||||
.expect("Missing `name` key for monitor")
|
.expect("Missing `type` key for output")
|
||||||
.as_str()
|
.as_str()
|
||||||
.expect("Key `name` for monitor is not a string");
|
.expect("Key `type` for output is not a string")
|
||||||
|
.to_owned();
|
||||||
println!("Loading output: {}", name);
|
|
||||||
|
|
||||||
let (out_sender, out_receiver) = mpsc::channel();
|
let (out_sender, out_receiver) = mpsc::channel();
|
||||||
output_senders.push(out_sender);
|
output_senders.push(out_sender);
|
||||||
|
|
||||||
let mut output = plugins::output::factory(name, &out_config);
|
let filter = Filter::new(&out_config);
|
||||||
|
|
||||||
thread::spawn(move || loop {
|
thread::spawn(move || {
|
||||||
let message = out_receiver.recv().unwrap();
|
log::info!("+> output: {}", out_type);
|
||||||
output.process_message(message);
|
match outputs::factory(&out_type, &out_config) {
|
||||||
|
Ok(mut output) => loop {
|
||||||
|
let message = out_receiver.recv().unwrap();
|
||||||
|
if filter.is_message_allowed(&message) {
|
||||||
|
output.process_message(message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => log::error!("Cannot instantiate output {}: {}", out_type, e),
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let output_senders = output_senders;
|
let output_senders = output_senders;
|
||||||
|
|
|
@ -1,10 +1,33 @@
|
||||||
#[derive(Debug, Clone)]
|
use serde::Deserialize;
|
||||||
|
pub use std::convert::TryFrom;
|
||||||
|
|
||||||
|
// pub type Level = u8;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Hash, Deserialize)]
|
||||||
pub enum Level {
|
pub enum Level {
|
||||||
Debug = 0,
|
Debug = 0,
|
||||||
Info = 10,
|
Notice = 1,
|
||||||
Warning = 20,
|
Anomaly = 2,
|
||||||
Error = 30,
|
Issue = 3,
|
||||||
Critical = 40,
|
Critical = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for Level {
|
||||||
|
type Error = ();
|
||||||
|
fn try_from(level: &str) -> Result<Self, ()> {
|
||||||
|
match serde_yaml::from_str(level) {
|
||||||
|
Ok(v) => v,
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
// match level {
|
||||||
|
// "Debug" => Ok(Level::Debug),
|
||||||
|
// "Notice" => Ok(Level::Notice),
|
||||||
|
// "Anomaly" => Ok(Level::Anomaly),
|
||||||
|
// "Issue" => Ok(Level::Issue),
|
||||||
|
// "Critical" => Ok(Level::Critical),
|
||||||
|
// _ => Err(()),
|
||||||
|
// }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
|
@ -4,7 +4,7 @@ pub use std::collections::HashMap;
|
||||||
pub use std::sync::mpsc;
|
pub use std::sync::mpsc;
|
||||||
|
|
||||||
pub trait Monitor {
|
pub trait Monitor {
|
||||||
fn new(config: &HashMap<String, serde_yaml::Value>) -> Self
|
fn new(config: &HashMap<String, serde_yaml::Value>) -> Result<Self, Box<dyn std::error::Error>>
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
fn run(&mut self, sender: &mpsc::Sender<Message>);
|
fn run(&mut self, sender: &mpsc::Sender<Message>);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
pub use crate::message::*;
|
pub use crate::message::*;
|
||||||
pub use crate::monitor::*;
|
pub use crate::monitor::*;
|
||||||
use serde_derive::*;
|
use serde::Deserialize;
|
||||||
|
|
||||||
extern crate regex;
|
extern crate regex;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
@ -15,39 +15,49 @@ pub struct DHCPLeases {
|
||||||
rgx_date_start: Regex,
|
rgx_date_start: Regex,
|
||||||
rgx_date_ends: Regex,
|
rgx_date_ends: Regex,
|
||||||
}
|
}
|
||||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Deserialize)]
|
||||||
struct DHCPLeasesConfig {
|
struct DHCPLeasesConfig {
|
||||||
|
#[serde(default)]
|
||||||
path: String,
|
path: String,
|
||||||
mac_rules: HashMap<String, Vec<String>>,
|
#[serde(default)]
|
||||||
|
mac_rules: HashMap<String, Option<Vec<String>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Monitor for DHCPLeases {
|
impl Monitor for DHCPLeases {
|
||||||
fn new(config: &HashMap<String, serde_yaml::Value>) -> Self {
|
fn new(
|
||||||
let config_node = config.get("config").expect("Missing `config` key").clone();
|
config: &HashMap<String, serde_yaml::Value>,
|
||||||
let config =
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
serde_yaml::from_value(config_node).expect("Invalid config for wifi_availability");
|
let config_node = config
|
||||||
|
.get("config")
|
||||||
|
.unwrap_or(&serde_yaml::Mapping::new().into())
|
||||||
|
.clone();
|
||||||
|
let config = serde_yaml::from_value(config_node).expect("Invalid config for dhcp_leases");
|
||||||
|
|
||||||
// Regex compilation
|
// Regex compilation
|
||||||
let rgx_lease = Regex::new(r"(?s)lease\s+(\d+(?:\.\d+){3})\s*\{\n?(.*?)\}").unwrap();
|
let rgx_lease = Regex::new(r"(?s)lease\s+(\d+(?:\.\d+){3})\s*\{\n?(.*?)\}").unwrap();
|
||||||
let rgx_mac =
|
let rgx_mac =
|
||||||
Regex::new(r"^\s*hardware\s+ethernet\s([a-f0-9]{2}(?:\:[a-f0-9]{2}){5})\s*;").unwrap();
|
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_start = Regex::new(r"^\s*starts\s+(.*?)\s*;").unwrap();
|
||||||
let rgx_date_ends = Regex::new(r"^\s*ends\s+(.*?)\s*;").unwrap();
|
let rgx_date_ends = Regex::new(r"^\s*ends\s+(.*?)\s*;").unwrap();
|
||||||
|
|
||||||
DHCPLeases {
|
Ok(DHCPLeases {
|
||||||
config: config,
|
config: config,
|
||||||
rgx_lease: rgx_lease,
|
rgx_lease: rgx_lease,
|
||||||
rgx_mac: rgx_mac,
|
rgx_mac: rgx_mac,
|
||||||
rgx_date_start: rgx_date_start,
|
rgx_date_start: rgx_date_start,
|
||||||
rgx_date_ends: rgx_date_ends,
|
rgx_date_ends: rgx_date_ends,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
fn run(&mut self, sender: &mpsc::Sender<Message>) {
|
fn run(&mut self, sender: &mpsc::Sender<Message>) {
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
|
|
||||||
|
let config_file_path = match self.config.path.len() {
|
||||||
|
0 => "/var/lib/dhcp/dhcpd.leases",
|
||||||
|
_ => &self.config.path,
|
||||||
|
};
|
||||||
let mut config_file =
|
let mut config_file =
|
||||||
File::open(&self.config.path).expect("Could not open DHCP leases file");
|
File::open(config_file_path).expect("Could not open DHCP leases file");
|
||||||
let mut config_content = String::new();
|
let mut config_content = String::new();
|
||||||
config_file
|
config_file
|
||||||
.read_to_string(&mut config_content)
|
.read_to_string(&mut config_content)
|
||||||
|
@ -91,19 +101,26 @@ impl Monitor for DHCPLeases {
|
||||||
if starts <= now && now < ends {
|
if starts <= now && now < ends {
|
||||||
// Lease is active
|
// Lease is active
|
||||||
if let Some(rules) = self.config.mac_rules.get(mac) {
|
if let Some(rules) = self.config.mac_rules.get(mac) {
|
||||||
for rule in rules {
|
// Found rules
|
||||||
if content.find(rule).is_none() {
|
if let Some(rules_list) = rules {
|
||||||
unauthorized_macs.push(mac.to_owned());
|
// Rules contains one or more entries
|
||||||
|
for rule in rules_list {
|
||||||
|
if content.find(rule).is_none() {
|
||||||
|
unauthorized_macs.push(mac.to_owned());
|
||||||
|
|
||||||
sender
|
sender
|
||||||
.send(Message {
|
.send(Message {
|
||||||
emitter: "dhcp_leases".to_owned(),
|
emitter: "dhcp_leases".to_owned(),
|
||||||
level: Level::Error,
|
level: Level::Issue,
|
||||||
msg_type: "dhcp_leases.unauthorized_mac.rule".to_owned(),
|
msg_type: "dhcp_leases.unauthorized_mac.rule".to_owned(),
|
||||||
text: format!("Mismatching rule '{}' for device {}", rule, mac),
|
text: format!(
|
||||||
})
|
"Mismatching rule '{}' for device {}",
|
||||||
.unwrap();
|
rule, mac
|
||||||
break;
|
),
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -112,7 +129,7 @@ impl Monitor for DHCPLeases {
|
||||||
sender
|
sender
|
||||||
.send(Message {
|
.send(Message {
|
||||||
emitter: "dhcp_leases".to_owned(),
|
emitter: "dhcp_leases".to_owned(),
|
||||||
level: Level::Error,
|
level: Level::Issue,
|
||||||
msg_type: "dhcp_leases.unauthorized_mac.unknown".to_owned(),
|
msg_type: "dhcp_leases.unauthorized_mac.unknown".to_owned(),
|
||||||
text: format!("Unauthorized device on network: {}", mac),
|
text: format!("Unauthorized device on network: {}", mac),
|
||||||
})
|
})
|
||||||
|
@ -121,7 +138,19 @@ impl Monitor for DHCPLeases {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if unauthorized_macs.len() > 0 {}
|
if unauthorized_macs.len() > 0 {
|
||||||
|
sender
|
||||||
|
.send(Message {
|
||||||
|
emitter: "dhcp_leases".to_owned(),
|
||||||
|
level: Level::Issue,
|
||||||
|
msg_type: "dhcp_leases.unknown_mac".to_owned(),
|
||||||
|
text: format!(
|
||||||
|
"The following macs are not allowed: {:?}",
|
||||||
|
unauthorized_macs
|
||||||
|
),
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
// let leases: Vec<(&str, &str)> = lease_rgx
|
// let leases: Vec<(&str, &str)> = lease_rgx
|
||||||
// .captures_iter(&config_content)
|
// .captures_iter(&config_content)
|
|
@ -0,0 +1,75 @@
|
||||||
|
pub use crate::message::{Level, Message};
|
||||||
|
pub use crate::monitor::*;
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DummySender {
|
||||||
|
cnt: i64,
|
||||||
|
config: DummySenderConfig,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct DummySenderConfig {
|
||||||
|
#[serde(default)]
|
||||||
|
message: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
level: Option<Level>,
|
||||||
|
#[serde(default)]
|
||||||
|
msg_type: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
period: Option<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Monitor for DummySender {
|
||||||
|
fn new(
|
||||||
|
config: &HashMap<String, serde_yaml::Value>,
|
||||||
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
let config_node = config
|
||||||
|
.get("config")
|
||||||
|
.unwrap_or(&serde_yaml::Mapping::new().into())
|
||||||
|
.clone();
|
||||||
|
let config: DummySenderConfig =
|
||||||
|
serde_yaml::from_value(config_node).expect("Invalid config for dummy_sender");
|
||||||
|
|
||||||
|
Ok(DummySender {
|
||||||
|
cnt: 0,
|
||||||
|
config: config,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn run(&mut self, sender: &mpsc::Sender<Message>) {
|
||||||
|
log::info!(
|
||||||
|
"Sending message {} from {:?}",
|
||||||
|
self.cnt,
|
||||||
|
std::thread::current().id()
|
||||||
|
);
|
||||||
|
sender
|
||||||
|
.send(Message {
|
||||||
|
emitter: "dummy_sender".to_owned(),
|
||||||
|
level: match &self.config.level {
|
||||||
|
Some(l) => l.clone(),
|
||||||
|
None => Level::Notice,
|
||||||
|
},
|
||||||
|
msg_type: match &self.config.msg_type {
|
||||||
|
Some(s) => s.to_owned(),
|
||||||
|
None => "dummy_sender.message".to_owned(),
|
||||||
|
},
|
||||||
|
text: match &self.config.message {
|
||||||
|
Some(s) => s.to_owned(),
|
||||||
|
None => format!(
|
||||||
|
"This is message {} from {:?}",
|
||||||
|
self.cnt,
|
||||||
|
std::thread::current().id()
|
||||||
|
),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
self.cnt += 1;
|
||||||
|
|
||||||
|
let delay_ms = match self.config.period {
|
||||||
|
Some(sec) => (sec * 1000.0) as u64,
|
||||||
|
None => 2000,
|
||||||
|
};
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(delay_ms));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
pub mod dhcp_leases;
|
||||||
|
pub mod dummy_sender;
|
||||||
|
pub mod wifi_availability;
|
||||||
|
|
||||||
|
use crate::monitor::*;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
pub fn factory(
|
||||||
|
name: &str,
|
||||||
|
config: &HashMap<String, serde_yaml::Value>,
|
||||||
|
) -> Result<Box<Monitor>, Box<dyn std::error::Error>> {
|
||||||
|
match name {
|
||||||
|
"dummy_sender" => Ok(Box::new(dummy_sender::DummySender::new(&config)?)),
|
||||||
|
"wifi_availability" => Ok(Box::new(wifi_availability::WifiAvailability::new(&config)?)),
|
||||||
|
"dhcp_leases" => Ok(Box::new(dhcp_leases::DHCPLeases::new(&config)?)),
|
||||||
|
_ => panic!("Unknown monitor name: {}", name),
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
|
@ -1,22 +1,29 @@
|
||||||
use crate::message::Message;
|
use crate::message::{Level, Message};
|
||||||
use crate::monitor::*;
|
use crate::monitor::*;
|
||||||
use serde_derive::*;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct WifiAvailability {
|
pub struct WifiAvailability {
|
||||||
config: WifiAvailabilityConfig,
|
config: WifiAvailabilityConfig,
|
||||||
}
|
}
|
||||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct WifiAvailabilityConfig {
|
struct WifiAvailabilityConfig {
|
||||||
iface: String,
|
iface: String,
|
||||||
ssid: String,
|
ssid: String,
|
||||||
|
#[serde(default)]
|
||||||
essid: Vec<String>,
|
essid: Vec<String>,
|
||||||
|
#[serde(default)]
|
||||||
ping_targets: Vec<String>,
|
ping_targets: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Monitor for WifiAvailability {
|
impl Monitor for WifiAvailability {
|
||||||
fn new(config: &HashMap<String, serde_yaml::Value>) -> Self {
|
fn new(
|
||||||
let config_node = config.get("config").expect("Missing `config` key").clone();
|
config: &HashMap<String, serde_yaml::Value>,
|
||||||
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
let config_node = config
|
||||||
|
.get("config")
|
||||||
|
.unwrap_or(&serde_yaml::Mapping::new().into())
|
||||||
|
.clone();
|
||||||
let config =
|
let config =
|
||||||
serde_yaml::from_value(config_node).expect("Invalid config for wifi_availability");
|
serde_yaml::from_value(config_node).expect("Invalid config for wifi_availability");
|
||||||
|
|
||||||
|
@ -24,7 +31,7 @@ impl Monitor for WifiAvailability {
|
||||||
// Activate iface
|
// Activate iface
|
||||||
//ip link set wlan0 up
|
//ip link set wlan0 up
|
||||||
|
|
||||||
wa
|
Ok(wa)
|
||||||
}
|
}
|
||||||
fn run(&mut self, sender: &mpsc::Sender<Message>) {
|
fn run(&mut self, sender: &mpsc::Sender<Message>) {
|
||||||
// iw dev wlan0 link: get is connected
|
// iw dev wlan0 link: get is connected
|
||||||
|
@ -43,7 +50,7 @@ impl Monitor for WifiAvailability {
|
||||||
.send(Message {
|
.send(Message {
|
||||||
emitter: "wifi_availability".to_owned(),
|
emitter: "wifi_availability".to_owned(),
|
||||||
msg_type: "ping.failed".to_owned(),
|
msg_type: "ping.failed".to_owned(),
|
||||||
level: 10,
|
level: Level::Issue,
|
||||||
text: format!("Cannot ping {}", target),
|
text: format!("Cannot ping {}", target),
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
|
@ -1,52 +1,9 @@
|
||||||
extern crate globset;
|
|
||||||
|
|
||||||
pub use std::collections::HashMap;
|
pub use std::collections::HashMap;
|
||||||
|
|
||||||
use globset::Glob;
|
|
||||||
use std::iter::Map;
|
|
||||||
|
|
||||||
use crate::message::*;
|
use crate::message::*;
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Filter {
|
|
||||||
level: Level,
|
|
||||||
types: Vec<Glob>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Filter {
|
|
||||||
fn new(config: &HashMap<String, serde_yaml::Value>) -> Self {
|
|
||||||
let mut globs = vec![];
|
|
||||||
|
|
||||||
let types = config
|
|
||||||
.get("types")
|
|
||||||
.expect("Missing types key")
|
|
||||||
.as_sequence()
|
|
||||||
.expect("types must be a list of strings")
|
|
||||||
.iter()
|
|
||||||
.map(|x| {
|
|
||||||
Glob::new(x.as_str().expect("filter must be a list of strings"))
|
|
||||||
.expect("Invalid glob pattern")
|
|
||||||
.compile_matcher()
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// let glob = Glob::new("*.rs")?.compile_matcher();
|
|
||||||
Filter {
|
|
||||||
level: config.get("level").to.unwrap_or(Level::Debug),
|
|
||||||
types: types,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fn is_message_filtered(&self, message: Message) -> bool {
|
|
||||||
// if message.level < self.level{
|
|
||||||
// return true
|
|
||||||
// }
|
|
||||||
// if
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Output {
|
pub trait Output {
|
||||||
fn new(config: &HashMap<String, serde_yaml::Value>) -> Self
|
fn new(config: &HashMap<String, serde_yaml::Value>) -> Result<Self, Box<dyn std::error::Error>>
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
fn process_message(&mut self, message: Message);
|
fn process_message(&mut self, message: Message);
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
use crate::message::Message;
|
||||||
|
pub use crate::output::*;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Espeak {
|
||||||
|
config: EspeakConfig,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct EspeakConfig {
|
||||||
|
#[serde(default = "espeak".to_owned())]
|
||||||
|
espeak: String,
|
||||||
|
#[serde(default = vec![])]
|
||||||
|
args: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Output for Espeak {
|
||||||
|
fn new(
|
||||||
|
config: &HashMap<String, serde_yaml::Value>,
|
||||||
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
let node = config
|
||||||
|
.get("config")
|
||||||
|
.unwrap_or(&serde_yaml::Value::Null)
|
||||||
|
.clone();
|
||||||
|
let cfg = serde_yaml::from_value(node).expect("Invalid config for Espeak");
|
||||||
|
|
||||||
|
Ok(Espeak { config: cfg })
|
||||||
|
}
|
||||||
|
fn process_message(&mut self, message: Message) {
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
Command::new(&self.config.espeak)
|
||||||
|
.args(&self.config.args)
|
||||||
|
.arg(message.text)
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.status()
|
||||||
|
.expect("failed to execute process");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,298 @@
|
||||||
|
extern crate blurz;
|
||||||
|
|
||||||
|
use blurz::bluetooth_adapter::BluetoothAdapter;
|
||||||
|
use blurz::bluetooth_device::BluetoothDevice;
|
||||||
|
use blurz::bluetooth_gatt_characteristic::BluetoothGATTCharacteristic;
|
||||||
|
use blurz::bluetooth_gatt_service::BluetoothGATTService;
|
||||||
|
use blurz::bluetooth_session::BluetoothSession;
|
||||||
|
use globset::{Glob, GlobMatcher};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::message::{Level, Message};
|
||||||
|
pub use crate::output::*;
|
||||||
|
|
||||||
|
pub struct BluetoothLightbulb {
|
||||||
|
config: BluetoothLightbulbConfig,
|
||||||
|
session: BluetoothSession,
|
||||||
|
levels: HashMap<Level, LightConfig>,
|
||||||
|
msg_types: Vec<(GlobMatcher, LightConfig)>,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct BluetoothLightbulbConfig {
|
||||||
|
#[serde(default)]
|
||||||
|
mac: HashMap<String, ()>,
|
||||||
|
#[serde(default)]
|
||||||
|
levels: HashMap<Level, LightConfig>,
|
||||||
|
#[serde(default)]
|
||||||
|
msg_types: HashMap<String, LightConfig>,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
enum Animation {
|
||||||
|
None,
|
||||||
|
Smooth,
|
||||||
|
Bounce,
|
||||||
|
Blink,
|
||||||
|
RampUp,
|
||||||
|
RampDown,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
struct LightConfig {
|
||||||
|
#[serde(default)]
|
||||||
|
color: Option<u32>,
|
||||||
|
#[serde(default)]
|
||||||
|
animation: Option<Animation>,
|
||||||
|
#[serde(default)]
|
||||||
|
repeat: Option<u64>,
|
||||||
|
#[serde(default)]
|
||||||
|
speed: Option<f64>,
|
||||||
|
}
|
||||||
|
impl LightConfig {
|
||||||
|
fn merge(&mut self, other: &Self) {
|
||||||
|
if other.color.is_some() {
|
||||||
|
self.color = other.color;
|
||||||
|
}
|
||||||
|
if other.animation.is_some() {
|
||||||
|
self.animation = other.animation.clone();
|
||||||
|
}
|
||||||
|
if other.repeat.is_some() {
|
||||||
|
self.repeat = other.repeat;
|
||||||
|
}
|
||||||
|
if other.speed.is_some() {
|
||||||
|
self.speed = other.speed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl BluetoothLightbulb {
|
||||||
|
fn set_color(&self, characs: &Vec<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 {
|
||||||
|
charac
|
||||||
|
.write_value(vec![0x55, 0x13, r, g, b, '\r' as u8, '\n' as u8], None)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initiate_scan(&self, adapter: &BluetoothAdapter) {
|
||||||
|
if !adapter.is_discovering().unwrap() {
|
||||||
|
log::info!("bluetooth adapter {}: Set scan ON", adapter.get_id());
|
||||||
|
adapter
|
||||||
|
.start_discovery()
|
||||||
|
.expect("Cannot activate scanning on bluetooth device");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Output for BluetoothLightbulb {
|
||||||
|
fn new(
|
||||||
|
config: &HashMap<String, serde_yaml::Value>,
|
||||||
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
let config_node = config
|
||||||
|
.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 msg_types = config
|
||||||
|
.msg_types
|
||||||
|
.iter()
|
||||||
|
.map(|(k, v)| {
|
||||||
|
(
|
||||||
|
Glob::new(k)
|
||||||
|
.expect("Invalid glob pattern")
|
||||||
|
.compile_matcher(),
|
||||||
|
v.clone(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let levels = config
|
||||||
|
.levels
|
||||||
|
.iter()
|
||||||
|
.map(|(k, v)| (k.clone(), v.clone()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let session =
|
||||||
|
BluetoothSession::create_session(None).expect("Could not initialize bluetooth session");
|
||||||
|
|
||||||
|
let ret = BluetoothLightbulb {
|
||||||
|
config: config,
|
||||||
|
msg_types: msg_types,
|
||||||
|
levels: levels,
|
||||||
|
session: session,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize bluetooth device
|
||||||
|
let adapter =
|
||||||
|
BluetoothAdapter::init(&ret.session).expect("Could not initialize bluetooth adapter");
|
||||||
|
// Power on
|
||||||
|
if !adapter.is_powered().unwrap() {
|
||||||
|
log::info!("bluetooth adapter {}: Set power ON", adapter.get_id());
|
||||||
|
adapter
|
||||||
|
.set_powered(true)
|
||||||
|
.expect("Cannot power on bluetooth device");
|
||||||
|
}
|
||||||
|
// Start scan
|
||||||
|
ret.initiate_scan(&adapter);
|
||||||
|
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
fn process_message(&mut self, message: Message) {
|
||||||
|
let adapter =
|
||||||
|
BluetoothAdapter::init(&self.session).expect("Could not initialize bluetooth adapter");
|
||||||
|
|
||||||
|
let mut characs = vec![];
|
||||||
|
let mut found_macs: HashMap<String, bool> = HashMap::new();
|
||||||
|
for mac in self.config.mac.keys() {
|
||||||
|
found_macs.insert(mac.to_owned(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
for dev_path in adapter.get_device_list().unwrap() {
|
||||||
|
let device = BluetoothDevice::new(&self.session, dev_path);
|
||||||
|
|
||||||
|
let addr = device.get_address().unwrap();
|
||||||
|
if self.config.mac.contains_key(&addr) {
|
||||||
|
if !device.is_connected().unwrap() {
|
||||||
|
log::info!("Connecting to {}", addr);
|
||||||
|
match device.connect(5000) {
|
||||||
|
Ok(_) => {
|
||||||
|
let power = device.get_tx_power().expect("Could not request tx power");
|
||||||
|
log::info!("Connected to {}, tx power={}", addr, power);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Could not connect to {}: {}", addr, e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for srv_path in device.get_gatt_services().unwrap() {
|
||||||
|
let service = BluetoothGATTService::new(&self.session, srv_path);
|
||||||
|
let service_uuid = service.get_uuid().unwrap();
|
||||||
|
|
||||||
|
if service_uuid == "a8b3fff0-4834-4051-89d0-3de95cddd318" {
|
||||||
|
for charac_path in service.get_gatt_characteristics().unwrap() {
|
||||||
|
let charac = BluetoothGATTCharacteristic::new(
|
||||||
|
&self.session,
|
||||||
|
charac_path.clone(),
|
||||||
|
);
|
||||||
|
let charac_uuid = charac.get_uuid().unwrap();
|
||||||
|
|
||||||
|
if charac_uuid == "a8b3fff1-4834-4051-89d0-3de95cddd318" {
|
||||||
|
characs.push(charac);
|
||||||
|
found_macs.insert(addr.clone(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let macs_not_found: HashMap<String, bool> = found_macs
|
||||||
|
.into_iter()
|
||||||
|
.filter(|(_, v)| *v == false)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if macs_not_found.len() > 0 {
|
||||||
|
log::warn!(
|
||||||
|
"Could not find configured lightbulbs: {:?}",
|
||||||
|
macs_not_found.keys()
|
||||||
|
);
|
||||||
|
self.initiate_scan(&adapter);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find out color & pattern to play
|
||||||
|
let mut cfg = LightConfig {
|
||||||
|
color: Some(0xffffff),
|
||||||
|
animation: Some(Animation::Smooth),
|
||||||
|
repeat: Some(1),
|
||||||
|
speed: Some(1.0),
|
||||||
|
};
|
||||||
|
for (type_glob, type_cfg) in &self.msg_types {
|
||||||
|
if type_glob.is_match(&message.msg_type) {
|
||||||
|
cfg.merge(type_cfg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(level_cfg) = self.levels.get(&message.level) {
|
||||||
|
cfg.merge(level_cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let color = cfg.color.unwrap();
|
||||||
|
let r = ((color >> 16) & 0xff) as f64 / 255.0;
|
||||||
|
let g = ((color >> 8) & 0xff) as f64 / 255.0;
|
||||||
|
let b = ((color >> 0) & 0xff) as f64 / 255.0;
|
||||||
|
let anim = cfg.animation.unwrap();
|
||||||
|
let speed = cfg.speed.unwrap();
|
||||||
|
|
||||||
|
// Play animation
|
||||||
|
log::debug!("Playing {:?} {:?}", anim, color);
|
||||||
|
|
||||||
|
for _ in 0..cfg.repeat.unwrap() {
|
||||||
|
match anim {
|
||||||
|
Animation::None => {
|
||||||
|
self.set_color(&characs, 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);
|
||||||
|
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);
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(
|
||||||
|
(250.0 / speed) as u64,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Animation::Smooth => {
|
||||||
|
for i in 0..10 {
|
||||||
|
let mult = i as f64 / 10.0;
|
||||||
|
self.set_color(&characs, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Animation::Blink => {
|
||||||
|
for i in 0..4 {
|
||||||
|
let (r, g, b) = match i % 2 {
|
||||||
|
0 => (r, g, b),
|
||||||
|
_ => (0.0, 0.0, 0.0),
|
||||||
|
};
|
||||||
|
self.set_color(&characs, r, g, b);
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(
|
||||||
|
(400.0 / speed) as u64,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Animation::RampUp => {
|
||||||
|
for i in 0..20 {
|
||||||
|
let mult = i as f64 / 20.0;
|
||||||
|
self.set_color(&characs, 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);
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
Animation::RampDown => {
|
||||||
|
self.set_color(&characs, 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(&characs, 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
pub mod espeak;
|
||||||
|
pub mod light_beewi_bbl227;
|
||||||
|
pub mod stdout;
|
||||||
|
|
||||||
|
use crate::output::*;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
pub fn factory(
|
||||||
|
name: &str,
|
||||||
|
config: &HashMap<String, serde_yaml::Value>,
|
||||||
|
) -> Result<Box<Output>, Box<dyn std::error::Error>> {
|
||||||
|
match name {
|
||||||
|
"stdout" => Ok(Box::new(stdout::Stdout::new(&config)?)),
|
||||||
|
"espeak" => Ok(Box::new(espeak::Espeak::new(&config)?)),
|
||||||
|
"light_beewi_bbl227" => Ok(Box::new(light_beewi_bbl227::BluetoothLightbulb::new(
|
||||||
|
&config,
|
||||||
|
)?)),
|
||||||
|
_ => panic!("Unknown monitor name: {}", name),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
use crate::message::*;
|
||||||
|
pub use crate::output::*;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Stdout {}
|
||||||
|
|
||||||
|
impl Output for Stdout {
|
||||||
|
fn new(
|
||||||
|
_config: &HashMap<String, serde_yaml::Value>,
|
||||||
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
Ok(Stdout {})
|
||||||
|
}
|
||||||
|
fn process_message(&mut self, message: Message) {
|
||||||
|
log::info!(
|
||||||
|
"{}{}: {}\x1B[m",
|
||||||
|
match message.level {
|
||||||
|
Level::Debug => "\x1B[2m",
|
||||||
|
Level::Notice => "",
|
||||||
|
Level::Anomaly => "\x1B[33m",
|
||||||
|
Level::Issue => "\x1B[31m",
|
||||||
|
Level::Critical => "\x1B[31;40;1m",
|
||||||
|
},
|
||||||
|
message.msg_type,
|
||||||
|
message.text
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,2 +0,0 @@
|
||||||
pub mod monitor;
|
|
||||||
pub mod output;
|
|
|
@ -1,15 +0,0 @@
|
||||||
pub mod dhcp_leases;
|
|
||||||
pub mod tester;
|
|
||||||
pub mod wifi_availability;
|
|
||||||
|
|
||||||
use crate::monitor::*;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
pub fn factory(name: &str, config: &HashMap<String, serde_yaml::Value>) -> Box<Monitor + Send> {
|
|
||||||
match name {
|
|
||||||
"tester" => Box::new(tester::Tester::new(&config)),
|
|
||||||
"wifi_availability" => Box::new(wifi_availability::WifiAvailability::new(&config)),
|
|
||||||
"dhcp_leases" => Box::new(dhcp_leases::DHCPLeases::new(&config)),
|
|
||||||
_ => panic!("Unknown monitor name: {}", name),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
pub use crate::message::Message;
|
|
||||||
pub use crate::monitor::*;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Tester {
|
|
||||||
cnt: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Monitor for Tester {
|
|
||||||
fn new(config: &HashMap<String, serde_yaml::Value>) -> Self {
|
|
||||||
Tester { cnt: 0 }
|
|
||||||
}
|
|
||||||
fn run(&mut self, sender: &mpsc::Sender<Message>) {
|
|
||||||
println!("-- Sending message {}", self.cnt);
|
|
||||||
sender
|
|
||||||
.send(Message {
|
|
||||||
emitter: "tester".to_string(),
|
|
||||||
level: 10,
|
|
||||||
msg_type: "string".to_string(),
|
|
||||||
text: format!("This is message number {}", self.cnt),
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
self.cnt += 1;
|
|
||||||
std::thread::sleep(std::time::Duration::from_millis(2000));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
extern crate blurz;
|
|
||||||
|
|
||||||
// use blurz::bluetooth_adapter::BluetoothAdapter;
|
|
||||||
// use blurz::bluetooth_device::BluetoothDevice;
|
|
||||||
// use blurz::bluetooth_gatt_characteristic::BluetoothGATTCharacteristic;
|
|
||||||
// use blurz::bluetooth_gatt_service::BluetoothGATTService;
|
|
||||||
|
|
||||||
// use crate::message::Message;
|
|
||||||
// pub use crate::output::*;
|
|
||||||
// use serde_derive::*;
|
|
||||||
|
|
||||||
// #[derive(Debug)]
|
|
||||||
// pub struct BluetoothLightbulb<'a> {
|
|
||||||
// config: BluetoothLightbulbConfig,
|
|
||||||
// adapter: BluetoothAdapter<'a>,
|
|
||||||
// }
|
|
||||||
// #[derive(Debug, PartialEq, Serialize, Deserialize)]
|
|
||||||
// struct BluetoothLightbulbConfig {
|
|
||||||
// mac: Vec<String>,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl Output for BluetoothLightbulb {
|
|
||||||
// fn new(config: &HashMap<String, serde_yaml::Value>) -> Self {
|
|
||||||
// let config_node = config.get("config").expect("Missing `config` key").clone();
|
|
||||||
// let config =
|
|
||||||
// serde_yaml::from_value(config_node).expect("Invalid config for wifi_availability");
|
|
||||||
|
|
||||||
// let adapter = BluetoothAdapter::init().expect("Cannot initialize bluetooth adapter");
|
|
||||||
|
|
||||||
// BluetoothLightbulb { config: config }
|
|
||||||
// }
|
|
||||||
// fn process_message(&mut self, message: Message) {
|
|
||||||
// println!("Received message: {:?}", message);
|
|
||||||
|
|
||||||
// let device = Device::new(dev_path);
|
|
||||||
// }
|
|
||||||
// }
|
|
|
@ -1,19 +0,0 @@
|
||||||
use crate::message::Message;
|
|
||||||
pub use crate::output::*;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Espeak {}
|
|
||||||
|
|
||||||
impl Output for Espeak {
|
|
||||||
fn new(config: &HashMap<String, serde_yaml::Value>) -> Self {
|
|
||||||
Espeak {}
|
|
||||||
}
|
|
||||||
fn process_message(&mut self, message: Message) {
|
|
||||||
use std::process::{Command, Stdio};
|
|
||||||
let res = Command::new("espeak")
|
|
||||||
.arg(message.text)
|
|
||||||
.stdout(Stdio::null())
|
|
||||||
.status()
|
|
||||||
.expect("failed to execute process");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
pub mod bluetooth_lightbulb;
|
|
||||||
pub mod espeak;
|
|
||||||
pub mod stdout;
|
|
||||||
|
|
||||||
use crate::output::*;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
pub fn factory(name: &str, config: &HashMap<String, serde_yaml::Value>) -> Box<Output + Send> {
|
|
||||||
match name {
|
|
||||||
"stdout" => Box::new(stdout::Stdout::new(&config)),
|
|
||||||
"espeak" => Box::new(espeak::Espeak::new(&config)),
|
|
||||||
_ => panic!("Unknown monitor name: {}", name),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
use crate::message::Message;
|
|
||||||
pub use crate::output::*;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Stdout {}
|
|
||||||
|
|
||||||
impl Output for Stdout {
|
|
||||||
fn new(config: &HashMap<String, serde_yaml::Value>) -> Self {
|
|
||||||
Stdout {}
|
|
||||||
}
|
|
||||||
fn process_message(&mut self, message: Message) {
|
|
||||||
println!("Received message: {:?}", message);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue