#![deny(missing_docs)]
pub use async_trait::async_trait;
use btleplug::api::{Central, Characteristic, Manager as _, Peripheral as _, ScanFilter};
use btleplug::platform::{Adapter, Manager, Peripheral};
use futures::stream::StreamExt;
use std::fmt;
use std::sync::Arc;
use tokio::time::{self, Duration};
use uuid::Uuid;
mod control;
mod polar_uuid;
mod response;
pub use control::{
ControlPoint, ControlPointCommand, ControlPointResponseCode, ControlResponse, StreamSettings,
};
use polar_uuid::{NotifyUuid, StringUuid};
pub use response::{Acc, Ecg, HeartRate, PmdData, PmdRead};
#[derive(Debug)]
pub enum Error {
NoBleAdaptor,
NoControlPoint,
NoDevice,
NotConnected,
NoDataType,
CharacteristicNotFound,
InvalidData,
InvalidLength,
NullCommand,
WrongResponse,
WrongType,
BleError(btleplug::Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let msg = match self {
Error::NoBleAdaptor => "No BLE adaptor".to_string(),
Error::NoControlPoint => "No control point".to_string(),
Error::NoDevice => "No device".to_string(),
Error::NotConnected => "Not connected".to_string(),
Error::NoDataType => "No data type".to_string(),
Error::CharacteristicNotFound => "Characteristic not found".to_string(),
Error::InvalidData => "Invalid data".to_string(),
Error::InvalidLength => "Invalid length".to_string(),
Error::NullCommand => "Null command".to_string(),
Error::WrongResponse => "Wrong response".to_string(),
Error::WrongType => "Wrong type".to_string(),
Error::BleError(er) => format!("BLE error: {:?}", er),
};
write!(f, "Arctic Error: {}", msg)
}
}
impl std::error::Error for Error {}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum H10MeasurementType {
Ecg,
Acc,
}
impl TryFrom<u8> for H10MeasurementType {
type Error = ();
fn try_from(data: u8) -> Result<H10MeasurementType, ()> {
match data {
0x0 => Ok(H10MeasurementType::Ecg),
0x2 => Ok(H10MeasurementType::Acc),
_ => Err(()),
}
}
}
impl H10MeasurementType {
fn as_u8(&self) -> u8 {
match *self {
H10MeasurementType::Ecg => 0x0,
H10MeasurementType::Acc => 0x2,
}
}
fn as_bytes(&self) -> u8 {
match *self {
H10MeasurementType::Ecg => 3,
H10MeasurementType::Acc => 6,
}
}
}
#[derive(Debug)]
pub struct SupportedFeatures {
pub ecg: bool,
pub ppg: bool,
pub acc: bool,
pub ppi: bool,
pub gyro: bool,
pub mag: bool,
}
impl SupportedFeatures {
pub fn new(mes: u8) -> SupportedFeatures {
SupportedFeatures {
ecg: (mes & 0b00000001) != 0,
ppg: (mes & 0b00000010) != 0,
acc: (mes & 0b00000100) != 0,
ppi: (mes & 0b00001000) != 0,
gyro: (mes & 0b00100000) != 0,
mag: (mes & 0b01000000) != 0,
}
}
}
#[async_trait]
pub trait EventHandler: Send + Sync {
async fn battery_update(&self, _battery_level: u8) {}
async fn heart_rate_update(&self, _ctx: &PolarSensor, _heartrate: HeartRate) {}
async fn measurement_update(&self, _ctx: &PolarSensor, _data: PmdRead) {}
async fn should_continue(&self) -> bool {
true
}
}
pub type PolarResult<T> = std::result::Result<T, Error>;
pub enum NotifyStream {
Battery,
HeartRate,
MeasurementCP,
MeasurementData,
}
impl From<NotifyStream> for Uuid {
fn from(item: NotifyStream) -> Self {
NotifyUuid::from(item).into()
}
}
pub struct PolarSensor {
device_id: String,
ble_manager: Manager,
ble_device: Option<Peripheral>,
event_handler: Option<Arc<dyn EventHandler>>,
control_point: Option<ControlPoint>,
data_type: Option<Vec<H10MeasurementType>>,
range: u8,
sample_rate: u8,
}
impl PolarSensor {
pub async fn new(device_id: String) -> PolarResult<PolarSensor> {
let ble_manager = Manager::new().await.map_err(Error::BleError)?;
if device_id.len() != 8 {
return Err(Error::InvalidLength);
}
Ok(PolarSensor {
device_id,
ble_manager,
ble_device: None,
event_handler: None,
control_point: None,
data_type: None,
range: 8,
sample_rate: 200,
})
}
pub async fn connect(&mut self) -> PolarResult<()> {
let adapters_result = self.ble_manager.adapters().await.map_err(Error::BleError);
if let Ok(adapters) = adapters_result {
if adapters.is_empty() {
return Err(Error::NoBleAdaptor);
}
let central = adapters.into_iter().next().unwrap();
central
.start_scan(ScanFilter::default())
.await
.map_err(Error::BleError)?;
time::sleep(Duration::from_secs(2)).await;
self.ble_device = self.find_device(¢ral).await;
if let Some(device) = &self.ble_device {
device.connect().await.map_err(Error::BleError)?;
device.discover_services().await.map_err(Error::BleError)?;
let controller = ControlPoint::new(device).await?;
self.control_point = Some(controller);
return Ok(());
}
return Err(Error::NoDevice);
}
Err(Error::NoBleAdaptor)
}
pub async fn subscribe(&self, stream: NotifyStream) -> PolarResult<()> {
let device = self.device().await?;
if let Ok(true) = device.is_connected().await {
let characteristic = find_characteristic(device, stream.into()).await?;
return device
.subscribe(&characteristic)
.await
.map_err(Error::BleError);
}
Err(Error::NotConnected)
}
pub async fn unsubscribe(&self, stream: NotifyStream) -> PolarResult<()> {
let device = self.device().await?;
if let Ok(true) = device.is_connected().await {
let characteristic = find_characteristic(device, stream.into()).await?;
return device
.unsubscribe(&characteristic)
.await
.map_err(Error::BleError);
}
Err(Error::NotConnected)
}
pub async fn is_connected(&self) -> bool {
if let Some(device) = &self.ble_device {
if let Ok(value) = device.is_connected().await {
return value;
}
}
false
}
pub async fn rssi(&self) -> Option<i16> {
let device = self.device().await.ok()?;
if let Ok(Some(prop)) = device.properties().await {
return prop.rssi;
}
None
}
pub async fn info(&self) {
println!(
"Model Number: {:?}",
self.read_string(StringUuid::ModelNumber.into()).await
);
println!(
"Manufacturer Name: {:?}",
self.read_string(StringUuid::ManufacturerName.into()).await
);
println!(
"Hardware Revision: {:?}",
self.read_string(StringUuid::HardwareRevision.into()).await
);
println!(
"Firmware Revision: {:?}",
self.read_string(StringUuid::FirmwareRevision.into()).await
);
println!(
"Software Revision: {:?}",
self.read_string(StringUuid::SoftwareRevision.into()).await
);
println!(
"Serial Number: {:?}",
self.read_string(StringUuid::SerialNumber.into()).await
);
println!(
"System ID: {:?}",
self.read(StringUuid::SystemId.into()).await
);
}
pub async fn body_location(&self) {
println!(
"Body Location: {:?}",
self.read(StringUuid::BodyLocation.into()).await
);
}
async fn start_measurement(&self, ty: H10MeasurementType) -> PolarResult<()> {
let controller = self.controller().await?;
let mut command = vec![0x02u8, ty.as_u8()];
match ty {
H10MeasurementType::Acc => {
command.push(0x02);
command.push(0x01);
command.push(self.range);
command.push(0x00);
command.push(0x00);
command.push(0x01);
command.push(self.sample_rate);
command.push(0x00);
command.push(0x01);
command.push(0x01);
command.push(0x10);
command.push(0x00);
}
H10MeasurementType::Ecg => {
command.push(0x00);
command.push(0x01);
command.push(0x82);
command.push(0x00);
command.push(0x01);
command.push(0x01);
command.push(0x0e);
command.push(0x00);
}
}
controller
.send_command(self.device().await?, command)
.await?;
Ok(())
}
async fn stop_measurement(&self, data_type: H10MeasurementType) -> PolarResult<()> {
let controller = self.controller().await?;
controller
.send_command(self.device().await?, [3, data_type.as_u8()].to_vec())
.await
}
pub async fn settings(&self) -> PolarResult<Vec<StreamSettings>> {
let mut out: Vec<StreamSettings> = vec![];
if let Some(types) = &self.data_type {
for ty in types {
out.push(StreamSettings::new(
&self
.get_pmd_response(ControlPointCommand::GetMeasurementSettings, *ty)
.await?,
)?);
}
} else {
return Err(Error::NoDataType);
}
Ok(out)
}
async fn internal_settings(&self, ty: H10MeasurementType) -> PolarResult<()> {
let controller = self.controller().await?;
controller
.send_command(self.device().await?, [1, ty.as_u8()].to_vec())
.await
}
pub async fn features(&self) -> PolarResult<SupportedFeatures> {
if let Ok(controller) = self.controller().await {
if let Ok(device) = self.device().await {
return Ok(SupportedFeatures::new(controller.read(device).await?[1]));
}
return Err(Error::NoDevice);
}
Err(Error::NoControlPoint)
}
async fn controller(&self) -> PolarResult<&ControlPoint> {
if let Some(controller) = &self.control_point {
return Ok(controller);
}
Err(Error::NoControlPoint)
}
pub async fn start(&self, ty: H10MeasurementType) -> PolarResult<ControlResponse> {
self.get_pmd_response(ControlPointCommand::RequestMeasurementStart, ty)
.await
}
pub async fn stop(&self, ty: H10MeasurementType) -> PolarResult<ControlResponse> {
self.get_pmd_response(ControlPointCommand::StopMeasurement, ty)
.await
}
pub fn data_type_push(&mut self, data_type: H10MeasurementType) {
match &mut self.data_type {
Some(types) => {
if types.len() != 2 && types[0] != data_type {
types.push(data_type);
}
}
None => {
self.data_type = Some(vec![data_type]);
}
};
}
pub fn data_type_pop(&mut self, data_type: H10MeasurementType) {
if let Some(data) = &mut self.data_type {
data.retain(|x| *x != data_type);
if data.is_empty() {
self.data_type = None;
}
}
}
pub fn data_type(&self) -> &Option<Vec<H10MeasurementType>> {
&self.data_type
}
pub fn range(&mut self, range: u8) -> PolarResult<()> {
if range == 2 || range == 4 || range == 8 {
if let Some(ty) = &self.data_type {
if ty.contains(&H10MeasurementType::Acc) {
self.range = range;
return Ok(());
}
return Err(Error::WrongType);
}
return Err(Error::NoDataType);
}
Err(Error::InvalidData)
}
pub fn sample_rate(&mut self, rate: u8) -> PolarResult<()> {
if rate == 25 || rate == 50 || rate == 100 || rate == 200 {
if let Some(ty) = &self.data_type {
if ty.contains(&H10MeasurementType::Acc) {
self.sample_rate = rate;
return Ok(());
}
return Err(Error::WrongType);
}
return Err(Error::NoDataType);
}
Err(Error::InvalidData)
}
async fn device(&self) -> PolarResult<&Peripheral> {
if let Some(device) = &self.ble_device {
return Ok(device);
}
Err(Error::NoDevice)
}
async fn read(&self, uuid: Uuid) -> PolarResult<Vec<u8>> {
let device = self.device().await?;
if let Ok(char) = find_characteristic(device, uuid).await {
return device.read(&char).await.map_err(Error::BleError);
}
Err(Error::CharacteristicNotFound)
}
async fn read_string(&self, uuid: Uuid) -> PolarResult<String> {
let data = self.read(uuid).await?;
let string = String::from_utf8_lossy(&data).into_owned();
Ok(string.trim_matches(char::from(0)).to_string())
}
pub fn event_handler<H: EventHandler + 'static>(&mut self, event_handler: H) {
self.event_handler = Some(Arc::new(event_handler));
}
async fn get_pmd_response(
&self,
command: ControlPointCommand,
ty: H10MeasurementType,
) -> PolarResult<ControlResponse> {
let mut response: PolarResult<ControlResponse> = Err(Error::NoDevice);
if let Some(device) = &self.ble_device {
self.subscribe(NotifyStream::MeasurementCP).await?;
let mut notification_stream = device.notifications().await.map_err(Error::BleError)?;
match command {
ControlPointCommand::Null => return Err(Error::NullCommand),
ControlPointCommand::GetMeasurementSettings => self.internal_settings(ty).await?,
ControlPointCommand::RequestMeasurementStart => self.start_measurement(ty).await?,
ControlPointCommand::StopMeasurement => self.stop_measurement(ty).await?,
};
while let Some(data) = notification_stream.next().await {
if data.uuid == NotifyUuid::MeasurementCP.into() {
response = Ok(ControlResponse::new(data.value)
.await
.expect("err value getting response"));
break;
}
}
}
self.unsubscribe(NotifyStream::MeasurementCP).await?;
response
}
pub async fn event_loop(&self) -> PolarResult<()> {
let _ = self
.get_pmd_response(
ControlPointCommand::StopMeasurement,
H10MeasurementType::Acc,
)
.await?;
let _ = self
.get_pmd_response(
ControlPointCommand::StopMeasurement,
H10MeasurementType::Ecg,
)
.await?;
if let Some(types) = &self.data_type {
for ty in types {
let _ = self
.get_pmd_response(ControlPointCommand::RequestMeasurementStart, *ty)
.await?;
}
}
let eh = &self
.event_handler
.as_ref()
.expect("Arctic: Event loop requires an event handler.");
if let Some(device) = &self.ble_device {
let mut notification_stream = device.notifications().await.map_err(Error::BleError)?;
while let Some(data) = notification_stream.next().await {
if eh.should_continue().await {
if data.uuid == NotifyUuid::BatteryLevel.into() {
let battery = data.value[0];
eh.battery_update(battery).await;
} else if data.uuid == NotifyUuid::HeartMeasurement.into() {
let hr = HeartRate::new(data.value)?;
eh.heart_rate_update(self, hr).await;
} else if data.uuid == NotifyUuid::MeasurementData.into() {
if let Ok(response) = PmdRead::new(data.value) {
eh.measurement_update(self, response).await;
} else {
eprintln!("Invalid data received from PMD data stream.");
}
}
} else {
break;
}
}
}
if let Some(types) = &self.data_type {
for ty in types {
self.get_pmd_response(ControlPointCommand::StopMeasurement, *ty)
.await?;
}
}
Ok(())
}
async fn find_device(&self, central: &Adapter) -> Option<Peripheral> {
for p in central.peripherals().await.unwrap() {
if p.properties()
.await
.unwrap()
.unwrap()
.local_name
.iter()
.any(|name| name.starts_with("Polar") && name.ends_with(&self.device_id))
{
return Some(p);
}
}
None
}
}
async fn find_characteristic(device: &Peripheral, uuid: Uuid) -> PolarResult<Characteristic> {
device
.characteristics()
.iter()
.find(|c| c.uuid == uuid)
.ok_or(Error::CharacteristicNotFound)
.cloned()
}
#[cfg(test)]
mod test {
use super::*;
macro_rules! aw {
($e:expr) => {
tokio_test::block_on($e)
};
}
#[test]
fn type_push() {
let mut polar = aw!(PolarSensor::new("dummy ID".to_string())).unwrap();
polar.data_type_push(H10MeasurementType::Acc);
assert_eq!(polar.data_type, Some(vec![H10MeasurementType::Acc]));
polar.data_type_push(H10MeasurementType::Ecg);
assert_eq!(
polar.data_type,
Some(vec![H10MeasurementType::Acc, H10MeasurementType::Ecg])
);
polar.data_type_push(H10MeasurementType::Ecg);
assert_eq!(
polar.data_type,
Some(vec![H10MeasurementType::Acc, H10MeasurementType::Ecg])
);
polar.data_type_push(H10MeasurementType::Acc);
assert_eq!(
polar.data_type,
Some(vec![H10MeasurementType::Acc, H10MeasurementType::Ecg])
);
}
#[test]
fn type_pop() {
let mut polar = aw!(PolarSensor::new("dummy ID".to_string())).unwrap();
polar.data_type_push(H10MeasurementType::Acc);
polar.data_type_push(H10MeasurementType::Ecg);
polar.data_type_pop(H10MeasurementType::Acc);
assert_eq!(polar.data_type, Some(vec![H10MeasurementType::Ecg]));
polar.data_type_pop(H10MeasurementType::Acc);
assert_eq!(polar.data_type, Some(vec![H10MeasurementType::Ecg]));
polar.data_type_pop(H10MeasurementType::Ecg);
assert_eq!(polar.data_type, None);
}
}