1
0
Fork 0
mirror of https://codeberg.org/icewind/mitemp-rs.git synced 2026-06-03 17:24:08 +02:00

api refactoring

This commit is contained in:
Robin Appelman 2020-02-08 13:33:02 +01:00
commit bdcc13bad6
3 changed files with 142 additions and 74 deletions

View file

@ -2,23 +2,27 @@
Read Xiaomi MI Temperature and Humidity Sensor over BLE Read Xiaomi MI Temperature and Humidity Sensor over BLE
## Usafe ## Usage
```rust ```rust
use mitemp::{adapter_by_mac, listen, BDAddr}; use mitemp::{adapter_by_mac, BDAddr, Sensor};
use std::str::FromStr; use std::str::FromStr;
fn main() -> Result<(), btleplug::Error> { fn main() -> Result<(), btleplug::Error> {
env_logger::init();
let addr = BDAddr::from_str("00:1A:7D:DA:71:08").unwrap(); let addr = BDAddr::from_str("00:1A:7D:DA:71:08").unwrap();
let adapter = adapter_by_mac(addr)?; let adapter = adapter_by_mac(addr)?;
let device = BDAddr::from_str("58:2d:34:35:f3:d4").unwrap(); let device = BDAddr::from_str("58:2d:34:35:f3:d4").unwrap();
let sensor = Sensor::new(adapter, device);
let rx = listen(adapter, device); let rx = sensor.listen();
loop { loop {
let data = rx.recv().unwrap(); let data = rx.recv().unwrap();
dbg!(data); dbg!(data);
} }
} }
``` ```
## License ## License

View file

