4.7 固件升级 – 大疆创新SDK技术支持论坛 您所在的位置:网站首页 大疆提示固件版本不一致 4.7 固件升级 – 大疆创新SDK技术支持论坛

4.7 固件升级 – 大疆创新SDK技术支持论坛

2024-05-21 12:44| 来源: 网络整理| 查看: 265

sample代码架构 sample代码实现 开发注意事项 4.7.1 sample代码架构

固件升级功能只在机场上支持,Pilot2上云不支持。sample对固件的功能实现主要包含如下步骤:

前端上传固件离线升级包 机场上报设备固件属性 前端点击固件升级,将请求发送给后端,创建固件升级任务;后端收到前端请求后,将固件升级消息下发给机场,机场对升级消息进行相应。 固件升级是一个持续的动作,机场会通过thing/product/{gateway_sn}/events上报固件升级进度,云端收到进度通知,并会调用websocket通知前端。 4.7.2 sample代码实现

1. 用户从设备下载页面下载固件离线升级包,以机场固件包为例,开发者需要从机场下载页面下载离线固件升级包。并在前端页面将固件升级包上传到后端。

2. 机场在固件版本发生变化(如机场开机、机场固件升级等)时,会通过thing/product/{device_sn}/state主题向云端上报固件版本号firmware_version。

所有mqtt消息都会经过InboundMessageRouter#determineTargetChannels方法,在该方法中找到通过DeviceTopicEnum#find方法找到消息对应的主题,如果是 thing/product/{device_sn}/state 主题的消息会被选择到ChannelName.INBOUND_STATE通道中。

