文章目录前言Mesh组网基本理解扫描设备组网meshAddress添加与重连UUID连接登录修改信息控制与接收设备数据总结

前言

上面的几篇文章都是在说Android网络编程方面的内容,我本来就有打算做成一个系列。但最近因为工作的原因,一直在研究蓝牙mesh组网对蓝牙设备进行控制,研究了近两个星期,总算有了点自己的理解。先对蓝牙Mesh 组网做一个总结,下面的文章会继续写Android 网络编程方面的内容。网上关于Mesh 组网的理论解释倒是很多,但是很少有关于Android 代码具体实现的,这篇文章将基于Android Mesh 组网的代码实现进行讲解,希望能带给大家一些帮助。

Mesh组网基本理解

蓝牙技术联盟写了解密蓝牙mesh系列,一共10篇文章 讲述了蓝牙mesh理论内容以及整个流程 ,想要详细了解的可以点击查看。下面我们简单介绍下Mesh组网到底是什么:

MESH是一种新型的无线网络架构,蓝牙Mesh组网内每台设备均通过低功耗蓝牙无线连接进行通信,而这些设备被称之为节点。每个节点都能发送和接收消息,消息能够在节点之间被中继,从而让消息传输至比无线电波正常传输距离更远的位置。归结成一句话:蓝牙Mesh组网 就是一种在同一个网络内任意蓝牙设备都进行数据交互的技术。这样的话,APP 只要能发现组网内的任何一台设备,就能由设备发现组网内的其他Mesh设备,并和任何一台设备建立连接并控制。

其实,我对蓝牙Mesh 的组网也仅仅限于上面的理解。作为一个Android 开发人员,我更关注的是Android 代码到底如何去实现蓝牙设备的组网。在网上也找到了一些项目和代码,仔细研究了下也是很迷茫。后来找到了泰凌微提供的蓝牙Mesh灯项目以及开放的Mesh 组网流程的源码,才真正的算是实现了Android 组网。我将这个资料上传到了百度网盘 ,提取码是6i57。也上传到了资源,点击即可下载,里面有一个Android Mesh组网的项目及具体源码、SDK开发手册以及加密手册。当然,这个项目的知识产权还是归泰凌微所有,如果有任何侵权行为联系本人,本人将立即下架这两个项目,发布的原因还是想给大家普及一下Android 如何实现Mesh 组网的,没有任何盈利行为。

扫描设备

因为下面的分析是基于上面项目的代码去分析的,所以需要你们下载下来项目跑起来。

无论是连接已经完成组网设备还是将一个待组网设备进行组网,APP 做的第一步永远是扫描,扫描到所有的蓝牙设备,然后拿到指定的Mesh组网的设备进行组网操作。下面是代码分析:

在扫描界面执行: startScan(params) 启动扫描,然后会执行一个循环任务EventLoopTask,在循环任务中每200毫秒查询一次状态,然后 startLeScan() 方法中判断设备是否正在扫描,如果没有,则在LeBluetooth类中开启扫描startScan()。

在注册TelinkLightService的时候,会创建一个LightAdapter并启动,会设置设备的扫描结果回调setLeScanCallback(LeScanCallback callback),然后当上面的扫描开启后,会将扫描得到的设备返回的这个回调中的onLeScan()方法中.在这个方法中,会先对设备本身返回的信息进行处理和判断,符合标准的设备返回到在扫描界面设置监听的performed方法中的,然后执行onLeScan(),扫描这一步就完成了。

扫描完拿到三个信息 device rssi 以及scanRecord 。蓝牙设备的地址可以根据device.getAddress()直接获取 比如说我拿到的灯设备 scanRecord 的数据如下:

02:01:05:05:09:4D:65:73:68:09:FF:11:02:11:02:35:43:68:38:1E:FF:11:02:11:02:35:43:68:38:21:43:01:35:00:10:19:44:45:4C:20:68:73:65:4D:20:47:53:54:42:4A:00:00:00:00:00:00:00:00:00:00:00:00

​ 在DefaultAdvertiseDataFilter类中可以看到 有meshName,meshAddress,meshUUID,productUUID等数据。很明显我们可以根据这些数据分析当前设备在哪个mesh 下,地址又是什么。

