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]
|
[package]
|
||||||
name = "mitemp"
|
name = "mitemp"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
authors = ["Robin Appelman <robin@icewind.nl>"]
|
authors = ["Robin Appelman <robin@icewind.nl>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "Read Xiaomi MI Temperature and Humidity Sensor over BLE"
|
description = "Read Xiaomi MI Temperature and Humidity Sensor over BLE"
|
||||||
license = "MIT/Apache-2.0"
|
license = "MIT/Apache-2.0"
|
||||||
|
|
||||||
[dependencies]
|
[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"
|
num_enum = "0.4.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
||||||
37
README.md
37
README.md
|
|
@ -4,10 +4,8 @@ Read Xiaomi MI Temperature and Humidity Sensor over BLE
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Using channels
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use mitemp::{adapter_by_mac, BDAddr, Sensor};
|
use mitemp::{adapter_by_mac, listen, BDAddr};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
fn main() -> Result<(), btleplug::Error> {
|
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 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 sensor = Sensor::new(adapter, device);
|
let rx = listen(adapter)?;
|
||||||
|
|
||||||
let rx = sensor.listen();
|
|
||||||
loop {
|
loop {
|
||||||
let data = rx.recv().unwrap();
|
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
|
## License
|
||||||
|
|
||||||
Licensed under either of
|
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;
|
use std::str::FromStr;
|
||||||
|
|
||||||
fn main() -> Result<(), btleplug::Error> {
|
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 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 sensor = Sensor::new(adapter, device);
|
let rx = listen(adapter)?;
|
||||||
|
|
||||||
let rx = sensor.listen();
|
|
||||||
loop {
|
loop {
|
||||||
let data = rx.recv().unwrap();
|
let data = rx.recv().unwrap();
|
||||||
dbg!(data);
|
println!("{}: {:?}", data.mac, data.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
172
src/lib.rs
172
src/lib.rs
|
|
@ -1,43 +1,27 @@
|
||||||
pub use btleplug::api::BDAddr;
|
pub use btleplug::api::BDAddr;
|
||||||
use btleplug::api::{Central, CentralEvent};
|
use btleplug::api::{Central, CentralEvent, Peripheral};
|
||||||
use btleplug::bluez::adapter::ConnectedAdapter;
|
use btleplug::bluez::adapter::ConnectedAdapter;
|
||||||
use btleplug::bluez::manager::Manager;
|
use btleplug::bluez::manager::Manager;
|
||||||
use btleplug::bluez::protocol::hci::LEAdvertisingData;
|
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::marker::PhantomData;
|
use std::sync::mpsc::{channel, Receiver};
|
||||||
use std::ops::Deref;
|
use std::thread::spawn;
|
||||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
pub trait SensorState {}
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Sensor {
|
||||||
#[derive(Clone)]
|
pub mac: BDAddr,
|
||||||
pub struct Inactive {}
|
pub data: SensorData,
|
||||||
|
|
||||||
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(Default, Clone, Debug)]
|
#[derive(Default, Clone, Copy, Debug)]
|
||||||
struct SensorInnerData {
|
struct SensorRawData {
|
||||||
battery: u8,
|
battery: u8,
|
||||||
temperature: i16,
|
temperature: i16,
|
||||||
humidity: u16,
|
humidity: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SensorInnerData {
|
impl SensorRawData {
|
||||||
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,
|
||||||
|
|
@ -51,67 +35,41 @@ impl SensorInnerData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sensor<Inactive> {
|
pub fn listen<P: Peripheral, A: Central<P> + 'static>(
|
||||||
pub fn new(adapter: ConnectedAdapter, sensor_mac: BDAddr) -> Self {
|
adapter: A,
|
||||||
Sensor {
|
) -> Result<Receiver<Sensor>, btleplug::Error> {
|
||||||
mac: sensor_mac,
|
let (tx, rx) = channel();
|
||||||
adapter,
|
|
||||||
data: Arc::default(),
|
|
||||||
state: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn activate(self, tx: Option<Sender<SensorData>>) -> Sensor<Active> {
|
let mut sensors: HashMap<BDAddr, SensorRawData> = HashMap::new();
|
||||||
let data = self.data.clone();
|
|
||||||
let sensor_mac = self.mac.clone();
|
|
||||||
|
|
||||||
self.adapter.on_event(Box::new(move |ev| {
|
let event_receiver = adapter.event_receiver().unwrap();
|
||||||
match ev {
|
|
||||||
CentralEvent::DeviceDiscovered(discovered_mac, advertising_data)
|
// start scanning for devices
|
||||||
| CentralEvent::DeviceUpdated(discovered_mac, advertising_data)
|
adapter.start_scan()?;
|
||||||
if sensor_mac == discovered_mac =>
|
|
||||||
{
|
spawn(move || {
|
||||||
if let (Some(sensor_update), Ok(mut data)) =
|
while let Ok(event) = event_receiver.recv() {
|
||||||
(parse_advertising_data(advertising_data), data.lock())
|
match event {
|
||||||
{
|
CentralEvent::DeviceDiscovered(bd_addr) | CentralEvent::DeviceUpdated(bd_addr) => {
|
||||||
data.update(sensor_update);
|
let peripheral = adapter.peripheral(bd_addr).unwrap();
|
||||||
if let Some(tx) = &tx {
|
for data in peripheral.properties().service_data.values() {
|
||||||
let _ = tx.send(SensorData::from(data.deref()));
|
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();
|
Ok(rx)
|
||||||
|
|
||||||
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)]
|
#[derive(Default, Clone, Debug)]
|
||||||
|
|
@ -121,8 +79,8 @@ pub struct SensorData {
|
||||||
pub humidity: f32,
|
pub humidity: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&SensorInnerData> for SensorData {
|
impl From<SensorRawData> for SensorData {
|
||||||
fn from(inner: &SensorInnerData) -> Self {
|
fn from(inner: SensorRawData) -> Self {
|
||||||
SensorData {
|
SensorData {
|
||||||
battery: inner.battery,
|
battery: inner.battery,
|
||||||
temperature: inner.temperature as f32 / 10.0,
|
temperature: inner.temperature as f32 / 10.0,
|
||||||
|
|
@ -160,32 +118,32 @@ enum SensorUpdate {
|
||||||
TemperatureAndHumidity(i16, u16),
|
TemperatureAndHumidity(i16, u16),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_advertising_data(advertising_data: &[LEAdvertisingData]) -> Option<SensorUpdate> {
|
struct InvalidServiceData;
|
||||||
for item in advertising_data {
|
|
||||||
if let LEAdvertisingData::ServiceData16(_, service_data) = item {
|
fn parse_advertising_data(service_data: &[u8]) -> Result<SensorUpdate, InvalidServiceData> {
|
||||||
let sensor_type = &service_data[1..4];
|
let sensor_type = &service_data[1..4];
|
||||||
assert_eq!(sensor_type, &[0x20, 0xaa, 0x01]);
|
if sensor_type != &[0x20, 0xaa, 0x01] {
|
||||||
let sensor_type = SensorType::try_from(service_data[11]).ok()?;
|
return Err(InvalidServiceData);
|
||||||
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]]),
|
|
||||||
)),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
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