public class InboundMessageRouter extends AbstractMessageRouter { @Override @Router(inputChannel = ChannelName.INBOUND) protected Collection determineTargetChannels(Message message) { // 获取消息头 MessageHeaders headers = message.getHeaders(); String topic = headers.get(MqttHeaders.RECEIVED_TOPIC).toString(); byte[] payload = (byte[])message.getPayload(); log.debug("received topic :{} \t payload :{}", topic, new String(payload)); // 根据消息topic不同,将消息转发到不同的channel中。 //  thing/product/{device_sn}/state主题消息会被转发到ChannelName.INBOUND_STATE通道中。 DeviceTopicEnum topicEnum = DeviceTopicEnum.find(topic); MessageChannel bean = (MessageChannel) SpringBeanUtils.getBean(topicEnum.getBeanName()); return Collections.singleton(bean); }}

上报固件版本号的消息被转发到ChannelName.INBOUND_STATE中后,通过

StateRouter#stateDataRouterFlow方法中定义的流程处理 public IntegrationFlow stateDataRouterFlow() { return IntegrationFlows .from(ChannelName.INBOUND_STATE) .transform(Message.class, source -> { // 将上报的state消息根据消息内容转换为Java DTO。 try { TopicStateRequest response = Common.getObjectMapper().readValue((byte[]) source.getPayload(), new TypeReference() {}); String topic = String.valueOf(source.getHeaders().get(MqttHeaders.RECEIVED_TOPIC)); String from = topic.substring((THING_MODEL_PRE + PRODUCT).length(), topic.indexOf(STATE_SUF)); return response.setFrom(from) // 根据消息的gateway_sn和消息内容将直播消息转换为对应的Java DTO。 .setData(Common.getObjectMapper().convertValue(response.getData(), getTypeReference(response.getGateway(), response.getData()))); } catch (IOException e) { throw new CloudSDKException(e); } }, null) // 根据消息内容序列化后的DTO class类型将消息提交给不同的channel进行处理 // 上报固件版本号的消息交给ChannelName.INBOUND_STATE_DOCK_FIRMWARE_VERSION通道处理 .route(response -> StateDataKeyEnum.find(response.getData().getClass()), mapping -> Arrays.stream(StateDataKeyEnum.values()).forEach(key -> mapping.channelMapping(key, key.getChannelName()))) .get();}

private Class getTypeReference(String gatewaySn, Object data) { Set keys = ((Map) data).keySet(); // 根据gateway_sn查找上线时的缓存,从缓存中获取当前上报state消息的设备是RC还是Dock switch (SDKManager.getDeviceSDK(gatewaySn).getType()) { case RC: return RcStateDataKeyEnum.find(keys).getClassType(); case DOCK: return DockStateDataKeyEnum.find(keys).getClassType(); default: throw new CloudSDKException(CloudSDKErrorEnum.WRONG_DATA, "Unexpected value: " + SDKManager.getDeviceSDK(gatewaySn).getType()); }}

ChannelName.INBOUND_STATE_DOCK_FIRMWARE_VERSION通道中的消息会被交给sdk包中的AbstractDeviceService#dockFirmwareVersionUpdate方法进行处理,sdk包中的默认实现是抛出异常,开发者需要定义AbstractDeviceService类的实现类,实现dockFirmwareVersionUpdate方法,处理固件版本号上报的逻辑,示例代码的实现为SDKDeviceService#dockFirmwareVersionUpdate:

@Overridepublic void dockFirmwareVersionUpdate(TopicStateRequest request, MessageHeaders headers) { // 上报的固件版本号为空,直接返回 if (!StringUtils.hasText(request.getData().getFirmwareVersion())) { return; }

DeviceDTO device = DeviceDTO.builder() .deviceSn(request.getFrom()) .firmwareVersion(request.getData().getFirmwareVersion()) // 固件是否需要一致性升级 .firmwareStatus(request.getData().getNeedCompatibleStatus() ? DeviceFirmwareStatusEnum.UNKNOWN : DeviceFirmwareStatusEnum.CONSISTENT_UPGRADE) .build(); // 调用deviceService#updateDevice更新固件升级状态 boolean isUpd = deviceService.updateDevice(device); if (!isUpd) { log.error("Data update of firmware version failed. SN: {}", request.getFrom()); }}

deviceService#updateDevice:

@Overridepublic Boolean updateDevice(DeviceDTO deviceDTO) { // 将设备的固件版本号保存进数据库 int update = mapper.update(this.deviceDTO2Entity(deviceDTO), new LambdaUpdateWrapper().eq(DeviceEntity::getDeviceSn, deviceDTO.getDeviceSn())); return update > 0;}

3. 前端通过Post ${url.manage.prefix}${url.manage.version}/devices/{workspace_id}/devices/ota创建固件升级任务,对应后端请求接口为DeviceController#createOtaJob:

@PostMapping("/{workspace_id}/devices/ota")public HttpResultResponse createOtaJob(@PathVariable("workspace_id") String workspaceId, @RequestBody List upgradeDTOS) { // 调用deviceService#createDeviceOtaJob处理 return deviceService.createDeviceOtaJob(workspaceId, upgradeDTOS);}

deviceService#createDeviceOtaJob:

@Overridepublic HttpResultResponse createDeviceOtaJob(String workspaceId, List upgradeDTOS) { // 查找固件版本是否存在,不存在直接报错 List deviceOtaFirmwares = deviceFirmwareService.getDeviceOtaFirmware(workspaceId, upgradeDTOS); if (deviceOtaFirmwares.isEmpty()) { return HttpResultResponse.error(); } // 检查设备是否在线 Optional deviceOpt = deviceRedisService.getDeviceOnline(deviceOtaFirmwares.get(0).getSn()); if (deviceOpt.isEmpty()) { throw new RuntimeException("Device is offline."); } // 检查机场是否可以进行固件升级,如机场需要在空闲状态下才能进行固件升级等 DeviceDTO device = deviceOpt.get(); String gatewaySn = DeviceDomainEnum.DOCK == device.getDomain() ? device.getDeviceSn() : device.getParentSn(); checkOtaConditions(gatewaySn); // 调用sdk包封装好的接口abstractFirmwareService#otaCreate,下发固件升级任务 TopicServicesResponse response = abstractFirmwareService.otaCreate( SDKManager.getDeviceSDK(gatewaySn), new OtaCreateRequest().setDevices(deviceOtaFirmwares)); ServicesReplyData serviceReply = response.getData(); String bid = response.getBid(); if (!serviceReply.getResult().isSuccess()) { return HttpResultResponse.error(serviceReply.getResult()); } deviceOtaFirmwares.forEach(deviceOta -> deviceRedisService.setFirmwareUpgrading(deviceOta.getSn(), EventsReceiver.builder().bid(bid).sn(deviceOta.getSn()).build())); return HttpResultResponse.success();}

abstractFirmwareService#otaCreate:

public TopicServicesResponse otaCreate(GatewayManager gateway, OtaCreateRequest request) { // 下发mqtt消息给设备,创建固件升级任务 return servicesPublish.publish( new TypeReference() {}, gateway.getGatewaySn(), FirmwareMethodEnum.OTA_CREATE.getMethod(), request);}

4. 设备上报固件升级进度。机场会通过thing/product/{gateway_sn}/events主题持续上报云端固件升级进度。所有mqtt消息都会经过InboundMessageRouter#determineTargetChannels方法,在该方法中找到通过DeviceTopicEnum#find方法找到消息对应的主题,如果是 thing/product/{device_sn}/events 主题的消息会被选择到ChannelName.INBOUND_EVENTS通道中。

public class InboundMessageRouter extends AbstractMessageRouter { @Override @Router(inputChannel = ChannelName.INBOUND) protected Collection determineTargetChannels(Message message) { // 获取消息头 MessageHeaders headers = message.getHeaders(); String topic = headers.get(MqttHeaders.RECEIVED_TOPIC).toString(); byte[] payload = (byte[])message.getPayload(); log.debug("received topic :{} \t payload :{}", topic, new String(payload)); // 根据消息topic不同,将消息转发到不同的channel中。 // thing/product/{gateway_sn}/events会被转发到ChannelName.INBOUND_EVENTS通道中。 DeviceTopicEnum topicEnum = DeviceTopicEnum.find(topic); MessageChannel bean = (MessageChannel) SpringBeanUtils.getBean(topicEnum.getBeanName()); return Collections.singleton(bean); }}

消息被转发到ChannelName.INBOUND_EVENTS中后,通过EventsRouter#stateDataRouterFlow方法中定义的流程处理

@Beanpublic IntegrationFlow eventsMethodRouterFlow() { return IntegrationFlows // 处理ChannelName.INBOUND_EVENT中的消息 .from(ChannelName.INBOUND_EVENTS) .transform(Message.class, source -> { try { // 将上报的消息序列化为Request实体 TopicEventsRequest data = Common.getObjectMapper().readValue((byte[]) source.getPayload(), TopicEventsRequest.class); String topic = String.valueOf(source.getHeaders().get(MqttHeaders.RECEIVED_TOPIC)); // 设置上报消息的sn return data.setFrom(topic.substring((THING_MODEL_PRE + PRODUCT).length(), topic.indexOf(EVENTS_SUF))) .setData(Common.getObjectMapper().convertValue(data.getData(), EventsMethodEnum.find(data.getMethod()).getClassType())); } catch (IOException e) { throw new CloudSDKException(e); } }, null) .route( // 固件升级进度上报的消息,经过该方法会将消息推送给ChannelName.INBOUND_EVENTS_OTA_PROGRESS通道处理 response -> EventsMethodEnum.find(response.getMethod()), mapping -> Arrays.stream(EventsMethodEnum.values()).forEach( methodEnum -> mapping.channelMapping(methodEnum, methodEnum.getChannelName()))) .get();}

ChannelName.INBOUND_EVENTS_OTA_PROGRESS的消息会交给AbstractFirmwareService#otaProgress处理,在sdk中默认实现为抛出异常信息,开发者需要定义AbstractFirmwareService的实现类,实现otaProgress方法。示例代码中有默认实现:DeviceFirmwareServiceImpl#otaProgress

@Overridepublic TopicEventsResponse otaProgress(TopicEventsRequest request, MessageHeaders headers) { // 获取gateway_sn String sn = request.getGateway();

EventsReceiver eventsReceiver = new EventsReceiver() .setBid(request.getBid()) .setOutput(request.getData().getOutput()) .setResult(request.getData().getResult());

log.info("SN: {}, {} ===> Upgrading progress: {}", sn, request.getMethod(), eventsReceiver.getOutput().getProgress()); // 如果固件升级没有成功,打印报错日志 if (!eventsReceiver.getResult().isSuccess()) { log.error("SN: {}, {} ===> Error: {}", sn, request.getMethod(), eventsReceiver.getResult()); } // 检查设备是否在线,没有在线,直接返回 Optional deviceOpt = deviceRedisService.getDeviceOnline(sn); if (deviceOpt.isEmpty()) { return null; }

OtaProgressStatusEnum statusEnum = eventsReceiver.getOutput().getStatus(); DeviceDTO device = deviceOpt.get(); // 通过websocket协议,将固件升级消息推送给前端。 handleProgress(device.getWorkspaceId(), sn, eventsReceiver, statusEnum.isEnd()); handleProgress(device.getWorkspaceId(), device.getChildDeviceSn(), eventsReceiver, statusEnum.isEnd());

return new TopicEventsResponse().setData(MqttReply.success());}

 

4.7.3 开发注意事项

1. 固件升级目前需要注意关闭4G,并保证飞行器开机后进行升级。

2. 机场和飞机固件某些版本有放回滚策略,升级报错,请检查是否触发放回滚。可以使用DJI Assistant2手动升级查看是否报错。

3. 固件升级接口也可实现固件降级的逻辑。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有