0. GATT协议
从通信角度来看,处于连接状态时的两个设备,它们各自充当两种角色中的一种:
- 服务端(Server):包含被GATT客户端读取或写入的特征数据的设备。esp32就是服务端,为手机提供各种服务
- 客户端(Client):从GATT服务器中读取数据或向GATT服务器写入数据的设备。手机电脑就是客户端,连接到esp32,并且查询想要的数据。
1. 蓝牙广播自身信息
esp32作为服务端,要想被发现,就需要不断的广播自己的信息,这样才能被客户端发现
import bluetooth #导入BLE功能模块
ble = bluetooth.BLE() #创建BLE设备
ble.active(True) #打开BLE
#设置BLE广播数据并开始广播
ble.gap_advertise(100, adv_data = b'\x02\x01\x06\x03\x09\x41\x42')
上面gap_advertise
函数就是在不断的广播蓝牙的信息,打开手机的蓝牙调试软件,我们发现多了一个AB的蓝牙设备。广播的意思就是大声吆喝,谁都能听的见,所有的外设设别都是在不断广播的,但是当蓝牙设备被连接时,其他中心设备将无法再搜索到该设备。
ble.gap_advertise 是 MicroPython 中用于蓝牙广播的函数,以下是对它的详细介绍:
ble.gap_advertise(interval_us, adv_data=None, resp_data=None, connectable=True)
- interval_us:广播间隔,单位为微秒。广播设备将按照这个间隔周期性地发送广播数据,该值会被向下舍入到最接近的 625 微秒的整数倍。例如,如果设置为 1000 微秒,实际广播间隔将是 1000 微秒;如果设置为 1500 微秒,则会被舍入到 1250 微秒。将其设置为 None 可以停止广播,或者在广播已停止的情况下更新扫描响应负载。
- adv_data:广播数据,可以是实现缓冲区协议的任意类型,如 bytes、bytearray、str。它将包含在所有广播中,用于向其他设备传递设备的相关信息,如设备名称、服务 UUID 等。
- resp_data:扫描响应数据,同样可以是实现缓冲区协议的任意类型。当有设备发起扫描请求时,广播设备会将 resp_data 作为扫描响应发送给请求设备,它通常包含更多的设备信息,如设备的外观、制造商信息等。
- connectable:布尔值,表示设备是否可连接。默认为 True,即设备在广播时允许其他设备连接。如果设置为 False,则设备只进行广播,不允许其他设备进行连接。
2. 广播数据包结构
蓝牙的信息按照一定的数据结构编码成二进制,最终就得到了 b'\x02\x01\x06\x03\x09\x41\x42'
。蓝牙广播包的最大长度是37个字节,其中设备地址(AdvA)占用了6个字节,只有31个字节(AdvData)是可用的。这31个可用的字节又按照一定的格式来组织,被分割为n个AD Structure。
- AdvA:表示广播方的地址,即蓝牙设备的MAC地址,长度为6字节。
- Data:表示数据包,AdvData由若干个广播数据单元(即AD Structure)组成。AD Structure的结构=Length+AD Type+AD data。
- Length:表示该AD Structure数据的总长度,即为AD Type与AD Data的长度和(即不含 Length字段本身的1字节)。
- AD Type:表示该广播数据代表的含义,如设备名、UUID等。
- AD Data:表示具体的数据内容。
我们按照AD Structure结构来解析一下上面的adv_data数据
AD Structure 1
字段 | 长度 | 取值 | 说明 |
---|---|---|---|
Length | 1字节 | 0x02 | 表示AD Type与AD Data的总长度。 |
AD Type | 1字节 | 0x01 | 表示该广播数据代表的含义。此处取值固定为0x01,表示设备标识 |
AD Data | 1字节 | 0x06 | 表示蓝牙设备的物理连接能力。esp32只支持LE(低功耗蓝牙),不支持BR/EDR(经典蓝牙),一般都将设备设为处于普通发现模式,所以我们只设置Bit1和Bit2,即0x06(b00000110)。 |
bit0:LE受限可发现模式。
bit1:LE通用可发现模式。
bit2:不支持BR/EDR。
bit3:对Same Device Capable(控制器)同时支持BLE和BR/EDR。
bit4:对Same Device Capable(主机)同时支持BLE和BR/EDR。
bit5~7:预留。
AD Structure 2
字段 | 长度(字节) | 取值 | 说明 |
---|---|---|---|
Length | 1字节 | 0x03 | 表示AD Type与AD Data的总长度。 |
AD Type | 1字节 | 0x09 | 表示蓝牙的名称 |
AD Data | 2字节 | 0x41,0x42 | 蓝牙的名称,asiic表示的就是AB |
AD Type可以从蓝牙联盟的官网查看,我这里罗列了几个常见的
ad_types:
- name: Flags
value: 0x01
- name: Complete List of 16-bit Service Class UUIDs
value: 0x03
- name: Complete List of 128 bit service Class UUIDs
value: 0x07
- name: Shortened Local Name
value: 0x08
- name: Complete Local Name
value: 0x09
- name: Transmit power level
value: 0x0A
- name: Appearance
value: 0x19
- name: Manufacturer Specific Data
value: 0xFF
3. 修改中文蓝牙名称
蓝牙广播的数据最终都需要编码成utf-8,我们可以使用encode将中文名称编码成utf-8,然后构建成AD Structure的数据结构,将蓝牙模式与蓝牙名称拼接在一起广播出去
import bluetooth #导入BLE功能模块
ble = bluetooth.BLE() #创建BLE设备
ble.active(True) #打开BLE
name = "中国蓝牙".encode() # 编码成utf-8格式
adv_mode = bytearray(b'\x02\x01\x06') # 正常蓝牙模式, ad struct 1
adv_name = bytearray((len(name) + 1, 0x09)) + name # 0x09是蓝牙名称,ad struct 2
adv_data = adv_mode + adv_name
ble.gap_advertise(100, adv_data = adv_data)
4. 数据收发交互
连接到esp32的蓝牙之后,就要考虑怎么传输数据了,蓝牙的数据交互依靠的是服务和特性。
- 服务services:蓝牙可以提供很多服务,例如键盘鼠标服务,心率监控服务,环境监测服务等,每个服务都有自己的uuid。
- 特性:有了服务,用户就可以通过调用这个服务获取自己想要的数据了,特性可以理解为接口数据。
直接说蓝牙服务跟特性太抽象了,举几个不太恰当的例子来帮助理解。比如我们要开发一个环境监控服务,这个服务有几个数据接口,例如读取温度的接口,读取湿度的接口,通过每个接口就可以获取想要的数据了,同样我们可以再实现一个电量监控服务,监控设备的电量,也提供一个电量读取的接口,随着服务的增多,我们可以考虑设计一个工厂类
class EnvService:
def get_temperature(self):
return "38.5"
def get_humidity(self):
return "0.25"
class BatteryService:
def get_battery(self):
return "78"
class FactoryService:
def __call__(self, fs):
if fs == "env":
return EnvService()
elif fs == "battery":
return BatteryService()
es = FactoryService("env")
bs = FactoryService("battery")
print(es.get_temperature())
print(es.get_humidity())
print(bs.get_batteryself())
使用蓝牙的时候你怎么能让别人知道你定义的是什么服务和接口呢?蓝牙联盟使用uuid来区分不同的服务和特性。蓝牙联盟定义了非常多的标准服务和特性。当你使用0x1124大家就知道你是一个hid服务,当你使用0x1106大家就知道你是一个文件传输服务,当你使用0x180F就知道你是一个电量监控服务。知道了你是什么服务以后就可以通过相应的接口来进行通信。接口也是使用uuid来定义的,例如0x2A19就是电池电量的监控接口,通过这个uuid来进行数据交互。操作uuid来读取数据,听起来就很茫然,其实是你把这个uuid传入到蓝牙sdk中,sdk会返回一个句柄(接口)来给你操作
env_service_uuid = 0x181A # 定义服务的uuid,映射就是环境监控服务
# 定义特性(接口)的uuid, 还需要指明这个特性是否可读写
env_tmp_uuid = (0x2A6E, WRITE | READ) # 温度特性
env_hum_uuid = (0x2A6F, WRITE | READ) # 湿度特性
# 组成环境检测服务,服务的uuid以及特性的uuid
env_service = (service_uuid, (env_tmp_uuid, env_hum_uuid))
services = (env_service, )
# 到蓝牙的sdk注册服务,拿到数据接口,通过这个接口可以读写数据
((tem_handle, hum_handle, ), ) = ble.gatts_register_services(services)
import struct
# 温度特性写入数据
ble.gatts_write(tem_handle, struct.pack("<B", int(100)))
# 对所有连接上的蓝牙设备发送数据,蓝牙只能连接一个中心设备,所以conn_handles只有一个0值
# 很多文章直接写成ble.gatts_notify(0, tem_handle)
for handle in conn_handles:
# Notify connected centrals to issue a read.
ble.gatts_notify(handle, tem_handle)
conn_handles是所有连接的蓝牙设备,例如可能有很多手机都连接了这个esp32,就会有多个conn_handle,所以需要先把数据写到特性接口中,然后notify到所有连接的手机。
5. 蓝牙事件处理
现在的问题就是我们怎么知道哪些手机连接了esp32,esp32收到的数据又是来自哪个手机呢?这就需要蓝牙的中断事件来帮我们获取想要的数据了,蓝牙中断监听各种事件,例如有设备连接了,或者连接断开了,或者收到了其他设备的数据,都是通过事件触发的。我们在中断函数中监听蓝牙的各种事件
import bluetooth #导入BLE功能模块
ble = bluetooth.BLE() #创建BLE设备
ble.active(True) #打开BLE
name = "中国蓝牙".encode() # 编码成utf-8格式
adv_mode = bytearray(b'\x02\x01\x06') # 正常蓝牙模式, ad struct 1
adv_name = bytearray((len(name) + 1, 0x09)) + name # 0x09是蓝牙名称,ad struct 2
adv_data = adv_mode + adv_name
ble.gap_advertise(100, adv_data = adv_data)
def ble_irq(event, data): # 蓝牙中断函数
if event == 1: #蓝牙已连接
# 作为外设设备,一旦被中心设备连接之后就无法再被其他设备连接,所以conn_handle只能为0
conn_handle, addr_type, addr = data
print(f"fd = [{conn_handle}] connect")
elif event == 2: #蓝牙断开连接
conn_handle, addr_type, addr = data
print(f"fd = [{conn_handle}] disconnect")
ble.gap_advertise(100, adv_data = adv_data)
elif event == 3: #收到数据
# 作为中心设备,可能会连接很多外设设备,即各种各样的服务,例如hid服务,env服务,battery服务等,通过conn_handle来区分是哪个设备(服务)发来的数据
# 通过attr_handle来区分收到的是哪个特性的数据
conn_handle, attr_handle = data
print(f"fd = [{conn_handle}], char = [{attr_handle}] recive msg")
ble.irq(ble_irq)
event=1就是蓝牙连接事件,event=2就是断开蓝牙,envent=3就是收到了数据
更多的事件可以在micropython的文档中找到,这里给出一部分常用的
from micropython import const
_IRQ_CENTRAL_CONNECT = const(1)
_IRQ_CENTRAL_DISCONNECT = const(2)
_IRQ_GATTS_WRITE = const(3)
_IRQ_GATTS_READ_REQUEST = const(4)
_IRQ_SCAN_RESULT = const(5)
_IRQ_SCAN_DONE = const(6)
_IRQ_PERIPHERAL_CONNECT = const(7)
_IRQ_PERIPHERAL_DISCONNECT = const(8)
_IRQ_GATTC_SERVICE_RESULT = const(9)
_IRQ_GATTC_SERVICE_DONE = const(10)
_IRQ_GATTC_CHARACTERISTIC_RESULT = const(11)
_IRQ_GATTC_CHARACTERISTIC_DONE = const(12)
_IRQ_GATTC_DESCRIPTOR_RESULT = const(13)
_IRQ_GATTC_DESCRIPTOR_DONE = const(14)
_IRQ_GATTC_READ_RESULT = const(15)
_IRQ_GATTC_READ_DONE = const(16)
_IRQ_GATTC_WRITE_DONE = const(17)
_IRQ_GATTC_NOTIFY = const(18)
data是一个元组,其中conn_handle表示连接的句柄,不同的手机连接就会得到不同的句柄,通过conn_handle就可以区分不同的设备了。attr_handle则是特性句柄,event=3表示的是esp32接收数据的事件,通过conn_handle我们可以知道是哪个设备发来的数据,通过attr_handle我们则能知道发过来的是哪个特性的数据。
6. 数据收发实现
import struct
import time
import bluetooth #导入BLE功能模块
ble = bluetooth.BLE() #创建BLE设备
ble.active(True) #打开BLE
name = "中国蓝牙".encode() # 编码成utf-8格式
adv_mode = bytearray(b'\x02\x01\x06') # 正常蓝牙模式, ad struct 1
adv_name = bytearray((len(name) + 1, 0x09)) + name # 0x09是蓝牙名称,ad struct 2
adv_data = adv_mode + adv_name
env_service_uuid = bluetooth.UUID(0x181A) # 定义服务的uuid,映射就是环境监控服务
# 定义特性(接口)的uuid, 还需要指明这个特性是否可读写
env_tmp_uuid = (bluetooth.UUID(0x2A6E), bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY ) # 温度特性
env_hum_uuid = (bluetooth.UUID(0x2A6F), bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY) # 湿度特性
# 组成环境检测服务,服务的uuid以及特性的uuid
env_service = (env_service_uuid, (env_tmp_uuid, env_hum_uuid))
services = (env_service, )
# 到蓝牙的sdk注册服务,拿到数据接口,通过这个接口可以读写数据
((tem_handle, hum_handle, ), ) = ble.gatts_register_services(services)
ble.gap_advertise(100, adv_data = adv_data)
conn_handles = []
def ble_irq(event, data): # 蓝牙中断函数
if event == 1: #蓝牙已连接
conn_handle, addr_type, addr = data
conn_handles.append(conn_handle)
print(f"fd = [{conn_handle}] connect")
elif event == 2: #蓝牙断开连接
conn_handle, addr_type, addr = data
conn_handles.remove(conn_handle)
print(f"fd = [{conn_handle}] disconnect")
ble.gap_advertise(100, adv_data = adv_data)
elif event == 3: #收到数据
conn_handle, attr_handle = data
print(f"fd = [{conn_handle}], char = [{attr_handle}] recive msg")
ble.irq(ble_irq)
while True:
time.sleep(1)
# 温度特性写入数据
ble.gatts_write(tem_handle, struct.pack("<B", int(100)))
# 对所有连接上的蓝牙设备发送数据
for handle in conn_handles:
ble.gatts_notify(handle, tem_handle)
这里面特性权限让人非常困惑,为什么设置的READ
权限?我把READ权限给去掉之后再试,发现手机没办法读取数据了。突然意识到这个权限是开发给客户端的,就像我们平时写的服务一样,如果给用户提供了READ权限,客户端才能从服务端读取数据,否则是查询不到的。notify
则表示客户端是否可以监听服务发送的数据,服务的数据是不断变化的,大部分时候我们希望服务端数据更新之后,可以收到服务端通知,所以需要设置notify
权限
权限,是指给客户端开通的权限,开通了read权限,客户端才能读取服务器的数据,开通了notify权限,用户才能收到服务器的通知
随着我深入理解,发现下面这段代码写的也有问题,
while True:
time.sleep(1)
# 温度特性写入数据
ble.gatts_write(tem_handle, struct.pack("<B", int(100)))
# 对所有连接上的蓝牙设备发送数据
for handle in conn_handles:
ble.gatts_notify(handle, tem_handle)
我本意是不断更新本地数据,然后把这个数据通知给客户端读取,后来发现这两个函数并不是这个意思
BLE.gatts_write(value_handle, data)
把数据写入到本地,客户端通过value_handle可以读取本地的数据
BLE.gatts_notify(conn_handle, value_handle, data=None, /)
发送通知给某个(conn_handle)连接的客户端,如果data设置的None,那么就把本地当前的值发送出去(通过gatts_write可以把值写入到本地),否则,如果data不是None,那就会把data的值发送给客户端,并且local value将会被更新为data
还有一点需要注意,函数的gatts
的意思是gatt services
,意思是服务使用的,还有gattc
,是个客户端使用的,这个设计的真是莫名其妙。esp32再连接后只能作为一个角色,要不就是客户端,要不就是服务端,只能二选一,当esp32作为服务端的时候难道还可以调用gattc客户端的函数不成?为啥不统一呢?
说回正题,gatts_write
把data
写入到了本地,客户端读取数据的时候是直接读取的local value,也就是本地值,所以如果我们的值没有发生变化,不需要一直往本地写
# 温度特性写入数据
# 值没有发生变化,所以放到while循环外侧
ble.gatts_write(tem_handle, struct.pack("<B", int(100)))
while True:
time.sleep(1)
# 对所有连接上的蓝牙设备发送数据
for handle in conn_handles:
ble.gatts_notify(handle, tem_handle)
后来又一寻思,客户端是主动查询的数据,读取的是服务端本地的值,服务端的值又没有变化,为啥要notify
客户端呢?所以最后变成了
# 温度特性写入数据
# 值没有发生变化,也不需要主动通知到客户端,客户端自己读取
ble.gatts_write(tem_handle, struct.pack("<B", int(100)))
真实情况是,温度值是不断变化的,所以我们还是要放到while
循环中
# 温度特性写入数据
# 值没有发生变化,所以放到while循环外侧
i = 0
while True:
time.sleep(1)
i += 1
i = i%100;
ble.gatts_write(tem_handle, struct.pack("<B", i))
# 对所有连接上的蓝牙设备发送数据
for handle in conn_handles:
ble.gatts_notify(handle, tem_handle)
7. 实现蓝牙串口
esp32接收手机发送的数据,然后调用串口打印出来,接收手机发送的消息,其实就是数据往esp32写入数据,首先要给特性赋于WRITE
权限,这样客户端才能往服务器写入数据。蓝牙联盟并没有定义串口的服务,因此需要我们自己搞一个uuid来作为蓝牙串口的service,并且自己定义数据收发的特性,由于是自己定义的uuid,所以手机肯定无法识别这个是啥服务,因此会直接显示unknown service
import struct
import time
import bluetooth
ADV_FLAGS_TYPE=const(0x01)
ADV_NAME_TYPE=const(0x09)
ADV_APPERANCE_TYPE=const(0x19)
class BasePeripheral:
def __init__(self,name,apperance):
self.ble = bluetooth.BLE() #创建BLE设备
self.ble.active(True) #打开BLE
self.name = name
self.conn_handles = set()
self.ble.irq(self._irq)
self.adv_data = bytearray()
self._services = []
self.attr_names = []
self.mode_map = {'r':bluetooth.FLAG_READ,'w':bluetooth.FLAG_WRITE,'n':bluetooth.FLAG_NOTIFY}
self.handle_map = None
self.msg = ''
self.apperance=apperance
def _append(self, adv_type, value):
self.adv_data += struct.pack('BB',len(value)+1,adv_type)+value
def add_service(self, service_uuid, *attrs_info):
service_uuid = bluetooth.UUID(service_uuid)
attrs = []
for attr_name, attr_uuid, modes in attrs_info:
self.attr_names.append(attr_name)
mode = 0
for m in modes:
mode = mode | self.mode_map.get(m,0)
attrs.append((bluetooth.UUID(attr_uuid), mode))
self._services.append((service_uuid, tuple(attrs)))
def start(self):
_handles = self.ble.gatts_register_services(self._services)
handles = []
for i in _handles:
if isinstance(i, tuple):
handles.extend(i)
else:
handles.append(i)
self.handle_map = dict(zip(self.attr_names, handles))
print(self.handle_map)
self._advertise()
def _advertise(self):
_append(ADV_FLAGS_TYPE,struct.pack('B',0x06))
_append(ADV_NAME_TYPE,self.name.encode())
_append(ADV_APPERANCE_TYPE, struct.pack('<h', self.apperance))
self.ble.gap_advertise(100, adv_data = self.adv_data)
def _irq(self, event, data):
if event == 1: #蓝牙已连接
conn_handle, _, _ = data
self.conn_handles.add(conn_handle)
print(f"fd = [{conn_handle}] connect")
elif event == 2: #蓝牙断开连接
conn_handle, _, _ = data
self.conn_handles.remove(conn_handle)
print(f"fd = [{conn_handle}] disconnect")
self._advertise()
elif event == 3: #收到客户端发送过来的数据,并读取数据到msg
conn_handle, attr_handle = data
print(f"fd = [{conn_handle}], char = [{attr_handle}] recive msg")
buffer = self.ble.gatts_read(attr_handle)
self.msg = buffer.decode('UTF-8').strip()
# 数据写入本地,并通知客户端,给客户端设置了读取权限
# 客户端读取数据时,获取的是gatts_write写入的本地数据
def write(self, attr_name, data, notify=False):
value_handle = self.handle_map[attr_name]
self.ble.gatts_write(value_handle, data)
if notify:
for conn_handle in self.conn_handles:
# Notify connected centrals to issue a read.
self.ble.gatts_notify(conn_handle, value_handle)
# 给客户端设置了写入权限,esp32作为服务器才能接收客户端发来的数据
def read(self, attr_name):
value_handle = self.handle_map[attr_name]
buffer = self.ble.gatts_read(value_handle)
self.msg = buffer.decode('UTF-8').strip()
return self.msg
bp = BasePeripheral("中国蓝牙", 960)
# 我们是在手机上看到这两个特性,并针对这两个特性进行数据操作
# 手机上显示tx,表示发送数据到服务器,也就是需要赋于客户端写权限
# 手机上显示rx,表示接收服务器发送来的数据,也就是需要赋于客户端读权限
# esp32发送数据给客户端的时候,要使用rx特性
# 我们https://www.uuid.online/随便生成三个uuid来作为service id和两个特性的id
# 每个uuid都是由自己的含义的,我们随机生成的uuid无法映射到有意义的服务,因此手机连接之后
# 会显示为unknown service,特性也一样
bp.add_service(0x181A, ('rx', 0x2A6E, 'rn'), ('tx', 0x2A6F, 'w'))
bp.start()
bp.write('rx',struct.pack("<B", int(100)))
while True:
time.sleep(1)
msg = bp.read('tx')
if msg:
print(msg)
8. 客户端与服务端
有一天走在路上,突然意识到,手机是客户端,向服务器请求数据,例如请求今天的温度,今天的湿度,连接方式是tcp或者udp。突然就想通了esp32为什么是服务器了,因为esp32也是给手机提供温度,湿度等信息,手机连接的方式是ble。所以esp32广播,然后手机作为客户端去主动连接,esp32提供客户端想要的服务和数据。
服务端提供了温度和湿度这两个数据接口,让手机可以读取和监听,因此就需要赋于这两个字段的权限。假如我们写了一篇wiki想要分享给其他人,wiki就是我们提供的服务,其他人就是客户端,如果其他人想要看,我们就得给这篇wiki赋于读权限,如果用户想要实时监看这篇wiki的更新,我们就需要赋于这篇wiki通知的权限,一旦有变更,平台就会把这个wiki的更新发送给用户,而esp32的notify则是蓝牙协议帮我们实现的,我们只要赋于这个特性notify的权限,数据更新之后就可以通知给其他用户了。
9. 蓝牙外观
蓝牙还有外观,我拿着esp32陷入了沉思。后来才明白,原来这里说的是当我们连接的时候,显示的图标是什么样子的,例如蓝牙耳机连接了,手机上看到就是耳机的图标,电脑连接了,显示的就是电脑
在介绍AD Structure结构的时候,AD Type表示广播的类型,其中 0x19表示的是蓝牙外观Appearance,下面是一些常用的外观值,注意不要搞错本文档,无论其标题或内容如何,都不是蓝牙专利/版权许可协议中定义的规范
#define BLE_APPEARANCE_UNKNOWN 0
#define BLE_APPEARANCE_GENERIC_PHONE 64
#define BLE_APPEARANCE_GENERIC_COMPUTER 128
#define BLE_APPEARANCE_GENERIC_WATCH 192
#define BLE_APPEARANCE_WATCH_SPORTS_WATCH 193
#define BLE_APPEARANCE_GENERIC_CLOCK 256
#define BLE_APPEARANCE_GENERIC_DISPLAY 320
#define BLE_APPEARANCE_GENERIC_REMOTE_CONTROL 384
#define BLE_APPEARANCE_GENERIC_EYE_GLASSES 448
#define BLE_APPEARANCE_GENERIC_TAG 512
#define BLE_APPEARANCE_GENERIC_KEYRING 576
#define BLE_APPEARANCE_GENERIC_MEDIA_PLAYER 640
#define BLE_APPEARANCE_GENERIC_BARCODE_SCANNER 704
#define BLE_APPEARANCE_GENERIC_THERMOMETER 768
#define BLE_APPEARANCE_THERMOMETER_EAR 769
#define BLE_APPEARANCE_GENERIC_HEART_RATE_SENSOR 832
#define BLE_APPEARANCE_HEART_RATE_SENSOR_HEART_RATE_BELT 833
#define BLE_APPEARANCE_GENERIC_BLOOD_PRESSURE 896
#define BLE_APPEARANCE_BLOOD_PRESSURE_ARM 897
#define BLE_APPEARANCE_BLOOD_PRESSURE_WRIST 898
#define BLE_APPEARANCE_GENERIC_HID 960
#define BLE_APPEARANCE_HID_KEYBOARD 961
#define BLE_APPEARANCE_HID_MOUSE 962
#define BLE_APPEARANCE_HID_JOYSTICK 963
#define BLE_APPEARANCE_HID_GAMEPAD 964
#define BLE_APPEARANCE_HID_DIGITIZERSUBTYPE 965
#define BLE_APPEARANCE_HID_CARD_READER 966
#define BLE_APPEARANCE_HID_DIGITAL_PEN 967
#define BLE_APPEARANCE_HID_BARCODE 968
#define BLE_APPEARANCE_GENERIC_GLUCOSE_METER 1024
#define BLE_APPEARANCE_GENERIC_RUNNING_WALKING_SENSOR 1088
#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_IN_SHOE 1089
#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_ON_SHOE 1090
#define BLE_APPEARANCE_RUNNING_WALKING_SENSOR_ON_HIP 1091
#define BLE_APPEARANCE_GENERIC_CYCLING 1152
#define BLE_APPEARANCE_CYCLING_CYCLING_COMPUTER 1153
#define BLE_APPEARANCE_CYCLING_SPEED_SENSOR 1154
#define BLE_APPEARANCE_CYCLING_CADENCE_SENSOR 1155
#define BLE_APPEARANCE_CYCLING_POWER_SENSOR 1156
#define BLE_APPEARANCE_CYCLING_SPEED_CADENCE_SENSOR 1157
我们来修改一下外观试试
#设置BLE广播数据并开始广播
import bluetooth #导入BLE功能模块
import struct
ble = bluetooth.BLE() #创建BLE设备
ble.active(True) #打开BLE
#设置BLE广播数据并开始广播
payload = bytearray()
def _append(adv_type, value):
global payload
payload += struct.pack('BB',len(value)+1,adv_type)+value
ADV_FLAGS_TYPE=const(0x01)
ADV_NAME_TYPE=const(0x09)
ADV_APPERANCE_TYPE=const(0x19)
_append(ADV_FLAGS_TYPE,struct.pack('B',0x01))
_append(ADV_NAME_TYPE,'我是个键盘'.encode())
_append(ADV_APPERANCE_TYPE, struct.pack('<h', 961))
ble.gap_advertise(100, adv_data = payload)
uuid
UUID是一个 128 位无符号整数,通常使用十六进制字符串来表示,以连字号分隔的五组来显示,形式为 8-4-4-4-12,总共有 36个字符(即三十二个英数字母和四个连字号)。例如:
123e4567-e89b-12d3-a456-426655440000
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
-
数字 M的四位表示 UUID 版本,当前规范有5个版本,M可选值为1, 2, 3, 4, 5。这5个版本使用不同算法,利用不同的信息来产生UUID,各版本有各自优势,适用于不同情景。具体使用的信息,通常使用版本1和版本4
- version 1, date-time & MAC address,时间戳&mac地址
- version 2, date-time & group/user id
- version 3, MD5 hash & namespace
- version 4, pseudo-random number,128位伪随机数
- version 5, SHA-1 hash & namespace
-
数字 N的一至四个最高有效位表示 UUID 变体( variant ),有固定的两位10xx因此只可能取值8, 9, a, b
可以直接在网上搜索一些uuid生成的网站,https://www.uuid.online/