Compare commits

...

2 Commits

23 changed files with 1690 additions and 2 deletions

View File

@ -2,6 +2,9 @@ package com.multictrl.modules.business.handler;
import cn.hutool.core.util.StrUtil;
import com.multictrl.common.constant.BusinessConstant;
import com.multictrl.modules.business.q20.handler.Q20EventsHandler;
import com.multictrl.modules.business.q20.handler.Q20OsdTopicHandler;
import com.multictrl.modules.business.q20.handler.Q20StateTopicHandler;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -26,7 +29,13 @@ public class TopicDistributor {
private final StateHandler stateHandler;
private final ServicesReplyHandler servicesReplyHandler;
private final EventsHandler eventsHandler;
private final Q20OsdTopicHandler q20OsdTopicHandler;
private final Q20EventsHandler q20EventsHandler;
private final Q20StateTopicHandler q20StateTopicHandler;
private final Map<String, MessageHandler> handlerMap = new ConcurrentHashMap<>();
private final Map<String, MessageHandler> q20HandlerMap = new ConcurrentHashMap<>();
private static final String Q20_TOPIC_PREFIX = "thing/device/";
@PostConstruct
public void init() {
@ -36,13 +45,18 @@ public class TopicDistributor {
handlerMap.put(BusinessConstant.STATE, stateHandler);
handlerMap.put(BusinessConstant.EVENTS, eventsHandler);
handlerMap.put(BusinessConstant.SERVICES_REPLY, servicesReplyHandler);
q20HandlerMap.put(BusinessConstant.OSD, q20OsdTopicHandler);
q20HandlerMap.put(BusinessConstant.EVENTS, q20EventsHandler);
q20HandlerMap.put(BusinessConstant.STATE, q20StateTopicHandler);
}
public void route(String topic, String payload) {
String method = StrUtil.subAfter(topic, "/", true);
String gateway = StrUtil.subAfter(StrUtil.subBefore(topic, "/", true),
"/", true);
MessageHandler handler = handlerMap.get(method);
Map<String, MessageHandler> map = topic.startsWith(Q20_TOPIC_PREFIX) ? q20HandlerMap : handlerMap;
MessageHandler handler = map.get(method);
if (handler != null) {
handler.handleMessage(topic, payload, gateway);
} else {

View File

@ -0,0 +1,74 @@
package com.multictrl.modules.business.q20.handler;
import cn.hutool.json.JSONObject;
import com.multictrl.common.constant.BusinessConstant;
import com.multictrl.common.utils.CacheUtils;
import com.multictrl.common.utils.JsonUtils;
import com.multictrl.modules.business.handler.MessageHandler;
import com.multictrl.modules.business.q20.influxdb.Q20BatteryReport;
import com.multictrl.modules.business.service.InfluxService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* Q20电池数据处理method: battery
* 上报智能电池详细状态id, full_charge_capacity, charge_remaining, voltage, current,
* low_volt_warn_value, status(0异常/1开机/2充电中/3需保养), temperature, cycle_index,
* time_flying, time_remaining, charge_time_remaining, health, flags, uid, version
*
* @author 938693313@qq.com
* @since 1.0.0 2026/5/12
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class Q20BatteryHandler implements MessageHandler {
private final InfluxService influxService;
@Override
public void handleMessage(String topic, String payload, String gateway) {
JSONObject message = JsonUtils.parseObject(payload, JSONObject.class);
if (message != null) {
String deviceSn = message.getStr(BusinessConstant.GATEWAY);
JSONObject data = message.getJSONObject(BusinessConstant.DATA);
if (data != null) {
// 以电池id区分多块电池缓存key加上电池id后缀
Integer batteryId = data.getInt("id");
String cacheKey = Q20Constant.Q20_BATTERY + deviceSn
+ (batteryId != null ? "_" + batteryId : "");
CacheUtils.set(cacheKey, data);
saveToInflux(deviceSn, batteryId, data);
log.debug("Q20 battery --> deviceSn: {}, id: {}, charge_remaining: {}%, status: {}",
deviceSn, batteryId, data.getFloat("charge_remaining"), data.getInt("status"));
}
} else {
log.debug("Q20 battery --> payload解析失败解析后为null");
}
}
private void saveToInflux(String deviceSn, Integer batteryId, JSONObject data) {
Q20BatteryReport report = Q20BatteryReport.builder()
.deviceSn(deviceSn)
.battery_id(batteryId != null ? String.valueOf(batteryId) : null)
.order_id(data.getStr("order_id"))
.full_charge_capacity(data.getInt("full_charge_capacity"))
.charge_remaining(data.getFloat("charge_remaining"))
.voltage(data.getInt("voltage"))
.current(data.getInt("current"))
.low_volt_warn_value(data.getInt("low_volt_warn_value"))
.status(data.getInt("status"))
.temperature(data.getFloat("temperature"))
.cycle_index(data.getInt("cycle_index"))
.time_flying(data.getLong("time_flying"))
.time_remaining(data.getInt("time_remaining"))
.charge_time_remaining(data.getInt("charge_time_remaining"))
.health(data.getInt("health"))
.flags(data.getInt("flags"))
.uid(data.getStr("uid"))
.version(data.getStr("version"))
.build();
influxService.addRecord(report);
}
}

View File

@ -0,0 +1,47 @@
package com.multictrl.modules.business.q20.handler;
/**
* Q20设备相关常量
*
* @author 938693313@qq.com
* @since 1.0.0 2026/5/12
*/
public interface Q20Constant {
// Q20 osd topic method字段值
String METHOD_OSD = "osd";
String METHOD_RTK = "rtk";
String METHOD_RC = "rc";
String METHOD_MOUNT = "mount";
String METHOD_BATTERY = "battery";
String METHOD_OBS = "obs";
// Q20 state topic method字段值
String METHOD_VERSION = "version";
String METHOD_NOTIFY = "notify";
String METHOD_HMS = "hms";
// Q20 events topic method字段值
String METHOD_LIFTOFF = "liftoff";
String METHOD_ARRIVAL = "arrival";
String METHOD_TAKEOFF = "takeoff";
String METHOD_LAND = "land";
String METHOD_NAVIGATE = "navigate";
String METHOD_ROUTE_UPLOAD = "route_upload";
String METHOD_ROUTE_EXECUTE = "route_execute";
String METHOD_ROUTE_AUTO = "route_auto";
String METHOD_OTA = "ota";
// Q20 缓存key前缀
String Q20_OSD = "q20_osd_";
String Q20_RTK = "q20_rtk_";
String Q20_RC = "q20_rc_";
String Q20_MOUNT = "q20_mount_";
String Q20_BATTERY = "q20_battery_";
String Q20_OBS = "q20_obs_";
String Q20_VERSION = "q20_version_";
String Q20_HMS = "q20_hms_";
String Q20_IN_WORK = "q20_in_work_";
String Q20_LIFTOFF = "q20_liftoff_";
String Q20_ARRIVAL = "q20_arrival_";
String Q20_ROUTE_EXECUTE = "q20_route_execute_";
String Q20_ROUTE_AUTO = "q20_route_auto_";
}

View File

@ -0,0 +1,193 @@
package com.multictrl.modules.business.q20.handler;
import cn.hutool.json.JSONObject;
import com.multictrl.common.constant.BusinessConstant;
import com.multictrl.common.utils.CacheUtils;
import com.multictrl.common.utils.JsonUtils;
import com.multictrl.modules.business.handler.MessageHandler;
import com.multictrl.modules.business.service.MqttPushService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* Q20设备事件消息处理topic: thing/device/{device_sn}/events
* 若need_reply=1需回复 events_reply 主题
* method: liftoff(起飞事件) | arrival(到达事件) | takeoff(起飞指令进度) | land(降落指令进度) |
* navigate(指点飞行进度) | route_upload(航线上传结果) | route_execute(航线执行进度) |
* route_auto(一键飞行进度) | ota(固件升级进度)
*
* @author 938693313@qq.com
* @since 1.0.0 2026/5/12
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class Q20EventsHandler implements MessageHandler {
private final MqttPushService mqttPushService;
@Override
public void handleMessage(String topic, String payload, String gateway) {
JSONObject message = JsonUtils.parseObject(payload, JSONObject.class);
if (message == null) {
log.debug("Q20 events --> payload解析失败解析后为null");
return;
}
String deviceSn = message.getStr(BusinessConstant.GATEWAY);
Integer needReply = message.getInt(BusinessConstant.NEED_REPLY);
if (needReply != null && needReply == 1) {
JSONObject data;
Object dataObject = message.get(BusinessConstant.DATA);
if (dataObject instanceof JSONObject) {
data = (JSONObject) dataObject;
} else {
data = new JSONObject();
message.set(BusinessConstant.DATA, data);
}
data.clear();
data.set("result", 0);
message.remove(BusinessConstant.NEED_REPLY);
message.remove(BusinessConstant.GATEWAY);
mqttPushService.pushMessageByClient1(topic + BusinessConstant._REPLY, message.toString());
log.debug("Q20 events --> 已回复, deviceSn: {}, topic: {}", deviceSn, topic + BusinessConstant._REPLY);
}
// 重新解析避免回复时修改了data对象
message = JsonUtils.parseObject(payload, JSONObject.class);
if (message == null) {
return;
}
String method = message.getStr(BusinessConstant.METHOD);
if (method == null) {
log.debug("Q20 events --> method字段缺失, topic: {}", topic);
return;
}
JSONObject data = message.getJSONObject(BusinessConstant.DATA);
if (data == null) {
return;
}
switch (method) {
case Q20Constant.METHOD_LIFTOFF:
handleLiftoff(deviceSn, data);
break;
case Q20Constant.METHOD_ARRIVAL:
handleArrival(deviceSn, data);
break;
case Q20Constant.METHOD_TAKEOFF:
handleProgress(deviceSn, "起飞指令", data);
break;
case Q20Constant.METHOD_LAND:
handleProgress(deviceSn, "降落指令", data);
break;
case Q20Constant.METHOD_NAVIGATE:
handleProgress(deviceSn, "指点飞行", data);
break;
case Q20Constant.METHOD_ROUTE_UPLOAD:
handleRouteUpload(deviceSn, data);
break;
case Q20Constant.METHOD_ROUTE_EXECUTE:
handleRouteExecute(deviceSn, data);
break;
case Q20Constant.METHOD_ROUTE_AUTO:
handleRouteAuto(deviceSn, data);
break;
case Q20Constant.METHOD_OTA:
handleOta(deviceSn, data);
break;
default:
log.debug("Q20 events --> 未知method: {}, deviceSn: {}", method, deviceSn);
}
}
/**
* 起飞事件飞机从停机状态转变到起飞状态时上报
* type: command(指令飞行) | waypoint(航线飞行) | leapfrog(蛙跳飞行) | manual(手动飞行)
*/
private void handleLiftoff(String deviceSn, JSONObject data) {
CacheUtils.set(Q20Constant.Q20_LIFTOFF + deviceSn, data);
log.info("Q20 events --> 起飞事件, deviceSn: {}, type: {}, wayline: {}",
deviceSn, data.getStr("type"), data.getStr("wayline"));
}
/**
* 到达事件飞机飞到某位置停降时上报
* type: land(直接降落) | preLand(准备降落) | emergency(应急降落)
*/
private void handleArrival(String deviceSn, JSONObject data) {
CacheUtils.set(Q20Constant.Q20_ARRIVAL + deviceSn, data);
log.info("Q20 events --> 到达事件, deviceSn: {}, type: {}, code: {}",
deviceSn, data.getStr("type"), data.getStr("code"));
}
/**
* 通用指令进度takeoff / land / navigate
* status: in_progress | ok | failed | canceled
*/
private void handleProgress(String deviceSn, String label, JSONObject data) {
String status = data.getStr("status");
JSONObject progress = data.getJSONObject("progress");
int percent = progress != null ? progress.getInt("percent", 0) : 0;
log.debug("Q20 events --> {}进度, deviceSn: {}, status: {}, percent: {}%",
label, deviceSn, status, percent);
}
/**
* 航线上传结果need_reply=1已在上方统一处理
*/
private void handleRouteUpload(String deviceSn, JSONObject data) {
String status = data.getStr("status");
JSONObject progress = data.getJSONObject("progress");
String stepKey = progress != null ? progress.getStr("step_key") : null;
log.info("Q20 events --> 航线上传结果, deviceSn: {}, status: {}, step: {}",
deviceSn, status, stepKey);
}
/**
* 航线执行进度缓存最新进度供前端轮询
* progress含: step_key, percent, remaining_distance, first_point_distance,
* first_point_remaining_time, first_point_progress, landing_progress
*/
private void handleRouteExecute(String deviceSn, JSONObject data) {
CacheUtils.set(Q20Constant.Q20_ROUTE_EXECUTE + deviceSn, data);
String status = data.getStr("status");
JSONObject progress = data.getJSONObject("progress");
if (progress != null) {
log.debug("Q20 events --> 航线执行进度, deviceSn: {}, status: {}, step: {}, percent: {}%, remain: {}m",
deviceSn, status, progress.getStr("step_key"),
progress.getInt("percent"), progress.getInt("remaining_distance"));
} else {
log.debug("Q20 events --> 航线执行进度, deviceSn: {}, status: {}", deviceSn, status);
}
}
/**
* 一键飞行执行进度缓存最新进度供前端轮询
*/
private void handleRouteAuto(String deviceSn, JSONObject data) {
CacheUtils.set(Q20Constant.Q20_ROUTE_AUTO + deviceSn, data);
String status = data.getStr("status");
JSONObject progress = data.getJSONObject("progress");
if (progress != null) {
log.debug("Q20 events --> 一键飞行进度, deviceSn: {}, status: {}, step: {}, percent: {}%, remain: {}m",
deviceSn, status, progress.getStr("step_key"),
progress.getInt("percent"), progress.getInt("remaining_distance"));
} else {
log.debug("Q20 events --> 一键飞行进度, deviceSn: {}, status: {}", deviceSn, status);
}
}
/**
* 固件升级进度
*/
private void handleOta(String deviceSn, JSONObject data) {
String status = data.getStr("status");
JSONObject progress = data.getJSONObject("progress");
int percent = progress != null ? progress.getInt("percent", 0) : 0;
log.info("Q20 events --> 固件升级进度, deviceSn: {}, status: {}, step: {}, percent: {}%",
deviceSn, status,
progress != null ? progress.getStr("step_key") : null, percent);
}
}

View File

@ -0,0 +1,60 @@
package com.multictrl.modules.business.q20.handler;
import cn.hutool.json.JSONObject;
import com.multictrl.common.constant.BusinessConstant;
import com.multictrl.common.utils.CacheUtils;
import com.multictrl.common.utils.JsonUtils;
import com.multictrl.modules.business.handler.MessageHandler;
import com.multictrl.modules.business.service.MqttPushService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* Q20系统健康状态处理method: hmsstate topic
* 设备状态变化时触发上报包含各子系统故障码数组
* gnss, ins, battery, power, gimbal, system, structure, radar, vision, flight_control
* 以及故障状态位码code2, code3, code4
* 若need_reply=1需回复 state_reply 主题
*
* @author 938693313@qq.com
* @since 1.0.0 2026/5/12
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class Q20HmsHandler implements MessageHandler {
private final MqttPushService mqttPushService;
@Override
public void handleMessage(String topic, String payload, String gateway) {
JSONObject message = JsonUtils.parseObject(payload, JSONObject.class);
if (message != null) {
String deviceSn = message.getStr(BusinessConstant.GATEWAY);
JSONObject data = message.getJSONObject(BusinessConstant.DATA);
Integer needReply = message.getInt(BusinessConstant.NEED_REPLY);
if (needReply != null && needReply == 1) {
data.clear();
data.set("result", 0);
message.remove(BusinessConstant.NEED_REPLY);
mqttPushService.pushMessageByClient2(topic + BusinessConstant._REPLY, message.toString());
log.debug("Q20 hms --> 已回复, deviceSn: {}", deviceSn);
}
// 重新解析避免回复时修改了data对象
message = JsonUtils.parseObject(payload, JSONObject.class);
if (message == null) {
return;
}
data = message.getJSONObject(BusinessConstant.DATA);
if (data != null) {
CacheUtils.set(Q20Constant.Q20_HMS + deviceSn, data);
log.debug("Q20 hms --> deviceSn: {}, gnss: {}, ins: {}, battery: {}",
deviceSn, data.getJSONArray("gnss"), data.getJSONArray("ins"),
data.getJSONArray("battery"));
}
} else {
log.debug("Q20 hms --> payload解析失败解析后为null");
}
}
}

View File

@ -0,0 +1,64 @@
package com.multictrl.modules.business.q20.handler;
import cn.hutool.json.JSONObject;
import com.multictrl.common.constant.BusinessConstant;
import com.multictrl.common.utils.CacheUtils;
import com.multictrl.common.utils.JsonUtils;
import com.multictrl.modules.business.handler.MessageHandler;
import com.multictrl.modules.business.q20.influxdb.Q20MountReport;
import com.multictrl.modules.business.service.InfluxService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* Q20负载数据处理method: mount
* 上报挂载负载状态mount_id, mount_port, mount_type, payload(yaw/pitch/roll/mode/zoom等)
* 支持ZT30(四光)Z40T(双光)A30(三光)GL60P(探照灯)4C(抛投器)SZY/YKX/EayLoad10(索降)
* LC(物流箱)PARACHUTE(降落伞)等各类型负载
*
* @author 938693313@qq.com
* @since 1.0.0 2026/5/12
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class Q20MountHandler implements MessageHandler {
private final InfluxService influxService;
@Override
public void handleMessage(String topic, String payload, String gateway) {
JSONObject message = JsonUtils.parseObject(payload, JSONObject.class);
if (message != null) {
String deviceSn = message.getStr(BusinessConstant.GATEWAY);
JSONObject data = message.getJSONObject(BusinessConstant.DATA);
if (data != null) {
CacheUtils.set(Q20Constant.Q20_MOUNT + deviceSn, data);
saveToInflux(deviceSn, data);
log.debug("Q20 mount --> deviceSn: {}, mount_type: {}, mount_port: {}",
deviceSn, data.getStr("mount_type"), data.getInt("mount_port"));
}
} else {
log.debug("Q20 mount --> payload解析失败解析后为null");
}
}
private void saveToInflux(String deviceSn, JSONObject data) {
JSONObject payload = data.getJSONObject("payload");
Q20MountReport report = Q20MountReport.builder()
.deviceSn(deviceSn)
.order_id(data.getStr("order_id"))
.mount_id(data.getStr("mount_id"))
.mount_port(data.getInt("mount_port"))
.mount_type(data.getStr("mount_type"))
.payload_yaw(payload != null ? payload.getFloat("yaw") : null)
.payload_pitch(payload != null ? payload.getFloat("pitch") : null)
.payload_roll(payload != null ? payload.getFloat("roll") : null)
.payload_mode(payload != null ? payload.getInt("mode") : null)
.payload_zoom(payload != null ? payload.getFloat("zoom") : null)
.build();
influxService.addRecord(report);
}
}

View File

@ -0,0 +1,58 @@
package com.multictrl.modules.business.q20.handler;
import cn.hutool.json.JSONObject;
import com.multictrl.common.constant.BusinessConstant;
import com.multictrl.common.utils.CacheUtils;
import com.multictrl.common.utils.JsonUtils;
import com.multictrl.modules.business.handler.MessageHandler;
import com.multictrl.modules.business.service.MqttPushService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* Q20设备通知事件处理method: notifystate topic
* 设备状态变化时触发上报包含index, type, level(0紧急/2严重/4告警/6提示),
* message, timestamp, value(故障码), clear_codes(清除码)
* 若need_reply=1需回复 state_reply 主题
*
* @author 938693313@qq.com
* @since 1.0.0 2026/5/12
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class Q20NotifyHandler implements MessageHandler {
private final MqttPushService mqttPushService;
@Override
public void handleMessage(String topic, String payload, String gateway) {
JSONObject message = JsonUtils.parseObject(payload, JSONObject.class);
if (message != null) {
String deviceSn = message.getStr(BusinessConstant.GATEWAY);
JSONObject data = message.getJSONObject(BusinessConstant.DATA);
Integer needReply = message.getInt(BusinessConstant.NEED_REPLY);
if (needReply != null && needReply == 1) {
data.clear();
data.set("result", 0);
message.remove(BusinessConstant.NEED_REPLY);
mqttPushService.pushMessageByClient2(topic + BusinessConstant._REPLY, message.toString());
log.debug("Q20 notify --> 已回复, deviceSn: {}", deviceSn);
}
// 重新解析避免回复时修改了data对象
message = JsonUtils.parseObject(payload, JSONObject.class);
if (message == null) {
return;
}
data = message.getJSONObject(BusinessConstant.DATA);
if (data != null) {
log.warn("Q20 notify --> deviceSn: {}, type: {}, level: {}, message: {}, value: {}",
deviceSn, data.getStr("type"), data.getInt("level"),
data.getStr("message"), data.getStr("value"));
}
} else {
log.debug("Q20 notify --> payload解析失败解析后为null");
}
}
}

View File

@ -0,0 +1,81 @@
package com.multictrl.modules.business.q20.handler;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import com.multictrl.common.constant.BusinessConstant;
import com.multictrl.common.utils.CacheUtils;
import com.multictrl.common.utils.JsonUtils;
import com.multictrl.modules.business.handler.MessageHandler;
import com.multictrl.modules.business.q20.influxdb.Q20ObsReport;
import com.multictrl.modules.business.service.InfluxService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* Q20避障数据处理method: obs
* 上报避障传感器状态cp_distance(避障距离/m), cp_enable(避障开关),
* ver_distances(垂直方向[下方,上方]障碍物距离数组/-1表示无障碍物),
* around_distances(水平方向8方位障碍物距离数组/-1表示无障碍物从正前方顺时针)
*
* @author 938693313@qq.com
* @since 1.0.0 2026/5/12
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class Q20ObsHandler implements MessageHandler {
private final InfluxService influxService;
@Override
public void handleMessage(String topic, String payload, String gateway) {
JSONObject message = JsonUtils.parseObject(payload, JSONObject.class);
if (message != null) {
String deviceSn = message.getStr(BusinessConstant.GATEWAY);
JSONObject data = message.getJSONObject(BusinessConstant.DATA);
if (data != null) {
CacheUtils.set(Q20Constant.Q20_OBS + deviceSn, data);
saveToInflux(deviceSn, data);
log.debug("Q20 obs --> deviceSn: {}, cp_distance: {}m, cp_enable: {}",
deviceSn, data.getFloat("cp_distance"), data.getInt("cp_enable"));
}
} else {
log.debug("Q20 obs --> payload解析失败解析后为null");
}
}
private void saveToInflux(String deviceSn, JSONObject data) {
JSONArray verDistances = data.getJSONArray("ver_distances");
JSONArray aroundDistances = data.getJSONArray("around_distances");
Q20ObsReport report = Q20ObsReport.builder()
.deviceSn(deviceSn)
.order_id(data.getStr("order_id"))
.cp_distance(data.getFloat("cp_distance"))
.cp_enable(data.getInt("cp_enable"))
.ver_down(getFloat(verDistances, 0))
.ver_up(getFloat(verDistances, 1))
.around_0(getFloat(aroundDistances, 0))
.around_1(getFloat(aroundDistances, 1))
.around_2(getFloat(aroundDistances, 2))
.around_3(getFloat(aroundDistances, 3))
.around_4(getFloat(aroundDistances, 4))
.around_5(getFloat(aroundDistances, 5))
.around_6(getFloat(aroundDistances, 6))
.around_7(getFloat(aroundDistances, 7))
.build();
influxService.addRecord(report);
}
private Float getFloat(JSONArray array, int index) {
if (array == null || array.size() <= index) {
return null;
}
Object val = array.get(index);
if (val == null) {
return null;
}
return ((Number) val).floatValue();
}
}

View File

@ -0,0 +1,114 @@
package com.multictrl.modules.business.q20.handler;
import cn.hutool.json.JSONObject;
import com.multictrl.common.constant.BusinessConstant;
import com.multictrl.common.utils.CacheUtils;
import com.multictrl.common.utils.JsonUtils;
import com.multictrl.modules.business.handler.MessageHandler;
import com.multictrl.modules.business.q20.influxdb.Q20OsdReport;
import com.multictrl.modules.business.service.FlightTaskService;
import com.multictrl.modules.business.service.InfluxService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* Q20基础遥测数据处理method: osd
* 定频上报飞行数据longitude, latitude, height, altitude, vs, gs, heading,
* pitch, roll, battery, voltage, mode, flying, fly_time, fly_distance, home_location, state等
*
* @author 938693313@qq.com
* @since 1.0.0 2026/5/12
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class Q20OsdHandler implements MessageHandler {
private final FlightTaskService flightTaskService;
private final InfluxService influxService;
@Override
public void handleMessage(String topic, String payload, String gateway) {
JSONObject message = JsonUtils.parseObject(payload, JSONObject.class);
if (message != null) {
String deviceSn = message.getStr(BusinessConstant.GATEWAY);
JSONObject data = message.getJSONObject(BusinessConstant.DATA);
if (data != null) {
CacheUtils.set(Q20Constant.Q20_OSD + deviceSn, data);
trackFlyingState(deviceSn, data);
saveToInflux(deviceSn, data);
}
} else {
log.debug("Q20 osd --> payload解析失败解析后为null");
}
}
private void trackFlyingState(String deviceSn, JSONObject data) {
Integer flying = data.getInt("flying");
if (flying == null) {
return;
}
// 起飞中首次检测到飞行状态时更新架次开始
if (flying == 1) {
if (CacheUtils.get(Q20Constant.Q20_IN_WORK + deviceSn) == null) {
flightTaskService.updateTaskStart(deviceSn);
log.debug("{}设备架次开始,更新架次表", deviceSn);
}
CacheUtils.set(Q20Constant.Q20_IN_WORK + deviceSn, true);
}
// 刚从飞行切到非飞行架次结束
if (flying == 0 && CacheUtils.get(Q20Constant.Q20_IN_WORK + deviceSn) != null) {
CacheUtils.delete(Q20Constant.Q20_IN_WORK + deviceSn);
flightTaskService.updateTaskComplete(deviceSn);
log.debug("{}设备架次结束,更新架次表", deviceSn);
}
}
private void saveToInflux(String deviceSn, JSONObject data) {
Double longitude = data.getDouble("longitude");
Double latitude = data.getDouble("latitude");
if (longitude == null || latitude == null || longitude == 0 || latitude == 0) {
return;
}
JSONObject homeLocation = data.getJSONObject("home_location");
JSONObject state = data.getJSONObject("state");
Q20OsdReport report = Q20OsdReport.builder()
.deviceSn(deviceSn)
.order_id(data.getStr("order_id"))
.longitude(longitude)
.latitude(latitude)
.height(data.getFloat("height"))
.altitude(data.getFloat("altitude"))
.vs(data.getFloat("vs"))
.gs(data.getFloat("gs"))
.heading(data.getFloat("heading"))
.pitch(data.getFloat("pitch"))
.roll(data.getFloat("roll"))
.battery(data.getInt("battery"))
.voltage(data.getInt("voltage"))
.mode(data.getStr("mode"))
.flying(data.getInt("flying"))
.fly_time(data.getLong("fly_time"))
.fly_distance(data.getFloat("fly_distance"))
.home_set(data.getInt("home_set"))
.home_distance(data.getFloat("home_distance"))
.home_altitude(homeLocation != null ? homeLocation.getFloat("altitude") : null)
.home_latitude(homeLocation != null ? homeLocation.getFloat("latitude") : null)
.home_longitude(homeLocation != null ? homeLocation.getFloat("longitude") : null)
.state_safe_enabled(state != null ? state.getInt("safe_enabled") : null)
.state_rtk_enabled(state != null ? state.getInt("rtk_enabled") : null)
.state_rtk_connected(state != null ? state.getInt("rtk_connected") : null)
.state_rc_connected(state != null ? state.getInt("rc_connected") : null)
.state_obs_enabled(state != null ? state.getInt("obs_enabled") : null)
.state_accel_cal(state != null ? state.getInt("accel_cal") : null)
.state_gyro_cal(state != null ? state.getInt("gyro_cal") : null)
.state_hor_cal(state != null ? state.getInt("hor_cal") : null)
.state_mag_cal(state != null ? state.getInt("mag_cal") : null)
.state_fpv_live(state != null ? state.getInt("fpv_live") : null)
.state_stream_live(state != null ? state.getInt("stream_live") : null)
.build();
influxService.addRecord(report);
}
}

View File

@ -0,0 +1,66 @@
package com.multictrl.modules.business.q20.handler;
import cn.hutool.json.JSONObject;
import com.multictrl.common.constant.BusinessConstant;
import com.multictrl.common.utils.JsonUtils;
import com.multictrl.modules.business.handler.MessageHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* Q20设备OSD主题分发器
* 接收 thing/device/{device_sn}/osd 主题消息根据payload中的method字段分发到对应处理器
* method: osd(基础遥测) | rtk(RTK定位) | rc(遥控链路) | mount(负载) | battery(电池) | obs(避障)
*
* @author 938693313@qq.com
* @since 1.0.0 2026/5/12
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class Q20OsdTopicHandler implements MessageHandler {
private final Q20OsdHandler q20OsdHandler;
private final Q20RtkHandler q20RtkHandler;
private final Q20RcHandler q20RcHandler;
private final Q20MountHandler q20MountHandler;
private final Q20BatteryHandler q20BatteryHandler;
private final Q20ObsHandler q20ObsHandler;
@Override
public void handleMessage(String topic, String payload, String gateway) {
JSONObject message = JsonUtils.parseObject(payload, JSONObject.class);
if (message == null) {
log.debug("Q20 osd topic --> payload解析失败解析后为null");
return;
}
String method = message.getStr(BusinessConstant.METHOD);
if (method == null) {
log.debug("Q20 osd topic --> method字段缺失, topic: {}", topic);
return;
}
switch (method) {
case Q20Constant.METHOD_OSD:
q20OsdHandler.handleMessage(topic, payload, gateway);
break;
case Q20Constant.METHOD_RTK:
q20RtkHandler.handleMessage(topic, payload, gateway);
break;
case Q20Constant.METHOD_RC:
q20RcHandler.handleMessage(topic, payload, gateway);
break;
case Q20Constant.METHOD_MOUNT:
q20MountHandler.handleMessage(topic, payload, gateway);
break;
case Q20Constant.METHOD_BATTERY:
q20BatteryHandler.handleMessage(topic, payload, gateway);
break;
case Q20Constant.METHOD_OBS:
q20ObsHandler.handleMessage(topic, payload, gateway);
break;
default:
log.debug("Q20 osd topic --> 未知method: {}", method);
}
}
}

View File

@ -0,0 +1,82 @@
package com.multictrl.modules.business.q20.handler;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import com.multictrl.common.constant.BusinessConstant;
import com.multictrl.common.utils.CacheUtils;
import com.multictrl.common.utils.JsonUtils;
import com.multictrl.modules.business.handler.MessageHandler;
import com.multictrl.modules.business.q20.influxdb.Q20RcReport;
import com.multictrl.modules.business.service.InfluxService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* Q20遥控链路数据处理method: rc
* 上报遥控与链路信息priority, rc_channel_state, rc_state,
* link数组(id/type/connected/inuse/sqe/band), up_loss_rate, down_loss_rate, network信息
*
* @author 938693313@qq.com
* @since 1.0.0 2026/5/12
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class Q20RcHandler implements MessageHandler {
private final InfluxService influxService;
@Override
public void handleMessage(String topic, String payload, String gateway) {
JSONObject message = JsonUtils.parseObject(payload, JSONObject.class);
if (message != null) {
String deviceSn = message.getStr(BusinessConstant.GATEWAY);
JSONObject data = message.getJSONObject(BusinessConstant.DATA);
if (data != null) {
CacheUtils.set(Q20Constant.Q20_RC + deviceSn, data);
saveToInflux(deviceSn, data);
log.debug("Q20 rc --> deviceSn: {}, priority: {}, rc_state: {}",
deviceSn, data.getInt("priority"), data.getStr("rc_state"));
}
} else {
log.debug("Q20 rc --> payload解析失败解析后为null");
}
}
private void saveToInflux(String deviceSn, JSONObject data) {
// 从link数组中取inuse=1的链路无则取第一条
JSONObject inuseLink = null;
JSONArray links = data.getJSONArray("link");
if (links != null) {
for (int i = 0; i < links.size(); i++) {
JSONObject link = links.getJSONObject(i);
if (link != null && Integer.valueOf(1).equals(link.getInt("inuse"))) {
inuseLink = link;
break;
}
}
if (inuseLink == null && !links.isEmpty()) {
inuseLink = links.getJSONObject(0);
}
}
JSONObject network = data.getJSONObject("network");
Q20RcReport report = Q20RcReport.builder()
.deviceSn(deviceSn)
.order_id(data.getStr("order_id"))
.priority(data.getInt("priority"))
.rc_channel_state(data.getStr("rc_channel_state"))
.rc_state(data.getStr("rc_state"))
.up_loss_rate(data.getFloat("up_loss_rate"))
.down_loss_rate(data.getFloat("down_loss_rate"))
.link_inuse_id(inuseLink != null ? inuseLink.getInt("id") : null)
.link_inuse_type(inuseLink != null ? inuseLink.getInt("type") : null)
.link_inuse_sqe(inuseLink != null ? inuseLink.getInt("sqe") : null)
.link_inuse_band(inuseLink != null ? inuseLink.getInt("band") : null)
.network_type(network != null ? network.getInt("type") : null)
.network_quality(network != null ? network.getInt("quality") : null)
.build();
influxService.addRecord(report);
}
}

View File

@ -0,0 +1,67 @@
package com.multictrl.modules.business.q20.handler;
import cn.hutool.json.JSONObject;
import com.multictrl.common.constant.BusinessConstant;
import com.multictrl.common.utils.CacheUtils;
import com.multictrl.common.utils.JsonUtils;
import com.multictrl.modules.business.handler.MessageHandler;
import com.multictrl.modules.business.q20.influxdb.Q20RtkReport;
import com.multictrl.modules.business.service.InfluxService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* Q20 RTK数据处理method: rtk
* 上报RTK定位信息fix_type, latitude, longitude, altitude, ellipsoid_height,
* satellite_visible, eph, epv, velocity, yaw, cog, horizontal_acc, vertical_acc, velocity_acc
*
* @author 938693313@qq.com
* @since 1.0.0 2026/5/12
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class Q20RtkHandler implements MessageHandler {
private final InfluxService influxService;
@Override
public void handleMessage(String topic, String payload, String gateway) {
JSONObject message = JsonUtils.parseObject(payload, JSONObject.class);
if (message != null) {
String deviceSn = message.getStr(BusinessConstant.GATEWAY);
JSONObject data = message.getJSONObject(BusinessConstant.DATA);
if (data != null) {
CacheUtils.set(Q20Constant.Q20_RTK + deviceSn, data);
saveToInflux(deviceSn, data);
log.debug("Q20 rtk --> deviceSn: {}, fix_type: {}, satellites: {}",
deviceSn, data.getInt("fix_type"), data.getInt("satellite_visible"));
}
} else {
log.debug("Q20 rtk --> payload解析失败解析后为null");
}
}
private void saveToInflux(String deviceSn, JSONObject data) {
Q20RtkReport report = Q20RtkReport.builder()
.deviceSn(deviceSn)
.order_id(data.getStr("order_id"))
.fix_type(data.getInt("fix_type"))
.latitude(data.getDouble("latitude"))
.longitude(data.getDouble("longitude"))
.altitude(data.getFloat("altitude"))
.ellipsoid_height(data.getFloat("ellipsoid_height"))
.satellite_visible(data.getInt("satellite_visible"))
.eph(data.getFloat("eph"))
.epv(data.getFloat("epv"))
.velocity(data.getFloat("velocity"))
.yaw(data.getFloat("yaw"))
.cog(data.getFloat("cog"))
.horizontal_acc(data.getFloat("horizontal_acc"))
.vertical_acc(data.getFloat("vertical_acc"))
.velocity_acc(data.getFloat("velocity_acc"))
.build();
influxService.addRecord(report);
}
}

View File

@ -0,0 +1,54 @@
package com.multictrl.modules.business.q20.handler;
import cn.hutool.json.JSONObject;
import com.multictrl.common.constant.BusinessConstant;
import com.multictrl.common.utils.JsonUtils;
import com.multictrl.modules.business.handler.MessageHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* Q20设备State主题分发器
* 接收 thing/device/{device_sn}/state 主题消息根据payload中的method字段分发到对应处理器
* method: version(固件版本) | notify(设备通知事件) | hms(系统健康状态)
*
* @author 938693313@qq.com
* @since 1.0.0 2026/5/12
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class Q20StateTopicHandler implements MessageHandler {
private final Q20VersionHandler q20VersionHandler;
private final Q20NotifyHandler q20NotifyHandler;
private final Q20HmsHandler q20HmsHandler;
@Override
public void handleMessage(String topic, String payload, String gateway) {
JSONObject message = JsonUtils.parseObject(payload, JSONObject.class);
if (message == null) {
log.debug("Q20 state topic --> payload解析失败解析后为null");
return;
}
String method = message.getStr(BusinessConstant.METHOD);
if (method == null) {
log.debug("Q20 state topic --> method字段缺失, topic: {}", topic);
return;
}
switch (method) {
case Q20Constant.METHOD_VERSION:
q20VersionHandler.handleMessage(topic, payload, gateway);
break;
case Q20Constant.METHOD_NOTIFY:
q20NotifyHandler.handleMessage(topic, payload, gateway);
break;
case Q20Constant.METHOD_HMS:
q20HmsHandler.handleMessage(topic, payload, gateway);
break;
default:
log.debug("Q20 state topic --> 未知method: {}", method);
}
}
}

View File

@ -0,0 +1,58 @@
package com.multictrl.modules.business.q20.handler;
import cn.hutool.json.JSONObject;
import com.multictrl.common.constant.BusinessConstant;
import com.multictrl.common.utils.CacheUtils;
import com.multictrl.common.utils.JsonUtils;
import com.multictrl.modules.business.handler.MessageHandler;
import com.multictrl.modules.business.service.MqttPushService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* Q20固件版本状态处理method: versionstate topic
* 设备状态变化时触发上报包含firmware_version, offboard_version, upgrade_status,
* vision_version, protocol_version, vehicle_type
* 若need_reply=1需回复 state_reply 主题
*
* @author 938693313@qq.com
* @since 1.0.0 2026/5/12
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class Q20VersionHandler implements MessageHandler {
private final MqttPushService mqttPushService;
@Override
public void handleMessage(String topic, String payload, String gateway) {
JSONObject message = JsonUtils.parseObject(payload, JSONObject.class);
if (message != null) {
String deviceSn = message.getStr(BusinessConstant.GATEWAY);
JSONObject data = message.getJSONObject(BusinessConstant.DATA);
Integer needReply = message.getInt(BusinessConstant.NEED_REPLY);
if (needReply != null && needReply == 1) {
data.clear();
data.set("result", 0);
message.remove(BusinessConstant.NEED_REPLY);
mqttPushService.pushMessageByClient2(topic + BusinessConstant._REPLY, message.toString());
log.debug("Q20 version --> 已回复, deviceSn: {}", deviceSn);
}
// 重新解析避免回复时修改了data对象
message = JsonUtils.parseObject(payload, JSONObject.class);
if (message == null) {
return;
}
data = message.getJSONObject(BusinessConstant.DATA);
if (data != null) {
CacheUtils.set(Q20Constant.Q20_VERSION + deviceSn, data);
log.debug("Q20 version --> deviceSn: {}, firmware: {}, vehicle_type: {}",
deviceSn, data.getStr("firmware_version"), data.getStr("vehicle_type"));
}
} else {
log.debug("Q20 version --> payload解析失败解析后为null");
}
}
}

View File

@ -0,0 +1,112 @@
package com.multictrl.modules.business.q20.influxdb;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.influxdb.annotations.Column;
import com.influxdb.annotations.Measurement;
import com.multictrl.common.utils.DateUtils;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.Instant;
/**
* Q20设备电池数据 InfluxDB记录
*
* @author 938693313@qq.com
* @since 1.0.0 2026/5/12
*/
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
@Schema(name = "Q20设备电池上报数据")
@Measurement(name = "q20_battery")
public class Q20BatteryReport {
@Schema(description = "设备SN")
@Column(tag = true)
private String deviceSn;
@Schema(description = "电池ID")
@Column(tag = true)
private String battery_id;
@Schema(description = "架次号")
@Column(tag = true)
private String order_id;
@Schema(hidden = true)
@Column(timestamp = true)
@JsonProperty("_time")
private Instant time;
@Schema(description = "满电容量单位mAh")
@Column
private Integer full_charge_capacity;
@Schema(description = "剩余电量,单位%")
@Column
private Float charge_remaining;
@Schema(description = "电压单位mV")
@Column
private Integer voltage;
@Schema(description = "电流单位mA放电为正充电为负")
@Column
private Integer current;
@Schema(description = "低电量警告阈值,单位%")
@Column
private Integer low_volt_warn_value;
@Schema(description = "电池状态0异常/1开机/2充电中/3需保养")
@Column
private Integer status;
@Schema(description = "电池温度,单位℃")
@Column
private Float temperature;
@Schema(description = "充放电循环次数")
@Column
private Integer cycle_index;
@Schema(description = "本次飞行时长,单位秒")
@Column
private Long time_flying;
@Schema(description = "剩余可飞时间,单位秒")
@Column
private Integer time_remaining;
@Schema(description = "剩余充电时间,单位秒")
@Column
private Integer charge_time_remaining;
@Schema(description = "电池健康度,单位%")
@Column
private Integer health;
@Schema(description = "电池标志位")
@Column
private Integer flags;
@Schema(description = "电池唯一标识")
@Column
private String uid;
@Schema(description = "电池固件版本")
@Column
private String version;
private String timeStr;
public void setTime(Instant time) {
this.time = time;
this.timeStr = DateUtils.utcToTime(time);
}
}

View File

@ -0,0 +1,80 @@
package com.multictrl.modules.business.q20.influxdb;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.influxdb.annotations.Column;
import com.influxdb.annotations.Measurement;
import com.multictrl.common.utils.DateUtils;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.Instant;
/**
* Q20设备负载数据 InfluxDB记录
*
* @author 938693313@qq.com
* @since 1.0.0 2026/5/12
*/
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
@Schema(name = "Q20设备负载上报数据")
@Measurement(name = "q20_mount")
public class Q20MountReport {
@Schema(description = "设备SN")
@Column(tag = true)
private String deviceSn;
@Schema(description = "架次号")
@Column(tag = true)
private String order_id;
@Schema(hidden = true)
@Column(timestamp = true)
@JsonProperty("_time")
private Instant time;
@Schema(description = "负载ID")
@Column
private String mount_id;
@Schema(description = "负载挂载口")
@Column
private Integer mount_port;
@Schema(description = "负载类型ZT30/Z40T/A30/GL60P/4C/SZY/YKX/EayLoad10/LC/PARACHUTE等")
@Column
private String mount_type;
@Schema(description = "云台偏航角,单位°")
@Column
private Float payload_yaw;
@Schema(description = "云台俯仰角,单位°")
@Column
private Float payload_pitch;
@Schema(description = "云台横滚角,单位°")
@Column
private Float payload_roll;
@Schema(description = "云台模式")
@Column
private Integer payload_mode;
@Schema(description = "变焦倍数")
@Column
private Float payload_zoom;
private String timeStr;
public void setTime(Instant time) {
this.time = time;
this.timeStr = DateUtils.utcToTime(time);
}
}

View File

@ -0,0 +1,96 @@
package com.multictrl.modules.business.q20.influxdb;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.influxdb.annotations.Column;
import com.influxdb.annotations.Measurement;
import com.multictrl.common.utils.DateUtils;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.Instant;
/**
* Q20设备避障数据 InfluxDB记录
*
* @author 938693313@qq.com
* @since 1.0.0 2026/5/12
*/
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
@Schema(name = "Q20设备避障上报数据")
@Measurement(name = "q20_obs")
public class Q20ObsReport {
@Schema(description = "设备SN")
@Column(tag = true)
private String deviceSn;
@Schema(description = "架次号")
@Column(tag = true)
private String order_id;
@Schema(hidden = true)
@Column(timestamp = true)
@JsonProperty("_time")
private Instant time;
@Schema(description = "避障距离单位m")
@Column
private Float cp_distance;
@Schema(description = "避障开关1开启/0关闭")
@Column
private Integer cp_enable;
@Schema(description = "下方障碍物距离单位m-1表示无障碍物")
@Column
private Float ver_down;
@Schema(description = "上方障碍物距离单位m-1表示无障碍物")
@Column
private Float ver_up;
@Schema(description = "水平正前方障碍物距离单位m-1表示无障碍物")
@Column
private Float around_0;
@Schema(description = "水平右前方障碍物距离单位m-1表示无障碍物")
@Column
private Float around_1;
@Schema(description = "水平正右方障碍物距离单位m-1表示无障碍物")
@Column
private Float around_2;
@Schema(description = "水平右后方障碍物距离单位m-1表示无障碍物")
@Column
private Float around_3;
@Schema(description = "水平正后方障碍物距离单位m-1表示无障碍物")
@Column
private Float around_4;
@Schema(description = "水平左后方障碍物距离单位m-1表示无障碍物")
@Column
private Float around_5;
@Schema(description = "水平正左方障碍物距离单位m-1表示无障碍物")
@Column
private Float around_6;
@Schema(description = "水平左前方障碍物距离单位m-1表示无障碍物")
@Column
private Float around_7;
private String timeStr;
public void setTime(Instant time) {
this.time = time;
this.timeStr = DateUtils.utcToTime(time);
}
}

View File

@ -0,0 +1,172 @@
package com.multictrl.modules.business.q20.influxdb;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.influxdb.annotations.Column;
import com.influxdb.annotations.Measurement;
import com.multictrl.common.utils.DateUtils;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.Instant;
/**
* Q20设备OSD基础遥测数据 InfluxDB记录
*
* @author 938693313@qq.com
* @since 1.0.0 2026/5/12
*/
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
@Schema(name = "Q20设备OSD上报数据")
@Measurement(name = "q20_osd")
public class Q20OsdReport {
@Schema(description = "设备SN")
@Column(tag = true)
private String deviceSn;
@Schema(description = "架次号")
@Column(tag = true)
private String order_id;
@Schema(hidden = true)
@Column(timestamp = true)
@JsonProperty("_time")
private Instant time;
@Schema(description = "位置经度WGS84精确到小数点后7位")
@Column
private Double longitude;
@Schema(description = "位置纬度WGS84精确到小数点后7位")
@Column
private Double latitude;
@Schema(description = "相对起飞点高度单位m")
@Column
private Float height;
@Schema(description = "海拔高度单位m")
@Column
private Float altitude;
@Schema(description = "垂直飞行速度单位m/s向上为负向下为正")
@Column
private Float vs;
@Schema(description = "水平飞行速度对地速度单位m/s")
@Column
private Float gs;
@Schema(description = "航向,取值范围[0,360]正北为0顺时针递增单位°")
@Column
private Float heading;
@Schema(description = "俯仰角,取值范围[-90,90],仰为正俯为负,单位°")
@Column
private Float pitch;
@Schema(description = "横滚角,取值范围[-90,90],向左为正向右为负,单位°")
@Column
private Float roll;
@Schema(description = "电池容量百分比,多电池为融合总电量,单位%")
@Column
private Integer battery;
@Schema(description = "电池电压多电池为平均电压单位V")
@Column
private Integer voltage;
@Schema(description = "飞行模式MANUAL/ALTCTL/POSCTL/AUTO.MISSION/AUTO.RTL/AUTO.LAND等")
@Column
private String mode;
@Schema(description = "是否飞行中1飞行中0非飞行中")
@Column
private Integer flying;
@Schema(description = "飞行时长,单位秒")
@Column
private Long fly_time;
@Schema(description = "飞行里程单位m")
@Column
private Float fly_distance;
@Schema(description = "返航点是否已设置1已设置0未设置")
@Column
private Integer home_set;
@Schema(description = "距离返航点距离单位m")
@Column
private Float home_distance;
@Schema(description = "返航点相对高度单位m")
@Column
private Float home_altitude;
@Schema(description = "返航点纬度")
@Column
private Float home_latitude;
@Schema(description = "返航点经度")
@Column
private Float home_longitude;
@Schema(description = "安全开关1已启用0未启用")
@Column
private Integer state_safe_enabled;
@Schema(description = "RTK是否启用1已启用0未启用")
@Column
private Integer state_rtk_enabled;
@Schema(description = "RTCM是否连接1已连接0未连接")
@Column
private Integer state_rtk_connected;
@Schema(description = "遥控是否连接1已连接0未连接")
@Column
private Integer state_rc_connected;
@Schema(description = "避障是否开启1已启用0未启用")
@Column
private Integer state_obs_enabled;
@Schema(description = "加速度计校准状态1已校准0未校准")
@Column
private Integer state_accel_cal;
@Schema(description = "陀螺仪校准状态1已校准0未校准")
@Column
private Integer state_gyro_cal;
@Schema(description = "水平校准状态1已校准0未校准")
@Column
private Integer state_hor_cal;
@Schema(description = "指南针校准状态1已校准0未校准")
@Column
private Integer state_mag_cal;
@Schema(description = "FPV推流状态1开启0关闭")
@Column
private Integer state_fpv_live;
@Schema(description = "云台推流状态0关闭1主码流2子码流")
@Column
private Integer state_stream_live;
private String timeStr;
public void setTime(Instant time) {
this.time = time;
this.timeStr = DateUtils.utcToTime(time);
}
}

View File

@ -0,0 +1,92 @@
package com.multictrl.modules.business.q20.influxdb;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.influxdb.annotations.Column;
import com.influxdb.annotations.Measurement;
import com.multictrl.common.utils.DateUtils;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.Instant;
/**
* Q20设备遥控链路数据 InfluxDB记录
*
* @author 938693313@qq.com
* @since 1.0.0 2026/5/12
*/
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
@Schema(name = "Q20设备RC链路上报数据")
@Measurement(name = "q20_rc")
public class Q20RcReport {
@Schema(description = "设备SN")
@Column(tag = true)
private String deviceSn;
@Schema(description = "架次号")
@Column(tag = true)
private String order_id;
@Schema(hidden = true)
@Column(timestamp = true)
@JsonProperty("_time")
private Instant time;
@Schema(description = "遥控优先级")
@Column
private Integer priority;
@Schema(description = "遥控通道状态")
@Column
private String rc_channel_state;
@Schema(description = "遥控状态")
@Column
private String rc_state;
@Schema(description = "上行丢包率,单位%")
@Column
private Float up_loss_rate;
@Schema(description = "下行丢包率,单位%")
@Column
private Float down_loss_rate;
@Schema(description = "当前使用链路id")
@Column
private Integer link_inuse_id;
@Schema(description = "当前使用链路类型0未知/1RC遥控/2图传/3网络")
@Column
private Integer link_inuse_type;
@Schema(description = "当前使用链路信号质量,单位%")
@Column
private Integer link_inuse_sqe;
@Schema(description = "当前使用链路频段单位MHz")
@Column
private Integer link_inuse_band;
@Schema(description = "网络类型0未知/1WiFi/24G/35G")
@Column
private Integer network_type;
@Schema(description = "网络信号质量,单位%")
@Column
private Integer network_quality;
private String timeStr;
public void setTime(Instant time) {
this.time = time;
this.timeStr = DateUtils.utcToTime(time);
}
}

View File

@ -0,0 +1,104 @@
package com.multictrl.modules.business.q20.influxdb;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.influxdb.annotations.Column;
import com.influxdb.annotations.Measurement;
import com.multictrl.common.utils.DateUtils;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.Instant;
/**
* Q20设备RTK定位数据 InfluxDB记录
*
* @author 938693313@qq.com
* @since 1.0.0 2026/5/12
*/
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
@Schema(name = "Q20设备RTK上报数据")
@Measurement(name = "q20_rtk")
public class Q20RtkReport {
@Schema(description = "设备SN")
@Column(tag = true)
private String deviceSn;
@Schema(description = "架次号")
@Column(tag = true)
private String order_id;
@Schema(hidden = true)
@Column(timestamp = true)
@JsonProperty("_time")
private Instant time;
@Schema(description = "定位类型0未定位/1单点定位/2差分定位/4固定解")
@Column
private Integer fix_type;
@Schema(description = "纬度WGS84")
@Column
private Double latitude;
@Schema(description = "经度WGS84")
@Column
private Double longitude;
@Schema(description = "海拔高度单位m")
@Column
private Float altitude;
@Schema(description = "椭球高单位m")
@Column
private Float ellipsoid_height;
@Schema(description = "可见卫星数")
@Column
private Integer satellite_visible;
@Schema(description = "水平位置精度因子单位m")
@Column
private Float eph;
@Schema(description = "垂直位置精度因子单位m")
@Column
private Float epv;
@Schema(description = "速度单位m/s")
@Column
private Float velocity;
@Schema(description = "偏航角,单位°")
@Column
private Float yaw;
@Schema(description = "航迹角,单位°")
@Column
private Float cog;
@Schema(description = "水平精度单位m")
@Column
private Float horizontal_acc;
@Schema(description = "垂直精度单位m")
@Column
private Float vertical_acc;
@Schema(description = "速度精度单位m/s")
@Column
private Float velocity_acc;
private String timeStr;
public void setTime(Instant time) {
this.time = time;
this.timeStr = DateUtils.utcToTime(time);
}
}

View File

@ -45,7 +45,7 @@ mqtt:
username: admin
password: Aros2023
subClientId: dj-one-sub${random.int(10)}
subTopic: thing/product/#,sys/product/#
subTopic: thing/product/#,sys/product/#,thing/device/#
pubClientId: dj-one-pub${random.int(10)}
client2:
url: tcp://${host.ip}:61637

BIN
doc/Q20数据上报.docx Normal file

Binary file not shown.

BIN
doc/Q20状态事件.docx Normal file

Binary file not shown.