From 406a1e9e5f7b927d70a579825aa26264ca8a8b83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E5=B0=8F=E4=BA=91?= Date: Tue, 20 Jan 2026 09:29:24 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/thingsboard/AttributeKey.java | 20 ++++- .../model/thingsboard/TelemetryKey.java | 75 ++++++++++++++++++- .../constants/DeviceTelemetry.java | 42 +++++++++++ 3 files changed, 133 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/ruoyi/device/domain/model/thingsboard/AttributeKey.java b/src/main/java/com/ruoyi/device/domain/model/thingsboard/AttributeKey.java index 449df77..62194bf 100644 --- a/src/main/java/com/ruoyi/device/domain/model/thingsboard/AttributeKey.java +++ b/src/main/java/com/ruoyi/device/domain/model/thingsboard/AttributeKey.java @@ -100,18 +100,34 @@ public class AttributeKey { if (v instanceof Boolean) return (Boolean) v; return Boolean.parseBoolean(v.toString()); }); - } else if (value instanceof Long || value instanceof Integer) { + } else if (value instanceof Integer) { + // Integer 类型优先处理 + return (AttributeKey) new AttributeKey<>(name, Integer.class, v -> { + if (v == null) return null; + if (v instanceof Number) return ((Number) v).intValue(); + return Integer.parseInt(v.toString()); + }); + } else if (value instanceof Long) { + // Long 类型 return (AttributeKey) new AttributeKey<>(name, Long.class, v -> { if (v == null) return null; if (v instanceof Number) return ((Number) v).longValue(); return Long.parseLong(v.toString()); }); - } else if (value instanceof Double || value instanceof Float) { + } else if (value instanceof Double) { + // Double 类型优先处理 return (AttributeKey) new AttributeKey<>(name, Double.class, v -> { if (v == null) return null; if (v instanceof Number) return ((Number) v).doubleValue(); return Double.parseDouble(v.toString()); }); + } else if (value instanceof Float) { + // Float 类型 + return (AttributeKey) new AttributeKey<>(name, Float.class, v -> { + if (v == null) return null; + if (v instanceof Number) return ((Number) v).floatValue(); + return Float.parseFloat(v.toString()); + }); } else { // 默认为 String 类型 return (AttributeKey) new AttributeKey<>(name, String.class, v -> v != null ? v.toString() : null); diff --git a/src/main/java/com/ruoyi/device/domain/model/thingsboard/TelemetryKey.java b/src/main/java/com/ruoyi/device/domain/model/thingsboard/TelemetryKey.java index 9c798f8..9a545d8 100644 --- a/src/main/java/com/ruoyi/device/domain/model/thingsboard/TelemetryKey.java +++ b/src/main/java/com/ruoyi/device/domain/model/thingsboard/TelemetryKey.java @@ -2,6 +2,7 @@ package com.ruoyi.device.domain.model.thingsboard; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.ruoyi.device.domain.model.thingsboard.attributes.psdk.PsdkDevice; +import com.ruoyi.device.domain.model.thingsboard.attributes.battery.BatteryData; import java.util.List; import java.util.Map; @@ -103,6 +104,11 @@ public class TelemetryKey { return (TelemetryKey) createPsdkWidgetValuesKeyWithoutRegistering(); } + // 特殊处理:battery 字段 + if ("battery".equals(name)) { + return (TelemetryKey) createBatteryKeyWithoutRegistering(); + } + // 根据值的实际类型推断 if (value instanceof Boolean) { return (TelemetryKey) new TelemetryKey<>(name, Boolean.class, v -> { @@ -110,18 +116,34 @@ public class TelemetryKey { if (v instanceof Boolean) return (Boolean) v; return Boolean.parseBoolean(v.toString()); }); - } else if (value instanceof Long || value instanceof Integer) { + } else if (value instanceof Integer) { + // Integer 类型优先处理 + return (TelemetryKey) new TelemetryKey<>(name, Integer.class, v -> { + if (v == null) return null; + if (v instanceof Number) return ((Number) v).intValue(); + return Integer.parseInt(v.toString()); + }); + } else if (value instanceof Long) { + // Long 类型 return (TelemetryKey) new TelemetryKey<>(name, Long.class, v -> { if (v == null) return null; if (v instanceof Number) return ((Number) v).longValue(); return Long.parseLong(v.toString()); }); - } else if (value instanceof Double || value instanceof Float) { + } else if (value instanceof Double) { + // Double 类型优先处理 return (TelemetryKey) new TelemetryKey<>(name, Double.class, v -> { if (v == null) return null; if (v instanceof Number) return ((Number) v).doubleValue(); return Double.parseDouble(v.toString()); }); + } else if (value instanceof Float) { + // Float 类型 + return (TelemetryKey) new TelemetryKey<>(name, Float.class, v -> { + if (v == null) return null; + if (v instanceof Number) return ((Number) v).floatValue(); + return Float.parseFloat(v.toString()); + }); } else { // 默认为 String 类型 return (TelemetryKey) new TelemetryKey<>(name, String.class, v -> v != null ? v.toString() : null); @@ -183,6 +205,55 @@ public class TelemetryKey { ); } + /** + * 创建 battery 的遥测键(不注册到 REGISTRY) + * 用于解析 Python 风格的字典字符串为 BatteryData + * 注意:不能调用 of() 方法,避免递归更新 + */ + @SuppressWarnings("unchecked") + private static TelemetryKey createBatteryKeyWithoutRegistering() { + ObjectMapper objectMapper = new ObjectMapper(); + // 配置 ObjectMapper 忽略未知字段 + objectMapper.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + return new TelemetryKey<>( + "battery", + BatteryData.class, + value -> { + if (value == null) return null; + + try { + // 如果已经是 BatteryData 类型,直接返回 + if (value instanceof BatteryData) { + return (BatteryData) value; + } + + // 如果是字符串,需要处理 Python 风格的字典格式 + if (value instanceof String) { + String jsonStr = (String) value; + + // 将 Python 风格的字典转换为标准 JSON 格式 + // 1. 将单引号替换为双引号 + jsonStr = jsonStr.replace("'", "\""); + + // 2. 处理 True/False/None (如果有的话) + jsonStr = jsonStr.replace(": True", ": true") + .replace(": False", ": false") + .replace(": None", ": null"); + + return objectMapper.readValue(jsonStr, BatteryData.class); + } + + // 如果是其他对象(如 JsonNode),转换为 JSON 再解析 + String json = objectMapper.writeValueAsString(value); + return objectMapper.readValue(json, BatteryData.class); + } catch (Exception e) { + throw new RuntimeException("Failed to parse battery: " + e.getMessage(), e); + } + } + ); + } + /** * 值解析器接口 */ diff --git a/src/main/java/com/ruoyi/device/domain/model/thingsboard/constants/DeviceTelemetry.java b/src/main/java/com/ruoyi/device/domain/model/thingsboard/constants/DeviceTelemetry.java index 4d34412..ea26e68 100644 --- a/src/main/java/com/ruoyi/device/domain/model/thingsboard/constants/DeviceTelemetry.java +++ b/src/main/java/com/ruoyi/device/domain/model/thingsboard/constants/DeviceTelemetry.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.ruoyi.device.domain.model.thingsboard.AttributeKey; import com.ruoyi.device.domain.model.thingsboard.TelemetryKey; import com.ruoyi.device.domain.model.thingsboard.attributes.psdk.PsdkDevice; +import com.ruoyi.device.domain.model.thingsboard.attributes.battery.BatteryData; import java.util.ArrayList; import java.util.Arrays; @@ -272,6 +273,46 @@ public class DeviceTelemetry { } ); + /** + * 电池信息 + * 包含电池列表、电量百分比、降落电量、剩余飞行时间、返航电量等信息 + */ + public static final TelemetryKey BATTERY = TelemetryKey.of( + "battery", + BatteryData.class, + value -> { + if (value == null) return null; + + try { + // 如果已经是 BatteryData 类型,直接返回 + if (value instanceof BatteryData) { + return (BatteryData) value; + } + + // 如果是字符串,需要处理 Python 风格的字典格式 + if (value instanceof String) { + String jsonStr = (String) value; + + // 将 Python 风格的字典转换为标准 JSON 格式 + // 1. 将单引号替换为双引号 + jsonStr = jsonStr.replace("'", "\""); + + // 2. 处理 True/False/None (如果有的话) + jsonStr = jsonStr.replace(": True", ": true") + .replace(": False", ": false") + .replace(": None", ": null"); + + return OBJECT_MAPPER.readValue(jsonStr, BatteryData.class); + } + + // 如果是其他对象(如 JsonNode),转换为 JSON 再解析 + String json = OBJECT_MAPPER.writeValueAsString(value); + return OBJECT_MAPPER.readValue(json, BatteryData.class); + } catch (Exception e) { + throw new RuntimeException("Failed to parse battery: " + e.getMessage(), e); + } + } + ); /** * 限高 @@ -343,6 +384,7 @@ public class DeviceTelemetry { TEMPERATURE, HUMIDITY, PSDK_WIDGET_VALUES, + BATTERY, FlightTask_Step_Code, Sub_Device_Online_Status, Total_Flight_Sorties,