总结:执行SDK提供的开始扫描接口startScan()的时候,可以从设备发现的回调中拿到具体的蓝牙设备和广播。mac 地址和设备名称等信息可以从设备中拿到,而设备所属的Mesh 组网名称、设备的MeshAddress(设备在组网内的唯一标识,通讯地址)等信息可以在广播中获取到。其中productUUID是指的产品类型,可以在设备中自定义这个类型。而meshUUID则是厂商默认设置的值,至于status 这个值是厂商的预留值,也是可以在设备中自定义的信息,总的来说,在这个APP 的代码中,并没有实际用到这三个参数。

组网

meshAddress

在onLeScan() 方法里,会调用一个mesh.getDeviceAddress() 的方法,这个方法很重要,这里面拿到的meshAddress就是后面要修改新加入组网设备的meshAddress,他在后面会被设为newMeshAddress然后保存下来。因为在蓝牙mesh组网下,meshAddress是用来确定设备的,他在这个组网内是唯一固定的。那这个方法实现的逻辑是怎样的呢?比如说要加入一个新设备要加入当前的组网,不管他原先的meshAddress是多少 ,只看我当前组网下的meshAddress。比如说有两个设备,地址分别是 1、3,在这个方法里,会遍历1-254的值,然后就会返回一个2 ,这个2就是要设置的新meshAddress。meshAddress的范围就是1-254,当然了你也可以修改这个范围。

添加与重连

组网的时候,SDK提供了两个接口,一个是添加新的设备进行组网,调用的是TelinkLightService.Instance().updateMesh(params);更新接口,这个接口能够更改设备本身出厂时的参数meshName、password、ltk三个信息,这三个信息修改成功, 就标志着设备已经组⽹网成功,将设备成功加入到新的组网下。另一个是如果当前设备的meshName,password是你要组⽹网的名称,可以执⾏**TelinkLightService.Instance().autoConnect(connectParams);**自动重连接口,直接把APP 跟设备连接起来。

UUID

在组网之前,我们需要知道UUID。UUID是根据一定算法,计算得到的一长串数字,这个数字的产生使用了多种元素,所以使得这串数字不会重复,每次生成都会产生不一样的序列,所以可以用来作为唯一标识。

在蓝牙协议中,UUID被用来标识蓝牙设备所提供的服务,并非是标识蓝牙设备本身哦,一个蓝牙设备可以提供多种服务,比如A2DP(蓝牙音频传输)、HEADFREE(免提)、PBAP(电话本)、SPP(串口通信)等等,每种服务都对应一个UUID,其中在蓝牙协议栈里,这些默认提供的profile是都有对应的UUID的,也就是默认的UUID,比如SPP,00001101-0000-1000-8000-00805F9B34FB就是一个非常 well-known的UUID,基本上所有的蓝牙板不修改的话都是这个值。但是,不同的设备也不同的UUID,如果是与一个蓝牙开发板进行通信,需要APP 和 蓝牙设备的UUID 保持一致。而APP代码中的UuidInformation类里面就可以设置你要控制的低功耗蓝牙设备的UUID。一般都要修改服务、状态通知、控制、OTA、加密这五个UUID。下面进行的登录,修改参数、控制设备等等操作都需要UUID 的验证。

下面我们主要讲一下更新接口的实现,其实自动重连接口就是比更新接口少了修改设备信息这一步。

连接

发现设备后,拿到要组网的设备的本身的信息,然后就开始进行连接了。其实就是走的低功耗蓝牙设备的connectGatt(this, false, mGattCallback); 连接方法。

更新参数执行updateMesh()⽅法来更新设备的meshName、password、ltk,也还是会执⾏行一个循环任务 EventLoopTask,在循环任务中每200毫秒查询一次状态,然后update()方法中进行connect(),然后在LightController类中执行connect(),最终会执行Peripheral类的connect()⽅法,然后通过此BluetoothDevice的connectGatt(this, false, mGattCallback)方法获取设备连接。⽆论当连接上设备或者失去连接时会回调onConnectionStateChange(),当连接成功后调用discoverServices函数尝试发现服务,当设备是否找到服务时,会回调onServicesDiscovered()函数,然后会在LightPeripheral类中的onServicesDiscovered()回调给LightController,最后发给 LightAdapter类CONNECT_SUCCESS. 连接成功后会执行登录的⽅法。

