【物联网】关于 GATT (Generic Attribute Profile)基本概念与三种操作(Read / Write / Notify)的理解


“BLE 读写”在这里具体指什么?

在你的系统里,树莓派是 BLE CentralArduino 是 BLE Peripheral
Central 和 Peripheral 通过 **GATT 特征(Characteristic)**交互:

  • 读(Read):Central 主动读取某个特征当前的值。
    在 bleak 里是 await client.read_gatt_char(UUID)

    你当前并不依赖“主动读”,而是用 Notify 被动接收(更省电、实时)。

  • 写(Write):Central 把数据写入一个“可写”的特征,驱动 Peripheral 执行操作。
    在 bleak 里是 await client.write_gatt_char(UUID, b"...")(注意必须是 bytes)。

你现在的实现:

  • BLE 写(Central → Peripheral):
    RPi 调用 write_gatt_char(COMMAND_UUID, payload.encode()),把命令写入 Arduino 的 commandCharacteristic(BLEWrite),Arduino 在 commandCharacteristic.written() 分支里解析并控制 LED

  • BLE 读(有两种做法):

    1. 主动读:read_gatt_char(SENSOR_UUID)……(你没用)
    2. 订阅通知 Notifystart_notify(SENSOR_UUID, on_notify),Arduino 每次 sensorCharacteristic.writeValue(json),Central 就自动收到回调。
      这就是你代码里的“读传感器数据”的实际完成方式。

一句话:你用 write 做“下发命令”,用 notify 完成“读数据”。


“MQTT 桥(bridge)”是什么意思?

你的 Python 脚本同时连两张“网”:

  1. 一边是 BLE(和 Arduino 说话)
  2. 一边是 MQTT(和云端 Broker 说话)

脚本把两边的消息“转接”起来,所以叫 桥接(bridge)

  • 从 BLE 收到的 传感器 JSONpublish 到 MQTT 主题(iot/sensors/data
  • 从 MQTT 收到的 命令write_gatt_char 写到 BLE 的命令特征(COMMAND_UUID

整个脚本不做复杂业务,只负责协议转换与转发,这就是“MQTT 桥”。


“上行 / 下行”到底指什么?

在物联网里通常以“设备(Node)→ 云”为上行(uplink),反方向为下行(downlink)
对照你的 Topic 命名就一目了然:

上行 (Node → Edge → Cloud):
Arduino(传感器) --BLE Notify--> RPi  --MQTT publish-->  Broker
Topic: iot/sensors/data下行 (Cloud → Edge → Node):
Broker  --MQTT publish-->  RPi  --BLE write-->  Arduino(执行器)
Topic: iot/commands/arduino

所以你截图里注释是:

  • TOPIC_SENSOR = "iot/sensors/data"上行:RPi 把从 BLE 收到的数据发到 MQTT
  • TOPIC_CMD = "iot/commands/arduino"下行:RPi 订阅这个主题,拿到命令再写回 BLE

和你的代码逐项对应

  • BLE Notify(读数据)

    await client.start_notify(SENSOR_UUID, on_ble_notify)  # 订阅上行特征
    def on_ble_notify(sender, data):s = data.decode("utf-8")mqtt_client.publish("iot/sensors/data", s)         # → 上行到云
    

    Arduino 端对应:

    BLEStringCharacteristic sensorCharacteristic(..., BLERead|BLENotify, 100);
    sensorCharacteristic.writeValue(json); // 改值 → Central 收到 Notify
    
  • BLE Write(下发命令)

    mqtt_client.subscribe("iot/commands/arduino")          # 订阅下行
    await client.write_gatt_char(CMD_UUID, payload.encode("utf-8"))
    

    Arduino 端对应:

    BLEStringCharacteristic commandCharacteristic(..., BLEWrite, 100);
    if (commandCharacteristic.written()) { processCommand(commandCharacteristic.value()); }
    

什么时候会用“BLE Read”而不是“Notify”?

  • Read:偶尔读一次静态信息(固件版本、电池电量快照等)。
  • Notify:连续流式数据(传感器、心率、IMU),Central 只订阅一次即可被动接收,省电且实时。
    你的 DHT11 场景显然属于 Notify 更合适

  • BLE 读写就是对 GATT 特征的操作:Central 可以 read 当前值、write 新值;外设还有 notify 主动推送。我们用 notify 做上行数据,用 write 做下行命令。”
  • MQTT 桥是把 BLE 和 MQTT 两个世界打通的中间件:BLE→MQTT 发布传感器、MQTT→BLE 下发命令。”
  • 上行就是从设备到云,下行就是从云到设备;在我们项目里上行用 iot/sensors/data,下行用 iot/commands/arduino。”
    下面把 GATT 基本概念Read / Write / Notify 三种操作讲清楚,并且逐一映射到你现在的代码与项目里的作用。看完你就能用“协议层 → 代码 → 演示”的角度完整回答老师的问题。

0) 角色与协议栈(1 句话)

  • Arduino Nano 33 IoT = Peripheral(外设):提供数据与控制接口。
  • Raspberry Pi = Central(中心):连接外设,读数据、下发命令。
  • ATT/GATT:BLE 连接后,以“属性表(ATT)”承载“服务/特征(GATT)”。

1) GATT 是什么?怎么放东西?

