开发设备端OTA能力

任务背景信息

设备端OTA升级很大程度上依赖于设备自身的物理空间和功能实现。针对硬件开发者,EnOS平台提供包含OTA能力的Device SDK,支持以下功能:

  • 设备上报固件版本信息:设备与云端建连时上报一次自身的版本信息,云端记录设备的版本号;

  • 设备接收云端升级推送请求:云端推送升级请求后,设备可接收到升级请求并下载固件文件;

  • 设备向云端主动请求可升级的固件版本:支持设备主动向云端请求,并查询可升级的版本信息;

  • 设备上报升级进度和升级失败的错误原因:设备进入升级流程后,可向云端上报升级的进度或升级失败的原因,方便运维人员进行远程升级管理;

备注

一般而言,设备的OTA升级需要具备相应的物理条件,EnOS平台提供设备OTA升级所需的云端消息通道和文件下载服务,并提供设备端SDK方便开发者调用接口能力,而真正的设备端固件升级则依赖于设备端Flash是否具备足够的物理空间,开发者需自行实现文件下载后的固件切换、引导和重启等业务逻辑。

固件升级TOPIC

EnOS云端提供的固件升级的消息topic,升级请求支持使用MQTT进行消息的发送和订阅:

  1. 设备端上报固件版本给云端:设备端发布权限,用于设备与云端每次建连成功后上报当前的版本号信息:

    请求topic: /sys/${productkey}/${devicekey}/ota/device/inform

    响应topic: /sys/${productkey}/${devicekey}/ota/device/inform_reply

  2. 设备端接收云端OTA请求:设备端订阅权限,用于接收云端推送的OTA升级通知,包含固件名称、版本号、MD5和下载URL等信息:

    请求topic: /sys/${productkey}/${devicekey}/ota/device/upgrade

    响应topic: /sys/${productkey}/${devicekey}/ota/device/upgrade_reply

  3. 设备端上报固件升级进度:设备通过HTTPS循环请求固件下载,并通过这个topic上报固件升级事件、错误码和下载进度的百分比等信息:

    请求topic: /sys/${productkey}/${devicekey}/ota/device/progress

    响应topic: /sys/${productkey}/${devicekey}/ota/device/progress_reply

  4. 设备主动请求可升级的固件版本:云端创建一个OTA升级策略为“设备请求升级”,设备可通过这个topic发布升级请求,接收云端返回的可用的升级版本信息:

    请求topic: /sys/${productkey}/${devicekey}/ota/device/getversion

    响应topic: /sys/${productkey}/${devicekey}/ota/device/getversion_reply

开始前准备

步骤1 安装Device SDK

下载enos-mqtt-sdk-java。如果使用Maven,其依赖如下:

<dependency>
  <groupId>com.envisioniot</groupId>
  <artifactId>enos-mqtt</artifactId>
  <version>2.1.2</version>
</dependency>

步骤2:下载设备固件

  • 通过REST API getFile下载,查看API文档的步骤为:在EnOS 管理控制台导航栏中点击EnOS API > API文档 > Common File Service > getFile

  • 通过云端服务SDK

示例代码

以下为通过Java SDK进行设备端OTA能力开发的示例代码:

import com.envisioniot.enos.iot_mqtt_sdk.core.IConnectCallback;
import com.envisioniot.enos.iot_mqtt_sdk.core.MqttClient;
import com.envisioniot.enos.iot_mqtt_sdk.core.exception.EnvisionException;
import com.envisioniot.enos.iot_mqtt_sdk.core.msg.IMessageHandler;
import com.envisioniot.enos.iot_mqtt_sdk.core.msg.IMqttDeliveryMessage;
import com.envisioniot.enos.iot_mqtt_sdk.core.profile.DefaultProfile;
import com.envisioniot.enos.iot_mqtt_sdk.message.downstream.ota.OtaUpgradeCommand;
import com.envisioniot.enos.iot_mqtt_sdk.message.upstream.ota.*;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * device firmware over-the-air
 */
public class OtaSample {

    //Please use your own productKey, deviceKey, and deviceSecret
    static String productKey = "testPK";
    static String deviceKey = "testDK";
    static String deviceSecret = "testDS";

    //Use the actual mqtt-broker url
    static String brokerUrl = "tcp://{mqtt-broker-url}";

    static MqttClient client;

    public static void main(String[] args) throws Exception {
        client = new MqttClient(new DefaultProfile(brokerUrl, productKey, deviceKey, deviceSecret));
        initWithCallback(client);

        //Report firmware version first
        reportVersion("initVersion");

        //        upgradeFirmwareByCloudPush();


        //        upgradeFirmwareByDeviceReq();
    }

