这一章节主要是解析机场和无人机的OSD位置数据上报处理流程,废话不多说,上源码!!!!有了上一章节的准备,本地环境请自行搭建。
源码解析(设备上报OSD位置数据处理)
1、OSD数据路由点
处理逻辑如下:
/**
*
* @author sean.zhou
* @date 2021/11/17
* @version 0.1
*/
@Configuration
public class OsdRouter {
/**
* 配置OSD路由器流程,该流程从指定的输入通道接收消息,进行处理,并根据处理结果将消息路由到不同的通道。
*
* @return 配置好的IntegrationFlow实例。
*/
@Bean
public IntegrationFlow osdRouterFlow() {
return IntegrationFlows
// 从名为INBOUND_OSD的通道接收消息
.from(ChannelName.INBOUND_OSD)
// 对接收到的消息进行转换,提取并修改其负载
.transform(Message.class, source -> {
try {
// 使用Jackson的ObjectMapper将字节数组负载反序列化为TopicOsdRequest对象
TopicOsdRequest response = Common.getObjectMapper().readValue((byte[]) source.getPayload(), new TypeReference<TopicOsdRequest>() {});
// 从消息头中获取MQTT接收到的主题,并解析出特定的部分作为新的来源信息
String topic = String.valueOf(source.getHeaders().get(MqttHeaders.RECEIVED_TOPIC));
// 修改来源信息,基于预定义的前缀和后缀截取主题中的特定部分
return response.setFrom(topic.substring((THING_MODEL_PRE + PRODUCT).length(), topic.indexOf(OSD_SUF)));
} catch (IOException e) {
// 如果反序列化失败,则抛出自定义的异常
throw new CloudSDKException(e);
}
}, null)
// 对转换后的TopicOsdRequest对象进行处理
.<TopicOsdRequest>handle((response, headers) -> {
// 根据响应中的网关ID获取对应的网关管理器
GatewayManager gateway = SDKManager.getDeviceSDK(response.getGateway());
// 根据网关类型和是否为网关本身确定设备类型枚举
OsdDeviceTypeEnum typeEnum = OsdDeviceTypeEnum.find(gateway.getType(), response.getFrom().equals(response.getGateway()));
// 处理响应中的数据
Map<String, Object> data = (Map<String, Object>) response.getData();
// 如果不是网关类型,则处理负载数据,添加额外的索引位置信息
if (!typeEnum.isGateway()) {
List payloadData = (List) data.getOrDefault(PayloadModelConst.PAYLOAD_KEY, new ArrayList<>());
// 遍历所有索引位置,如果数据中包含这些键,则将其值添加到payloadData中
PayloadModelConst.getAllIndexWithPosition().stream().filter(data::containsKey)
.map(data::get).forEach(payloadData::add);
data.put(PayloadModelConst.PAYLOAD_KEY, payloadData);
}
// 使用ObjectMapper将处理后的数据转换回相应的类型,并更新响应对象
return response.setData(Common.getObjectMapper().convertValue(data, typeEnum.getClassType()));
})
// 根据处理后的响应数据类型将消息路由到不同的通道
.<TopicOsdRequest, OsdDeviceTypeEnum>route(response -> OsdDeviceTypeEnum.find(response.getData().getClass()),
mapping -> Arrays.stream(OsdDeviceTypeEnum.values()).forEach(key -> mapping.channelMapping(key, key.getChannelName())))
.get(); // 完成并返回配置好的IntegrationFlow实例
}
}
2、数据处理业务逻辑入口
/**
* @author sean
* @version 1.7
* @date 2023/6/30
*/
public class AbstractDeviceService {
/**
* 机场osd数据入口
* @param request data
* @param headers The headers for a {@link Message}.
* @return status_reply
*/
@ServiceActivator(inputChannel = ChannelName.INBOUND_OSD_DOCK)
public void osdDock(TopicOsdRequest<OsdDock> request, MessageHeaders headers) {
throw new UnsupportedOperationException("osdDock not implemented");
}
/**
* 机场无人机osd数据入口
* @param request data
* @param headers The headers for a {@link Message}.
* @return status_reply
*/
@ServiceActivator(inputChannel = ChannelName.INBOUND_OSD_DOCK_DRONE)
public void osdDockDrone(TopicOsdRequest<OsdDockDrone> request, MessageHeaders headers) {
throw new UnsupportedOperationException("osdDockDrone not implemented");
}
/**
* 遥控器osd数据入口
* @param request data
* @param headers The headers for a {@link Message}.
* @return status_reply
*/
@ServiceActivator(inputChannel = ChannelName.INBOUND_OSD_RC)
public void osdRemoteControl(TopicOsdRequest<OsdRemoteControl> request, MessageHeaders headers) {
throw new UnsupportedOperationException("osdRemoteControl not implemented");
}
/**
* 遥控无人机osd数据入口
* @param request data
* @param headers The headers for a {@link Message}.
* @return status_reply
*/
@ServiceActivator(inputChannel = ChannelName.INBOUND_OSD_RC_DRONE)
public void osdRcDrone(TopicOsdRequest<OsdRcDrone> request, MessageHeaders headers) {
throw new UnsupportedOperationException("osdRcDrone not implemented");
}
}
3、实际处理业务逻辑代码解析
(1)机场OSD数据
上报json如下:
{
"tid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx",
"bid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx",
"timestamp": 1667220916697,
"data": {
"network_state": {
"type": 2,
"quality": 0,
"rate": 5.0970001220703125
},
"drone_charge_state": {
"state": 0,
"capacity_percent": 100
},
"drone_in_dock": 1,
"rainfall": 0,
"wind_speed": 0,
"environment_temperature": 24,
"temperature": 24.9,
"humidity": 62,
"latitude": 22.907809968,
"longitude": 113.703482143,
"height": 34.174125671386719,
"alternate_land_point": {
"latitude": 22.907898319908661,
"longitude": 113.70347329676635,
"safe_land_height": 0,
"is_configured": 1
},
"first_power_on": 1631945855969,
"position_state": {
"is_calibration": 1,
"is_fixed": 2,
"quality": 5,
"gps_number": 6,
"rtk_number": 25
},
"storage": {
"total": 82045336,
"used": 51772
},
"mode_code": 1,
"cover_state": 0,
"supplement_light_state": 0,
"emergency_stop_state": 0,
"air_conditioner_mode": 0,
"battery_store_mode": 1,
"alarm_state": 0,
"putter_state": 0,
"sub_device": {
"device_sn": "1581F5BKD225D00BP891",
"device_model_key": "0-67-0",
"device_online_status": 0,
"device_paired": 1
}
},
"gateway": "dock_sn"
}
实际代码如下:
@Override
public void osdDock(TopicOsdRequest<OsdDock> request, MessageHeaders headers) {
String from = request.getFrom();
//查询当前osd数据对应的设备是否在线
Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(from);
//设备不在线或工作空间不存在,不处理
if (deviceOpt.isEmpty() || !StringUtils.hasText(deviceOpt.get().getWorkspaceId())) {
deviceOpt = deviceService.getDeviceBySn(from);
if (deviceOpt.isEmpty()) {
log.error("Please restart the drone.");
return;
}
}
DeviceDTO device = deviceOpt.get();
//机场未绑定
if (!StringUtils.hasText(device.getWorkspaceId())) {
log.error("Please bind the dock first.");
}
//如果设备有子设备-飞机,查询飞机的状态并设置到机场信息中
if (StringUtils.hasText(device.getChildDeviceSn())) {
deviceService.getDeviceBySn(device.getChildDeviceSn()).ifPresent(device::setChildren);
}
//保存机场在线情况到redis
deviceRedisService.setDeviceOnline(device);
//覆盖/保存机场osd信息到redis
fillDockOsd(from, request.getData());
//推送机场位置信息到连接了websoket的web端
deviceService.pushOsdDataToWeb(device.getWorkspaceId(), BizCodeEnum.DOCK_OSD, from, request.getData());
}
//判断覆盖redis里面存储的机场osd信息
private void fillDockOsd(String dockSn, OsdDock dock) {
Optional<OsdDock> oldDockOpt = deviceRedisService.getDeviceOsd(dockSn, OsdDock.class);
//机场的任务编码不为空不处理数据 说明机场状态不变
if (Objects.nonNull(dock.getJobNumber())) {
return;
}
//不存在机场状态信息数据
if (oldDockOpt.isEmpty()) {
deviceRedisService.setDeviceOsd(dockSn, dock);
return;
}
OsdDock oldDock = oldDockOpt.get();
//判断机场模式不为空 使用旧数据机场连接状态覆盖新的数据连接状态 修改redis并返回
if (Objects.nonNull(dock.getModeCode())) {
dock.setDrcState(oldDock.getDrcState());
deviceRedisService.setDeviceOsd(dockSn, dock);
return;
}
//判断连接状态不为空 使用新数据机场连接状态覆盖老的数据连接状态 修改redis
if (Objects.nonNull(dock.getDrcState()) ) {
oldDock.setDrcState(dock.getDrcState());
deviceRedisService.setDeviceOsd(dockSn, oldDock);
}
}
(2)机场对应无人机OSD数据
对应上报的json如下:
{
"bid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx",
"data": {
"52-0-0": {
"measure_target_altitude": 0,
"measure_target_distance": 591.70001220703125,
"measure_target_error_state": 1,
"measure_target_latitude": 0,
"measure_target_longitude": 0,
"payload_index": "52-0-0",
"version": 1
},
"activation_time": 1667935211,
"attitude_head": 41.7,
"attitude_pitch": 2.7,
"attitude_roll": 0,
"battery": {
"batteries": [
{
"capacity_percent": 95,
"firmware_version": "02.00.20.44",
"high_voltage_storage_days": 16,
"index": 0,
"loop_times": 137,
"sn": "4BUPJ99DAD009W",
"sub_type": 0,
"temperature": 33.3,
"type": 0,
"voltage": 24303
},
{
"capacity_percent": 85,
"firmware_version": "02.00.20.44",
"high_voltage_storage_days": 5,
"index": 1,
"loop_times": 82,
"sn": "4BUPJ9EDAD01CE",
"sub_type": 0,
"temperature": 32,
"type": 0,
"voltage": 24311
}
],
"capacity_percent": 90,
"landing_power": 0,
"remain_flight_time": 0,
"return_home_power": 0
},
"distance_limit_status": {
"distance_limit": 5000,
"state": 0
},
"elevation": 0,
"firmware_version": "05.01.0214",
"gear": 1,
"height": 38.417465209960938,
"height_limit": 120,
"home_distance": 0,
"horizontal_speed": 0,
"latitude": 0,
"longitude": 0,
"maintain_status": {
"maintain_status_array": [
{
"last_maintain_flight_time": 0,
"last_maintain_time": 0,
"last_maintain_type": 1,
"state": 0
},
{
"last_maintain_flight_time": 0,
"last_maintain_time": 0,
"last_maintain_type": 2,
"state": 0
},
{
"last_maintain_flight_time": 0,
"last_maintain_time": 0,
"last_maintain_type": 3,
"state": 0
}
]
},
"mode_code": 0,
"night_lights_state": 0,
"obstacle_avoidance": {
"downside": 1,
"horizon": 1,
"upside": 1
},
"position_state": {
"gps_number": 0,
"is_fixed": 0,
"quality": 0,
"rtk_number": 0
},
"storage": {
"total": 0,
"used": 0
},
"total_flight_distance": 0,
"total_flight_sorties": 0,
"total_flight_time": 0,
"track_id": "",
"vertical_speed": 0,
"wind_direction": 0,
"wind_speed": 0
},
"tid": "2d2040eb-23b0-43dc-b7ac-64838276c4ac",
"timestamp": 1670422793916,
"gateway": "dock_sn"
}
对应代码如下
@Override
public void osdDockDrone(TopicOsdRequest<OsdDockDrone> request, MessageHeaders headers) {
String from = request.getFrom();
//查询当前osd数据对应的设备是否在线
Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(from);
//设备不在线或工作空间不存在,不处理
if (deviceOpt.isEmpty()) {
deviceOpt = deviceService.getDeviceBySn(from);
if (deviceOpt.isEmpty()) {
log.error("Please restart the drone.");
return;
}
}
//如果无人机不存在工作空间打印日志
if (!StringUtils.hasText(deviceOpt.get().getWorkspaceId())) {
log.error("Please restart the drone.");
}
DeviceDTO device = deviceOpt.get();
//保存设备到redis设置在线
deviceRedisService.setDeviceOnline(device);
//保存无人机osd数据到redis
deviceRedisService.setDeviceOsd(from, request.getData());
//推送机无人机位置信息到连接了websoket的web端
deviceService.pushOsdDataToWeb(device.getWorkspaceId(), BizCodeEnum.DEVICE_OSD, from, request.getData());
}
(3)遥控器OSD数据
遥控器相关的json数据自行拼装一下:
@Override
public void osdRemoteControl(TopicOsdRequest<OsdRemoteControl> request, MessageHeaders headers) {
String from = request.getFrom();
//查询当前osd数据对应的设备是否在线
Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(from);
//设备不在线或工作空间不存在,不处理
if (deviceOpt.isEmpty()) {
deviceOpt = deviceService.getDeviceBySn(from);
if (deviceOpt.isEmpty()) {
log.error("Please restart the drone.");
return;
}
}
//如果设备有子设备-飞机,查询飞机的状态并设置到遥控器信息中
DeviceDTO device = deviceOpt.get();
if (StringUtils.hasText(device.getChildDeviceSn())) {
deviceService.getDeviceBySn(device.getChildDeviceSn()).ifPresent(device::setChildren);
}
//设置遥控器在线
deviceRedisService.setDeviceOnline(device);
OsdRemoteControl data = request.getData();
//推送当前遥控器位置信息 到 相同空间所有其他连接了websoket的遥控器
deviceService.pushOsdDataToPilot(device.getWorkspaceId(), from,
new DeviceOsdHost()
.setLatitude(data.getLatitude())
.setLongitude(data.getLongitude())
.setHeight(data.getHeight()));
//推送当前遥控器信息到连接了websoket的web端
deviceService.pushOsdDataToWeb(device.getWorkspaceId(), BizCodeEnum.RC_OSD, from, data);
}
(4)遥控器对应无人机OSD数据
遥控无人机对应的json,全部被包含在机场无人机数据字段内如下,请自行拼装:
@Override
public void osdRcDrone(TopicOsdRequest<OsdRcDrone> request, MessageHeaders headers) {
String from = request.getFrom();
//查询当前osd数据对应的设备是否在线
Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(from);
//设备不在线或工作空间不存在,不处理
if (deviceOpt.isEmpty()) {
deviceOpt = deviceService.getDeviceBySn(from);
if (deviceOpt.isEmpty()) {
log.error("Please restart the drone.");
return;
}
}
DeviceDTO device = deviceOpt.get();
//如果无人机不存在工作空间打印日志
if (!StringUtils.hasText(device.getWorkspaceId())) {
log.error("Please bind the drone first.");
}
//设置无人机在线 --添加在线时间
deviceRedisService.setDeviceOnline(device);
OsdRcDrone data = request.getData();
//推送当前无人机位置信息 到 相同空间所有其他连接了websoket的遥控器
deviceService.pushOsdDataToPilot(device.getWorkspaceId(), from,
new DeviceOsdHost()
.setLatitude(data.getLatitude())
.setLongitude(data.getLongitude())
.setElevation(data.getElevation())
.setHeight(data.getHeight())
.setAttitudeHead(data.getAttitudeHead())
.setElevation(data.getElevation())
.setHorizontalSpeed(data.getHorizontalSpeed())
.setVerticalSpeed(data.getVerticalSpeed()));
//推送当前遥控器信息到连接了websoket的web端
deviceService.pushOsdDataToWeb(device.getWorkspaceId(), BizCodeEnum.DEVICE_OSD, from, data);
}
具体的本地测试,我这里就不再做了,大家自行使用mqtt去做测试。
总结
无人机位置和姿态信息等数据的处理比较简单,看下来不外乎几个动作。存储redis,和相同工作空间设备共享信息,推送数据的web端供地图显示。今天的代码解析就到这里,有不懂的请留言大家一起讨论~~ 举个例子:为什么机场OSD数据处理的时候fillDockOsd方法里面覆盖redis数据的时候几个判断逻辑是这样的?机场模式和机场连接状态的关系?