GATT(Generic Attribute Profile)是“把数据和操作放进一张属性表”的方式。表里每一行叫 Attribute(属性),最常见的 4 种行:

  1. Primary Service(主服务声明)
  2. Characteristic Declaration(特征声明)
  3. Characteristic Value(特征的值)← 真正的数据在这里
  4. Descriptor(描述符):最常用的是 CCCD(0x2902),专门用来开/关该特征的 Notify/Indicate

你在 Arduino 里写的

BLEService sensorService(SERVICE_UUID);
BLEStringCharacteristic sensorCharacteristic(SENSOR_CHAR_UUID, BLERead|BLENotify, 100);
BLEStringCharacteristic commandCharacteristic(COMMAND_CHAR_UUID, BLEWrite, 100);
BLE.addService(sensorService);

ArduinoBLE 会自动把“服务 + 两个特征(及它们的声明/值/CCCD)”注册到这张属性表里,Central 一连上就能按 UUID 查到它们。


2) 三种操作:Read / Write / Notify(概念 → 报文 → 你的代码)

A. Read(Central 主动读)

  • 概念:Central 发送“Read Request”,外设回“Read Response(带当前值)”。
  • 什么时候用:偶尔拿一次“快照”数据(版本号、电量、一次性读取等)。
  • 你项目里不依赖主动 Read,而是用 Notify(更实时、省电)。但你给了 BLERead 权限,便于用 App 或脚本手动读试试。

你可以怎么演示(可选)
RPi 端:

value = await client.read_gatt_char(SENSOR_UUID)  # 主动读一次
print(value.decode())

Arduino 端:无需额外代码,Characteristic 有 BLERead 权限即可。


B. Write(Central 写入)

  • 概念:Central 把数据写到某个“可写”特征的 Characteristic Value 里。
    (有两种写法:有应答/无应答。bleak 默认“有应答”,更稳。)
  • 典型用途下发命令、设置参数
  • 在 ATT 层:Central 发“Write Request”,外设收后可更新本地状态、驱动硬件。

你的代码如何实现

  • 外设(Arduino)定义“可写特征”

    BLEStringCharacteristic commandCharacteristic(COMMAND_CHAR_UUID, BLEWrite, 100);
    

    BLEWrite 表示这个特征接收写操作。

  • 外设接收写入并执行(控制 LED)

    if (commandCharacteristic.written()) {             // 有人刚写过来String cmd = commandCharacteristic.value();    // 取到写入的数据(字符串/JSON)processCommand(cmd);                           // 解析 {"led":"on"} / LED_ON ...
    }
    
  • 中央(RPi/bleak)写入命令

    await client.write_gatt_char(CMD_UUID, payload.encode("utf-8"))
    # 注意:第二个参数必须是 bytes;JSON 字符串要 .encode()
    
  • 项目中的作用:完成 rubric 里的
    “Receive commands from RPi” + “Operate actuators (LED)”
    命令路径是:MQTT(云)→ RPi(订阅)→ BLE Write → Arduino(处理)→ LED 变更


