Nordic 52832作为HID 键盘连接配对电视/投影后控制没反应问题的分析和解决

news2024/12/23 1:23:34

问题现象:我们的一款HID键盘硬件一直都工作的很好,连接配对后使用起来和原装键盘效果差不多,但是后面陆续有用户反馈家里的电视等蓝牙设备配对连接我们的键盘后,虽然显示已连接,但实际上控制不了。设备涉及到了好些品牌,比如坚果投影、海信电视等。

SDK版本: nRF5_SDK_17.0.2_d674dde

SoftDevice: S132

问题分析:

我们买了坚果投影回来测试,发现的确如用户反馈的现象一致,而且是必现的,必显就好分析。我们将nRF log日志级别改为debug,抓取了坚果配对过程的log,然后以同样方式抓取了正常使用的极米这一过程的log,对比日志发现连接都是正常的,日志都打印了Connected信息:

00> 

00> <info> app: Connected

00> 

00> <info> app: Connected

00> 

在main.c中我们对BLE事件注册了一个handler,针对BLE_GAP_EVT_CONNECTED和BLE_GAP_EVT_DISCONNECTED等事件进行了处理,上面的日志也说明了协议栈向我们的handler上报了已和设备连接上的事件。

 在日志后面按下按键发送数据的地方,坚果返回了错误码

00> <info> app: sd_ble_gatts_hvx err_code = 8.

 极米此处无错误

00> <info> app: sd_ble_gatts_hvx err_code = 0.

00> 

00> <info> app: hid send key err_code = 0

所以问题点就是此处,先来看一下报错位置的代码

 红色框中的代码就是当按下按键, 将HID键值通知到设备的处理,也就是说sd_ble_gatts_hvx返回了错误码0x08(NRF_ERROR_INVALID_STATE)。

根据sd_ble_gatts_hvx的方法定义,可以看到0x08错误的条件有三个:

* @retval ::NRF_ERROR_INVALID_STATE One or more of the following is true:

 *                                   - Invalid Connection State

 *                                   - Notifications and/or indications not enabled in the CCCD

 *                                   - An ATT_MTU exchange is ongoing

这段代码最开始的conn_handle != BLE_CONN_HANDLE_INVALID判断可以排除条件1,设备当前的连接是正常的,而且日志中也没有disconnected事件。

在Nordic论坛搜到类似的问题,说是CCCD没有使能,所以我们一开始也是从这个方向着手排查,在main.c中hids_init(void)方法里我们有对HID服务添加事件回调:

hids_init_obj.evt_handler   = on_hids_evt;

在坚果和极米的日志中都有输出相同数量的BLE_HIDS_EVT_NOTIF_ENABLED事件,所以HID 服务的CCCD应该已经使能了。

00> <info> app: ble_srv_is_notification_enabled = 1 p_hids->evt_handler = 295104 

00> <info> app: BLE_HIDS_EVT_NOTIF_ENABLED

接下来看an ATT_MTU exchange is ongoing这个情况是否存在。在两份日志的最开始处都有请求更新ATT MTU的信息,并且是在打印Connected之前:

00> <debug> nrf_ble_gatt: Requesting to update ATT MTU to 185 bytes on connection 0x0.

00> 

00> <info> app: HID on_connect conn_handle = 0.

00> 

00> <info> app: Connected

极米在后面的日志中有ATT MTU交换完成的信息

00> <debug> nrf_ble_gatt: ATT MTU updated to 185 bytes on connection 0x0 (response).

00> 

00> <info> app: Data len is set to 0xB6(182)

00> 

00> <debug> app: ATT MTU exchange completed. central 0xB9 peripheral 0xB9

而坚果没有,即使等待很长时间也没有,所以sd_ble_gatts_hvx发送数据会因为 An ATT_MTU exchange is ongoing 直接返回NRF_ERROR_INVALID_STATE,这样看很有可能就是我们目前碰到的问题的原因所在。那为什么要在连接后立马发MTU交换请求呢?为什么在坚果这里就没有交换完成的日志?

我们先分析第一个疑问。

蓝牙核心规范中只提到GATT client可以向GATT server发起ATT_EXCHANGE_MTU_REQ请求以告诉对方自己能够接收的最大数据长度(Client Rx MTU),server端收到请求后,需要通过ATT_EXCHANGE_MTU_RSP应答也告诉client自己这边的接收最大值(Server Rx MTU)。在MTU应答后两者的数据交互就使用两者能接收的MTU最小值了,一般MTU请求在连接后只会发一次。

