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:
parent
f822c3f03f
commit
bdcc13bad6
3 changed files with 142 additions and 74 deletions
10
README.md
10
README.md
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
197
src/lib.rs
197
src/lib.rs
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue