蓝牙 Mesh(SIG)

更新时间:2024-03-19 03:17:33下载pdf

蓝牙 Mesh,也就是将蓝牙设备组成网络,每个蓝牙设备可以通过网络内的蓝牙设备进行通讯,将一端的蓝牙信息通过 Mesh 网络传到较远的另一端。本文介绍安卓版 商用照明 App SDK 的蓝牙 Mesh 相关接口和调用示例。

基础概念

蓝牙技术联盟(Bluetooth Special Interest Group,简称蓝牙 SIG)蓝牙技术全面支持 Mesh 网状网络。标准蓝牙 Mesh 也叫做 SIG Mesh,是蓝牙技术联盟提出的 Mesh 网络的通讯标准。使用蓝牙 Mesh 进行组网及设备功能的更新,均需要满足标准蓝牙 Mesh 的标准。

名词解释

专有名词 说明
大小类 每个 Mesh 设备都对应一款产品,每个产品都有自己的大小类标示,SDK 中以 pcctype 作为大小类标示。
Mesh 群组 localId 2 字节,localId 用于区分每个 Mesh 群组在 Mesh 网中的 唯一标识。当您想控制某个群组中的设备时,就向 Mesh 网络发送此群组对应的 localId 命令即可。
Mesh 节点 nodeId 2 字节,nodeId 用于区分每个 Mesh 设备在 Mesh 网中的 唯一标识。当您想控制某个设备时,就向 Mesh 网络发送此设备对应的 nodeId 命令即可
本地连接 已配网设备通过蓝牙连接,来控制 Mesh 和指令操作。
网关连接 已配网设备通过网关连接(网关需和设备在一起,距离不能太远),来控制 Mesh 和指令操作。

多步操作

因为设备的操作,例如增删操作、群组操作,都需要本地蓝牙命令执行一次、云端记录一次。因此,向本地 Mesh 网同步操作信息的同时,也需要向云端同步操作信息。

设备规则(大小类)

  • 组成:设备类别值由 设备类型 + 大类 + 小类 组成后小端排列。

    • 设备类型:
      • 标准型:1
      • 透传型:2
    • 大类编号:蓝牙 Mesh 产品目前分为三大类
      • 照明类:01,表示 1-5 路 RGBWC 彩灯
      • 电工类:02,表示 1-6 路插座
      • 遥控器:05,表示 1-6 个按键
    • 小类编号:
      • 1-5 路灯:1-5
      • 1-6 路排插:1-6
      • 1-6 个遥控器按键:1-6
  • 举例:

    产品 类别值 说明
    标准五路灯 1510 小端转换后得到 1015:
    • 1:标准设备
    • 01:灯
    • 5:5 路灯
    标准四路插座 2410 小端转换后得到 1024:
    • 1:标准设备
    • 02:插座
    • 4:4 路插座
    透传型设备 xx20 小端转换后得到 20xx:2 表示透传设备

准备工作

  • 手机系统要求:蓝牙使用需要安卓 4.3 以及以上版本。
  • Manifest 权限
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
    
  • 蓝牙权限检测:每次扫描和连接前,都要进行检测,否则 App 无法正常使用蓝牙。
    • App 在使用蓝牙连接或者扫描操作前,需要检查 App 定位权限是否被允许。
    • 检查蓝牙状态是否为开启。

      该部分检查逻辑,商用照明 SDK 未提供 API,您可自行检测。

配网

配网就是把未加入到蓝牙 Mesh 网络的蓝牙设备,通过一定的通讯过程,将其加入到蓝牙 Mesh 网络中。

扫描待配网子设备

扫描附近符合 SIG 标准的蓝牙设备。当扫描到周围有符合协议规范的待配网设备后,可以对这些设备进行配网。

接口说明

// 开启扫描
void startSearch();
// 停止扫描
void stopSearch();

示例代码

IThingBlueMeshSearchListener iThingBlueMeshSearchListener=new IThingBlueMeshSearchListener() {
    @Override
    public void onSearched(SearchDeviceBean deviceBean) {

    }

    @Override
    public void onSearchFinish() {

    }
};
// 待配网的蓝牙 Mesh 设备 UUID 是固定的
UUID[] MESH_PROVISIONING_UUID = {UUID.fromString("00001827-0000-1000-8000-00805f9b34fb")};
SearchBuilder searchBuilder = new SearchBuilder()
                                .setServiceUUIDs(MESH_PROVISIONING_UUID)    // 蓝牙 Mesh 的 UUID 是固定值
                .setTimeOut(100)        // 扫描时长,单位秒
                .setThingBlueMeshSearchListener(iThingBlueMeshSearchListener).build();