    public static void upgradeFirmwareByCloudPush() {
        client.setArrivedMsgHandler(OtaUpgradeCommand.class, new IMessageHandler<OtaUpgradeCommand, IMqttDeliveryMessage>() {
            @Override
            public IMqttDeliveryMessage onMessage(OtaUpgradeCommand otaUpgradeCommand, List<String> list) throws Exception {
                System.out.println("receive command: " + otaUpgradeCommand);

                Firmware firmware = otaUpgradeCommand.getFirmwareInfo();

                //TODO: download firmware from firmware.fileUrl

                //Mock reporting progress
                reportUpgradeProgress("20", "20");
                TimeUnit.SECONDS.sleep(2);

                reportUpgradeProgress("25", "25");
                TimeUnit.SECONDS.sleep(20);

                reportUpgradeProgress("80", "80");
                TimeUnit.SECONDS.sleep(20);

                //Firmware upgrade success, report new version
                reportVersion(otaUpgradeCommand.getFirmwareInfo().version);

                return null;
            }
        });
    }

    public static void upgradeFirmwareByDeviceReq() throws Exception {
        List<Firmware> firmwareList = getFirmwaresFromCloud();
        String version = null;
        for (Firmware firmware : firmwareList) {
            version = firmware.version;
            StringBuffer sb = new StringBuffer();
            sb.append("Firmware=>[");
            sb.append("version=" + firmware.version);
            sb.append("signMethod=" + firmware.signMethod);
            sb.append("sign=" + firmware.sign);
            sb.append("fileUrl=" + firmware.fileUrl);
            sb.append("fileSize=" + firmware.fileSize);
            sb.append("]");
            System.out.println(sb.toString());
        }
        if (version != null) {
            reportUpgradeProgress("20", "20");
            TimeUnit.SECONDS.sleep(10);
            reportUpgradeProgress("80", "80");
            TimeUnit.SECONDS.sleep(20);
            reportVersion(version);
        }
    }

    public static void reportVersion(String version) throws Exception {
        OtaVersionReportRequest.Builder builder = new OtaVersionReportRequest.Builder();
        builder.setProductKey(productKey).setDeviceKey(deviceKey).setVersion(version);
        OtaVersionReportRequest request = builder.build();
        System.out.println("send =>" + request.toString());
        client.fastPublish(builder.build());
    }

    private static void reportUpgradeProgress(String progress, String desc) throws Exception {
        OtaProgressReportRequest.Builder builder = new OtaProgressReportRequest.Builder();
        builder.setStep(progress).setDesc(desc);
        client.fastPublish(builder.build());
    }

    private static List<Firmware> getFirmwaresFromCloud() throws Exception {
        OtaGetVersionRequest.Builder builder = new OtaGetVersionRequest.Builder();
        builder.setProductKey(productKey).setDeviceKey(deviceKey);
        OtaGetVersionRequest request = builder.build();
        OtaGetVersionResponse response = client.publish(request);
        System.out.println("send getversion request =>" + request.toString());
        System.out.println("receive getversion response =>" + response.toString());
        return response.getFirmwareList();
    }

    private static void initWithCallback(MqttClient client) {
        System.out.println("start connect with callback ... ");
        try {
            client.connect(new IConnectCallback() {
                @Override
                public void onConnectSuccess() {
                    System.out.println("connect success");
                }

                @Override
                public void onConnectLost() {
                    System.out.println("onConnectLost");
                }

                @Override
                public void onConnectFailed(int reasonCode) {
                    System.out.println("onConnectFailed : " + reasonCode);
                }

            });
        } catch (EnvisionException e) {
            e.printStackTrace();
        }
    }
}

固件升级协议

设备端上报版本号

设备端可发送当前的固件版本号给云端,只有上报过固件版本号的设备,才能在控制台创建升级任务。

上行TOPIC

  • 请求topic: /sys/${productkey}/${devicekey}/ota/device/inform

  • 响应topic: /sys/${productkey}/${devicekey}/ota/device/inform_reply

请求数据格式:

{
    "id":"123",
    "version":"1.0",
    "method":"ota.device.inform",
    "params": {
        "version":"xxxxxxxx"
    }
}

响应数据格式:

{
    "id": "123",
    "code": 200,
    "data": {}
}

参数说明:

参数

类型

是否必须

描述

id

Long

可选

消息ID,保留值

version

String

必需

协议版本号,目前协议版本1.0

method

String

必需

请求方法,必需为ota.device.inform

params

Object

必需

上报版本号参数

version

String

必需

上报的版本号

code

Integer

必需

结果返回码,200代表请求成功执行

设备接收云端请求

在控制台创建云端推送的固件升级任务,符合升级条件的在线设备,将会收到云端推送过来的固件信息,如果当前设备不在线,将在下次上线时接收到升级请求。