规范并没有明确禁止server也可以发送ATT_EXCHANGE_MTU_REQ做同样的事情,即client 和server都能发出ATT MTU交换请求,所以Nordic协议栈会按照蓝牙规范处理MTU交换请求和应答。

这部分的处理主要在nrf_ble_gatt.c中

前面提到在connected打印前发出了MTU交换请求,上图红框部分on_connected_evt(p_gatt, p_ble_evt)就是发出请求的地方,正好是在BLE_GAP_EVT_CONNECTED事件里调用的。

先判断了当前设备的GAP角色,如果是peripheral, 将当前periph设备所需要的MTU赋值给当前连接conn_handle对应的p_link ->att_mtu_desired,目前我们的硬件是HID键盘,所以这里是peripheral

接着判断了当前连接需要的mtu是否大于生效的mtu,满足条件就请求MTU交换。可以看到在Nordic中,如果使用gatt库,默认设备连接上后满足这个条件就会发出交换请求。

那么需要看下p_link ->att_mtu_desired和p_link->att_mtu_effective值分别是多少,在哪里初始化的。

p_link->att_mtu_desired = p_gatt->att_mtu_desired_periph, 在当前文件中搜索att_mtu_desired_periph,找到下面这个方法

 nrf_ble_gatt_init有点眼熟,我们main.c中gatt_init()方法里调用这个方法进行了gatt初始化。

NRF_BLE_GATT_DEF(m_gatt);    /**< GATT module instance. */

static void gatt_init(void)

{

    ret_code_t err_code = nrf_ble_gatt_init(&m_gatt, gatt_evt_handler);

    APP_ERROR_CHECK(err_code);

}

我们这个工程NRF_BLE_GATT_LINK_COUNT=1, 会继续调用link_init(&p_gatt->links[i]);

 

p_link ->att_mtu_desired默认值是NRF_SDH_BLE_GATT_MAX_MTU_SIZE

p_link->att_mtu_effective默认值是BLE_GATT_ATT_MTU_DEFAULT

 //sdk_config.h

// <o> NRF_SDH_BLE_GATT_MAX_MTU_SIZE - Static maximum MTU size.

#ifndef NRF_SDH_BLE_GATT_MAX_MTU_SIZE

#define NRF_SDH_BLE_GATT_MAX_MTU_SIZE 185

#endif

//ble_gatt.h

/** @brief Default ATT MTU, in bytes. */

#define BLE_GATT_ATT_MTU_DEFAULT          23

综上,我们这个工程需要的mtu是185, 生效的是23(蓝牙协议允许的最小值, 蓝牙设备默认会使用这个值),所以连接上后会发出MTU交换请求,告诉对方本地能够接收的最大数据长度为185。但是不知道坚果为什么没有应答,导致HID设备的MTU交换状态一直处于进行中,蓝牙规范有提及如果发出MTU请求之后,在没有接收到应答之前,不可以向对端设备发送通知或指示。

我们看了下原始的sdk例子工程,NRF_SDH_BLE_GATT_MAX_MTU_SIZE是23, 改成23后, 重新和坚果配对,连接上后可以正常控制了,此时的log里也没有看到之前的请求交换的信息,证明了我们的分析是正确的,就是因为MTU交换没有完成造成的。

至于坚果为什么没有回复交换请求应答,是没有收到还是交换请求时机不对,我们也不清楚,不过作为Peripheral来说,本身也不用主动去请求MTU交换,交给Master来负责就好了,所以,最终决定连接后不发送这个请求,如果过后想要交换MTU, 调用sd_ble_gattc_exchange_mtu_request()即可。

改成23带来了新的问题,HID键盘除了要控制蓝牙设备外,还要和app连接进行通信,默认23字节会使得两者之间数据交互需要更长的时间,之前改成185就是为了缩短这个耗时。所以不能简单的改NRF_SDH_BLE_GATT_MAX_MTU_SIZE成23。因为如果是23,即使app这边请求185大小的client_mtu, HID键盘收到交换请求后,在这个逻辑中会使用p_link->att_mtu_desired(默认初始化为NRF_SDH_BLE_GATT_MAX_MTU_SIZE)来调用sd_ble_gatts_exchange_mtu_reply做应答,sd_ble_gatts_exchange_mtu_reply传入的是server_mtu,双方最终使用的是client_mtu和server_mtu的最小值,和这个方法里的p_link->att_mtu_effective = MIN(client_mtu, p_link->att_mtu_desired)一样。 

