大疆上云api 源码解析(上下线功能)

基础概念

  • 网关设备:能够直接连接云平台的设备,如遥控器、大疆机场均为网关设备
  • MQTT: 物联网通用的MQTT5.0标准协议,是一个客户端服务端架构的发布/定义模式的消息传输协议。

Pilot

当pilot2与云平台建立mqtt连接后,进行设备绑定后。
设备向云平台推送消息,topic为sys/product/{gateway_sn}/status 进行设备上线

 //  设备上线
{
	"tid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx",
	"bid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx",
	"method": "update_topo",
	"timestamp": 1234567890123,
	"data": {
		"domain": 2,
		"type": 119,
		"sub_type": 0,
		"device_secret": "secret",
		"nonce": "nonce",
		"version": "1",
		"sub_devices": [
			{
				"sn": "drone001",
				"domain": 0,
				"type": 60,
				"sub_type": 0,
				"index": "A",
				"device_secret": "secret",
				"nonce": "nonce",
				"version": "1"
			}
		]
	}
}
 //设备下线 
{
    "tid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx",
    "bid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx",
    "method": "update_topo",
    "timestamp": 1234567890123,
    "data": {
        "domain": 2,
        "type": 119,
        "sub_type": 0,
        "device_secret":"secret",
        "nonce":"nonce",
        "version": "1",
        "sub_devices":[]
    }
}
Java代码部分
  1. 继承org.springframework.integration.router.AbstractMessageRouter重写determineTargetChannels方法,根据不同的topic进入不同的MessageChannel
package com.dji.sdk.mqtt;

import com.dji.sdk.common.SpringBeanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.integration.annotation.Router;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.integration.router.AbstractMessageRouter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.Collections;

/**
 *
 * @author sean.zhou
 * @date 2021/11/10
 * @version 0.1
 */
@Component
public class InboundMessageRouter extends AbstractMessageRouter {

    private static final Logger log = LoggerFactory.getLogger(InboundMessageRouter.class);

    /**
     * All mqtt broker messages will arrive here before distributing them to different channels.
     * @param message message from mqtt broker
     * @return channel
     */
    @Override
    @Router(inputChannel = ChannelName.INBOUND)
    protected Collection<MessageChannel> 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));

        CloudApiTopicEnum topicEnum = CloudApiTopicEnum.find(topic);
        MessageChannel bean = (MessageChannel) SpringBeanUtils.getBean(topicEnum.getBeanName());

        return Collections.singleton(bean);
    }
}

  1. 根据上述topic 消息处理进入statusRouter中,最终根据消息中的sub_devices是不是空进入ChannelName.INBOUND_STATUS_OFFLINE或者ChannelName.INBOUND_STATUS_ONLINE
    在这里插入图片描述
package com.dji.sdk.mqtt.status;

import com.dji.sdk.cloudapi.device.UpdateTopo;
import com.dji.sdk.common.Common;
import com.dji.sdk.exception.CloudSDKException;
import com.dji.sdk.mqtt.ChannelName;
import com.dji.sdk.mqtt.MqttGatewayPublish;
import com.fasterxml.jackson.core.type.TypeReference;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.Objects;
import java.util.Optional;

import static com.dji.sdk.mqtt.TopicConst.*;

/**
 *
 * @author sean.zhou
 * @date 2021/11/12
 * @version 0.1
 */
@Configuration
public class StatusRouter {

    @Resource
    private MqttGatewayPublish gatewayPublish;

    @Bean
    public IntegrationFlow statusRouterFlow() {
        return IntegrationFlows
        		// 从ChannelName.INBOUND_STATUS 中来的消息都由我处理
                .from(ChannelName.INBOUND_STATUS)
                .transform(Message.class, source -> {
                    try {
                        TopicStatusRequest<UpdateTopo> response = Common.getObjectMapper().readValue((byte[]) source.getPayload(), new TypeReference<TopicStatusRequest<UpdateTopo>>() {});
                        String topic = String.valueOf(source.getHeaders().get(MqttHeaders.RECEIVED_TOPIC));
                        return response.setFrom(topic.substring((BASIC_PRE + PRODUCT).length(), topic.indexOf(STATUS_SUF)));
                    } catch (IOException e) {
                        throw new CloudSDKException(e);
                    }
                }, null)
                .<TopicStatusRequest<UpdateTopo>, Boolean>route(
                        response -> Optional.ofNullable(response.getData()).map(UpdateTopo::getSubDevices).map(CollectionUtils::isEmpty).orElse(true),
                        mapping -> mapping.channelMapping(true, ChannelName.INBOUND_STATUS_OFFLINE)
                                .channelMapping(false, ChannelName.INBOUND_STATUS_ONLINE))
                .get();
    }

    @Bean
    public IntegrationFlow replySuccessStatus() {
        return IntegrationFlows
                .from(ChannelName.OUTBOUND_STATUS)
                .handle(this::publish)
                .nullChannel();

    }

    private TopicStatusResponse publish(TopicStatusResponse request, MessageHeaders headers) {
        if (Objects.isNull(request)) {
            return null;
        }
        gatewayPublish.publishReply(request, headers);
        return request;
    }
}

3.进行上线逻辑后,最终进入ChannelName.OUTBOUND_STATUS中
在这里插入图片描述

