rnetmon/src/outputs/light_beewi_bbl227.rs

307 lines
11 KiB
Rust

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;
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,
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 targets {
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!("{}: 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("Could not start discovery session");
}
}
}
impl Output for BluetoothLightbulb {
fn new(config: serde_yaml::Value) -> Result<Self, Box<dyn std::error::Error>> {
let config: BluetoothLightbulbConfig = serde_yaml::from_value(config)?;
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)?;
let ret = BluetoothLightbulb {
config: config,
msg_types: msg_types,
levels: levels,
session: session,
};
// 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());
adapter
.set_powered(true)
.expect("Cannot power on bluetooth device");
}
Ok(ret)
}
fn process_message(&mut self, message: Message) {
let adapter =
BluetoothAdapter::init(&self.session).expect("Could not initialize bluetooth adapter");
let mut targets = 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(30000) {
Ok(_) => {
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);
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" {
targets.push((device.clone(), charac));
found_macs.insert(addr.clone(), true);
}
}
}
}
}
}
// List devices not found
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;
}
// Merge LightConfig properties
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);
}
// 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;
let b = ((color >> 0) & 0xff) as f64 / 255.0;
let anim = cfg.animation.unwrap();
let speed = cfg.speed.unwrap();
// Play animation
log::debug!("Playing {:?} {:X}", anim, color);
for _ in 0..cfg.repeat.unwrap() {
match anim {
Animation::None => {
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(&targets, r, g, b);
std::thread::sleep(std::time::Duration::from_millis(
(250.0 / speed) as u64,
));
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,
));
}
}
Animation::Smooth => {
for i in 0..10 {
let mult = i as f64 / 10.0;
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(&targets, 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(&targets, 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(&targets, r * mult, g * mult, b * mult);
}
std::thread::sleep(std::time::Duration::from_millis(100));
self.set_color(&targets, 0.0, 0.0, 0.0);
std::thread::sleep(std::time::Duration::from_millis(100));
}
Animation::RampDown => {
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(&targets, r * mult, g * mult, b * mult);
}
self.set_color(&targets, 0.0, 0.0, 0.0);
std::thread::sleep(std::time::Duration::from_millis(100));
}
}
}
self.set_color(&targets, 0.0, 0.0, 0.0);
}
}