C. Notify(Peripheral 主动推送)

  • 概念:外设并不会“无限制地广播值”,而是当 Central 订阅后,在值变化时主动推送“通知”。
    订阅是通过写 CCCD(0x2902)为 0x0001 来“打开通知”。Central 的 API(如 start_notify)内部就是在写这个 CCCD。
  • 优点事件驱动、省电、实时。Central 不用不停轮询 Read。
  • 在 ATT 层:外设每次更新这个特征的 Value,就发送一个 Handle Value Notification 给所有订阅者。

你的代码如何实现

  • 外设(Arduino)允许通知并更新特征值

    BLEStringCharacteristic sensorCharacteristic(SENSOR_CHAR_UUID, BLERead | BLENotify, 100);
    // 每 5 秒更新一次值;若有人订阅,就会收到 Notify
    sensorCharacteristic.writeValue(jsonString);
    
  • 中央(RPi/bleak)开启订阅并处理回调

    await client.start_notify(SENSOR_UUID, on_ble_notify)
    def on_ble_notify(sender, data):s = data.decode('utf-8')           # data 是字节串mqtt_client.publish(TOPIC_SENSOR, s)
    
  • 项目中的作用:完成 rubric 里的
    “Send the data to RPi via BLE”(Arduino 把 DHT11 JSON 主动推过来),
    同时与 “Publish the data to a MQTT topic” 无缝衔接(回调里直接 publish)。

小知识:如果 Central 没订阅,writeValue() 只会更新本地的特征值,不会发网络通知;Read 仍能读到新值。只有订阅后,才会看到“被动推送”。


3) 把三者串在一起:一次完整数据/命令的“包级视角”

上行(传感器 → 云)

  1. Arduino 每 5 秒 sensorCharacteristic.writeValue(json)
  2. RPi 之前已 start_notify,它会自动收到 Notification(ATT 的 Handle Value Notification)
  3. RPi 在回调里 mqtt.publish("iot/sensors/data", json)
  4. Broker(AWS) 收到消息,前端或测试终端 mosquitto_sub 能看到

下行(云 → 执行器)

  1. AWS 发布到 iot/commands/arduino{"led":"on"}LED_ON
  2. RPi MQTT 回调触发,调用 write_gatt_char(CMD_UUID, bytes)
  3. ArduinocommandCharacteristic.written() 被触发,processCommand() 解析并
    digitalWrite(LED_BUILTIN, HIGH) → LED 点亮

4) 代码里和 GATT 的“硬知识”细节

  • UUID 的意义:Central 根据 UUID 定位到具体 Service / Characteristic。你用的自定义 UUID(不冲突即可)。
  • 属性权限(Properties)BLERead | BLENotifyBLEWrite 是对 Characteristic Declaration 里 Properties 字段的具体配置。
  • CCCD(0x2902):当你在 RPi 调用 start_notify() 时,底层其实是往该特征的 CCCD 写 0x0001。取消订阅则写回 0x0000。
  • MTU/长度:BLE 经典 ATT MTU 23 字节(有效负载 20),ArduinoBLE 会做分片/重组;你把字符串特征长度设为 100,建议 JSON 保持精简(温度/湿度/时间戳足够),避免手机 App/不同 Central 的兼容问题。
  • Write with / without responsebleak 默认“有应答”(更稳),ArduinoBLE 也支持;如果后期要“更快但不在乎丢包”,可改为“无应答”版本,但课堂演示不需要。

  • GATT 是什么?
    “连接后设备以属性表的方式暴露数据和操作。我们用 1 个 Service,放了 2 个 Characteristic:一个温湿度(Read | Notify),一个命令(Write)。Central 按 UUID 找到它们。”
  • 为什么用 Notify 而不是不停 Read?
    “Notify 是事件驱动、低延迟、低功耗;Central 订阅一次,外设有新值就主动推。我们的 DHT11 就属于流式数据场景。”
  • Write 的安全/可靠性?
    “默认 Write with Response,Central 会收到写成功的确认。命令是 JSON/文本,外设解析后控制 LED。”
  • CCCD 是做什么的?
    “是特征的“通知开关”。start_notify() 实际上是写它为 0x0001;取消订阅是 0x0000。”

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/920534.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/920534.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JavaSE丨集合框架入门(二):从 0 掌握 Set 集合