我们的需求是连接上后不要自动发交换请求,所以在sdk_config.h里定义了一个新常量

// NRF_BLE_GATT_MTU_EXCHANGE_INITIATION_ENABLED 是否启用连接后发MTU交换请求, 1为启用,我们这里关闭

#ifndef NRF_BLE_GATT_MTU_EXCHANGE_INITIATION_ENABLED

#define NRF_BLE_GATT_MTU_EXCHANGE_INITIATION_ENABLED 0

#endif

NRF_SDH_BLE_GATT_MAX_MTU_SIZE还是保持185,这样app可以申请最大为185的MTU用来和设备通信。

修改nrf_ble_gatt.c中的on_connected_evt方法,在之前的p_link->att_mtu_desired > p_link->att_mtu_effective条件前增加了启用开关:

 

这样即实现了作为HID外设时不主动发MTU交换请求,又能正确处理Master请求交换MTU大小。 

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

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

相关文章

Golang | Leetcode Golang题解之第213题打家劫舍II

题目&#xff1a; 题解&#xff1a; func _rob(nums []int) int {first, second : nums[0], max(nums[0], nums[1])for _, v : range nums[2:] {first, second second, max(firstv, second)}return second }func rob(nums []int) int {n : len(nums)if n 1 {return nums[0]}…

Tekla Structures钢结构详图设计软件下载;Tekla Structures高效、准确的合作平台

Tekla Structures&#xff0c;它不仅集成了先进的三维建模技术&#xff0c;还融入了丰富的工程实践经验&#xff0c;为设计师、工程师和建筑商提供了一个高效、准确的合作平台。 在建筑项目的整个生命周期中&#xff0c;Tekla Structures都发挥着举足轻重的作用。从规划阶段开始…

部署nginx服务用于浏览服务器目录并实现账号密码认证登录

1、背景&#xff1a; 因公司业务需求&#xff0c;部署一套数据库备份中心服务&#xff0c;但是因为备份的数据库很多&#xff0c;有项目经理要求能经常去查看备份数据库情况及下载备份数据文件的需求。根据该需求&#xff0c;需要在备份数据库的服务器上部署一个nginx服务&…

mac中如何恢复因为破解脚本导致的IDEA无法启动的问题

问题 为了在mac中安装免费的2024版idea&#xff0c;导致下载了一个脚本&#xff0c;使用这个脚本后&#xff0c;但是发现idea还没有破解&#xff0c;相反导致idea无法启动&#xff0c;每次点击&#xff0c;都会弹出“cannot start IDE…” 问题排查 在访达中点击mac的应用程…

实时数仓Hologres OLAP场景核心能力介绍

作者&#xff1a;赵红梅 Hologres PD OLAP典型应用场景与痛点 首先介绍典型的OLAP场景以及在这些场景上的核心痛点&#xff0c;OLAP典型应用场景很多&#xff0c;总结有四类&#xff1a;第一类是BI报表分析类&#xff0c;例如BI报表&#xff0c;实时大屏&#xff0c;数据中台等…

java项目总结2

3.了解Java的内存分配 4.重载 定义&#xff1a;在一个类中&#xff0c;有相同名的&#xff0c;但是却是不同参数&#xff08;返回类型可以不一样&#xff09; 重载的优点&#xff1a; 1.减少定义方法时使用的单词 2.减少调用方法时候的麻烦&#xff08;比如sum的返回两个数的…

云计算【第一阶段(22)】Linux的进程和计划任务管理

目录 一、查看进程 1.1、程序和进程的关系 1.2、查看进程 1.2.1、静态查看进程信息ps ​编辑 1.2.1.1、实验 1.2.2、动态查看进程信息top 1.2.2.1、实验 1.2.2.2、top 命令全屏操作界面快捷键 1.2.3、pgrep根据特定条件查询进程pid信息 1.2.4、pstree命令以树形结构列出…

工程安全监测仪器:振弦采集仪的应用与发展

工程安全监测仪器&#xff1a;振弦采集仪的应用与发展 振弦采集仪是一种常见的工程安全监测仪器&#xff0c;广泛应用于建筑、桥梁、隧道、地铁等工程项目中。它通过监测振弦的振动变化&#xff0c;可以及时发现结构变形或损坏情况&#xff0c;为工程的安全运行提供重要数据支…

