开发设备端OTA能力¶
任务背景信息¶
设备端OTA升级很大程度上依赖于设备自身的物理空间和功能实现。针对硬件开发者,EnOS平台提供包含OTA能力的Device SDK,支持以下功能:
- 设备上报固件版本信息:设备与云端建连时上报一次自身的版本信息,云端记录设备的版本号; 
- 设备接收云端升级推送请求:云端推送升级请求后,设备可接收到升级请求并下载固件文件; 
- 设备向云端主动请求可升级的固件版本:支持设备主动向云端请求,并查询可升级的版本信息; 
- 设备上报升级进度和升级失败的错误原因:设备进入升级流程后,可向云端上报升级的进度或升级失败的原因,方便运维人员进行远程升级管理; 
备注
一般而言,设备的OTA升级需要具备相应的物理条件,EnOS平台提供设备OTA升级所需的云端消息通道和文件下载服务,并提供设备端SDK方便开发者调用接口能力,而真正的设备端固件升级则依赖于设备端Flash是否具备足够的物理空间,开发者需自行实现文件下载后的固件切换、引导和重启等业务逻辑。
固件升级TOPIC¶
EnOS云端提供的固件升级的消息topic,升级请求支持使用MQTT进行消息的发送和订阅:
- 设备端上报固件版本给云端:设备端发布权限,用于设备与云端每次建连成功后上报当前的版本号信息: - 请求topic: - /sys/${productkey}/${devicekey}/ota/device/inform- 响应topic: - /sys/${productkey}/${devicekey}/ota/device/inform_reply
- 设备端接收云端OTA请求:设备端订阅权限,用于接收云端推送的OTA升级通知,包含固件名称、版本号、MD5和下载URL等信息: - 请求topic: - /sys/${productkey}/${devicekey}/ota/device/upgrade- 响应topic: - /sys/${productkey}/${devicekey}/ota/device/upgrade_reply
- 设备端上报固件升级进度:设备通过HTTPS循环请求固件下载,并通过这个topic上报固件升级事件、错误码和下载进度的百分比等信息: - 请求topic: - /sys/${productkey}/${devicekey}/ota/device/progress- 响应topic: - /sys/${productkey}/${devicekey}/ota/device/progress_reply
- 设备主动请求可升级的固件版本:云端创建一个OTA升级策略为“设备请求升级”,设备可通过这个topic发布升级请求,接收云端返回的可用的升级版本信息: - 请求topic: - /sys/${productkey}/${devicekey}/ota/device/getversion- 响应topic: - /sys/${productkey}/${devicekey}/ota/device/getversion_reply
开始前准备¶
- 了解EnOS的OTA升级流程,参见设备固件升级概述。 
步骤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设置成相应的异常情况描述即可。