IThingBlueMeshSearch mMeshSearch = ThingOSMesh.activator().newThingBlueMeshSearch(searchBuilder);

// 开启扫描
mMeshSearch.startSearch();

// 停止扫描
mMeshSearch.stopSearch();

查询设备信息

扫描到目标设备以后,可以通过查询显示产品配置的名称和图标。

接口说明

void getActivatorDeviceInfo(String productId, String uuid, String mac, IThingDataCallback<ConfigProductInfoBean> callback);

参数说明

参数 类型 说明
productId String SearchDeviceBean.productId,注意需要将 byte[] 转换为 String 之后,再传入
uuid String 蓝牙 Mesh 设备为 null
mac String SearchDeviceBean.productId

示例代码

ThingOSActivator.deviceActivator().getActivatorDeviceInfo(
    // btye[] to String
    new String(bean.getProductId(), StandardCharsets.UTF_8),
    // uuid
    null,
    // mac
    scanDeviceBean.getMacAdress(),
    // callback
    new IThingDataCallback<ConfigProductInfoBean>() {
        @Override
        public void onSuccess(ConfigProductInfoBean result) {

        }

        @Override
        public void onError(String errorCode, String errorMessage) {

        }
});

回调说明

ConfigProductInfoBean 说明

属性 类型 说明
name String 产品名称,云端配置,一般是用户首次创建产品时的名称
icon String 产品图标

给蓝牙 Mesh 子设备配网

子设备入网有两种方式,一种是通过 App 蓝牙入网,另一种是通过网关直接配子设备入网。

接口说明

// 开启配网
void startActivator();
// 停止配网
void stopActivator();

示例代码

