
0 前言' E' J. h4 Q+ X1 U$ Q" o3 v* H5 N 自己的主要目的是学习BLE相关的知识,以及其具体的应用。因此不会像前一小节那样,组成那么长链路的系统。为最佳的学习BLE,就需要组成最小的闭环回路。 1 最小闭环回路9 b7 j, z$ `! W0 k 最小闭环系统,就是STM32WB的开发板和笔记本组成的系统。现在基本的笔记本自带蓝牙。7 A, g) x. K: Y7 o/ Q$ y. P" h* w4 a) _ 1 ![]() / N F+ \' d) K: L# [9 }+ [ / H" l& B a, Y0 K8 T- J & d. k [& J4 w8 f 2 系统软件环境 在开发板中的软件使用的是:P-NUCLEO-WB55.Nucleo\Applications\BLE\BLE_HeartRate项目工程。3 m v) [3 u5 j+ W! E3 R 笔记本使用上位机是自己编写的python代码。 2 ![]() 我们将开发板当作外围设备。将笔记本作为中心设备。: l, g ~6 [: N& N6 U) r 3 连接实验" i( A' r- u! n/ `6 Q. H 3.1 发现设备 下载好单片机程序,给单片机上电。4 s! [# K+ M; u8 s- K 在笔记本设置中打开蓝牙,并点击添加蓝牙。3 b) ~: G' A* |' [ 3 ![]() 可以看到我们的设备HRSTM。5 o4 R" m4 f1 w5 T 接下来使用我编写好代码:9 a3 v! |+ Y( s* b import bleak.backends.dotnet.discovery as Discovery& j1 W5 l& P( j, D/ L6 s% v import asyncio3 s4 I( t4 l: ?5 O async def run(): devices = await Discovery.discover() for d in devices:) {2 w/ g9 C9 F, U3 M# x/ T8 J O print(d) loop = asyncio.get_event_loop()& q6 Z( k! \$ z. z' M loop.run_until_complete(run()) 在输出窗口可以看到可以被发现了的蓝牙: B8:7C:6F:47:81:BF: MiKettle6 Y! a! ~2 e9 ^; u 6B:96:A5:AF:06:A7: Apple, Inc. (b'\x10\x05Q\x1c\x82.\xec') 80:E1:26:00:68:7C: HRSTM 我们也可以看到我们的蓝牙应用HRSTM。其中,一个很重要的东西就是设备的ID,这个是我们连接的一个关键,就和电话号码一样,而HRSTM就是持有人的别名。: ?5 W' w% ~$ i" h# ] 3.2 连接和发现服务 接下来,我们应用上面的地址,来连接和发现一下HRSTM有哪些服务: async with BleakClient(address, loop=loop) as client: x = await client.is_connected()0 F$ ~6 M9 ~$ `. S; ]6 T, d log.info("Connected: {0}".format(x)) log.info("[Service] {0}: {1}".format(service.uuid, service.description))0 l# F0 |0 q3 y9 C for char in service.characteristics:! C, P( _' j; |- i if "read" in char.properties: try:8 S7 M' H8 @) {. p value = bytes(await client.read_gatt_char(char.uuid)) V* X$ T7 \* ] except Exception as e:/ e# E# o1 p6 k; x$ G value = str(e).encode() else:" K, T+ G4 n) [2 ~* G: l) ]0 v value = None s. W$ g" C) O3 o' y log.info( "\t[Characteristic] {0}: ({1}) | Name: {2}, Value: {3} ".format( char.uuid, ",".join(char.properties), char.description, value )8 k! w: e! E1 v: l, i5 f ) for descriptor in char.descriptors: value = await client.read_gatt_descriptor(descriptor.handle) log.info( "\t\t[Descriptor] {0}: (Handle: {1}) | Value: {2} ".format( descriptor.uuid, descriptor.handle, bytes(value)& V5 D* i/ B9 i9 S0 S# K )$ A1 |3 a7 N/ _ J4 A S8 G9 H ) - Z) {5 t! h& M! P 输出的结果: Connected: True4 s2 o) A! m# e7 T' R& q" c: I [Service] 00001801-0000-1000-8000-00805f9b34fb: Generic Attribute Profile# J! s# e! i6 a! M+ E' b [Characteristic] 00002a05-0000-1000-8000-00805f9b34fb: (indicate) | Name: , Value: None [Descriptor] 00002902-0000-1000-8000-00805f9b34fb: (Handle: 4) | Value: b'\x02\x00'1 o: S- @% O' @/ C [Service] 00001800-0000-1000-8000-00805f9b34fb: Generic Access Profile; g: N" \6 q4 Q! a& \ [Characteristic] 00002a00-0000-1000-8000-00805f9b34fb: (read,write-without-response,write,authenticated-signed-writes) | Name: , Value: b'STM32WB' [Characteristic] 00002a01-0000-1000-8000-00805f9b34fb: (read,write-without-response,write,authenticated-signed-writes) | Name: , Value: b'@\x03' [Characteristic] 00002a04-0000-1000-8000-00805f9b34fb: (read) | Name: , Value: b'\xff\xff\xff\xff\x00\x00\xff\xff' k$ M+ i; H* g/ [6 L [Service] 0000180a-0000-1000-8000-00805f9b34fb: Device Information2 I( { x" H3 W% ~0 c [Characteristic] 00002a29-0000-1000-8000-00805f9b34fb: (read) | Name: , Value: b'STM\x00' [Service] 0000180d-0000-1000-8000-00805f9b34fb: Heart Rate3 R7 C& {( R' E3 j$ ^3 N [Characteristic] 00002a37-0000-1000-8000-00805f9b34fb: (notify) | Name: , Value: None% C/ Z; k0 [% z [Descriptor] 00002902-0000-1000-8000-00805f9b34fb: (Handle: 18) | Value: b'\x00\x00' [Characteristic] 00002a38-0000-1000-8000-00805f9b34fb: (read) | Name: , Value: b'\x04'1 C& s- D1 E2 v( U2 z( H [Characteristic] 00002a39-0000-1000-8000-00805f9b34fb: (write) | Name: , Value: None7 U* a8 F# Y/ k. O! h5 {, |* ~2 D 观察,返回的信息,我们大致可以通过,服务、特性、描述等知道其性能和功能。其中,发现UUID都是单片机中定义好的,在UUID.h 文件目录下: /* UUIDs for Heart Rate Service */ #define HEART_RATE_SERVICE_UUID (0x180D)1 `5 q5 e0 i( @/ C2 n% Y& l #define CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID (0x2902)& u" \( g2 h. [) \ l #define HEART_RATE_MEASURMENT_UUID (0x2A37) #define SENSOR_LOCATION_UUID (0x2A38)) R! {. H3 t; j4 w0 |: t #define CONTROL_POINT_UUID (0x2A39)8 D- t$ N4 [: L! f( @ K; F 3.3 通知和获取数据 通过STM的蓝牙软件,发现软件是有数据传递的。 4 ![]() % k, |, l4 y1 h 其采用的方式是通知的方式,我是用了一下程序: def notification_handler(sender, data):" K) K0 f. W5 T4 Z- K # strs=data.hex0 a: ~2 m3 f& K4 }* g print(f"{sender}: {data}")# T8 |- E, |( C, o byarray = bytearray(data) print(byarray.hex()) i6 e3 @' B; f" S0 i4 W async def run(address, loop, debug=False):, q ^6 a, z* a1 R async with BleakClient(address, loop=loop) as client:5 y# _6 p0 _' j( W+ Z x = await client.is_connected() await client.start_notify(CHARACTERISTIC_UUID, notification_handler) await asyncio.sleep(5.0, loop=loop) await client.stop_notify(CHARACTERISTIC_UUID) if __name__ == "__main__": if __name__ == "__main__":2 A7 Y" v% \2 ~& K( V* { address = ('80:E1:26:00:68:7C') #你的地址 loop = asyncio.get_event_loop(): M8 k. |/ W+ I/ Z loop.run_until_complete(run(address, loop, True)) 返回了一下数据: 00002a37-0000-1000-8000-00805f9b34fb: bytearray(b'\x1f7\x00\x14\x00\x00\x04')5 c/ N; P. [2 A* X: z1 K 1f370014000004 00002a37-0000-1000-8000-00805f9b34fb: bytearray(b'\x1f6\x00\x1e\x00\x00\x04')& |4 J( e6 z$ I; Y C 1f36001e0000048 ?" B$ l8 h6 T( o/ Z 00002a37-0000-1000-8000-00805f9b34fb: bytearray(b'\x1f4\x00(\x00\x00\x04')" M9 H4 \+ Z/ s 1f340028000004 其中00002a37是HEART_RATE_MEASURMENT_UUID ,数据是十六进制的1f36001e000004,其中0x36是心跳,1e是能量消耗,我将能量变化修改为,每次+10,从每次读回的值就可以看到。 HRSAPP_Context.MeasurementvalueChar.EnergyExpended += 10; : A* N! z4 q4 Z7 P; u. j' Q 4 小结8 B* F$ }4 k0 S. P8 ]: \8 u 以上步骤,从发现蓝牙,连接蓝牙,获取蓝牙服务,以及获取数据。其实,实验中还有许多的小问题,比如指示灯的闪烁,为啥STM官方的APP可以直接连接,自己的却不行? 我相信,在后面一步一步的代码解析,就会解释这些现象。3 I( ^) F3 W# p& b7 Z4 A, | ! a' J( c4 c2 ?6 t3 ] & B2 v& h/ N1 m! ~& ]+ D |
Nucleo-STM32WB09KE测评-蓝牙调试并使用OLED显示数据
【NUCLEO-WB09KE评测】创建BLE工程和开启调试信息
【NUCLEO-WB09KE评测】+2.串口打印
【NUCLEO-WB09KE评测】+5.实现标准电量服务
【NUCLEO-WB09KE评测】BLE例程HeartRate及工具探索
【NUCLEO-WB09KE评测】+3.蓝牙广播
STM32WB系列MCU低功耗测试
Nucleo-STM32WB09KE测评--IIC传感器读取
【NUCLEO-WB09KE评测】+4.蓝牙控制LED
【NUCLEO-WB09KE评测】+1.点灯