假日黑客–在玩《使命召唤》时跟踪我的心率

2021-01-10 13:29:16

假期里,我收到了Polar OH1 +作为圣诞节礼物。它的光学心率监测器具有与智能手表(包括我的Garmin跑步手表)类似的技术,但由于尺寸更小和更精确(至少对于跑步而言)。更轻巧,并适合较肉体的上臂:

像当今的任何现代小工具一样,它支持蓝牙(特别是低功耗蓝牙或BLE / BTLE),用于与手机和/或智能手表通话。令我感到困惑的是,它将与计算机配对吗?

这激起了我的好奇心。长期以来,我至少对在玩视频游戏时跟踪我的心跳感到有些好奇,如果没有其他原因可以引起好奇。那么从这个东西中获取数据有多容易呢?与过去的几年一样,我花了12月的时间花了些时间在锈迹斑斑的代码中,只是忘记了,不得不在第二年重新学习所有内容。因此,我想我可以尝试“真正的”项目。

过了一会儿快速搜索,我发现了前途光明的btleplug板条箱。让我们从附近所有设备中转储数据…

外部板条箱;使用std :: thread;使用std :: time :: Duration #[cfg(target_os =" linux")]使用btleplug :: bluez :: {adapter :: ConnectedAdapter,manager :: Manager}; #[cfg(target_os =" windows")]使用btleplug :: winrtble :: {适配器::适配器,管理器::管理器}; #[cfg(target_os =" macos")]使用btleplug :: corebluetooth :: {适配器::适配器,管理器::管理器};使用btleplug :: api :: {UUID,Central,Peripheral}; #[cfg(any(target_os =" windows",target_os =" macos"))] fn get_central(manager:& Manager)->适配器{让适配器=管理器.adapters().unwrap();适配器.into_iter().nth(0).unwrap()}#[cfg(target_os =" linux")] fn get_central(manager:& Manager)-> ConnectedAdapter {让适配器=管理器.adapters().unwrap();让适配器=适配器.into_iter().nth(0).unwrap();适配器.connect().unwrap()} fn main(){让manager = Manager :: new().unwrap();让Central = get_central(& manager);中央.start_scan().unwrap();线程::睡眠(持续时间:: from_secs(2));对于每英寸中央.peripherals(){println! (" {:?}&#34 ;,每); }}

(请注意,尽管该部分应跨平台运行,但我无法从WSL获得蓝牙的支持,因此这一切均在Windows上完成)。

A0:9E:1A:XX:XX:XX属性:PeripheralProperties {地址:A0:9E:1A:XX:XX:XX,地址类型:公共,本地名称:Some(" Polar OH1 XXXXXXXX"),tx_power_level :某些(-64),制造商数据:某些([]),发现计数:6,has_scan_response:true},特征:{}

Warning: Can only detect less than 5000 characters

其他可能令人感兴趣的位标志包括一种指示传感器是否认为已失去皮肤接触的方法。

现在是时候找出用户界面了。面对大量不同的选择,我最终选择了一个没有列出的native-windows-gui板条箱,因为怀疑我需要轻松访问基础的win32 API,因此跨平台支持,因为在很大程度上,这些只是一系列不错的便利性抽象。使用关联的native-windows-derive宏板条箱,我进行了基本的UI设置,运行速度非常快:

#[derive(Default,NwgUi)] pub struct BasicApp {#[nwg_control(size:(300,115),标志:" WINDOW | VISIBLE")]#[nwg_events(OnWindowClose:[BasicApp :: exit ])] window:nwg :: Window,#[nwg_layout(parent:window)] grid:nwg :: GridLayout,#[nwg_control(text:"-",readonly:true)]#[ nwg_layout_item(layout:grid,row:0,col:0)] hr:nwg :: TextInput,#[nwg_control(parent:window,interval:500,stopped:false)]#[nwg_events(OnTimerTick:[BasicApp :: draw_hr ]]] timer:nwg :: Timer,} impl BasicApp {fn draw_hr(& self){self .hr .set_text(" ??"); } fn exit(& self){nwg :: stop_thread_dispatch(); }}

现在我们如何实际更新值?这是我第一次遇到真正的生锈乐趣的地方:出于我尚不完全确定的原因,蓝牙通知处理程序需要'静态生命周期(类型中未指定-是因为它是Sync?),这意味着它无法干净地引用UI结构,而我试图彻底解决此问题的尝试却无济于事。最终我放弃了,做了肮脏的事情(真正的甲壳类动物避免了你的眼睛):

从这里开始,UI的进一步改进要求我丢弃漂亮的宏,并用等效的生成位替换它们。但是,这使我们可以访问基础的win32窗口样式标志。特别是,设置WS_EX_TOPMOST会在所有内容的顶部呈现窗口,WS_POPUP隐藏菜单栏,WS_EX_LAYERED为我们提供了一些透明控件,等等。代码实在令人讨厌,无法内联显示,但这就是从实际情况看这一切写作时间:

足够注意Gamer™,但是嘿,它起作用了。考虑到所有因素,我惊讶于在锈中实现该原型如此容易/成功。对于那些好奇的人,在玩CoD时,我的心率实际上基本上只停留在55-70范围内,但现在我可以确定了:)

1.我的朋友Matt在这个领域有丰富的经验,能够为我解决这个难题-00000000-0000-1000-8000-00805F9B34FB是“基础” 128位UUID