Bluetooth LE 介绍以及树莓派 (RPi) 如何连接低功耗蓝牙 BLE——分别用命令和C语言实现

本文主要是介绍Bluetooth LE 介绍以及树莓派 (RPi) 如何连接低功耗蓝牙 BLE——分别用命令和C语言实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

1、简介

2、BLE 的主要特点

3、BLE 协议架构

4、BLE 角色划分

5、BLE 数据收发

5.1 广播方式

5.2 广播报文


最近在做有关低功耗蓝牙 BLE 的项目,还是有必要整理一下 BLE 的资料。在树莓派连接低功耗蓝牙 BLE 用 C 语言实现已经实现,前期的准备这个折磨了我好久,网上关于这个的资料很少,大部分都是用 Python 来实现的,但是作为一名嵌入式工程师,我们还是得必须使用 C 语言来实现,所以我就开启了阅读源码的道路。本人第一次从源码中提取自己想要的 API 刚开始确实有点难度,踩过很多的坑,但是天不负有心人,只要坚持最终还是有所成就的!请相信自己。

1、BLE 简介

蓝牙(Bluetooth)是一种短距离的无线通讯技术,运行在 2.4GHz 免费频段,可实现固定设备、移动设备之间的数据交换。一般蓝牙 3.0 之前的 BR/EDR 蓝牙称为经典蓝牙,而将蓝牙 4.0 规范下的 LE 蓝牙称为低功耗蓝牙(BLE, Bluetooth Low Energy)。BLE 主要用于医疗保健、运动健身、信标、安防、家庭娱乐等邻域的新兴应用。相较经典蓝牙,低功耗蓝牙旨在保持同等通信范围的同时显著降低低功耗成本

2、BLE 的主要特点

  • 低功耗,使用纽扣电池就可以运行数月至数年。
  • 快连接毫秒级的连接速度,传统蓝牙甚至长达数分钟。
  • 远距离,长达数百米的通信距离,而传统蓝牙通常10米左右。

3、BLE 协议架构

Bluetooth LE 协议栈从下至上分为几个层级:Physical Layer(PHY)、Link Layer(LL)、Host Controller Interface(HCI)、Logical Link Control and Adaption Protocol Layer(L2CAP)、Attribute Protocol(ATT)、Security Manager Protocol(SMP)、Generic Attribute Protocol(GATT)、Generic Access Protocol(GAP)。

  • PHY:PHY 层主要负责在物理信道上发送和接收信息包。Bluetooth LE 使用 40个射频信道。频率范围:2402 MHz2480MHz
  • LL:LL 层是整个 BLE 协议栈的核心,也是 BLE 协议栈的难点和重点。LL 层要做的事情非常多,比如具体选择哪个射频通道进行通信,怎么识别空中的数据包,具体在哪个时间把数据包发送出去,怎么保证数据的完整性,ACK 如何接收,如何进行重传,以及如何对链路进行管理和控制等待。LL 层只负责把数据发出去或者收回来,对数据进行怎么的解析则交给上面的CAP 或者 GATT。(LL 层主要负责创建、修改和释放逻辑链路(以及,如果需要,它们相关的逻辑传输)。以及与失败之间的物理链路相关的参数的更新。它控制链路层状态机处于准备、广播、监听/扫描、发起连接、已连接五种状态之一)
  • HCI:HCI 层向主机和控制提供一个标准化的接口。该层可以由软件 API  实现或者使用硬件接口 UART、SPI、USB 来控制。
  • L2CAP:L2CAP 层负责对主机和协议栈之间交换的数据进行协议复用能力分段重组操作
  • ATT:ATT 层实现了属性服务器和属性客户端之间的点对点协议。ATT 客户端向 ATT 服务端发送命令、请求和确认。ATT 服务端向客户端发送响应、通知和指示。简单来说,ATT 层用来定义用户命令及命令操作的数据,比如读取某个数据或者写某个数据
  • SMP:SMP 层用于生成加密密钥和身份密钥。SMP 还管理加密密钥和身份密钥的存储,并负责生成随机地址并将随机地址解析为已知设备身份
  • GATT:GATT 层表示属性服务器和可选的属性客户端的功能。该配置文件描述了属性服务器中使用的服务、特征和属性的层次结构。该层提供用于发现、读取、写入和指示服务特性和属性的接口。
  • GAP:GAP 层代表所有蓝牙设备通用的基本功能,例如传输。协议和应用程序配置文件使用的模式和访问程序。GAP 服务包括设备发现、连接模式、安全、身份验证、关联模型和服务发现。GAP 目前主要用来进行广播,扫描和发起连接等

4、BLE 角色划分

在 Bluetooth LE 协议栈中不同的层级有不同的角色划分。这些角色划分互不影响。

  • LL:设备可划分为主机(Master或Central)从机(Periphere),从机广播,主机可以发起连接。
  • GAP:定义了4种特定角色:广播者、观察者、外围设备和中心设备
  • GATT:设备可以分为服务端客户端

当主机和从机建立连接之后才能相互互发数据

  • 主机,主机可以发起对从机的扫描连接。例如手机,通常作为BLE的主机设备
  • 从机,从机只能广播并等待主机的连接。例如智能手环,是作为BLE的从机设备

另外还有观察者(Observer)和广播者(Broadcaster),这两种角色不常使用,但也十分有用,例如 iBeacon,就可以使用广播者角色来做,只需要广播特定内容即可。

  • 观察者,观察者角色监听空中的广播事件,和主机唯一的区别是不能发起连接,只能持续扫描从机。
  • 广播者,广播者可以持续广播信息,和从机的唯一区别是不能被主机连接,只能广播数据。

 GATT 其实是一种属性传输协议,简单的讲可以认为是一种属性传输的应用协议。这个属性 的结构非常简单。它由服务组成,每个服务由不同数量的特征组成,每个特征又由很多其他的元素组成。

GATT 服务端 和 GATT 客户端这两种角色存在于  Bluetooth LE 连接建立之后。GATT 服务器存储通过属性协议传输的数据,并接受来自 GATT 客户端的属性协议请求、命令和确认。简而言之,通提供数据的一端称为 GATT 服务端访问数据的一端称为 GATT 客户端。

5、BLE 数据收发

5.1 广播方式

 上面整个数据包有以下问题:

  1. 没有对数据包进行分类组织,设备 B 无法找到自己想要的数据0x53。为此文明需要在 access address 之后加入两个字段:LLheader 和长度字节。LL header 用来表示数据包的 LL 类型,长度字节用来指明 payload 的长度。
  2. 设备 B 什么时候开启射频窗口以接收空中数据包?LL 层还必须定义通信时序。
  3. 当设备 B 拿到数据0x53后,该如何解析这个数据呢?这个就是 GAP 层要做的工作, GAP 层引入了 LTV(Length-Type-Value)结构来定义数据。广播包最大值为31字节

有了 PHY,LL 和 GAP ,就可以发送广播包,但广播包携带的信息极其有限,而且还有如下几大限制:

  1. 无法进行一对一双向通信(广播是一对多通信,而且是单方向的通信)
  2. 由于不支持组包和拆包,因此无法传输大数据
  3. 通信不可靠及效率低下。广播信道不能太多,否则江导致扫描效率低下。为此,BLE 只使用37(2402MHz) /38(2426MHz) /39(2480MHz) 三个信道进行广播和扫描,因此广播不支持跳频。由于广播是一对多的,所以广播也无法支持 ACK ,这些都使广播通信变得不可靠。
  4. 扫描端功耗高。由于扫描端不知道设备端何时广播,也不知设备选用哪个频道进行广播,扫描端只能拉长扫描窗口时间,并同时对37/38/39 三个通道进行扫描,主要功耗就会比较高。

而连接则可以很好解决上述问题。

到底什么叫连接(connect)?像有线UART,很容易理解,就是用线(Rx和Tx等)把设备A和设备B相连,即为理解。用“线”把两个设备相连,实际是让2个设备有共同的通信媒介并让两者时钟同步起来

5.2 广播报文

一个完整的BLE 广播报文由四部分组成,分别是前导码接入地址协议数据单元(PDU)和CRC校验码。

  • 前导码:用来同步时序,可以是0x55或者0xAA,由接入地址的第一个比特决定。如果接入地址的第一个比特是“0”,则前导码是0x55;如果接入地址的第一个比特是“1”,则前导码是0xAA。在广播报文里面,这一字节为0xAA。
  • 接入地址:长度为4个字节,广播报文的接入地址为0x8E89BED6
  • 协议数据单元(PDU):包含两个字节的报头和0~37字节的净荷
  • CRC校验码:长度为3个字节

PDU 数据报头(2个字节)由下面几个字段组成:

  • PDU Type(4bits):PDU类型,标识广播报文的类型
  • RFU(2bits):Reserved For Future ,保留位
  • TxAdd(1bit):发送地址类型,标识广播地址是公有地址还是随机地址
  • RxAdd(1bit):接收地址类型,广播报文不使用这一比特
  • Length(6bits):长度,标识净荷的长度(6~37字节)
  • RFU(2bits):Reserved For Future,保留位

公有地址和私有地址的区别(为什么说这个呢?因为接下的项目中我们需要连接 BLE ,会涉及到这个地址的连接类型 dst_type 的选择,还是有必要了解一下的)

  • 公有地址:类似 MAC 地址,由OUI 和一个唯一的数字组成
  • 随机地址:为防止设备被跟踪,广播地址可以是随机的。随机地址又分为静态设备地址(Static Device Address)、私有地址(Private Device Address)和不可解释私有地址(Nonresolvable Private Address)。

6、树莓派连接低功耗蓝牙 BLE (命令实现)

6.1 BLE 扫描

为了连接到 BLE 设备,我们首先需要扫描发现设备。其中一种方法是使用 hcitool 实用程序,它基本上是所有 HCI 命令(经典蓝牙和 BLE)的“瑞士刀”。经典蓝牙的扫描使用 scan,而 BLE 设备的扫描则使用 lescan。

pi@raspberrypi:~ $ sudo hcitool -i hci0 lescan
LE Scan ...
A0:76:4E:59:12 ESP32
... ....pi@raspberrypi:~ $ sudo hcitool leinfo A0:76:4E:59:12
Requesting information ...Handle: 64 (0x0040)Features: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

在上面的扫描过程中,我们会发现 ESP32 BLE 设备,这是因为我们在 ESP32 BLE 中设定的广播报文里含有设备名。

BLE 如何开启广播模式请参考官方文档,这里就不多说了。

6.2 BLE 连接与断开

hcitool 命令不是操作 GATT 的工具,通过它我们扫描发现 BLE 设备的地址后,就可以使用gatttool 命令来连接并操纵 BLE 设备的 GATT。该命令支持交互式和非交互式两种工作模式,在交互模式下,控制台提供了一个界面,使您可以发出命令并与设备进行交互,您将需要时再退出返回到终端。

使用下面命令可以用来连接、断开蓝牙 BLE。

pi@raspberrypi:~ $ gatttool -I -i hci0 -b A0:76:4E:59:12-I 进入交互模式
-i 指定蓝牙的接口设备,如果不指定默认使用 hci0
-b 指定连接的蓝牙设备的MAC地址[A0:76:4E:59:12][LE]> help         //查看使用帮助信息
... ...[A0:76:4E:59:12][LE]> connect  //有时第1此可能connect 不成功,多connect几次就ok
Attempting to connect to A0:76:4E:59:12
Error: connect to A0:76:4E:59:12: Function not implemented (38)[A0:76:4E:59:12][LE]> connect
Attempting to connect to A0:76:4E:59:12
Connection successful[A0:76:4E:59:12][LE]> disconnect     //断开BLE蓝牙设备的连接
(gatttool:2362): GLib-WARNING **: 10:07:24.803: Invalid file descriptor.[A0:76:4E:59:12][LE]> exit         //退出交互模式pi@raspberrypi:~ $

6.3 查看所有 Services

使用 primary 命令可以查看当前蓝牙设备提供的所有的服务。

[A0:76:4E:59:12][LE]> connect 
Attempting to connect to A0:76:4E:59:12
Connection successful[A0:76:4E:59:12][LE]>  primary     //发现BLE的GATT服务,这里有3个服务,最后1个为自定义服务
attr handle: 0x0001, end grp handle: 0x0005 uuid: 00001801-0000-1000-8000-00805f9b34fb
attr handle: 0x0014, end grp handle: 0x001c uuid: 00001800-0000-1000-8000-00805f9b34fb
attr handle: 0x0028, end grp handle: 0xffff uuid: 0000abf0-0000-1000-8000-00805f9b34fb

其中:

  • UUID: 00001801-0000-1000-8000-00805f9b34fb 为 Generic Attribute 标准服务,它的特征值handle范围为 0x00014~0x001C
  • UUID: 00001800-0000-1000-8000-00805f9b34fb 为 Generic Access 标准服务, 它的特征值handle范围为 0x0001~0x0005
  • UUID: 0000abf0-0000-1000-8000-00805f9b34fb 为 自定义服务, 它的特征值handle范围为0x0028~0xffff

不同的设备可能会不太一样。

6.4 查找服务特征值

使用 characteristics 命令可以查看当前蓝牙设备提供的所有的characteristics。每个特征值都有一个独一无二的 UUID 值,同时也有一个相应的 handle 值,它是在使用命令或编程访问特征值时的句柄。每个特征值也有它的相应属性,如可读、可写等。

[A0:76:4E:59:12][LE]> characteristics
handle: 0x0002, char properties: 0x20, char value handle: 0x0003, uuid:00002a05-0000-1000-8000-00805f9b34fb
handle: 0x0015, char properties: 0x02, char value handle: 0x0016, uuid:00002a00-0000-1000-8000-00805f9b34fb
handle: 0x0017, char properties: 0x02, char value handle: 0x0018, uuid:00002a01-0000-1000-8000-00805f9b34fb
handle: 0x0019, char properties: 0x02, char value handle: 0x001a, uuid:00002aa6-0000-1000-8000-00805f9b34fb
handle: 0x0029, char properties: 0x06, char value handle: 0x002a, uuid:0000abf1-0000-1000-8000-00805f9b34fb
handle: 0x002b, char properties: 0x12, char value handle: 0x002c, uuid:0000abf2-0000-1000-8000-00805f9b34fb
handle: 0x002e, char properties: 0x06, char value handle: 0x002f, uuid:0000abf3-0000-1000-8000-00805f9b34fb
handle: 0x0030, char properties: 0x12, char value handle: 0x0031, uuid:0000abf4-0000-1000-8000-00805f9b34fb

6.5 读服务特征值

使用 char-read-uuid 命令可以读取某个 characteristics 值。

[A0:76:4E:59:12][LE]> characteristics 0x0001 0x001c   //查找标准服务里的所有特征值
handle: 0x0002, char properties: 0x20, char value handle: 0x0003, uuid:00002a05-0000-1000-8000-00805f9b34fb
handle: 0x0015, char properties: 0x02, char value handle: 0x0016, uuid:00002a00-0000-1000-8000-00805f9b34fb[A0:76:4E:59:12][LE]> char-read-uuid 00002a05-0000-1000-8000-00805f9b34fb     //该特征值不具备可读属性,所以读取失败
Error: Read characteristics by UUID failed: Attribute can't be read[A0:76:4E:59:12][LE]>  char-read-uuid 00002a00-0000-1000-8000-00805f9b34fb    //该特征值可读
handle: 0x0016  value: 45 53 50 33 32    //("ESP32")[A0:76:4E:59:12][LE]> char-read-hnd 0x0016     //使用 handle 来读取该特征值的 handle(注意是0x0016,而不是0x0015)
Characteristic value/descriptor: 45 53 50 33 32 

3.6 写服务特征值

我们可以使用 char-write-req 命令来写相应的数据。

[A0:76:4E:59:12][LE]> char-read-hnd 0x002f
Characteristic value/descriptor: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00[A0:76:4E:59:12][LE]> char-write-req 0x002f 11223344
Characteristic value was written successfully[A0:76:4E:59:12][LE]> char-read-hnd 0x002f
Characteristic value/descriptor: 11 22 33 44

6.7 读写 Notification

从上面的操作中我们可以了解到 BLE通信就是对相应的特征值进行读、写操作。但通常数据的读写都是由主机(如手机)发起。那如果蓝牙从机(如ESP32)有数据更新(如传感器重新采样了),那如何通知主机来读取呢?对于这种情形的话,蓝牙BLE支持 Notification 机制,如果主机选择使能该机制之后,从机的数据更新将会主动发送给主机。

gatttool 的交互模式下并不能读到这些数据。如果想实时获取 Notification 的数据 话,则需要使用 gatttool 非交互模式来监听。

pi@raspberrypi:~ $ gatttool -I -i hci0 -b A0:76:4E:59:12
... ...[A0:76:4E:59:12][LE]> exit
(gatttool:2467): GLib-WARNING **: 14:52:01.698: Invalid file descriptor.pi@raspberrypi:~ $ sudo gatttool -i hci0 -b A0:76:4E:59:12 --char-write-req -a 0x002d -n 0100//此时在 BLE 控制终端随便输入一些数据,树莓派的 gatttool 命令下将会显示收到的数据。

7、树莓派连接低功耗蓝牙 BLE (C语言实现) 

下面这个代码 BLE 的 MAC 地址和 uuid 都要改成自己设备的,使用到了 BlueZ 库 和 Gattlib 库,具体的后续在说(这阵子实在没有充足的时间来对这个项目进行总结,如果你有什么疑惑的地方可以私聊我,或者直接留言)。