【前端实现】在父组件中调用公共子组件:注意事项逻辑示例 + 将后端数组数据格式转换为前端对象数组形式 + 增加和删除行

【前端】在父组件中调用公共子组件的实现方法 写在最前面一、调用公共子组件子组件CommonRow.vue父组件ParentComponent.vue 二、实现功能1. 将后端数组数据格式转换为前端对象数组形式2. 增加和删除row 三、小结 &#x1f308;你好呀&#xff01;我是 是Yu欸 &#x1f30c; 2…

优化页面加载时间

注&#xff1a;机翻&#xff0c;未校对。 本文年代久远&#xff0c;除了少部分不合时宜的&#xff0c;其他仍有借鉴意义。 Optimizing Page Load Time 优化页面加载时间 It is widely accepted that fast-loading pages improve the user experience. In recent years, many …

数组-螺旋矩阵

M螺旋矩阵 ||&#xff08;leetcode59&#xff09; /*** param {number} n* return {number[][]}*/ var generateMatrix function(n) {const maxNum n * n;let curNum 1;const matrix new Array(n).fill(0).map(() > new Array(n).fill(0));let row 0,column 0;const d…

06 threeJs-gui 界面库

1.引入GUI 如果需要使用lil-gui界面库对页面进行辅助调试和数值设置&#xff0c;则需在项目中进行引入&#xff0c;例如&#xff1a; import { GUI } from ../../build/three/examples/&#xff1b;/libs/lil-gui.module.min.js 2.实例化 交互界面 3.对需要在交互界面显示的数…

redis学习(001 介绍)

黑马程序员Redis入门到实战教程&#xff0c;深度透析redis底层原理redis分布式锁企业解决方案黑马点评实战项目 总时长 42:48:00 共175P 此文章包含第1p-第p4的内容 文章目录 介绍差异对比事务区别 认识redis 介绍 两种键值对方式对比 差异对比 事务区别 认识redis

uniapp 封装请求

新建request文件夹 下新建index.js 和index.js 或者创建units文件放入index.js 和api文件夹放入index.js(api.js)//看公司规范 1. index.js // 全局请求封装 // const base_url http://localhost:8080/devapi var base_url process.env.NODE_ENV development ? http://…

Ubuntu24.04(22.04+版本通用)Miniconda与Isaacgym

1. ubuntu24.04安装minicondda mkdir -p ~/miniconda3 wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda3/miniconda.sh解释下这段代码 bash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3~/miniconda3/miniconda.sh: 指向Mi…

UE5 02-给物体一个扭矩力

需要注意的是: 1.弹簧臂 可以使用绝对旋转 这样就可以不跟随父物体Player的旋转 2.弹簧臂 进行碰撞测试勾选,当这个弹簧线被遮挡,摄像机会切换到碰撞点位置 进行碰撞测试勾选,当这个弹簧线被遮挡,摄像机不会切换到碰撞点位置

SD-WebUI视频重绘:TemporalKit+EbsynthUtility避坑指南

AI视频重绘&#xff0c;在当下大家并不陌生。我们的实现方式大致可以分为三种: 第三方平台和discord上转绘&#xff0c;如DomoAI &#xff0c;GoEnhance AI 等。 优点&#xff1a;效果佳&#xff0c;门槛低。 缺点&#xff1a;需要科学上网&#xff0c;和支付一定的使用费用。…

Android设备信息(DevInfo)

软件介绍 设备信息&#xff08;DevInfo&#xff09;一款评分非常不错的手机硬件及各种信息检测应用&#xff0c;安卓设备硬件检测工具。可以全面查看手机的各种信息、包括&#xff1a;Android系统版本的详细信息、芯片CPU处理器的详细信息、全球卫星定位、测试功能、硬件温度、…

image媒体组件属性配合swiper轮播

图片组件&#xff08;image&#xff09; 先插入个图片试试&#xff0c;插入图片用src属性&#xff0c;这是图片&#xff1a; 代码如下&#xff1a; <template><view><swiper indicator-dots indicator-color "#126bae" indicator-active-color &…

高效生产力,手机也能工作#MixCopilot工作流

从一个DEMO说起&#xff1a; 制作完DEMO&#xff0c;体验完这一个手机专用的工作流之后&#xff0c;我感觉我以后再也不用待电脑前了&#xff0c;可以躺着玩手机了。。。 然后&#xff0c;我花了点时间把这个流程优化了&#xff0c;目前在MixCopilot的内部测试版已经支持此项功…