ThingSigMeshActivatorBuilder ThingSigMeshActivatorBuilder = new ThingSigMeshActivatorBuilder()
            .setSearchDeviceBeans(mSearchDeviceBeanList)
            .setSigMeshBean(sigMeshBean) // 蓝牙 Mesh 基本信息
            .setHomeId(gid)//当前配网所处的空间节点对应的 gid,必传
            .setTimeOut(100) // 超时时间
            .setThingBlueMeshActivatorListener(new IThingBlueMeshActivatorListener() {
     @Override
     public void onSuccess(String mac, DeviceBean deviceBean) {
         L.d(TAG, "subDevBean onSuccess: " + deviceBean.getName());
     }
     @Override
     public void onError(String mac, String errorCode, String errorMsg) {
         L.d(TAG, "config mesh error" + errorCode + " " + errorMsg);
     }
     @Override
     public void onFinish() {
      L.d(TAG, "config mesh onFinish");
     });

IThingBlueMeshActivator iThingBlueMeshActivator = ThingOSMesh.activator().newSigActivator()(ThingSigMeshActivatorBuilder);

// 开启配网
iThingBlueMeshActivator.startActivator();

// 停止配网
iThingBlueMeshActivator.stopActivator();

homeId 参数必须为当前配网所在区域对应的关系 ID,获取方式参考 获取区域对应的关系 ID

参数说明

参数 说明
mSearchDeviceBeanList 待配网的设备集合,通过 startSearch 扫描得到的
timeout 配网的超时时间设置,默认是 100s,如果配网设备过多,建议增加超时时长
sigMeshBean SigMeshBean

出参说明

参数 说明
DeviceBean 设备信息数据类型,参考 DeviceBean 数据模型
errorCode 参考 配网错误码 章节

蓝牙 Mesh 网关配网

蓝牙 Mesh 网关本质上为双模设备。

通过网关给蓝牙 Mesh 子设备配网

蓝牙 Mesh 子设备可以通过网关直接给子设备配网,配网的方式与 Zigbee 子设备入网方法一致。详情请参考 子设备配网

配网错误码

错误码 错误信息
21002 invite 失败
21004 provision 失败
21006 发送 public key 失败
21008 conform 失败
210010 random conform 失败
210014 发送数据失败
210016 composition data 失败
210018 添加 appkey 失败
210020 绑定 model 失败
210022 publication model 失败
210024 network transmit 失败
210026 云端注册失败
210027 设备地址分配已满
210034 notify 失败
20021 配网超时

设备

IThingBlueMeshDevice 类封装了对指定蓝牙 Mesh 内所有设备的操作。

查询设备实例

示例代码

IThingBlueMeshPlugin plugin = PluginManager.service(IThingBlueMeshPlugin.class);
IThingBlueMeshDevice mThingBlueMeshDevice = plugin.newSigMeshDeviceInstance("meshIdxxxx");

查询蓝牙 Mesh 连接状态

示例代码

DeviceBean deviceBean = ThingOSDevice.getDeviceBean(mDevId);
DeviceBean gwBean = ThingOSDevice.getDeviceBean(deviceBean.getParentId());

// 综合在线状态 (包括本地在线和网关在线)
boolean online = deviceBean.getIsOnline()
// 设备本地蓝牙在线状态
boolean localOnline = deviceBean.getIsLocalOnline()
// 设备网关在线状态 (需要网关在线且子设备在线,才认为网关真实在线)
boolean wifiOnline = deviceBean.isCloudOnline() && gwBean.getIsOnline()

判断是否为蓝牙 Mesh 设备和网关

示例代码

DeviceBean deviceBean = ThingOSDevice..getDeviceBean(mDevId);
// 判读是否是蓝牙 Mesh 设备 (子设备 + 网关)
if(deviceBean.isSigMesh()){
    // This device is sig mesh device"
}

// 判读是否是蓝牙 Mesh 网关设备
if(deviceBean.isSigMeshWifi()){
    // This device is sig mesh Wi-Fi device
}

重命名蓝牙 Mesh 子设备

接口说明

void renameMeshSubDev(String devId, String name, IResultCallback callback);

参数说明

参数 说明
devId 设备 ID
name 重命名名称
callback 回调

示例代码

IThingBlueMeshPlugin plugin = PluginManager.service(IThingBlueMeshPlugin.class);
IThingBlueMeshDevice mThingBlueMeshDevice = plugin.newSigMeshDeviceInstance("meshIdxxxx");

mThingBlueMeshDevice.renameMeshSubDev("devIdxxxx","设备新名称", new IResultCallback() {
     @Override
     public void onError(String code, String errorMsg) {
        // 重命名失败
     }

     @Override
     public void onSuccess() {
        // 重命名成功
     }
});

查询蓝牙 Mesh 子设备状态

云端查询到的 DP 数据可能不是当前设备实时的数据,您可以通过该命令去查询设备的当前数据值。结果通过 IMeshDevListeneronDpUpdate 方法返回。

接口说明

void querySubDevStatusByLocal(String pcc, final String nodeId, final IResultCallback callback);

参数说明

参数 说明
pcc 设备大小类
nodeId 设备的 nodeId
callback 回调

示例代码

IThingBlueMeshPlugin plugin = PluginManager.service(IThingBlueMeshPlugin.class);
IThingBlueMeshDevice mThingBlueMeshDevice = plugin.newSigMeshDeviceInstance("meshIdxxxx");

mThingBlueMeshDevice.querySubDevStatusByLocal(devBean.getCategory(), devBean.getNodeId(), new IResultCallback() {
    @Override
    public void onError(String code, String errorMsg) {
        // 查询错误
    }
    @Override
    public void onSuccess() {
        // 查询成功,见 IMeshDevListener 的回调
    }
});

组网成功在离线策略

当蓝牙 Mesh 成功组网之后,SDK 提供了若干种在离线策略。

  • 策略一:当蓝牙 Mesh 成功组网后,默认蓝牙 Mesh 下的子设备全部在线。
  • 策略二:当蓝牙 Mesh 成功组网后,查询蓝牙 Mesh 下所有子设备的在线状态。

以下是设置蓝牙 Mesh 在离线的策略的代码示例。

示例代码

// 设置采取查询蓝牙 Mesh 下所有子设备的在线状态策略
SigMeshConfiguration.OnlineMode onlineMode = SigMeshConfiguration.OnlineMode.RESPONSE_ONLINE;
ISigMeshControl sigMeshControl = ThingOSMesh.getSigMeshControl(mMeshId);
if (sigMeshControl != null){
    SigMeshConfiguration sigMeshConfiguration = sigMeshControl.getSigMeshConfiguration();
    if (sigMeshConfiguration != null){
        sigMeshConfiguration.setOnlineMode(onlineMode);
    }
}

在线状态策略

SigMeshConfiguration.OnlineMode

枚举值 说明
DEFAULT 组网成功之后,默认所有子设备在线
RESPONSE_ONLINE 组网成功之后,查询蓝牙 Mesh 下所有的子设备的在线状态

查询设备在线状态

方式一:根据蓝牙 Mesh 的在离线策略自动查询

根据选择蓝牙 Mesh 连接成功之后的在离线策略,进行智能查询子设备在线状态。根据 OnlineMode 查询设备在线状态:

  • OnlineModeRESPONSE_ONLINE 时,自动查询所有设备的在线状态。
  • OnlineModeDEFAULT 时,自动只查询离线设备的在线状态。

接口说明

ISigMeshControl sigMeshControl = ThingOSMesh.getSigMeshControl(mMeshId);
if (sigMeshControl != null){
    sigMeshControl.queryAllOnLineStatusByLocal(new IResultCallback() {
        @Override
        public void onError(String code, String error) {
             // 发送查询命令失败
        }

        @Override
        public void onSuccess() {
            // 发送查询命令成功
        }
    });
}

方式二:自定义查询设备在线状态

接口说明

ISigMeshControl sigMeshControl = ThingOSMesh.getSigMeshControl(mMeshId);
int queryStrategy = 0;
//查询策略,0:查询蓝牙 Mesh 下所有的子设备,1:仅查询离线设备
if (sigMeshControl != null){
    sigMeshControl.queryMeshDeviceOnlineStatusByLocal(queryStrategy, new IResultCallback() {
        @Override
        public void onError(String code, String error) {
             // 发送查询命令失败
        }

        @Override
        public void onSuccess() {
            // 发送查询命令成功
        }
    });
}

参数说明

枚举值 说明
queryStrategy 设置查询策略:
  • 0:查询蓝牙 Mesh 下所有子设备
  • 1:仅查询所有离线设备

移除蓝牙 Mesh 子设备

接口说明

void removeMeshSubDev(String devId, IResultCallback callback);

参数说明

参数 说明
devId 设备 ID
pcc 设备大小类
callback 回调

示例代码

IThingBlueMeshPlugin plugin = PluginManager.service(IThingBlueMeshPlugin.class);
IThingBlueMeshDevice mThingBlueMeshDevice = plugin.newSigMeshDeviceInstance("meshIdxxxx");
mThingBlueMeshDevice.removeMeshSubDev(devBean.getDevId(), devBean.getCategory(), new IResultCallback() {
    @Override
    public void onError(String code, String errorMsg) {

    }
    @Override
    public void onSuccess() {

    }
});

控制

由于涂鸦使用 DP 管理设备的功能,因此,蓝牙 Mesh 相关的控制是根据设备的 DP 信息来进行操作。IThingBlueMeshDevice 类提供了对蓝牙 Mesh 设备的操作。

指令下发格式

按照以下格式,发送控制指令:

{
    "(dpId1)":(dpValue1),
    "(dpId2)":(dpValue2)
}

DP 指令详情,参考 设备控制

设备控制指令下发

接口说明

void publishDps(String nodeId, String pcc, String dps, IResultCallback callback);

参数说明

参数 说明
nodeId 子设备本地编号
pcc 设备产品大小类
dps 设备功能的 DP ID 列表,更多详情,参考 设备控制
callback 回调

示例代码

String dps = {"1":false};

IThingBlueMeshPlugin plugin = PluginManager.service(IThingBlueMeshPlugin.class);
IThingBlueMeshDevice mThingBlueMeshDevice = plugin.newSigMeshDeviceInstance("meshIdxxxx");

mThingSigMeshDevice.publishDps(devBean.getNodeId(), devBean.getCategory(), dps, new IResultCallback() {
    @Override
    public void onError(String s, String s1) {
        // 发送 DPs 失败
    }

    @Override
    public void onSuccess() {
        // 发送 DPs 成功
    }
});

设备控制监听

Mesh 网内相关信息,例如 DP 数据、状态变更、设备名称和设备移除等,会实时同步到 IMeshDevListener

示例代码

mThingSigMeshDevice.registerMeshDevListener(new IMeshDevListener() {
    /**
    * 数据更新
    * @param nodeId    更新设备的 nodeId
    * @param dps       dp 数据
    * @param isFromLocal   数据来源,true 表示从本地蓝牙,false 表示从云端
    */
    @Override
    public void onDpUpdate(String nodeId, String dps,boolean isFromLocal) {
        // 可以通过 node 来找到相对应的 DeviceBean
        DeviceBean deviceBean = mThingBlueMeshDevice.getMeshSubDevBeanByNodeId(nodeId);
    }
    /**
    * 设备状态的上报
    * @param online    在线设备列表
    * @param offline   离线设备列表
    */
    @Override
    public void onStatusChanged(List<String> online, List<String> offline,String gwId) {

    }

    /**
    * 网络状态变化
    * @param devId
    * @param status
    */
    @Override
    public void onNetworkStatusChanged(String devId, boolean status) {

    }
    /**
    * raw 类型数据上报
    * @param bytes
    */
    @Override
    public void onRawDataUpdate(byte[] bytes) {

    }
    /**
    * 设备信息变更(名称等)
    * @param bytes
    */
    @Override
    public void onDevInfoUpdate(String devId) {

    }
    /**
    * 设备移除
    * @param devId
    */
    @Override
    public void onRemoved(String devId) {

    }
});

升级

您可以通过 OTA(Over-the-air)的方式给蓝牙 Mesh 子设备进行升级。

蓝牙 Mesh 低功耗设备在升级之前,需要被唤醒。不同设备的唤醒方式不同。

查询固件升级信息

接口说明

void requestUpgradeInfo(String devId, IRequestUpgradeInfoCallback callback);

参数说明

参数 说明
devId 需要升级的设备 ID
callback 检查回调

示例代码

IThingBlueMeshPlugin iThingBlueMeshPlugin = PluginManager.service(IThingBlueMeshPlugin.class);
iThingBlueMeshPlugin.getMeshInstance().requestUpgradeInfo(mDevID, new IRequestUpgradeInfoCallback() {
    @Override
    public void onSuccess(ArrayList<BLEUpgradeBean> bleUpgradeBeans) {

    }

    @Override
    public void onError(String errorCode, String errorMsg) {

    }
});

BLEUpgradeBean 返回固件升级的信息,提供以下信息:

字段 类型 描述
upgradeStatus int 升级状态:
  • 0:无新版本
  • 1:有新版本
  • 2:在升级中
version String 最新版本
currentVersion String 当前版本
timeout int 超时时间,单位:秒
upgradeType int
  • 0:App 提醒升级
  • 2:App 强制升级
  • 3:检测升级
type int 固件来源:
  • 0:Wi-Fi 模组
  • 1:蓝牙模组
  • 2:GPRS 模组
  • 3:Zigbee 模组
  • 9:MCU
typeDesc String 固件来源描述
lastUpgradeTime long 上次升级时间,单位:毫秒
url String 新固件下载地址,当 type 取值为 1 或者 9 的时候,url 有值
fileSize long 新固件大小
md5 String 新固件 MD5 值

升级蓝牙 Mesh 子设备

接口说明

void startOta();

示例代码

private MeshUpgradeListener mListener = new MeshUpgradeListener() {
    @Override
    public void onUpgrade(int percent) {
        // 升级进度
    }

    @Override
    public void onSendSuccess() {
        // 固件数据发送成功
    }

    @Override
    public void onUpgradeSuccess() {
        // 升级成功
        mMeshOta.onDestroy();
    }

    @Override
    public void onFail(String errorCode, String errorMsg) {
        // 升级失败
        mMeshOta.onDestroy();
    }
};

// 查询指定文件的字节流
byte data[] = getFromFile(path);

ThingBlueMeshOtaBuilder build = new ThingBlueMeshOtaBuilder()
        .setData(data)
        .setMeshId(mDevBean.getMeshId())
        .setMac(mDevBean.getMac())
        .setProductKey(mDevBean.getProductId())
        .setNodeId(mDevBean.getNodeId())
        .setDevId(mDevID)
        .setVersion("version")
        .setThingBlueMeshActivatorListener(mListener)
        .bulid();
IThingBlueMeshOta mMeshOta = ThingOSMesh.newOTA(build);

// 开始升级
mMeshOta.startOta();

入参说明

ThingBlueMeshOtaBuilder

参数 类型 说明
data byte[] 待升级固件的字节流
meshId String 设备蓝牙 meshId
mac String 设备 MAC 地址
productKey String 设备产品 ID
mNodeId String 设备 nodeId
devId String 设备 ID
version String 待升级固件的版本号

升级蓝牙 Mesh 网关设备

蓝牙 Mesh 网关升级和普通的 Wi-Fi 设备升级一样,以下是示例代码。

示例代码

private IOtaListener iOtaListener = new IOtaListener() {
    @Override
    public void onSuccess(int otaType) {
        // 升级成功
    }

    @Override
    public void onFailure(int otaType, String code, String error) {
        // 升级失败
    }

    @Override
    public void onProgress(int otaType, final int progress) {
        // 升级进度
    }
};
IThingOtaPlugin otaPlugin = PluginManager.service(IThingOtaPlugin.class));
IThingOta iThingOta = otaPlugin.newOTAInstance(devId);
iThingOta.setOtaListener(mOtaListener);
// 开始升级
iThingOta.startOta();

// 销毁升级
iThingOta.onDestroy();