登录

登录的过程是一个比较复杂的过程,涉及到多次的加密验证。这个验证之间的过程就会用到我们上面说的服务特征值UUID以及加密特征值UUID。

在LightAdapter类执行登录login的方法,最终的实现是在LightController类中的login方法.参考了BLE_LIGHT加密流程简介V1.9.pdf文档,登录校验的具体实现如下:

根据 meshName、password、randm 这 3 个参数生成一个 sk,然 后把 randm 和生成的 sk 的低 8 个 byte(校验用)一起发送给设备

设备获取到发过来的 randm 并和 BLE Light 本身存储的 meshname 和 password 进行加密获取一个 sk,将生成的 sk 的低 8Byte 和 Master 发过来的 sk 进行比较,如果正确,则表示认证成功。

设备也会随机生成8Byte的 rands、以及本地存储的 mesh name、password 这 3 个参数加密生成一个 sk,会把sk 和 rands 传给APP,然后在本地会根据 randm、rands、meshname、password 共同生成一个新的sk,后续的加密和解密都将使用刚刚生成的 sk。

APP 拿到设备发过来的sk后会进行校验,校验成功后获取8Byte的 rands,并根据该 rands 和randm、mesh name、password共同生成一个 sk,此时,APP 和 设备 两边的 sk 都是一样的,也就可以进行正常的加密和解密,同时的,登录的过程也就完成了。

修改信息

我上面说过,加新设备进行组网的过程就是修改meshName、password、ltk,这三个参数的过程。其实这么说也并不是特别准确,其实在修改这三个参数之前还需要对比meshAddress(就是扫描设备拿到的meshAddress)与上面保存的newMeshAddress 要不要修改,因为我们上面说过,因为在蓝牙mesh组网下,meshAddress是用来确定设备的,他在这个组网内是唯一固定的。修改成功后才能进行下面三个参数的修改,这三个参数修改完成就标志这新设备已经成功组网。

上面我说到LTK,可能有的朋友不知道这个是什么东西,meshName、password都很好理解,是mesh 组网的名称和密码,而LTK 是节点之间的通信秘钥,只要LTK一致,同一个组网间的设备就能正常通信。但是在代码中updateMesh(params)方法中,我们并没有给LTK赋值,所以当我们设置的时候将使用厂商默认LTK值。如果要修改自定义的LTK,可以调用params.setLtk() 方法进行赋值。

下面是代码分析:

登录的sk校验会出现在LightController类的LoginCommandCallback里面,然后在LightAdapter里面的ConnectionListener() 返回一个登录成功或者失败的回调。登录成功后要修改meshName, password, ltk这三个信息,这三个信息就是组网的标志。修改的过程发生在LightController类的reset方法,会先判断设备的meshAddress要不要修改,如果要修改的话就先修改meshAddress,然后在onDeviceAddressNotify()方法里继续执行reset方法。通过加密的方法将这三个参数发送给设备,具体的加密过程也可以参照上面的BLE_LIGHT加密流程简介V1.9.pdf 文档去解析下,最后发发送了一个重新检查的命令,等待设备解密并修改完成发出的确认信息就完成了信息的修改。修改命令的回调都在ResetCommandCallback里,收到最后的TAG_RESET_MESH_CHECK的确认后,继续往下执行,会发出一个RESET_MESH_SUCCESS事件 ,然后在ResetMeshListener的回调里设置STATUS_UPDATE_MESH_COMPLETED,表明已完成信息更新,扫描到的这个设备组网完成,最后在扫描界面onDeviceStatusChanged的方法里面继续执行加灯操作,直到扫不到设备。

控制与接收设备数据

控制与接收设备数据这里的代码就比较简单了,都有特定的方法去实现,唯一需要注意的一点就是发送数据的协议,一定要是当前设备的协议,否则无法进行控制。具体的实现可以参照下面的流程:

总结

以上就是蓝牙Mesh 组网的整个流程了,下面引用一张我们ios 同事画的流程图理解一下:

喜欢这篇文章或者对你有帮助的话希望能点个赞!

