esp32+micropython蓝牙讲解

0. GATT协议

从设备连接的角度看,

  • 外围设备:例如esp32,蓝牙耳机等,广播自身信息,让中心设备可以找到自己
  • 中心设备:例如手机,电脑等,扫描外设设备,选择想要连接的外设

中心设备可以连接多个外围设备,例如笔记本电脑作为中心设备,可以连接蓝牙耳机,蓝牙键盘等多个设备。外围设备只能被一个中心设备连接,你想想,蓝牙耳机可以被多个手机连接吗。

从通信角度来看,处于连接状态时的两个设备,它们各自充当两种角色中的一种:

  • 服务端(Server):包含被GATT客户端读取或写入的特征数据的设备。esp32就是服务端,为手机提供各种服务
  • 客户端(Client):从GATT服务器中读取数据或向GATT服务器写入数据的设备。手机电脑就是客户端,连接到esp32,并且查询想要的数据。

真的是让人摸不着头脑,讲道理esp32作为服务端应该可以为多个客户端提供的服务的,但是esp32又是外围设备,只能连接一个中心设备(手机),也就只能给一个手机提供服务。客户端是合理的,可以连接多个服务器,请求多个服务数据。

1. 蓝牙广播自身信息

我们将esp32作为外设设别,要想被发现,就需要不断的广播自己的信息,这样才能被中心设备发现。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的蓝牙设备。广播的意思就是大声吆喝,谁都能听的见,所有的外设设别都是在不断广播的,但是当蓝牙设备被连接时,其他中心设备将无法再搜索到该设备。

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
字段长度取值说明
Length1字节0x02表示AD Type与AD Data的总长度。
AD Type1字节0x01表示该广播数据代表的含义。此处取值固定为0x01,表示设备标识
AD Data1字节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
字段长度(字节)取值说明
Length1字节0x03表示AD Type与AD Data的总长度。
AD Type1字节0x09表示蓝牙的名称
AD Data2字节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_writedata写入到了本地,客户端读取数据的时候是直接读取的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/

  • 13
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值