实现 thingsboard 网关下设备 OTA 升级功能(三)

491 阅读3分钟

transport mqtt普通设备OTA升级分析

首先学习thingsboard怎么实现ota升级的,学到它的精髓后,就可以山寨了。

升级包流转图.png

channelRead()收到设备请求升级包的指令后,会来到processDevicePublish()方法处理,我们看到这几行:


} else if ((fwMatcher = FW_REQUEST_PATTERN.matcher(topicName)).find()) {
    getOtaPackageCallback(ctx, mqttMsg, msgId, fwMatcher, OtaPackageType.FIRMWARE);
} else if ((fwMatcher = SW_REQUEST_PATTERN.matcher(topicName)).find()) {
    getOtaPackageCallback(ctx, mqttMsg, msgId, fwMatcher, OtaPackageType.SOFTWARE);

这几行大概意思就是,匹配topic,若topic是请求升级包,则调用 getOtaPackageCallback()处理,所以关键就是看 getOtaPackageCallback()方法,看它是怎么把升级包发送给设备的。

private void getOtaPackageCallback(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg, int msgId, Matcher fwMatcher, OtaPackageType type) {
    String payload = mqttMsg.content().toString(UTF8);
    int chunkSize = StringUtils.isNotEmpty(payload) ? Integer.parseInt(payload) : 0;
    String requestId = fwMatcher.group("requestId");
    int chunk = Integer.parseInt(fwMatcher.group("chunk"));

    if (chunkSize > 0) {
        this.chunkSizes.put(requestId, chunkSize);
    } else {
        chunkSize = chunkSizes.getOrDefault(requestId, 0);
    }

    if (chunkSize > context.getMaxPayloadSize()) {
        sendOtaPackageError(ctx, PAYLOAD_TOO_LARGE);
        return;
    }

    String otaPackageId = otaPackSessions.get(requestId);

    if (otaPackageId != null) {
        sendOtaPackage(ctx, mqttMsg.variableHeader().packetId(), otaPackageId, requestId, chunkSize, chunk, type);
    } else {
        TransportProtos.SessionInfoProto sessionInfo = deviceSessionCtx.getSessionInfo();
        TransportProtos.GetOtaPackageRequestMsg getOtaPackageRequestMsg = TransportProtos.GetOtaPackageRequestMsg.newBuilder()
                .setDeviceIdMSB(sessionInfo.getDeviceIdMSB())
                .setDeviceIdLSB(sessionInfo.getDeviceIdLSB())
                .setTenantIdMSB(sessionInfo.getTenantIdMSB())
                .setTenantIdLSB(sessionInfo.getTenantIdLSB())
                .setType(type.name())
                .build();
        transportService.process(deviceSessionCtx.getSessionInfo(), getOtaPackageRequestMsg,
                new OtaPackageCallback(ctx, msgId, getOtaPackageRequestMsg, requestId, chunkSize, chunk));
    }
}

getOtaPackageCallback方法前面一大半是在提取参数,后面几行才干活。

String otaPackageId = otaPackSessions.get(requestId); 这行作用是缓存加速,若 otaPackageId != null,直接发送,我们先看直接发送,看它是怎么发送的,升级包从何而来。

private void sendOtaPackage(ChannelHandlerContext ctx, int msgId, String firmwareId, String requestId, int chunkSize, int chunk, OtaPackageType type) {
    log.trace("[{}] Send firmware [{}] to device!", sessionId, firmwareId);
    ack(ctx, msgId);
    try {
        byte[] firmwareChunk = context.getOtaPackageDataCache().get(firmwareId, chunkSize, chunk);
        deviceSessionCtx.getPayloadAdaptor()
                .convertToPublish(deviceSessionCtx, firmwareChunk, requestId, chunk, type)
                .ifPresent(deviceSessionCtx.getChannel()::writeAndFlush);
    } catch (Exception e) {
        log.trace("[{}] Failed to send firmware response!", sessionId, e);
    }
}

拿到firmwareId,它竟然就能 context.getOtaPackageDataCache() 这样获取到固件升级包的二进制数据,获取到升级包数据后,就写进Channel里,发送出去。

context.getOtaPackageDataCache()获取缓存服务,我使用的是redis缓存,就获取redis缓存服务。升级包是怎么存进去,在哪存的。

接下来,回到前面,若 otaPackageId == null,第一次获取升级包,它首先构造一条消息 getOtaPackageRequestMsg,然后transportService.process()发送出去,发给谁了,我猜发送thingsboard-core了,让 thingsboard-core 从数据库中读取DeviceId设备的的升级包,并将升级包存在缓存中,thingsboard-core 就只返回firmwareId,core就说,需要的话就自己去缓存中读去。core返回的数据firmwareId会传入 OtaPackageCallback的success()方法参数中。

private class OtaPackageCallback implements TransportServiceCallback<TransportProtos.GetOtaPackageResponseMsg> {
    private final ChannelHandlerContext ctx;
    private final int msgId;
    private final TransportProtos.GetOtaPackageRequestMsg msg;
    private final String requestId;
    private final int chunkSize;
    private final int chunk;

    OtaPackageCallback(ChannelHandlerContext ctx, int msgId, TransportProtos.GetOtaPackageRequestMsg msg, String requestId, int chunkSize, int chunk) {
        this.ctx = ctx;
        this.msgId = msgId;
        this.msg = msg;
        this.requestId = requestId;
        this.chunkSize = chunkSize;
        this.chunk = chunk;
    }

    @Override
    public void onSuccess(TransportProtos.GetOtaPackageResponseMsg response) {
        if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus())) {
            OtaPackageId firmwareId = new OtaPackageId(new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB()));
            otaPackSessions.put(requestId, firmwareId.toString());
            sendOtaPackage(ctx, msgId, firmwareId.toString(), requestId, chunkSize, chunk, OtaPackageType.valueOf(response.getType()));
        } else {
            sendOtaPackageError(ctx, response.getResponseStatus().toString());
        }
    }

    @Override
    public void onError(Throwable e) {
        log.trace("[{}] Failed to get firmware: {}", sessionId, msg, e);
        ctx.close();
    }
}

onSuccess()方法,拿到 firmwareId后,就去缓存中读升级包了,读到就发送出去。

至于升级包在哪行代码从数据库中读取的,又在哪行代码将升级包存入缓存中,我也不知道,但我知道了一个关键信息,我只需要构造下面这样一条消息,消息里面包含设备ID信息,再发送出去,我就能获取到该设备ID的升级包

TransportProtos.GetOtaPackageRequestMsg getOtaPackageRequestMsg = TransportProtos.GetOtaPackageRequestMsg.newBuilder()
        .setDeviceIdMSB(sessionInfo.getDeviceIdMSB())
        .setDeviceIdLSB(sessionInfo.getDeviceIdLSB())
        .setTenantIdMSB(sessionInfo.getTenantIdMSB())
        .setTenantIdLSB(sessionInfo.getTenantIdLSB())
        .setType(type.name())
        .build();

现在我们知道怎么获取升级包的数据了,想获取哪个设备的升级包,就能获取哪个设备的升级包,这个知识点到手,天下我有。