android ble mesh,Android 蓝牙Mesh组网代码详解相关推荐

  1. Android 蓝牙Mesh组网代码详解

    文章目录 前言 Mesh组网基本理解 扫描设备 组网 meshAddress 添加与重连 UUID 连接 登录 修改信息 控制与接收设备数据 总结 2020年2月23日补充 前言   上面的几篇文章都 ...

  2. android生命周期_Android开发 View的生命周期结合代码详解

    咱们以TextView控件为例: /** * Created by SunshineBoy on 2020/9/23. */ public class TestTextView extends and ...

  3. Android实战:CoolWeather酷欧天气(加强版数据接口)代码详解(上)

    -----------------------------------该文章代码已停更,可参考浩比天气(更新于2019/6/25)----------------------------------- ...

  4. android收藏功能demo,Android使用Realm数据库实现App中的收藏功能(代码详解)

    前 言 App数据持久化功能是每个App必不可少的功能,而Android最常用的数据持久化方式主要有以下的五种方式: 使用SharedPreferences存储数据: 文件存储数据: SQLite数据 ...

  5. Android系统性能优化(60)---LeakCanary使用详解

    Android内存优化(六)LeakCanary使用详解 1.概述 如果使用MAT来分析内存问题,会有一些难度,并且效率也不是很高,对于一个内存泄漏问题,可能要进行多次排查和对比.  为了能够简单迅速 ...

  6. [免费专栏] Android安全之数据存储与数据安全「详解」

    也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大 少走了弯路,也就错过了风景,无论如何,感谢经历 Android安全付费专栏长期更新,本篇最新内容请前往: [ ...

  7. Android 事件分发机制分析及源码详解

    Android 事件分发机制分析及源码详解 文章目录 Android 事件分发机制分析及源码详解 事件的定义 事件分发序列模型 分发序列 分发模型 事件分发对象及相关方法 源码分析 事件分发总结 一般 ...

  8. android edittext 过滤英文名称,Android 限制edittext 整数和小数位数 过滤器(详解)

    写了一个过滤器,根据需要限制edittext输入的整数和小数位,如下代码: package allone.verbank.apad.client.component; import android.t ...

  9. android平台下OpenGL ES 3.0实例详解顶点属性、顶点数组

    OpenGL ES 3.0学习实践 android平台下OpenGL ES 3.0从零开始 android平台下OpenGL ES 3.0绘制纯色背景 android平台下OpenGL ES 3.0绘 ...

最新文章

  1. mysql error number 1130,[转]mysql error number 1130的解决方法
  2. 哈哈,我的博客开通啦,欢迎光临~~~~~~~~~~~~
  3. 微信小程序通用开发框架小程序端包含若干基础组件
  4. 基于Pytorch再次解析AlexNet现代卷积神经网络
  5. Java的Runtime类介绍
  6. Apache Thrift快速入门教程
  7. JS JAVASCRIPT 判断两个日期相隔多少天
  8. android 怎么获取app 字体颜色,android app 修改字体
  9. Android Images
  10. MYSQL远程登录报错: Error No. 2003
  11. spring boot发送其他邮件
  12. eclipse 导入maven项目_一文轻松学会:从GitHub下载项目到eclispe
  13. 系统视频教学视频教程_Amesim综合液压系统视频教程专题更新通知
  14. Python第三方库:jieba库与中文分词概述(全面详解)
  15. 各家关节机器人示教器特点
  16. 第一天-虚拟机+CentOS6.7+工具软件安装
  17. JAVA Servlet进阶
  18. 面试时说上一家公司的离职原因
  19. 【软件需求工程】北理的恶龙们01——需求获取阶段准备工作
  20. html 实现3d效果代码,CSS3 3D环境实现立体 魔方效果代码

热门文章

  1. libIlmImf-2_2.so.22 :cannot open shared object file :No such file or direct
  2. Bert代码详解(二)重点
  3. 如何在TVM上集成Codegen(下)
  4. 使用nGraph的Intel®Xeon®上的高性能TensorFlow
  5. 空间点像素索引(三)
  6. 端口号被占用怎么解决
  7. 【其他】将幕布文章OPML转换为Markdown的方法
  8. TypeError: Total() missing 1 required positional argument: ‘self‘
  9. python 把字母转数字
  10. Failed to instantiate one or more classes