@ -1,4 +1,4 @@
use mitemp::{adapter_by_mac, listen, BDAddr}; use mitemp::{adapter_by_mac, BDAddr, Sensor};
use std::str::FromStr; use std::str::FromStr;
fn main() -> Result<(), btleplug::Error> { fn main() -> Result<(), btleplug::Error> {
@ -7,8 +7,9 @@ fn main() -> Result<(), btleplug::Error> {
let addr = BDAddr::from_str("00:1A:7D:DA:71:08").unwrap(); let addr = BDAddr::from_str("00:1A:7D:DA:71:08").unwrap();
let adapter = adapter_by_mac(addr)?; let adapter = adapter_by_mac(addr)?;
let device = BDAddr::from_str("58:2d:34:35:f3:d4").unwrap(); let device = BDAddr::from_str("58:2d:34:35:f3:d4").unwrap();
let sensor = Sensor::new(adapter, device);
let rx = listen(adapter, device); let rx = sensor.listen();
loop { loop {
let data = rx.recv().unwrap(); let data = rx.recv().unwrap();
dbg!(data); dbg!(data);

View file

@ -1,7 +1,3 @@
#![allow(dead_code)]
#![allow(unused_variables)]
#![allow(unused_mut)]
pub use btleplug::api::BDAddr; pub use btleplug::api::BDAddr;
use btleplug::api::{Central, CentralEvent}; use btleplug::api::{Central, CentralEvent};
use btleplug::bluez::adapter::ConnectedAdapter; use btleplug::bluez::adapter::ConnectedAdapter;
@ -9,54 +5,36 @@ use btleplug::bluez::manager::Manager;
use btleplug::bluez::protocol::hci::LEAdvertisingData; use btleplug::bluez::protocol::hci::LEAdvertisingData;
use num_enum::TryFromPrimitive; use num_enum::TryFromPrimitive;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::sync::mpsc::{channel, Receiver}; use std::marker::PhantomData;
use std::ops::Deref;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
pub fn listen(adapter: ConnectedAdapter, sensor_mac: BDAddr) -> Receiver<SensorData> { pub trait SensorState {}
let (tx, rx) = channel();
let mut sensor_data = Arc::new(Mutex::new(SensorData::default())); pub struct Inactive {}
adapter.on_event(Box::new(move |ev| { impl SensorState for Inactive {}
match ev {
CentralEvent::DeviceDiscovered(discovered_mac, data)
| CentralEvent::DeviceUpdated(discovered_mac, data)
if sensor_mac == discovered_mac =>
{
// dbg!(data);
for item in data {
match item {
LEAdvertisingData::ServiceData16(id, data) => {
if let Some(sensor_update) = parse_advertising_data(data) {
let updated = {
let mut data = sensor_data.lock().unwrap();
data.update(sensor_update);
data.clone()
};
tx.send(updated).unwrap()
}
}
_ => {}
}
}
}
_ => {}
};
}));
adapter.start_scan().unwrap(); pub struct Active {}
rx impl SensorState for Active {}
pub struct Sensor<State: SensorState> {
mac: BDAddr,
adapter: ConnectedAdapter,
data: Arc<Mutex<SensorInnerData>>,
state: PhantomData<State>,
} }
#[derive(Default, Clone, Debug)] #[derive(Default, Clone, Debug)]
pub struct SensorData { struct SensorInnerData {
pub battery: u8, battery: u8,
pub temperature: f32, temperature: i16,
pub humidity: f32, humidity: u16,
} }
impl SensorData { impl SensorInnerData {
fn update(&mut self, update: SensorUpdate) { fn update(&mut self, update: SensorUpdate) {
match update { match update {
SensorUpdate::Temperature(temp) => self.temperature = temp, SensorUpdate::Temperature(temp) => self.temperature = temp,
@ -70,11 +48,91 @@ impl SensorData {
} }
} }
impl Sensor<Inactive> {
pub fn new(adapter: ConnectedAdapter, sensor_mac: BDAddr) -> Self {
Sensor {
mac: sensor_mac,
adapter,
data: Arc::default(),
state: PhantomData,
}
}
fn activate(self, tx: Option<Sender<SensorData>>) -> Sensor<Active> {
let data = self.data.clone();
let sensor_mac = self.mac.clone();
self.adapter.on_event(Box::new(move |ev| {
match ev {
CentralEvent::DeviceDiscovered(discovered_mac, advertising_data)
| CentralEvent::DeviceUpdated(discovered_mac, advertising_data)
if sensor_mac == discovered_mac =>
{
if let (Some(sensor_update), Ok(mut data)) =
(parse_advertising_data(advertising_data), data.lock())
{
data.update(sensor_update);
if let Some(tx) = &tx {
let _ = tx.send(SensorData::from(data.deref()));
}
}
}
_ => {}
};
}));
self.adapter.start_scan().unwrap();
Sensor {
mac: self.mac,
adapter: self.adapter,
data: self.data,
state: PhantomData,
}
}
pub fn start(self) -> Sensor<Active> {
self.activate(None)
}
pub fn listen(self) -> Receiver<SensorData> {
let (tx, rx) = channel();
self.activate(Some(tx));
rx
}
}
impl Sensor<Active> {
pub fn get_data(&self) -> SensorData {
self.data
.lock()
.map(|data| SensorData::from(data.deref()))
.unwrap_or_default()
}
}
#[derive(Default, Clone, Debug)]
pub struct SensorData {
pub battery: u8,
pub temperature: f32,
pub humidity: f32,
}
impl From<&SensorInnerData> for SensorData {
fn from(inner: &SensorInnerData) -> Self {
SensorData {
battery: inner.battery,
temperature: inner.temperature as f32 / 10.0,
humidity: inner.humidity as f32 / 10.0,
}
}
}
pub fn adapter_by_mac(addr: BDAddr) -> Result<ConnectedAdapter, btleplug::Error> { pub fn adapter_by_mac(addr: BDAddr) -> Result<ConnectedAdapter, btleplug::Error> {
let manager = Manager::new()?; let manager = Manager::new()?;
let adapters = manager.adapters()?; let adapters = manager.adapters()?;
let mut adapter = adapters let adapter = adapters
.into_iter() .into_iter()
.find(|adapter| adapter.addr == addr) .find(|adapter| adapter.addr == addr)
.ok_or(btleplug::Error::DeviceNotFound)?; .ok_or(btleplug::Error::DeviceNotFound)?;
@ -94,32 +152,37 @@ enum SensorType {
#[derive(Debug)] #[derive(Debug)]
enum SensorUpdate { enum SensorUpdate {
Battery(u8), Battery(u8),
Temperature(f32), Temperature(i16),
Humidity(f32), Humidity(u16),
TemperatureAndHumidity(f32, f32), TemperatureAndHumidity(i16, u16),
} }
fn parse_advertising_data(data: &[u8]) -> Option<SensorUpdate> { fn parse_advertising_data(advertising_data: &[LEAdvertisingData]) -> Option<SensorUpdate> {
const SENSOR_HUMIDITY: u8 = 0x06; for item in advertising_data {
const SENSOR_TEMPERATURE: u8 = 0x04; if let LEAdvertisingData::ServiceData16(_, service_data) = item {
let sensor_type = &service_data[1..4];
let sensor_type = &data[1..4]; assert_eq!(sensor_type, &[0x20, 0xaa, 0x01]);
assert_eq!(sensor_type, &[0x20, 0xaa, 0x01]); let sensor_type = SensorType::try_from(service_data[11]).ok()?;
let sensor_type = SensorType::try_from(data[11]).ok()?; let data_length = service_data[13] as usize;
let data_length = data[13] as usize; assert_eq!(14 + data_length, service_data.len());
assert_eq!(14 + data_length, data.len()); let sensor_data = &service_data[14..14 + data_length];
let sensor_data = &data[14..14 + data_length]; return match sensor_type {
match sensor_type { SensorType::Battery => Some(SensorUpdate::Battery(sensor_data[0])),
SensorType::Battery => Some(SensorUpdate::Battery(sensor_data[0])), SensorType::Temperature => Some(SensorUpdate::Temperature(i16::from_le_bytes([
SensorType::Temperature => Some(SensorUpdate::Temperature( sensor_data[0],
i16::from_le_bytes([sensor_data[0], sensor_data[1]]) as f32 / 10.0, sensor_data[1],
)), ]))),
SensorType::Humidity => Some(SensorUpdate::Humidity( SensorType::Humidity => Some(SensorUpdate::Humidity(u16::from_le_bytes([
u16::from_le_bytes([sensor_data[0], sensor_data[1]]) as f32 / 10.0, sensor_data[0],
)), sensor_data[1],
SensorType::TemperatureAndHumidity => Some(SensorUpdate::TemperatureAndHumidity( ]))),
i16::from_le_bytes([sensor_data[0], sensor_data[1]]) as f32 / 10.0, SensorType::TemperatureAndHumidity => Some(SensorUpdate::TemperatureAndHumidity(
u16::from_le_bytes([sensor_data[2], sensor_data[3]]) as f32 / 10.0, i16::from_le_bytes([sensor_data[0], sensor_data[1]]),
)), u16::from_le_bytes([sensor_data[2], sensor_data[3]]),
)),
};
}
} }
None
} }