@Override
    public TopicStatusResponse<MqttReply> updateTopoOnline(TopicStatusRequest<UpdateTopo> request, MessageHeaders headers) {
    	// 获取子设备
        UpdateTopoSubDevice updateTopoSubDevice = request.getData().getSubDevices().get(0);
        // 获取sn
        String deviceSn = updateTopoSubDevice.getSn();
		// 从redis中查询是否在线 (如果之前已经上线,会将它们存放到Redis中,防止重复上线)
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(deviceSn);
        // 查询网关设备是否在线
        Optional<DeviceDTO> gatewayOpt = deviceRedisService.getDeviceOnline(request.getFrom());
        // 将设备相关信息注册到网关管理器中
        GatewayManager gatewayManager = SDKManager.registerDevice(request.getFrom(), deviceSn,
                request.getData().getDomain(), request.getData().getType(),
                request.getData().getSubType(), request.getData().getThingVersion(), updateTopoSubDevice.getThingVersion());
		// 如果设备和网关都已经上线了
        if (deviceOpt.isPresent() && gatewayOpt.isPresent()) {
        	// 再次上线推送上线成功消息
            deviceOnlineAgain(deviceOpt.get().getWorkspaceId(), request.getFrom(), deviceSn);
            return new TopicStatusResponse<MqttReply>().setData(MqttReply.success());
        }
		// 修改飞行器的绑定网关(有可能之前绑定在其它网关)
        changeSubDeviceParent(deviceSn, request.getFrom());
		// 转换成DTO
        DeviceDTO gateway = deviceGatewayConvertToDevice(request.getFrom(), request.getData());
        // 保存网关信息到数据库,存入redis中,设备上线
        Optional<DeviceDTO> gatewayEntityOpt = onlineSaveDevice(gateway, deviceSn, null);
        if (gatewayEntityOpt.isEmpty()) {
            log.error("Failed to go online, please check the status data or code logic.");
            return null;
        }
       
        DeviceDTO subDevice = subDeviceConvertToDevice(updateTopoSubDevice);
        // 保存飞行器信息到数据库,存入redis,设备上线
        Optional<DeviceDTO> subDeviceEntityOpt = onlineSaveDevice(subDevice, null, gateway.getDeviceSn());
        if (subDeviceEntityOpt.isEmpty()) {
            log.error("Failed to go online, please check the status data or code logic.");
            return null;
        }
        subDevice = subDeviceEntityOpt.get();
        gateway = gatewayEntityOpt.get();
        // 如果是机场,将飞行器绑定到机场的workspace中
        dockGoOnline(gateway, subDevice);
        // 订阅网关设备相关消息topic
        deviceService.gatewayOnlineSubscribeTopic(gatewayManager);

        if (!StringUtils.hasText(subDevice.getWorkspaceId())) {
            return new TopicStatusResponse<MqttReply>().setData(MqttReply.success());
        }

        // 订阅飞行器相关消息topic
        deviceService.subDeviceOnlineSubscribeTopic(gatewayManager);
        // 发布设备上线消息,通过websocket连接通知其他设备更新设备拓扑
        deviceService.pushDeviceOnlineTopo(gateway.getWorkspaceId(), gateway.getDeviceSn(), subDevice.getDeviceSn());

        log.debug("{} online.", subDevice.getDeviceSn());
        return new TopicStatusResponse<MqttReply>().setData(MqttReply.success());
    }
  1. 下线功能消息路由跳转同上,主要看不同的地方
 @Override
    public TopicStatusResponse<MqttReply> updateTopoOffline(TopicStatusRequest<UpdateTopo> request, MessageHeaders headers) {
    	// 获取网关管理器
        GatewayManager gatewayManager = SDKManager.registerDevice(request.getFrom(), null,
                request.getData().getDomain(), request.getData().getType(),
                request.getData().getSubType(), request.getData().getThingVersion(), null);
         // 订阅网关相关topic 主题
        deviceService.gatewayOnlineSubscribeTopic(gatewayManager);
        // 查找网关对应的上线设备
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(request.getFrom());
        if (deviceOpt.isEmpty()) {
            // 如果是null 表示第一次上线,保存网关信息
            DeviceDTO gatewayDevice = deviceGatewayConvertToDevice(request.getFrom(), request.getData());
            Optional<DeviceDTO> gatewayDeviceOpt = onlineSaveDevice(gatewayDevice, null, null);
            if (gatewayDeviceOpt.isEmpty()) {
                return null;
            }
            // 推送设备拓扑消息
            deviceService.pushDeviceOnlineTopo(gatewayDeviceOpt.get().getWorkspaceId(), request.getFrom(), null);
            return new TopicStatusResponse<MqttReply>().setData(MqttReply.success());
        }

        String deviceSn = deviceOpt.get().getChildDeviceSn();
        if (!StringUtils.hasText(deviceSn)) {
            return new TopicStatusResponse<MqttReply>().setData(MqttReply.success());
        }
		// 取消订阅飞行器相关主题
        deviceService.subDeviceOffline(deviceSn);
        return new TopicStatusResponse<MqttReply>().setData(MqttReply.success());
    }
  1. 存在全局定时任务检查设备在线状态
 @Scheduled(initialDelay = 10, fixedRate = 30, timeUnit = TimeUnit.SECONDS)
   private void deviceStatusListen() {
       int start = RedisConst.DEVICE_ONLINE_PREFIX.length();
   	// 获取所有在线的设备
       RedisOpsUtils.getAllKeys(RedisConst.DEVICE_ONLINE_PREFIX + "*").forEach(key -> {
           long expire = RedisOpsUtils.getExpire(key);
           // 如果过期时间小于30s
           if (expire <= 30) {
               DeviceDTO device = (DeviceDTO) RedisOpsUtils.get(key);
               if (null == device) {
                   return;
               }
               if (DeviceDomainEnum.DRONE == device.getDomain()) {
               //  飞行器下线
                   deviceService.subDeviceOffline(key.substring(start));
               } else {
               // 网关设备下线
                   deviceService.gatewayOffline(key.substring(start));
               }
               RedisOpsUtils.del(key);
           }
       });

       log.info("Subscriptions: {}", Arrays.toString(topicService.getSubscribedTopic()));
   }
  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值