use anyhow::{anyhow, Result};
use esp32_nimble::{
enums::{PowerLevel, PowerType},
BLEAdvertisementData, BLEDevice, BLEScan,
};
use esp_idf_hal::task::block_on;
use std::sync::{Arc, Mutex};
use crate::{
clock::Timer,
infra::{Poller, State, Switch},
message::{Notifier, Trigger},
};
pub fn initialize(power_level: PowerLevel) -> Result<()> {
let device = BLEDevice::take();
device.set_power(PowerType::Advertising, power_level)?;
device.set_power(PowerType::Scan, power_level)?;
Ok(())
}
type DeriveFn = fn(&State, Option<&[u8]>) -> (String, Option<Vec<u8>>);
pub struct Advertiser {
state: State,
payload: Option<Vec<u8>>,
derive: DeriveFn,
}
impl Advertiser {
pub fn new(state: State, derive: DeriveFn) -> Result<Self> {
let ret = Self {
state,
payload: None,
derive,
};
ret.apply()?;
Ok(ret)
}
fn apply(&self) -> Result<()> {
let device = BLEDevice::take();
let advertising = device.get_advertising();
let (name, payload) = (self.derive)(&self.state, self.payload.as_deref());
let mut data = BLEAdvertisementData::new();
data.name(&name);
if let Some(bytes) = &payload {
data.manufacturer_data(bytes);
}
advertising.lock().set_data(&mut data)?;
advertising.lock().start()?;
Ok(())
}
pub fn set_payload(&mut self, payload: Option<Vec<u8>>) -> Result<()> {
self.payload = payload;
self.apply()
}
}
impl Switch for Advertiser {
fn toggle(&mut self) -> Result<()> {
self.state.toggle();
self.apply()
}
}
pub struct ScannerConfig<T: Trigger> {
triggers: fn(&str) -> Option<&'static T>,
default_trigger: &'static T,
payload_trigger: &'static T,
scan_freq_hz: u64,
}
impl<T: Trigger> ScannerConfig<T> {
#[must_use]
pub fn new(
triggers: fn(&str) -> Option<&'static T>,
default_trigger: &'static T,
payload_trigger: &'static T,
scan_freq_hz: u64,
) -> Self {
Self {
triggers,
default_trigger,
payload_trigger,
scan_freq_hz,
}
}
}
pub struct Scanner<'a, T: Trigger> {
notifier: Notifier<T>,
timer: Timer<'a, T>,
state: Arc<Mutex<State>>,
payload: Arc<Mutex<Option<Vec<u8>>>>,
device: &'a BLEDevice,
scan: BLEScan,
config: ScannerConfig<T>,
}
impl<'a, T: Trigger> Scanner<'a, T> {
const WINDOW: i32 = 1000;
pub fn new(
notifier: Notifier<T>,
timer: Timer<'a, T>,
state: Arc<Mutex<State>>,
payload: Arc<Mutex<Option<Vec<u8>>>>,
config: ScannerConfig<T>,
) -> Result<Self> {
let device = BLEDevice::take();
let scan = BLEScan::new();
Ok(Self {
notifier,
timer,
state,
payload,
device,
scan,
config,
})
}
async fn do_scan(&mut self) -> Result<Option<&'static T>> {
let triggers = self.config.triggers;
let payload = Arc::clone(&self.payload);
let payload_trigger = self.config.payload_trigger;
Ok(self
.scan
.start(self.device, Self::WINDOW, move |_, data| {
data.name().and_then(|name| {
let name = String::from_utf8_lossy(name);
if let Some(trigger) = triggers(&name) {
if trigger == payload_trigger {
if let Some(mfg) = data.manufacture_data() {
if let Ok(mut stored) = payload.lock() {
let mut full = mfg
.company_identifier
.to_le_bytes()
.to_vec();
full.extend_from_slice(mfg.payload);
*stored = Some(full);
}
}
}
Some(trigger)
} else {
None
}
})
})
.await?)
}
}
impl<T: Trigger> Poller for Scanner<'_, T> {
fn poll(&mut self) -> Result<!> {
block_on(async {
loop {
self.timer.delay(self.config.scan_freq_hz).await?;
if self
.state
.lock()
.map_err(|e| anyhow!("Mutex lock error: {:?}", e))?
.is_off()
{
continue;
}
let trigger = if let Some(trigger) = self.do_scan().await? {
trigger
} else {
self.config.default_trigger
};
self.notifier.notify(trigger)?;
}
})
}
}