这个 demo 可以自己再改进一下,锻炼一下自己的编程能力。

#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <glib.h>
#include <gattlib.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>#define BLE_SCAN_TIMEOUT 5
#define bleaddr "A0:76:4E:55:ED:B2"                //target BLE device BT-MAC addr !!uppercase!! charactersconst char * write_uuid = "c303";//any esp32 writable characteristic uuid
static uuid_t uuid;//be used to change type :wrtite_uuid(char) to uuid(uuid_t)                        /*扫描回调函数*/
static void BLE_discovered_callback(void *adapter,const char* addr, const char* name, void* user_data)              //regular scanning
{if(name){printf("BLE Discovered %s - '%s'\n", addr, name);}else{printf("BLE Discovered %s - 'unknown'\n", addr);}return ;
}int main(int argc, char** argv)
{int rv,i;void * adapter ;                                 const char * adapter_name = NULL;                       //if NULL ,hci0 be choiced default in my situationchar uuid_str[MAX_LEN_UUID_STR + 1];                    //MAX_LEN_UUID_STR =37 gatt_connection_t * conn = NULL;                        rv =  gattlib_adapter_open(adapter_name,&adapter);     //open the Pi bt-device based on the dapter_name,if adapter_name is NULL,default Pi bt-device will be chose(hci0 in this situation) if(rv){printf("adapter open failed\n");return 1;}rv = gattlib_adapter_scan_enable(adapter,BLE_discovered_callback,BLE_SCAN_TIMEOUT,NULL);//gattlib_adapter_scan_enable_with_filter()if (rv) {	printf("Failed to scan.\n");return -1;}if(gattlib_adapter_scan_disable(adapter) != GATTLIB_SUCCESS){printf("Error: end scan failed,ready to find AD info\n\n");return -1;}puts("Scan completed\n");printf("start to connnect ...\n");conn = gattlib_connect(NULL,bleaddr, GATTLIB_CONNECTION_OPTIONS_LEGACY_DEFAULT);if (conn == NULL) {printf("Fail to connect to the bluetooth device : %s\n",bleaddr);return 1;}printf("coonect to BLE devices : %s sucess \n",bleaddr);memset(&uuid_str,0,sizeof(uuid_str));strncpy(uuid_str,write_uuid,sizeof(uuid_str));if (gattlib_string_to_uuid(uuid_str, strlen(uuid_str) + 1, &uuid) < 0) {printf("string to uuid_t failed \n");return -1;}for(i=49;i<54;i++)                                    //十进制的49 对应ascii码的31,即字符'1'{rv = gattlib_write_without_response_char_by_uuid(conn,&uuid,(uint8_t*)&i,1);if (rv != GATTLIB_SUCCESS) {if(rv == GATTLIB_NOT_FOUND){printf("Could not find GATT Characteristic with UUID %s");return -1;}else{printf("Error while writing GATT Characteristic with UUID %s (ret:%d)",uuid_str, rv);return -2;}}sleep(1);}gattlib_disconnect(conn);if(gattlib_adapter_close(adapter) != GATTLIB_SUCCESS) {printf("adapter close failed\n");}printf("\nnormally exit\n");return 0;}

这篇关于Bluetooth LE 介绍以及树莓派 (RPi) 如何连接低功耗蓝牙 BLE——分别用命令和C语言实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/535638

相关文章

W外链微信推广短连接怎么做?

制作微信推广链接的难点分析 一、内容创作难度 制作微信推广链接时,首先需要创作有吸引力的内容。这不仅要求内容本身有趣、有价值,还要能够激起人们的分享欲望。对于许多企业和个人来说,尤其是那些缺乏创意和写作能力的人来说,这是制作微信推广链接的一大难点。 二、精准定位难度 微信用户群体庞大,不同用户的需求和兴趣各异。因此,制作推广链接时需要精准定位目标受众,以便更有效地吸引他们点击并分享链接

性能测试介绍

性能测试是一种测试方法,旨在评估系统、应用程序或组件在现实场景中的性能表现和可靠性。它通常用于衡量系统在不同负载条件下的响应时间、吞吐量、资源利用率、稳定性和可扩展性等关键指标。 为什么要进行性能测试 通过性能测试,可以确定系统是否能够满足预期的性能要求,找出性能瓶颈和潜在的问题,并进行优化和调整。 发现性能瓶颈:性能测试可以帮助发现系统的性能瓶颈,即系统在高负载或高并发情况下可能出现的问题

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount