transport mqtt普通设备OTA升级分析
首先学习thingsboard怎么实现ota升级的,学到它的精髓后,就可以山寨了。
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();
现在我们知道怎么获取升级包的数据了,想获取哪个设备的升级包,就能获取哪个设备的升级包,这个知识点到手,天下我有。