mirror of
https://codeberg.org/icewind/mitemp-rs.git
synced 2026-06-03 17:24:08 +02:00
update to new btleplug and adjust api to listen for all sensors
This commit is contained in:
parent
ba7ead9848
commit
2d673c48fa
6 changed files with 76 additions and 184 deletions
|
|
@ -1,13 +1,13 @@
|
|||
[package]
|
||||
name = "mitemp"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
authors = ["Robin Appelman <robin@icewind.nl>"]
|
||||
edition = "2018"
|
||||
description = "Read Xiaomi MI Temperature and Humidity Sensor over BLE"
|
||||
license = "MIT/Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
btleplug = { version = "0.4.0", git = "https://github.com/icewind1991/btleplug", branch = "merged" }
|
||||
btleplug = { version = "0.5.0", git = "https://github.com/icewind1991/btleplug", branch = "store-service-data" }
|
||||
num_enum = "0.4.2"
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
|||
37
README.md
37
README.md
|
|
@ -4,10 +4,8 @@ Read Xiaomi MI Temperature and Humidity Sensor over BLE
|
|||
|
||||
## Usage
|
||||
|
||||
### Using channels
|
||||
|
||||
```rust
|
||||
use mitemp::{adapter_by_mac, BDAddr, Sensor};
|
||||
use mitemp::{adapter_by_mac, listen, BDAddr};
|
||||
use std::str::FromStr;
|
||||
|
||||
fn main() -> Result<(), btleplug::Error> {
|
||||
|
|
@ -15,44 +13,15 @@ fn main() -> Result<(), btleplug::Error> {
|
|||
|
||||
let addr = BDAddr::from_str("00:1A:7D:DA:71:08").unwrap();
|
||||
let adapter = adapter_by_mac(addr)?;
|
||||
let device = BDAddr::from_str("58:2d:34:35:f3:d4").unwrap();
|
||||
|
||||
let sensor = Sensor::new(adapter, device);
|
||||
|
||||
let rx = sensor.listen();
|
||||
let rx = listen(adapter)?;
|
||||
loop {
|
||||
let data = rx.recv().unwrap();
|
||||
dbg!(data);
|
||||
println!("{}: {:?}", data.mac, data.data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Using getter
|
||||
|
||||
```rust
|
||||
use mitemp::{adapter_by_mac, BDAddr, Sensor};
|
||||
use std::str::FromStr;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() -> Result<(), btleplug::Error> {
|
||||
env_logger::init();
|
||||
|
||||
let addr = BDAddr::from_str("00:1A:7D:DA:71:08").unwrap();
|
||||
let adapter = adapter_by_mac(addr)?;
|
||||
let device = BDAddr::from_str("58:2d:34:35:f3:d4").unwrap();
|
||||
|
||||
let sensor = Sensor::new(adapter, device).start();
|
||||
|
||||
loop {
|
||||
let data = sensor.get_data();
|
||||
dbg!(data);
|
||||
sleep(Duration::from_secs(3));
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Licensed under either of
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
use mitemp::{adapter_by_mac, BDAddr, Sensor};
|
||||
use std::str::FromStr;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() -> Result<(), btleplug::Error> {
|
||||
env_logger::init();
|
||||
|
||||
let addr = BDAddr::from_str("00:1A:7D:DA:71:08").unwrap();
|
||||
let adapter = adapter_by_mac(addr)?;
|
||||
let device = BDAddr::from_str("58:2d:34:35:f3:d4").unwrap();
|
||||
|
||||
let sensor = Sensor::new(adapter, device).start();
|
||||
|
||||
loop {
|
||||
let data = sensor.get_data();
|
||||
dbg!(data);
|
||||
sleep(Duration::from_secs(3));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use mitemp::{adapter_by_mac, BDAddr, Sensor};
|
||||
use mitemp::{adapter_by_mac, listen, BDAddr};
|
||||
use std::str::FromStr;
|
||||
|
||||
fn main() -> Result<(), btleplug::Error> {
|
||||
|
|
@ -6,13 +6,10 @@ fn main() -> Result<(), btleplug::Error> {
|
|||
|
||||
let addr = BDAddr::from_str("00:1A:7D:DA:71:08").unwrap();
|
||||
let adapter = adapter_by_mac(addr)?;
|
||||
let device = BDAddr::from_str("58:2d:34:35:f3:d4").unwrap();
|
||||
|
||||
let sensor = Sensor::new(adapter, device);
|
||||
|
||||
let rx = sensor.listen();
|
||||
let rx = listen(adapter)?;
|
||||
loop {
|
||||
let data = rx.recv().unwrap();
|
||||
dbg!(data);
|
||||
println!("{}: {:?}", data.mac, data.data);
|
||||
}
|
||||
}
|
||||
168
src/lib.rs
168
src/lib.rs
|
|
@ -1,43 +1,27 @@
|
|||
pub use btleplug::api::BDAddr;
|
||||
use btleplug::api::{Central, CentralEvent};
|
||||
use btleplug::api::{Central, CentralEvent, Peripheral};
|
||||
use btleplug::bluez::adapter::ConnectedAdapter;
|
||||
use btleplug::bluez::manager::Manager;
|
||||
use btleplug::bluez::protocol::hci::LEAdvertisingData;
|
||||
use num_enum::TryFromPrimitive;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::mpsc::{channel, Receiver};
|
||||
use std::thread::spawn;
|
||||
|
||||
pub trait SensorState {}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Inactive {}
|
||||
|
||||
impl SensorState for Inactive {}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Active {}
|
||||
|
||||
impl SensorState for Active {}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Sensor<State: SensorState> {
|
||||
mac: BDAddr,
|
||||
adapter: ConnectedAdapter,
|
||||
data: Arc<Mutex<SensorInnerData>>,
|
||||
state: PhantomData<State>,
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Sensor {
|
||||
pub mac: BDAddr,
|
||||
pub data: SensorData,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
struct SensorInnerData {
|
||||
#[derive(Default, Clone, Copy, Debug)]
|
||||
struct SensorRawData {
|
||||
battery: u8,
|
||||
temperature: i16,
|
||||
humidity: u16,
|
||||
}
|
||||
|
||||
impl SensorInnerData {
|
||||
impl SensorRawData {
|
||||
fn update(&mut self, update: SensorUpdate) {
|
||||
match update {
|
||||
SensorUpdate::Temperature(temp) => self.temperature = temp,
|
||||
|
|
@ -51,67 +35,41 @@ impl SensorInnerData {
|
|||
}
|
||||
}
|
||||
|
||||
impl Sensor<Inactive> {
|
||||
pub fn new(adapter: ConnectedAdapter, sensor_mac: BDAddr) -> Self {
|
||||
Sensor {
|
||||
mac: sensor_mac,
|
||||
adapter,
|
||||
data: Arc::default(),
|
||||
state: PhantomData,
|
||||
}
|
||||
}
|
||||
pub fn listen<P: Peripheral, A: Central<P> + 'static>(
|
||||
adapter: A,
|
||||
) -> Result<Receiver<Sensor>, btleplug::Error> {
|
||||
let (tx, rx) = channel();
|
||||
|
||||
fn activate(self, tx: Option<Sender<SensorData>>) -> Sensor<Active> {
|
||||
let data = self.data.clone();
|
||||
let sensor_mac = self.mac.clone();
|
||||
let mut sensors: HashMap<BDAddr, SensorRawData> = HashMap::new();
|
||||
|
||||
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()));
|
||||
let event_receiver = adapter.event_receiver().unwrap();
|
||||
|
||||
// start scanning for devices
|
||||
adapter.start_scan()?;
|
||||
|
||||
spawn(move || {
|
||||
while let Ok(event) = event_receiver.recv() {
|
||||
match event {
|
||||
CentralEvent::DeviceDiscovered(bd_addr) | CentralEvent::DeviceUpdated(bd_addr) => {
|
||||
let peripheral = adapter.peripheral(bd_addr).unwrap();
|
||||
for data in peripheral.properties().service_data.values() {
|
||||
if let Ok(update) = parse_advertising_data(data.as_slice()) {
|
||||
let sensor_data = sensors.entry(bd_addr).or_default();
|
||||
sensor_data.update(update);
|
||||
tx.send(Sensor {
|
||||
mac: bd_addr,
|
||||
data: (*sensor_data).into(),
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}));
|
||||
|
||||
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()
|
||||
}
|
||||
Ok(rx)
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
|
|
@ -121,8 +79,8 @@ pub struct SensorData {
|
|||
pub humidity: f32,
|
||||
}
|
||||
|
||||
impl From<&SensorInnerData> for SensorData {
|
||||
fn from(inner: &SensorInnerData) -> Self {
|
||||
impl From<SensorRawData> for SensorData {
|
||||
fn from(inner: SensorRawData) -> Self {
|
||||
SensorData {
|
||||
battery: inner.battery,
|
||||
temperature: inner.temperature as f32 / 10.0,
|
||||
|
|
@ -160,32 +118,32 @@ enum SensorUpdate {
|
|||
TemperatureAndHumidity(i16, u16),
|
||||
}
|
||||
|
||||
fn parse_advertising_data(advertising_data: &[LEAdvertisingData]) -> Option<SensorUpdate> {
|
||||
for item in advertising_data {
|
||||
if let LEAdvertisingData::ServiceData16(_, service_data) = item {
|
||||
struct InvalidServiceData;
|
||||
|
||||
fn parse_advertising_data(service_data: &[u8]) -> Result<SensorUpdate, InvalidServiceData> {
|
||||
let sensor_type = &service_data[1..4];
|
||||
assert_eq!(sensor_type, &[0x20, 0xaa, 0x01]);
|
||||
let sensor_type = SensorType::try_from(service_data[11]).ok()?;
|
||||
let data_length = service_data[13] as usize;
|
||||
assert_eq!(14 + data_length, service_data.len());
|
||||
let sensor_data = &service_data[14..14 + data_length];
|
||||
return match sensor_type {
|
||||
SensorType::Battery => Some(SensorUpdate::Battery(sensor_data[0])),
|
||||
SensorType::Temperature => Some(SensorUpdate::Temperature(i16::from_le_bytes([
|
||||
sensor_data[0],
|
||||
sensor_data[1],
|
||||
]))),
|
||||
SensorType::Humidity => Some(SensorUpdate::Humidity(u16::from_le_bytes([
|
||||
sensor_data[0],
|
||||
sensor_data[1],
|
||||
]))),
|
||||
SensorType::TemperatureAndHumidity => Some(SensorUpdate::TemperatureAndHumidity(
|
||||
i16::from_le_bytes([sensor_data[0], sensor_data[1]]),
|
||||
u16::from_le_bytes([sensor_data[2], sensor_data[3]]),
|
||||
)),
|
||||
};
|
||||
if sensor_type != &[0x20, 0xaa, 0x01] {
|
||||
return Err(InvalidServiceData);
|
||||
}
|
||||
let sensor_type = SensorType::try_from(service_data[11]).map_err(|_| InvalidServiceData)?;
|
||||
let data_length = service_data[13] as usize;
|
||||
|
||||
if 14 + data_length != service_data.len() {
|
||||
return Err(InvalidServiceData);
|
||||
}
|
||||
|
||||
None
|
||||
let sensor_data = &service_data[14..14 + data_length];
|
||||
Ok(match sensor_type {
|
||||
SensorType::Battery => SensorUpdate::Battery(sensor_data[0]),
|
||||
SensorType::Temperature => {
|
||||
SensorUpdate::Temperature(i16::from_le_bytes([sensor_data[0], sensor_data[1]]))
|
||||
}
|
||||
SensorType::Humidity => {
|
||||
SensorUpdate::Humidity(u16::from_le_bytes([sensor_data[0], sensor_data[1]]))
|
||||
}
|
||||
SensorType::TemperatureAndHumidity => SensorUpdate::TemperatureAndHumidity(
|
||||
i16::from_le_bytes([sensor_data[0], sensor_data[1]]),
|
||||
u16::from_le_bytes([sensor_data[2], sensor_data[3]]),
|
||||
),
|
||||
})
|
||||
}
|
||||
|
|
|
|||
12
test.py
12
test.py
|
|
@ -1,12 +0,0 @@
|
|||
from bluepy import btle
|
||||
|
||||
mac = '58:2d:34:35:f3:d4'
|
||||
p = btle.Peripheral (mac)
|
||||
|
||||
for s in p.getServices ():
|
||||
print ('Service:', s.uuid)
|
||||
for c in s.getCharacteristics ():
|
||||
print (' tCharacteristic:', c.uuid)
|
||||
print (' t t', c.propertiesToString ())
|
||||
if c.supportsRead ():
|
||||
print (' t t', c.read ())
|
||||
Loading…
Add table
Add a link
Reference in a new issue