下行TOPIC:

  • 请求topic: /sys/${productkey}/${devicekey}/ota/device/upgrade

  • 响应: /sys/${productkey}/${devicekey}/ota/device/upgrade_reply

请求数据格式:

{
    "id":"123",
    "version":"1.0",
    "method":"ota.device.upgrade",
    "params": {
        "version":"v1.0",
        "sign":"xxxxxxx",
        "signMethod":"md5",
        "fileUrl":"/1b30e0ea83002000/ota/kadak13",
        "fileSize":1024
    }
}

响应数据格式:

{
    "id": "123",
    "code": 200,
    "data": {}
}

参数说明:

参数

类型

是否必须

描述

id

Long

可选

消息ID,保留值

version

String

必需

协议版本号,目前协议版本1.0

method

String

必需

请求方法,必需为ota.device.getversion

params

Object

可选

code

Integer

必需

结果返回码,200代表请求执行成功

data

List

可选

返回的详细信息,JSON格式

version

String

必需

固件版本号

sign

String

必需

固件签名

signMethod

String

必需

固件签名算法

fileUrl

String

必需

固件文件url

fileSize

Long

必需

固件文件大小,单位:字节

设备上报固件升级进度

设备开始下载固件后,可以通过此topic上报升级进度,或上报升级错误的相关原因,运维人员可以在控制台中查看升级进度。

上行TOPIC:

  • 请求TOPIC:/sys/${productkey}/${devicekey}/ota/device/progress

  • 响应TOPIC:/sys/${productkey}/${devicekey}/ota/device/progress_reply

请求数据格式:

{
    "id":"123",
    "version":"1.0",
    "method":"ota.device.progress",
    "params": {
        "step":"1",
        "desc":"xxxxxxxx"
    }
}

响应数据格式:

{
    "id": "123",
    "code": 200,
    "data": {}
}

参数说明:

参数

类型

是否必须

描述

id

Long

可选

消息ID,保留值

version

String

必需

协议版本号,目前协议版本1.0

method

String

必需

请求方法,必需为ota.device.process

params

Object

必需

上报进度参数

step

String

必需

上报的进度,固件升级进度比,正常范围为[0, 100],升级失败 -1:代表升级失败, -2:代表下载失败, -3:代表校验失败, -4:代表烧写失败

desc

String

可选

当前步骤的描述信息,如果发生异常可以用此字段承载错误信息

code

Integer

必需

结果返回码,200代表请求成功执行

返回码

错误信息

释义

764

Device task not found

没有找到与该设备相关的ota升级任务

765

Device task not start

设备尚未启动ota升级任务

设备主动请求升级

对于非强制升级的场景,可以在控制台创建设备主动请求升级的升级任务,此时云端不会主动推送升级请求,而是等待设备主动请求,以获取可升级的版本信息,如果云端存在多个版本信息,则返回一个可升级版本的list,由设备或设备所有者选择进行升级。

上行TOPIC

  • 请求topic: /sys/${productkey}/${devicekey}/ota/device/getversion

  • 响应topic: /sys/${productkey}/${devicekey}/ota/device/getversion_reply

请求数据格式

{
    "id":"123",
    "version":"1.0",
    "method":"ota.device.getversion",
    "params": {
    }
}

响应数据格式:

{
    "id":"123",
    "code":200,
    "data": [
         {
            "version":"v1.0",
            "sign":"xxxxxxx",
            "signMethod":"md5",
            "fileUrl":"/1b30e0ea83002000/ota/kadak13",
            "fileSize":1024
        }
    ]
}

参数说明:

参数

类型

是否必须

描述

id

Long

可选

消息ID,保留值

version

String

必需

协议版本号,目前协议版本1.0

method

String

必需

请求方法,必需为ota.device.getversion

params

Object

可选

code

Integer

必需

结果返回码,200代表请求成功执行

data

List

可选

返回的详细信息,JSON格式,如果该设备在云端存在多个可升级的版本,将返回多版本信息

version

String

必需

固件版本号

sign

String

必需

固件签名

signMethod

String

必需

固件签名算法

fileUrl

String

必需

固件文件url

fileSize

Long

必需

固件文件大小,单位:字节

异常情况处理

对于固件升级过程的异常情况,可以在上报固件升级进度时,通过参数step和desc来表示异常情况。step小于0,表示异常情况,desc则是对异常情况的描述。

目前系统提供的异常情况step如下:

step值

含义

-1

升级失败

-2

下载失败

-3

校验失败

-4

烧写失败

若有其它自定义异常,可设置step为小于-4的数字,desc设置成相应的异常情况描述即可。