这节我们接着学习 Set 集合。一、Set 集合1.1 Set 概述java.util.Set 接口继承了 Collection 接口,是常用的一种集合类型。 相对于之前学习的List集合,Set集合特点如下:除了具有 Collection 集合的特点,还具有自己的一些特点&…

金属结构疲劳寿命预测与健康监测技术—— 融合能量法、红外热像技术与深度学习的前沿实践

理论基础与核心方法 疲劳经典理论及其瓶颈 1.1.疲劳失效的微观与宏观机理: 裂纹萌生、扩展与断裂的物理过程。 1.2.传统方法的回顾与评析。 1.3.引出核心问题:是否存在一个更具物理意义、能统一描述疲劳全过程(萌生与扩展)且试验量…

【贪心算法】day4

📝前言说明: 本专栏主要记录本人的贪心算法学习以及LeetCode刷题记录,按专题划分每题主要记录:(1)本人解法 本人屎山代码;(2)优质解法 优质代码;&#xff…

AI 与脑机接口的交叉融合:当机器 “读懂” 大脑信号,医疗将迎来哪些变革?

一、引言(一)AI 与脑机接口技术的发展现状AI 的崛起与广泛应用:近年来,人工智能(AI)技术迅猛发展,已广泛渗透至各个领域。从图像识别、自然语言处理到智能决策系统,AI 展现出强大的数…

uniapp vue3 canvas实现手写签名

userSign.vue <template><view class"signature"><view class"btn-box" v-if"orientation abeam"><button click"clearClick">重签</button><button click"finish">完成签名</butt…

页面跳转html

实现流程结构搭建&#xff08;HTML&#xff09;创建侧边栏容器&#xff0c;通过列表或 div 元素定义导航项&#xff0c;每个项包含图标&#xff08;可使用字体图标库如 Font Awesome&#xff09;和文字&#xff0c;为后续点击交互预留事件触发点。样式设计&#xff08;CSS&…

Spring Boot自动装配机制的原理

文章目录一、自动装配的核心触发点&#xff1a;SpringBootApplication二、EnableAutoConfiguration的作用&#xff1a;导入自动配置类三、自动配置类的加载&#xff1a;SpringFactoriesLoader四、自动配置类的条件筛选&#xff1a;Conditional注解五、自动配置的完整流程六、自…

(未完结)阶段小总结(一)——大数据与Java

jdk8-21特性核心特征&#xff1a;&#xff08;8&#xff09;lambda&#xff0c;stream api&#xff0c;optional&#xff0c;方法引用&#xff0c;函数接口&#xff0c;默认方法&#xff0c;新时间Api&#xff0c;函数式接口&#xff0c;并行流&#xff0c;ComletableFuture。&…

嵌入式Linux驱动开发:设备树与平台设备驱动

嵌入式Linux驱动开发&#xff1a;设备树与平台设备驱动 引言 本笔记旨在详细记录嵌入式Linux驱动开发中设备树&#xff08;Device Tree&#xff09;和平台设备驱动&#xff08;Platform Driver&#xff09;的核心概念与实现。通过分析提供的代码与设备树文件&#xff0c;我们…

【完整源码+数据集+部署教程】骨折检测系统源码和数据集:改进yolo11-EfficientHead

背景意义 骨折作为一种常见的骨骼损伤&#xff0c;其诊断和治疗对患者的康复至关重要。传统的骨折检测方法主要依赖于医生的经验和影像学检查&#xff0c;如X光、CT等&#xff0c;这不仅耗时&#xff0c;而且容易受到主观因素的影响。随着计算机视觉和深度学习技术的迅猛发展&a…

面试记录7 c++软件开发工程师

开目 一面&#xff1a; 自我介绍你做的xxx应用是用c做的吗&#xff0c;是在window平台吗具体做的事情是什么你说的2D3D的结构是什么样的&#xff0c;怎样去做校验有没有二维到三维或者三维到二维的数据转换两个向量怎么去做校验做的什么优化有调用第三方库吗是用的什么工具&…

计算机网络:服务器处理多客户端(并发服务器)

一、服务器处理多客户端&#xff08;并发服务器&#xff09;&#xff08;一&#xff09;listen:监听客户端的连接请求&#xff0c;放入请求队列&#xff08;二&#xff09;accpet&#xff1a;请求队列中提取已连接的请求&#xff0c;返回连接好的fd&#xff08;循环accpet即可&…

Ansible自动化运维:原理以及安装教程

目录 Linux Ansible&#xff1a;作用与原理详解 一、Ansible 的核心作用 1. 配置管理&#xff08;Configuration Management&#xff09; 2. 应用部署&#xff08;Application Deployment&#xff09; 3. 任务编排&#xff08;Orchestration&#xff09; 4. 其他扩展作用 二、A…

[机器学习]基于K-means聚类算法的鸢尾花数据及分类

基于Kmeans&#xff0c;对鸢尾花数据集前两个特征进行聚类分析通过迭代优化&#xff0c;将150个样本划分到K个簇中。目标函数&#xff1a;最小化所有样本到其所属簇中心的距离平方和。算法步骤&#xff1a;随机初始化K个簇中心。将每个样本分配到最近的中心。计算均值确定每个簇…

Altium Designer 22使用笔记(10)---PCB铺铜相关操作

目录 01 | 简 述 02 | 环境描述 03 | 铺 铜 04 | 铺铜挖空 05 | 敷铜合并 06 | 敷铜的修改 07 | 总 结 01 | 简 述 在PCB设计阶段&#xff0c;除了布局、布线操作需要频繁进行调整外&#xff0c;铺铜操作的使用也非常频繁&#xff1b;因此本篇文章的主要内容为&#xff…

leetcode 338 比特位计数

一、题目描述二、解题思路我们可以借助位运算的思想来解决这个问题。通过kk&(k-1)来消除k中最右边为1的比特位&#xff0c;每次消除后进行count&#xff0c;当k为0时&#xff0c;表示所有的1消除完毕&#xff0c;此时的count即为所有1的个数。三、代码实现时间复杂度&#…

PHP的md5()函数分析

MD5&#xff08;Message-Digest Algorithm 5&#xff09;是一种广泛使用的哈希函数&#xff0c;由Ronald Rivest于1991年设计&#xff0c;属于密码散列算法家族。其核心功能是将任意长度的输入数据&#xff08;如字符串、文件等&#xff09;通过不可逆的数学运算转换为固定长度…

【面试场景题】怎么做业务领域划分

文章目录一、核心原则&#xff1a;以业务为中心&#xff0c;而非技术二、具体步骤&#xff1a;从业务理解到边界定义1. 深入理解业务&#xff1a;梳理业务全景2. 识别核心领域与支撑领域3. 划分“限界上下文”&#xff1a;定义领域边界4. 定义领域内的“聚合”&#xff1a;细化…

海量小文件问题综述和解决攻略(二)

1. 解决NameNode的内存问题 上面的内容提到过每个block的元数据都需要加载到NameNode的内存中&#xff0c;这导致一个Hadoop集群在NameNode中存储的对象是有上限的&#xff0c;并且对象太多会带来启动时间较长以及网络延迟的问题。常见的有两种解决方案&#xff0c;减少集群的…

《开发避坑指南:从异常中读懂系统的“求救信号”》

异常现象从不只是孤立的“故障”&#xff0c;而是系统发出的“健康预警”。太多团队困在“出现问题-临时修复-再次复发”的循环里&#xff0c;将精力消耗在表面问题的扑救上&#xff0c;却忽视了背后潜藏的架构缺陷、逻辑漏洞与环境适配盲区。真正成熟的开发思维&#xff0c;是…