From 868b19dddc1a28cdcf2703dac7a07df4b0b74221 Mon Sep 17 00:00:00 2001
From: yanderong <307678080@qq.com>
Date: 星期二, 15 九月 2026 11:40:22 +0800
Subject: [PATCH] feat: 储能数据同步,新运维大楼大屏的配置
---
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/EmsState.java | 488 ++++++++++++++++++++++++++++
/dev/null | 26 -
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/UdanEmsDeviceState.java | 69 ++-
ruoyi-service/ruoyi-other/src/main/resources/sql/t_screen_content_alter.sql | 6
ruoyi-service/ruoyi-other/src/main/resources/mapper/other/EmindStationMapper.xml | 2
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/service/EmindStationManagementService.java | 4
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/PowerMeterState.java | 7
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/util/UdanEmsKafkaUtil.java | 39 +
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/config/UdanEmsKafkaProperties.java | 2
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/domain/EmindStation.java | 4
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/service/UdanEmsMessageProcessor.java | 319 ++++++++++++++----
11 files changed, 822 insertions(+), 144 deletions(-)
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/domain/EmindStation.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/domain/EmindStation.java
index becfb3d..f65b128 100644
--- a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/domain/EmindStation.java
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/domain/EmindStation.java
@@ -85,4 +85,8 @@
/** Last update time. */
@ApiModelProperty("Last update time")
private LocalDateTime updateTime;
+ /**
+ * 电站设备编号
+ */
+ private String stationCode;
}
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/service/EmindStationManagementService.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/service/EmindStationManagementService.java
index 0dbeda7..95f124e 100644
--- a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/service/EmindStationManagementService.java
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/service/EmindStationManagementService.java
@@ -48,13 +48,14 @@
station.setMaxPower(input.getMaxPower());
station.setCapacity(input.getCapacity());
station.setCommissioningTime(input.getCommissioningTime());
- station.setPlatformName(EmindStation.DEFAULT_PLATFORM_NAME);
+ station.setPlatformName(input.getPlatformName());
station.setSourceType(EmindStation.SOURCE_MANUAL);
station.setManagementDeleted(0);
station.setDelFlag(0);
station.setSyncedAt(now);
station.setCreateTime(now);
station.setUpdateTime(now);
+ station.setStationCode(input.getStationCode());
mapper.insert(station);
return station;
}
@@ -80,6 +81,7 @@
station.setCapacity(input.getCapacity());
station.setCommissioningTime(input.getCommissioningTime());
station.setUpdateTime(LocalDateTime.now(clock));
+ station.setStationCode(input.getStationCode());
mapper.updateById(station);
return station;
}
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/config/UdanEmsKafkaProperties.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/config/UdanEmsKafkaProperties.java
index 9a26c50..439c541 100644
--- a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/config/UdanEmsKafkaProperties.java
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/config/UdanEmsKafkaProperties.java
@@ -16,5 +16,5 @@
private String topic;
private String groupId = "group-device-state";
private String autoOffsetReset = "latest";
- private int pollSeconds = 3600;
+ private int pollSeconds = 3600; // 1个小时
}
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/EmsState.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/EmsState.java
new file mode 100644
index 0000000..49adeaa
--- /dev/null
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/EmsState.java
@@ -0,0 +1,488 @@
+package com.ruoyi.other.udan.model;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 优旦储能-设备
+ * @author Lux
+ * @version 1.0.0
+ * @since 2026/9/14 17:59
+ */
+@Data
+public class EmsState implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 设备id (可关联系统配置的电站设备号)
+ * 只能由字母、数字和下划线组成,长度不超过50个字节
+ */
+ private String deviceId;
+
+ /**
+ * 系统状态
+ */
+ private String emsStatus;
+
+ /**
+ * 需量
+ */
+ private Float demandPower;
+
+ /**
+ * 4G卡
+ */
+ private String emsSimCard;
+
+ /**
+ * 时间
+ * 1970-01-01T00:00:00Z 以来的毫秒数
+ */
+ private Long time;
+
+ /**
+ * proto协议版本
+ */
+ private String protoProtocolVersion;
+
+ /**
+ * 运行状态
+ */
+ private EmsRunStatus emsRunStatus;
+
+ /**
+ * 工作状态
+ */
+ private EmsWorkStatus emsWorkStatus;
+
+ /**
+ * 故障状态
+ */
+ private ErrorStatus faultStatus;
+
+ /**
+ * 并离网状态
+ */
+ private String onGridStatus;
+
+ /**
+ * 控制源
+ */
+ private ControlSrc controlSrc;
+
+ /**
+ * 指令模式
+ */
+ private EmuModeCode emuModeCode;
+
+ /**
+ * 计划模式
+ */
+ private EmuAutoMode emuAutoMod;
+
+ /**
+ * 防逆流控制
+ */
+ private YesNo overDischargeProtect;
+
+ /**
+ * 防过载控制
+ */
+ private YesNo overChargeProtect;
+
+ /**
+ * 需量控制
+ */
+ private YesNo demandControl;
+
+ /**
+ * 动态增容控制
+ */
+ private YesNo capacityExpansionModeCtrl;
+
+ /**
+ * 日充电量
+ * 单位:kWh
+ */
+ private Float dailyChargeEnergyKwh;
+
+ /**
+ * 日放电量
+ * 单位:kWh
+ */
+ private Float dailyDischargeEnergyKwh;
+
+ /**
+ * 月充电量
+ * 单位:kWh
+ */
+ private Float monthlyChargeEnergyKwh;
+
+ /**
+ * 月放电量
+ * 单位:kWh
+ */
+ private Float monthlyDischargeEnergyKwh;
+
+ /**
+ * 累计充电量
+ * 单位:kWh
+ */
+ private Double totalChargeEnergyKwh;
+
+ /**
+ * 累计放电量
+ * 单位:kWh
+ */
+ private Double totalDischargeEnergyKwh;
+
+ /**
+ * PCS总有功功率
+ * 单位:kW
+ */
+ private Float pcsSumsActivePower;
+
+ /**
+ * PCS总视在功率
+ * 单位:kVar
+ */
+ private Float pcsSumsApparentPower;
+
+ /**
+ * 直流母线总电压
+ * 单位:V
+ */
+ private Float dcBusVoltage;
+
+ /**
+ * 直流母线总电流
+ * 单位:A
+ */
+ private Float dcBusCurrent;
+
+ /**
+ * 总SOC
+ * 单位:%
+ */
+ private Float sumsSoc;
+
+ /**
+ * 电池总节数
+ */
+ private Integer cellSums;
+
+ /**
+ * 电池平均温度
+ * 单位:°C
+ */
+ private Float batteryAverageTempr;
+
+ /**
+ * 电池最大温差
+ * 单位:°C
+ */
+ private Float batteryMaxTemprDiff;
+
+ /**
+ * 最高单体电压值
+ * 单位:mV
+ */
+ private Integer maxCellVoltage;
+
+ /**
+ * 最低单体电压值
+ * 单位:mV
+ */
+ private Integer minCellVoltage;
+
+ /**
+ * 系统最大允许充电功率
+ * 单位:kW
+ */
+ private Float systemMaxAllowChgPower;
+
+ /**
+ * 系统最大允许放电功率
+ * 单位:kW
+ */
+ private Float systemMaxAllowDisPower;
+
+ /**
+ * 可充电量
+ * 单位:kWh
+ */
+ private Float rechargeableKwh;
+
+ /**
+ * 可放电量
+ * 单位:kWh
+ */
+ private Float dischargeableKwh;
+
+ /**
+ * 当前时刻最大允许充电功率(汇总)
+ * 单位:kW
+ */
+ private Float currentMaxAllowChgPower;
+
+ /**
+ * 当前时刻最大允许放电功率(汇总)
+ * 单位:kW
+ */
+ private Float currentMaxAllowDisPower;
+
+ /**
+ * SOC上限
+ * 单位:%
+ */
+ private Float socUpperLimit;
+
+ /**
+ * SOC下限
+ * 单位:%
+ */
+ private Float socLowerLimit;
+
+ /**
+ * 最大功率放电时长
+ * 单位:s
+ */
+ private Integer maxPowerDischargeDuration;
+
+ /**
+ * 最大功率充电时长
+ * 单位:s
+ */
+ private Integer maxPowerChargeDuration;
+
+ /**
+ * 与主机通讯状态
+ */
+ private String masterCommStatus;
+
+ /**
+ * 并网点正向有功电能
+ * 单位:kWh
+ */
+ private Double gridImportActiveEnergyKwh;
+
+ /**
+ * 并网点反向有功电能
+ * 单位:kWh
+ */
+ private Double gridExportActiveEnergyKwh;
+
+ /**
+ * 直流侧总功率
+ * 单位:kW
+ */
+ private Float dcTotalPower;
+
+ /**
+ * 负载功率
+ * 正放负充
+ * 单位:kW
+ */
+ private Float loadTotalPower;
+
+ /**
+ * 储能侧总功率
+ * 正放负充
+ * 单位:kW
+ */
+ private Float energyStoreTotalPower;
+
+ /**
+ * 高压侧电网功率
+ * 正放负充
+ * 单位:kW
+ */
+ private Float highSideGridPower;
+
+ /**
+ * 低压侧电网功率
+ * 正放负充
+ * 单位:kW
+ */
+ private Float lowSideGridPower;
+
+ /**
+ * 光伏功率
+ * 正放负充
+ * 单位:kW
+ */
+ private Float pvPower;
+
+ /**
+ * 总SOH
+ * 单位:%
+ */
+ private Float sumsSoh;
+
+ public enum ErrorStatus {
+ ERROR_STATUS_UNKNOWN(0), ERROR_STATUS_OCCURRING(1), ERROR_STATUS_NONE(2);
+ private final int code;
+ ErrorStatus(int code) { this.code = code; }
+ public int getCode() { return code; }
+ }
+
+
+ /**
+ * EMS运行状态
+ */
+ public enum EmsRunStatus {
+
+ /**
+ * 预留
+ */
+ EMS_RUN_STATUS_UNKNOWN(0),
+
+ /**
+ * 停止
+ */
+ EMS_RUN_STATUS_STOPPED(1),
+
+ /**
+ * 待机
+ */
+ EMS_RUN_STATUS_IDLE(2),
+
+ /**
+ * 运行
+ */
+ EMS_RUN_STATUS_RUNNING(3),
+
+ /**
+ * 测试
+ */
+ EMS_RUN_STATUS_TEST(4);
+
+ private final int code;
+
+ EmsRunStatus(int code) {
+ this.code = code;
+ }
+
+ public int getCode() {
+ return code;
+ }
+ }
+
+ /**
+ * EMS工作状态
+ */
+ public enum EmsWorkStatus {
+
+ EMS_WORK_STATUS_UNKNOWN(0),
+
+ EMS_WORK_STATUS_CHARGING(1),
+
+ EMS_WORK_STATUS_DISCHARGING(2),
+
+ EMS_WORK_STATUS_IDLE(3);
+
+ private final int code;
+
+ EmsWorkStatus(int code) {
+ this.code = code;
+ }
+
+ public int getCode() {
+ return code;
+ }
+ }
+
+ /**
+ * 控制源
+ */
+ public enum ControlSrc {
+
+ CONTROL_SRC_UNKNOWN(0),
+
+ CONTROL_SRC_LOCAL(1),
+
+ CONTROL_SRC_REMOTE(2),
+
+ CONTROL_SRC_OTHER(3);
+
+ private final int code;
+
+ ControlSrc(int code) {
+ this.code = code;
+ }
+
+ public int getCode() {
+ return code;
+ }
+ }
+
+ /**
+ * 指令模式
+ */
+ public enum EmuModeCode {
+
+ EMU_MODE_CODE_UNKNOWN(0),
+
+ EMU_MODE_CODE_SUB_SYSTEM(1),
+
+ EMU_MODE_CODE_PLAN(2),
+
+ EMU_MODE_CODE_GENERAL_INSTRUCTION(3);
+
+ private final int code;
+
+ EmuModeCode(int code) {
+ this.code = code;
+ }
+
+ public int getCode() {
+ return code;
+ }
+ }
+
+ /**
+ * 计划模式
+ */
+ public enum EmuAutoMode {
+
+ EMU_AUTO_MODE_UNKNOWN(0),
+
+ EMU_AUTO_MODE_24_HOUR_SCHEDULED_MODE(1),
+
+ EMU_AUTO_MODE_DEMAND_RESPONSE_SCHEDULED_MODE(2);
+
+ private final int code;
+
+ EmuAutoMode(int code) {
+ this.code = code;
+ }
+
+ public int getCode() {
+ return code;
+ }
+ }
+
+ /**
+ * 保留
+ */
+ public enum YesNo {
+
+ YES_NO_UNKNOWN(0), // 保留
+
+ YES_NO_YES(1), // 是
+
+ YES_NO_NO(2); // 否
+
+ private final int code;
+
+ YesNo(int code) {
+ this.code = code;
+ }
+
+ public int getCode() {
+ return code;
+ }
+ }
+}
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/PowerMeterState.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/PowerMeterState.java
index 707d629..4c5f63a 100644
--- a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/PowerMeterState.java
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/PowerMeterState.java
@@ -2,13 +2,18 @@
import lombok.Data;
+import java.io.Serializable;
+
/**
* @author Lux
* @version 1.0.0
* @since 2026/9/10 17:55
*/
@Data
-public class PowerMeterState {
+public class PowerMeterState implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
/** 峰用电总价,单位:元 */
private double cumulativeImportAmountPeak;
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/UdanEmsDatabaseRecord.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/UdanEmsDatabaseRecord.java
deleted file mode 100644
index f3fa9eb..0000000
--- a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/UdanEmsDatabaseRecord.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.ruoyi.other.udan.model;
-
-import lombok.Data;
-
-import java.time.Instant;
-
-/** 待写入数据库的业务对象;仅定义映射结果,不执行数据库操作。 */
-@Data
-public class UdanEmsDatabaseRecord {
- private String emsSystemId;
- private Instant eventTime;
- private int pcsCount;
- private int batteryStackCount;
- private int batteryClusterCount;
- private int electricMeterCount;
- private int powerMeterCount;
- private int airConditionerCount;
- private int fireCount;
- private int jpfCount;
- private int tmsCount;
- private int dehumidifierCount;
- private int upsCount;
- private int pvCount;
- private int chargeStationCount;
- private int extPeripheralCount;
-}
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/UdanEmsDeviceState.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/UdanEmsDeviceState.java
index 5c382c7..2119556 100644
--- a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/UdanEmsDeviceState.java
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/UdanEmsDeviceState.java
@@ -1,35 +1,60 @@
package com.ruoyi.other.udan.model;
+import com.ruoyi.other.emind.dto.EmindIncomeHistoryDto;
import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
/** iems-open-v1.0.5.proto 中 IEmsAllDeviceState 对应的业务对象。 */
@Data
public class UdanEmsDeviceState {
private String emsSystemId;
private long time;
- private EmsState ems;
- private List<PcsState> pcsStates = new ArrayList<>();
- private List<BatteryStackState> batteryStackStates = new ArrayList<>();
- private List<BatteryClusterState> batteryClusterStates = new ArrayList<>();
- private List<ElectricMeterState> electricMeterStates = new ArrayList<>();
- private List<PvState> pvStates = new ArrayList<>();
- private List<ChargeStationState> chargeStationStates = new ArrayList<>();
- private int powerMeterCount;
- private int airConditionerCount;
- private int fireCount;
- private int jpfCount;
- private int tmsCount;
- private int dehumidifierCount;
- private int upsCount;
- private int extPeripheralCount;
+ private String emsDeviceId;
+ private EmsState emsState;
+ private PowerMeterState powerMeterState;
- @Data public static class EmsState { private String deviceId; private int emsStatus; private float demandPower; private long time; private String protoProtocolVersion; private int emsRunStatus; private int emsWorkStatus; private int faultStatus; private int onGridStatus; private float dailyChargeEnergyKwh; private float dailyDischargeEnergyKwh; private float monthlyChargeEnergyKwh; private float monthlyDischargeEnergyKwh; private double totalChargeEnergyKwh; private double totalDischargeEnergyKwh; private float pcsSumsActivePower; private float sumsSoc; private float sumsSoh; private float dcBusVoltage; private float dcBusCurrent; }
- @Data public static class PcsState { private long time; private String deviceId; private int errorStatus; private int alarmStatus; private int runStatus; private int onGridStatus; private float acTotalActivePower; private float acTotalReactivePower; private float acTotalApparentPower; private float acTotalPowerFactor; private List<Float> dcTotalVoltage = new ArrayList<>(); private List<Float> dcCurrent = new ArrayList<>(); private List<Float> dcPower = new ArrayList<>(); private float dailyChargeEnergyKwh; private float dailyDischargeEnergyKwh; private double totalChargeEnergyKwh; private double totalDischargeEnergyKwh; private int controlMode; private int workStatus; private float ambientTemp; private int commStatus; }
- @Data public static class BatteryStackState { private long time; private String deviceId; private int workStatus; private int errorStatus; private float tv; private float tc; private float soh; private float soc; private float rechargeableCapacityKwh; private float dischargeableCapacityKwh; private double cumulativeDischargeKwh; private double cumulativeChargeKwh; private int avgCellVoltageMv; private int avgCellTemp; private int batteryStatus; private int commStatus; }
- @Data public static class BatteryClusterState { private long time; private String deviceId; private String stackDeviceId; private int workStatus; private float tv; private float tc; private float soh; private float soc; private float rechargeableCapacityKwh; private float dischargeableCapacityKwh; private double cumulativeChargeKwh; private double cumulativeDischargeKwh; private int avgCellTemp; private int avgCellVoltMv; private int commStatus; }
- @Data public static class ElectricMeterState { private long time; private String deviceId; private int meterType; private double cumulativeImportKwh; private double cumulativeExportKwh; private float acTotalActivePower; private float aPhaseActivePower; private float bPhaseActivePower; private float cPhaseActivePower; private int commStatus; }
- @Data public static class PvState { private String deviceId; private int commStatus; private float activePower; private float reactivePower; private float powerFactor; private float dailyPowerGeneration; private double totalPowerGeneration; private int runStatus; private int errorStatus; private int alarmStatus; }
- @Data public static class ChargeStationState { private String deviceId; private long time; private int runStatus; private int commStatus; }
+ public List<EmindIncomeHistoryDto> convert(Long stationId) {
+ List<EmindIncomeHistoryDto> dailyList = new ArrayList<>();
+
+ if(Objects.nonNull(this.getPowerMeterState())) {
+ EmindIncomeHistoryDto daily = new EmindIncomeHistoryDto();
+ daily.setStationId(stationId);
+ Optional.of(getPowerMeterState().getTime()).ifPresent(t -> {
+ LocalDate localDate = Instant.ofEpochMilli(t).atZone(ZoneId.systemDefault()).toLocalDate();
+ daily.setDate(localDate);
+ });
+
+ daily.setPrice(getPowerMeterState().getDailyImportAmount());
+ daily.setPriceName("");
+ Optional.of(getPowerMeterState().getDailyProfit()).map(String::valueOf).map(BigDecimal::new).ifPresent(daily::setIncome);
+ Optional.of(getPowerMeterState().getDailyExportKwhValley()).map(String::valueOf).map(BigDecimal::new).ifPresent(daily::setDischargedEnergyValley);
+ Optional.of(getPowerMeterState().getDailyExportKwhSharp()).map(String::valueOf).map(BigDecimal::new).ifPresent(daily::setDischargedEnergySharp);
+ Optional.of(getPowerMeterState().getDailyExportKwhPeak()).map(String::valueOf).map(BigDecimal::new).ifPresent(daily::setDischargedEnergyPeak);
+ Optional.of(getPowerMeterState().getDailyExportKwhShoulder()).map(String::valueOf).map(BigDecimal::new).ifPresent(daily::setDischargedEnergyFlat);
+ Optional.of(getPowerMeterState().getDailyExportKwh()).map(String::valueOf).map(BigDecimal::new).ifPresent(daily::setDischargedEnergy);
+ Optional.of(getPowerMeterState().getDailyImportKwhValley()).map(String::valueOf).map(BigDecimal::new).ifPresent(daily::setChargedEnergyValley);
+ Optional.of(getPowerMeterState().getDailyImportKwhSharp()).map(String::valueOf).map(BigDecimal::new).ifPresent(daily::setChargedEnergySharp);
+ Optional.of(getPowerMeterState().getDailyImportKwhPeak()).map(String::valueOf).map(BigDecimal::new).ifPresent(daily::setChargedEnergyPeak);
+ Optional.of(getPowerMeterState().getDailyImportKwhShoulder()).map(String::valueOf).map(BigDecimal::new).ifPresent(daily::setChargedEnergyFlat);
+ Optional.of(getPowerMeterState().getDailyImportKwh()).map(String::valueOf).map(BigDecimal::new).ifPresent(daily::setChargedEnergy);
+ dailyList.add(daily);
+ }
+ return dailyList;
+ }
+
+ // @Data public static class EmsState { private String deviceId; private int emsStatus; private float demandPower; private long time; private String protoProtocolVersion; private int emsRunStatus; private int emsWorkStatus; private int faultStatus; private int onGridStatus; private float dailyChargeEnergyKwh; private float dailyDischargeEnergyKwh; private float monthlyChargeEnergyKwh; private float monthlyDischargeEnergyKwh; private double totalChargeEnergyKwh; private double totalDischargeEnergyKwh; private float pcsSumsActivePower; private float sumsSoc; private float sumsSoh; private float dcBusVoltage; private float dcBusCurrent; }
+ // @Data public static class PcsState { private long time; private String deviceId; private int errorStatus; private int alarmStatus; private int runStatus; private int onGridStatus; private float acTotalActivePower; private float acTotalReactivePower; private float acTotalApparentPower; private float acTotalPowerFactor; private List<Float> dcTotalVoltage = new ArrayList<>(); private List<Float> dcCurrent = new ArrayList<>(); private List<Float> dcPower = new ArrayList<>(); private float dailyChargeEnergyKwh; private float dailyDischargeEnergyKwh; private double totalChargeEnergyKwh; private double totalDischargeEnergyKwh; private int controlMode; private int workStatus; private float ambientTemp; private int commStatus; }
+// @Data public static class BatteryStackState { private long time; private String deviceId; private int workStatus; private int errorStatus; private float tv; private float tc; private float soh; private float soc; private float rechargeableCapacityKwh; private float dischargeableCapacityKwh; private double cumulativeDischargeKwh; private double cumulativeChargeKwh; private int avgCellVoltageMv; private int avgCellTemp; private int batteryStatus; private int commStatus; }
+// @Data public static class BatteryClusterState { private long time; private String deviceId; private String stackDeviceId; private int workStatus; private float tv; private float tc; private float soh; private float soc; private float rechargeableCapacityKwh; private float dischargeableCapacityKwh; private double cumulativeChargeKwh; private double cumulativeDischargeKwh; private int avgCellTemp; private int avgCellVoltMv; private int commStatus; }
+// @Data public static class ElectricMeterState { private long time; private String deviceId; private int meterType; private double cumulativeImportKwh; private double cumulativeExportKwh; private float acTotalActivePower; private float aPhaseActivePower; private float bPhaseActivePower; private float cPhaseActivePower; private int commStatus; }
+// @Data public static class PvState { private String deviceId; private int commStatus; private float activePower; private float reactivePower; private float powerFactor; private float dailyPowerGeneration; private double totalPowerGeneration; private int runStatus; private int errorStatus; private int alarmStatus; }
+// @Data public static class ChargeStationState { private String deviceId; private long time; private int runStatus; private int commStatus; }
}
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/service/UdanEmsMessageProcessor.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/service/UdanEmsMessageProcessor.java
index 95e8088..d305f4d 100644
--- a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/service/UdanEmsMessageProcessor.java
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/service/UdanEmsMessageProcessor.java
@@ -3,112 +3,281 @@
import com.alibaba.fastjson.JSON;
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.WireFormat;
+import com.ruoyi.other.emind.domain.EmindStation;
+import com.ruoyi.other.emind.dto.EmindIncomeHistoryDto;
+import com.ruoyi.other.emind.mapper.EmindStationMapper;
+import com.ruoyi.other.emind.mapper.EmindStorageDashboardMapper;
+import com.ruoyi.other.udan.model.EmsState;
+import com.ruoyi.other.udan.model.PowerMeterState;
import com.ruoyi.other.udan.model.UdanEmsDeviceState;
import com.ruoyi.other.udan.util.UdanEmsKafkaUtil;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
+import javax.annotation.PostConstruct;
import java.io.IOException;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Objects;
-/** 将 iems-open-v1.0.5.proto 消息解析为业务对象,并映射为待入库对象。 */
+/** 将 IEmsAllDeviceState 中模型已有的 EMS 和能耗计字段转换为业务对象。 */
@Slf4j
@Component
@ConditionalOnProperty(prefix = "udan.ems.kafka", name = "enabled", havingValue = "true")
public class UdanEmsMessageProcessor {
private final UdanEmsKafkaUtil kafkaUtil;
+ private final EmindStorageDashboardMapper dashboardMapper;
+ private final EmindStationMapper stationMapper;
- /** 注入 Kafka 消费工具。 */
- public UdanEmsMessageProcessor(UdanEmsKafkaUtil kafkaUtil) { this.kafkaUtil = kafkaUtil; }
+ public UdanEmsMessageProcessor(UdanEmsKafkaUtil kafkaUtil, EmindStorageDashboardMapper dashboardMapper, EmindStationMapper stationMapper) {
+ this.kafkaUtil = kafkaUtil;
+ this.dashboardMapper = dashboardMapper;
+ this.stationMapper = stationMapper;
+ }
- /** 注册消息回调,避免消费者只消费但没有业务处理。 */
- //@PostConstruct
- public void register() { kafkaUtil.setMessageHandler(this::process); }
+ @PostConstruct
+ public void register() {
+ kafkaUtil.setMessageHandler(this::process);
+ }
- /** 解析消息、转换待入库对象并输出日志;此处暂不调用数据库。 */
- public void process(String emsSystemId, byte[] protobufBytes) {
+ public void process(String id, byte[] bytes, String emsDeviceId) {
try {
- UdanEmsDeviceState state = parse(emsSystemId, protobufBytes);
- log.info("2、UDAN EMS实际对象: {}", JSON.toJSONString(state));
+ LocalDateTime now = LocalDateTime.now();
+ UdanEmsDeviceState udanEmsDeviceState = parse(id, emsDeviceId, bytes);
+ if(StringUtils.isNotEmpty(udanEmsDeviceState.getEmsDeviceId())) {
+ log.info("2、UDAN EMS实际对象: {}", JSON.toJSONString(udanEmsDeviceState));
+ List<EmindStation> stations = stationMapper.selectRemoteActiveStations();
+ EmindStation station = stations.stream().filter(s -> udanEmsDeviceState.getEmsDeviceId().equals(s.getStationCode())).findFirst().orElse(null);
+ if(Objects.nonNull(station)) {
+ List<EmindIncomeHistoryDto> daily = udanEmsDeviceState.convert(station.getStationId());
+ dashboardMapper.upsertDaily(station.getStationId(), daily, now);
+ }else{
+ log.info("UDAN EMS数据同步错误-电站信息不存在!");
+ }
+ }
+
} catch (IOException e) {
- log.error("UDAN EMS protobuf解析失败: emsSystemId={}, bytes={}", emsSystemId, protobufBytes == null ? 0 : protobufBytes.length, e);
+ log.error("UDAN EMS protobuf解析失败: emsSystemId={}, bytes={}", id,
+ bytes == null ? 0 : bytes.length, e);
}
}
- /** 按顶层 IEmsAllDeviceState 字段号读取并构造真实业务对象。 */
+ /** 兼容不带 Header 的直接调用。 */
+ public void process(String id, byte[] bytes) {
+ process(id, bytes, null);
+ }
+
+ public UdanEmsDeviceState parse(String id, String emsDeviceId, byte[] bytes) throws IOException {
+ UdanEmsDeviceState state = new UdanEmsDeviceState();
+ state.setEmsSystemId(id);
+ state.setEmsDeviceId(emsDeviceId);
+ read(bytes, (field, type, in) -> {
+ if (field == 1 && type == 0) state.setTime(in.readUInt64());
+ else if (type == 2 && field == 2) state.setEmsState(parseEms(in.readBytes().toByteArray()));
+ else if (type == 2 && field == 8) state.setPowerMeterState(parsePowerMeter(in.readBytes().toByteArray()));
+ else skip(in, type);
+ });
+ return state;
+ }
+
+ /** 兼容不带 Header 的直接解析调用。 */
public UdanEmsDeviceState parse(String id, byte[] bytes) throws IOException {
- UdanEmsDeviceState s = new UdanEmsDeviceState();
- s.setEmsSystemId(id);
- CodedInputStream in = CodedInputStream.newInstance(bytes == null ? new byte[0] : bytes);
- while (!in.isAtEnd()) {
- int tag = in.readTag(); if (tag == 0) break;
- int field = WireFormat.getTagFieldNumber(tag); int type = WireFormat.getTagWireType(tag);
- if (field == 1 && type == WireFormat.WIRETYPE_VARINT) s.setTime(in.readUInt64());
- else if (type == WireFormat.WIRETYPE_LENGTH_DELIMITED) parseChild(s, field, in.readBytes().toByteArray());
- else in.skipField(tag);
- }
- return s;
+ return parse(id, null, bytes);
}
-
- /** 解析顶层各子消息,字段号来自 iems-open-v1.0.5.proto。 */
- private void parseChild(UdanEmsDeviceState s, int field, byte[] data) throws IOException {
- switch (field) {
- case 2: s.setEms(parseEms(data)); break;
- case 4: s.getPcsStates().add(parsePcs(data)); break;
- case 5: s.getBatteryStackStates().add(parseStack(data)); break;
- case 6: s.getBatteryClusterStates().add(parseCluster(data)); break;
- case 7: s.getElectricMeterStates().add(parseMeter(data)); break;
- case 8: s.setPowerMeterCount(s.getPowerMeterCount() + 1); break;
- case 9: s.setAirConditionerCount(s.getAirConditionerCount() + 1); break;
- case 11: s.setFireCount(s.getFireCount() + 1); break;
- case 13: s.setJpfCount(s.getJpfCount() + 1); break;
- case 14: s.setTmsCount(s.getTmsCount() + 1); break;
- case 18: s.setDehumidifierCount(s.getDehumidifierCount() + 1); break;
- case 19: s.setUpsCount(s.getUpsCount() + 1); break;
- case 20: s.getPvStates().add(parsePv(data)); break;
- case 21: s.getChargeStationStates().add(parseChargeStation(data)); break;
- case 23: s.setExtPeripheralCount(s.getExtPeripheralCount() + 1); break;
- default: break;
- }
- }
-
- /** 解析 EMS 状态对象中的实际字段。 */
- private UdanEmsDeviceState.EmsState parseEms(byte[] b) throws IOException {
- UdanEmsDeviceState.EmsState x = new UdanEmsDeviceState.EmsState();
- read(b, (f, t, in) -> { if (t == 2 && f == 1) x.setDeviceId(in.readString()); else if (t == 5 && f == 18) x.setDemandPower(in.readFloat()); else if (t == 2 && f == 21) x.setProtoProtocolVersion(in.readString()); else if (t == 5 && f == 33) x.setDailyChargeEnergyKwh(in.readFloat()); else if (t == 5 && f == 34) x.setDailyDischargeEnergyKwh(in.readFloat()); else if (t == 1 && f == 37) x.setTotalChargeEnergyKwh(in.readDouble()); else if (t == 1 && f == 38) x.setTotalDischargeEnergyKwh(in.readDouble()); else if (t == 5 && f == 43) x.setSumsSoc(in.readFloat()); else if (t == 5 && f == 69) x.setSumsSoh(in.readFloat()); else if (t == 5 && f == 41) x.setDcBusVoltage(in.readFloat()); else if (t == 5 && f == 42) x.setDcBusCurrent(in.readFloat()); else skip(in, t); });
+ private EmsState parseEms(byte[] bytes) throws IOException {
+ EmsState x = new EmsState();
+ read(bytes, (f, t, in) -> {
+ switch (f) {
+ case 1: x.setDeviceId(str(in,t)); break; case 9: x.setEmsStatus(enumName(in,t,"RunStatus")); break;
+ case 18: x.setDemandPower(f32(in,t)); break; case 20: x.setTime(u64(in,t)); break; case 21: x.setProtoProtocolVersion(str(in,t)); break;
+ case 22: x.setEmsRunStatus(enumAt(EmsState.EmsRunStatus.values(),in,t)); break; case 23: x.setEmsWorkStatus(enumAt(EmsState.EmsWorkStatus.values(),in,t)); break;
+ case 24: x.setFaultStatus(enumAt(EmsState.ErrorStatus.values(),in,t)); break; case 25: x.setOnGridStatus(enumName(in,t,"GridStatus")); break;
+ case 26: x.setControlSrc(enumAt(EmsState.ControlSrc.values(),in,t)); break; case 27: x.setEmuModeCode(enumAt(EmsState.EmuModeCode.values(),in,t)); break;
+ case 28: x.setEmuAutoMod(enumAt(EmsState.EmuAutoMode.values(),in,t)); break; case 29: x.setOverDischargeProtect(enumAt(EmsState.YesNo.values(),in,t)); break;
+ case 30: x.setOverChargeProtect(enumAt(EmsState.YesNo.values(),in,t)); break; case 31: x.setDemandControl(enumAt(EmsState.YesNo.values(),in,t)); break; case 32: x.setCapacityExpansionModeCtrl(enumAt(EmsState.YesNo.values(),in,t)); break;
+ case 33: x.setDailyChargeEnergyKwh(f32(in,t)); break; case 34: x.setDailyDischargeEnergyKwh(f32(in,t)); break; case 35: x.setMonthlyChargeEnergyKwh(f32(in,t)); break; case 36: x.setMonthlyDischargeEnergyKwh(f32(in,t)); break;
+ case 37: x.setTotalChargeEnergyKwh(f64(in,t)); break; case 38: x.setTotalDischargeEnergyKwh(f64(in,t)); break; case 39: x.setPcsSumsActivePower(f32(in,t)); break; case 40: x.setPcsSumsApparentPower(f32(in,t)); break;
+ case 41: x.setDcBusVoltage(f32(in,t)); break; case 42: x.setDcBusCurrent(f32(in,t)); break; case 43: x.setSumsSoc(f32(in,t)); break; case 44: x.setCellSums(u32(in,t)); break;
+ case 45: x.setBatteryAverageTempr(f32(in,t)); break; case 46: x.setBatteryMaxTemprDiff(f32(in,t)); break; case 47: x.setMaxCellVoltage(u32(in,t)); break; case 48: x.setMinCellVoltage(u32(in,t)); break;
+ case 49: x.setSystemMaxAllowChgPower(f32(in,t)); break; case 50: x.setSystemMaxAllowDisPower(f32(in,t)); break; case 51: x.setRechargeableKwh(f32(in,t)); break; case 52: x.setDischargeableKwh(f32(in,t)); break;
+ case 53: x.setCurrentMaxAllowChgPower(f32(in,t)); break; case 54: x.setCurrentMaxAllowDisPower(f32(in,t)); break; case 55: x.setSocUpperLimit(f32(in,t)); break; case 56: x.setSocLowerLimit(f32(in,t)); break;
+ case 57: x.setMaxPowerDischargeDuration(u32(in,t)); break; case 58: x.setMaxPowerChargeDuration(u32(in,t)); break; case 60: x.setMasterCommStatus(enumName(in,t,"CommStatus")); break;
+ case 61: x.setGridImportActiveEnergyKwh(f64(in,t)); break; case 62: x.setGridExportActiveEnergyKwh(f64(in,t)); break; case 63: x.setDcTotalPower(f32(in,t)); break; case 64: x.setLoadTotalPower(f32(in,t)); break;
+ case 65: x.setEnergyStoreTotalPower(f32(in,t)); break; case 66: x.setHighSideGridPower(f32(in,t)); break; case 67: x.setLowSideGridPower(f32(in,t)); break; case 68: x.setPvPower(f32(in,t)); break; case 69: x.setSumsSoh(f32(in,t)); break;
+ default: skip(in,t);
+ }
+ });
return x;
}
- /** 解析 PCS 实际字段。 */
- private UdanEmsDeviceState.PcsState parsePcs(byte[] b) throws IOException {
- UdanEmsDeviceState.PcsState x = new UdanEmsDeviceState.PcsState();
- read(b, (f, t, in) -> { if (t == 2 && f == 2) x.setDeviceId(in.readString()); else if (t == 5 && f == 13) x.setAcTotalActivePower(in.readFloat()); else if (t == 5 && f == 21) x.setAcTotalReactivePower(in.readFloat()); else if (t == 5 && f == 22) x.setAcTotalApparentPower(in.readFloat()); else if (t == 5 && f == 23) x.setAcTotalPowerFactor(in.readFloat()); else if (t == 5 && f == 17) x.setDailyChargeEnergyKwh(in.readFloat()); else if (t == 5 && f == 18) x.setDailyDischargeEnergyKwh(in.readFloat()); else if (t == 1 && f == 19) x.setTotalChargeEnergyKwh(in.readDouble()); else if (t == 1 && f == 20) x.setTotalDischargeEnergyKwh(in.readDouble()); else if (t == 5 && f == 59) x.setAmbientTemp(in.readFloat()); else skip(in, t); });
- return x;
+ private PowerMeterState parsePowerMeter(byte[] bytes) throws IOException {
+ PowerMeterState value = new PowerMeterState();
+ read(bytes, (field, type, input) -> {
+ switch (field) {
+ case 1: value.setCumulativeImportAmountPeak(f64(input, type)); break;
+ case 2: value.setCumulativeImportAmountShoulder(f64(input, type)); break;
+ case 3: value.setCumulativeImportAmountValley(f64(input, type)); break;
+ case 4: value.setCumulativeImportAmount(f64(input, type)); break;
+ case 5: value.setDailyImportAmount((float) f64(input, type)); break;
+ case 6: value.setMonthlyImportAmount(f64(input, type)); break;
+ case 7: value.setDailyProfit(f64(input, type)); break;
+ case 8: value.setYesterdayProfit(f64(input, type)); break;
+ case 9: value.setMonthlyProfit(f64(input, type)); break;
+ case 10: value.setLastMonthlyProfit(f64(input, type)); break;
+ case 11: value.setCumulativeProfit(f64(input, type)); break;
+ case 12: value.setCumulativeImportAmountSharp(f64(input, type)); break;
+ case 15: value.setCumulativeImportKwh(f64(input, type)); break;
+ case 16: value.setCumulativeImportKwhSharp(f64(input, type)); break;
+ case 17: value.setCumulativeImportKwhPeak(f64(input, type)); break;
+ case 18: value.setCumulativeImportKwhShoulder(f64(input, type)); break;
+ case 19: value.setCumulativeImportKwhValley(f64(input, type)); break;
+ case 20: value.setCumulativeExportKwh(f64(input, type)); break;
+ case 21: value.setCumulativeExportKwhSharp(f64(input, type)); break;
+ case 22: value.setCumulativeExportKwhPeak(f64(input, type)); break;
+ case 23: value.setCumulativeExportKwhShoulder(f64(input, type)); break;
+ case 24: value.setCumulativeExportKwhValley(f64(input, type)); break;
+ case 25: value.setMonthlyImportKwh(f64(input, type)); break;
+ case 26: value.setMonthlyImportKwhSharp(f64(input, type)); break;
+ case 27: value.setMonthlyImportKwhPeak(f64(input, type)); break;
+ case 28: value.setMonthlyImportKwhShoulder(f64(input, type)); break;
+ case 29: value.setMonthlyImportKwhValley(f64(input, type)); break;
+ case 30: value.setMonthlyExportKwh(f64(input, type)); break;
+ case 31: value.setMonthlyExportKwhSharp(f64(input, type)); break;
+ case 32: value.setMonthlyExportKwhPeak(f64(input, type)); break;
+ case 33: value.setMonthlyExportKwhShoulder(f64(input, type)); break;
+ case 34: value.setMonthlyExportKwhValley(f64(input, type)); break;
+ case 35: value.setDailyImportKwh(f32(input, type)); break;
+ case 36: value.setDailyImportKwhSharp(f32(input, type)); break;
+ case 37: value.setDailyImportKwhPeak(f32(input, type)); break;
+ case 38: value.setDailyImportKwhShoulder(f32(input, type)); break;
+ case 39: value.setDailyImportKwhValley(f32(input, type)); break;
+ case 40: value.setDailyExportKwh(f32(input, type)); break;
+ case 41: value.setDailyExportKwhSharp(f32(input, type)); break;
+ case 42: value.setDailyExportKwhPeak(f32(input, type)); break;
+ case 43: value.setDailyExportKwhShoulder(f32(input, type)); break;
+ case 44: value.setDailyExportKwhValley(f32(input, type)); break;
+ case 45: value.setCumulativeExportAmount(f64(input, type)); break;
+ case 46: value.setCumulativeExportAmountSharp(f64(input, type)); break;
+ case 47: value.setCumulativeExportAmountPeak(f64(input, type)); break;
+ case 48: value.setCumulativeExportAmountShoulder(f64(input, type)); break;
+ case 49: value.setCumulativeExportAmountValley(f64(input, type)); break;
+ case 50: value.setMonthlyImportAmountSharp(f64(input, type)); break;
+ case 51: value.setMonthlyImportAmountPeak(f64(input, type)); break;
+ case 52: value.setMonthlyImportAmountShoulder(f64(input, type)); break;
+ case 53: value.setMonthlyImportAmountValley(f64(input, type)); break;
+ case 54: value.setMonthlyExportAmount(f64(input, type)); break;
+ case 55: value.setMonthlyExportAmountSharp(f64(input, type)); break;
+ case 56: value.setMonthlyExportAmountPeak(f64(input, type)); break;
+ case 57: value.setMonthlyExportAmountShoulder(f64(input, type)); break;
+ case 58: value.setMonthlyExportAmountValley(f64(input, type)); break;
+ case 59: value.setDailyImportAmountSharp(f32(input, type)); break;
+ case 60: value.setDailyImportAmountPeak(f32(input, type)); break;
+ case 61: value.setDailyImportAmountShoulder(f32(input, type)); break;
+ case 62: value.setDailyImportAmountValley(f32(input, type)); break;
+ case 63: value.setDailyExportAmount(f32(input, type)); break;
+ case 64: value.setDailyExportAmountSharp(f32(input, type)); break;
+ case 65: value.setDailyExportAmountPeak(f32(input, type)); break;
+ case 66: value.setDailyExportAmountShoulder(f32(input, type)); break;
+ case 67: value.setDailyExportAmountValley(f32(input, type)); break;
+ case 68: value.setTime(u64(input, type)); break;
+ default: skip(input, type);
+ }
+ });
+ return value;
}
- /** 解析电池堆实际字段。 */
- private UdanEmsDeviceState.BatteryStackState parseStack(byte[] b) throws IOException { UdanEmsDeviceState.BatteryStackState x = new UdanEmsDeviceState.BatteryStackState(); read(b, (f,t,in)-> { if(t==2&&f==2)x.setDeviceId(in.readString()); else if(t==5&&f==7)x.setTv(in.readFloat()); else if(t==5&&f==8)x.setTc(in.readFloat()); else if(t==5&&f==9)x.setSoh(in.readFloat()); else if(t==5&&f==10)x.setSoc(in.readFloat()); else if(t==5&&f==31)x.setRechargeableCapacityKwh(in.readFloat()); else if(t==5&&f==32)x.setDischargeableCapacityKwh(in.readFloat()); else if(t==1&&f==33)x.setCumulativeDischargeKwh(in.readDouble()); else if(t==1&&f==34)x.setCumulativeChargeKwh(in.readDouble()); else skip(in,t); }); return x; }
- /** 解析电池簇实际字段。 */
- private UdanEmsDeviceState.BatteryClusterState parseCluster(byte[] b) throws IOException { UdanEmsDeviceState.BatteryClusterState x = new UdanEmsDeviceState.BatteryClusterState(); read(b,(f,t,in)->{if(t==2&&f==2)x.setDeviceId(in.readString());else if(t==2&&f==3)x.setStackDeviceId(in.readString());else if(t==5&&f==7)x.setTv(in.readFloat());else if(t==5&&f==8)x.setTc(in.readFloat());else if(t==5&&f==9)x.setSoh(in.readFloat());else if(t==5&&f==10)x.setSoc(in.readFloat());else skip(in,t);});return x; }
- /** 解析电表实际字段。 */
- private UdanEmsDeviceState.ElectricMeterState parseMeter(byte[] b) throws IOException { UdanEmsDeviceState.ElectricMeterState x = new UdanEmsDeviceState.ElectricMeterState(); read(b,(f,t,in)->{if(t==2&&f==2)x.setDeviceId(in.readString());else if(t==1&&f==17)x.setCumulativeImportKwh(in.readDouble());else if(t==1&&f==22)x.setCumulativeExportKwh(in.readDouble());else if(t==5&&f==54)x.setAcTotalActivePower(in.readFloat());else skip(in,t);});return x; }
- /** 解析光伏实际字段。 */
- private UdanEmsDeviceState.PvState parsePv(byte[] b) throws IOException { UdanEmsDeviceState.PvState x = new UdanEmsDeviceState.PvState(); read(b,(f,t,in)->{if(t==2&&f==1)x.setDeviceId(in.readString());else if(t==5&&f==18)x.setActivePower(in.readFloat());else if(t==5&&f==19)x.setReactivePower(in.readFloat());else if(t==5&&f==20)x.setPowerFactor(in.readFloat());else if(t==5&&f==21)x.setDailyPowerGeneration(in.readFloat());else if(t==1&&f==22)x.setTotalPowerGeneration(in.readDouble());else skip(in,t);});return x; }
- /** 解析充电桩实际字段。 */
- private UdanEmsDeviceState.ChargeStationState parseChargeStation(byte[] b) throws IOException { UdanEmsDeviceState.ChargeStationState x = new UdanEmsDeviceState.ChargeStationState(); read(b,(f,t,in)->{if(t==2&&f==1)x.setDeviceId(in.readString());else if(t==0&&f==2)x.setTime(in.readUInt64());else skip(in,t);});return x; }
+ private void read(byte[] bytes, Reader reader) throws IOException {
+ CodedInputStream input = CodedInputStream.newInstance(bytes == null ? new byte[0] : bytes);
+ while (!input.isAtEnd()) {
+ int tag = input.readTag();
+ if (tag == 0) {
+ break;
+ }
+ reader.read(WireFormat.getTagFieldNumber(tag), WireFormat.getTagWireType(tag), input);
+ }
+ }
- /** 遍历嵌套 protobuf 字段,并把字段号、线类型和读取器交给解析器。 */
- private void read(byte[] b, FieldReader reader) throws IOException { CodedInputStream in=CodedInputStream.newInstance(b); while(!in.isAtEnd()){int tag=in.readTag();if(tag==0)break;reader.read(WireFormat.getTagFieldNumber(tag),WireFormat.getTagWireType(tag),in);} }
- /** 跳过当前不需要映射的字段。 */
- private void skip(CodedInputStream in, int type) throws IOException {
+ private static String str(CodedInputStream input, int type) throws IOException {
+ if (type == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
+ return input.readString();
+ }
+ skip(input, type);
+ return null;
+ }
+
+ private static long u64(CodedInputStream input, int type) throws IOException {
+ if (type == WireFormat.WIRETYPE_VARINT) {
+ return input.readUInt64();
+ }
+ skip(input, type);
+ return 0L;
+ }
+
+ private static int u32(CodedInputStream input, int type) throws IOException {
+ return (int) u64(input, type);
+ }
+
+ private static float f32(CodedInputStream input, int type) throws IOException {
+ if (type == WireFormat.WIRETYPE_FIXED32) {
+ return input.readFloat();
+ }
+ skip(input, type);
+ return 0F;
+ }
+
+ private static double f64(CodedInputStream input, int type) throws IOException {
+ if (type == WireFormat.WIRETYPE_FIXED64) {
+ return input.readDouble();
+ }
+ skip(input, type);
+ return 0D;
+ }
+ private static String enumName(CodedInputStream input, int type, String prefix) throws IOException {
+ if (type != WireFormat.WIRETYPE_VARINT) {
+ skip(input, type);
+ return null;
+ }
+ int number = input.readInt32();
+ if ("RunStatus".equals(prefix)) {
+ return new String[]{"RUN_STATUS_UNKNOWN", "RUN_STATUS_STOPPED", "RUN_STATUS_RUNNING"}[number < 0 || number > 2 ? 0 : number];
+ }
+ if ("GridStatus".equals(prefix)) {
+ return new String[]{"GRID_STATUS_UNKNOWN", "GRID_STATUS_ON_GRID", "GRID_STATUS_OFF_GRID"}[number < 0 || number > 2 ? 0 : number];
+ }
+ if ("CommStatus".equals(prefix)) {
+ return new String[]{"COMM_STATUS_UNKNOWN", "COMM_STATUS_DISCONNECTED", "COMM_STATUS_CONNECTED"}[number < 0 || number > 2 ? 0 : number];
+ }
+ return prefix + "_" + number;
+ }
+ private static <T> T enumAt(T[] values, CodedInputStream input, int type) throws IOException {
+ int number = (int) u64(input, type);
+ return number >= 0 && number < values.length ? values[number] : null;
+ }
+
+ private static void skip(CodedInputStream input, int type) throws IOException {
switch (type) {
- case WireFormat.WIRETYPE_VARINT: in.readInt64(); break;
- case WireFormat.WIRETYPE_FIXED64: in.readFixed64(); break;
- case WireFormat.WIRETYPE_LENGTH_DELIMITED: in.readBytes(); break;
- case WireFormat.WIRETYPE_FIXED32: in.readFixed32(); break;
- default: break;
+ case WireFormat.WIRETYPE_VARINT:
+ input.readInt64();
+ break;
+ case WireFormat.WIRETYPE_FIXED64:
+ input.readFixed64();
+ break;
+ case WireFormat.WIRETYPE_LENGTH_DELIMITED:
+ input.readBytes();
+ break;
+ case WireFormat.WIRETYPE_FIXED32:
+ input.readFixed32();
+ break;
+ default:
+ throw new IOException("Unsupported protobuf wire type: " + type);
}
}
- @FunctionalInterface private interface FieldReader { void read(int field, int type, CodedInputStream input) throws IOException; }
+
+ @FunctionalInterface
+ private interface Reader {
+ void read(int field, int type, CodedInputStream input) throws IOException;
+ }
}
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/util/UdanEmsKafkaUtil.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/util/UdanEmsKafkaUtil.java
index 45fc1e1..0da14ad 100644
--- a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/util/UdanEmsKafkaUtil.java
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/util/UdanEmsKafkaUtil.java
@@ -8,7 +8,7 @@
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
-import org.apache.kafka.common.errors.WakeupException;
+import org.apache.kafka.common.header.Header;
import org.apache.kafka.common.serialization.ByteArrayDeserializer;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.slf4j.Logger;
@@ -20,13 +20,11 @@
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.time.Duration;
-import java.util.Base64;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
/**
* 优旦 EMS 平台 Kafka 消费工具。
@@ -48,7 +46,7 @@
});
private volatile KafkaConsumer<String, byte[]> consumer;
private volatile boolean running;
- private volatile MessageHandler messageHandler = (key, payload) -> { };
+ private volatile MessageHandler messageHandler = (key, payload, emsDeviceId) -> { };
private volatile ErrorHandler errorHandler = Throwable::printStackTrace;
/**
@@ -65,7 +63,7 @@
* 当 udan.ems.kafka.enabled=true 时由 Spring 在 Bean 初始化后自动调用。
* PostConstruct 创建并完成依赖注入后,自动执行这个方法一次
*/
- //@PostConstruct
+ @PostConstruct
public void start() {
validate();
running = true;
@@ -78,7 +76,7 @@
* @param handler 接收 EMS 系统 ID 和 protobuf 原始字节的处理器;传入 null 时忽略消息
*/
public void setMessageHandler(MessageHandler handler) {
- this.messageHandler = handler == null ? (key, payload) -> { } : handler;
+ this.messageHandler = handler == null ? (key, payload, emsDeviceId) -> { } : handler;
}
/**
@@ -93,16 +91,15 @@
/**
* 按配置的超时时间轮询一次 Kafka,并将取得的消息交给消息处理器。
*
- * @return 本次轮询取得的消息数量
* @throws IllegalStateException 消费者尚未启动时抛出
*/
- public int pollOnce() {
+ public void pollOnce() {
if (consumer == null) {
throw new IllegalStateException("Kafka consumer has not started");
}
ConsumerRecords<String, byte[]> records = consumer.poll(Duration.ofSeconds(Math.max(1, properties.getPollSeconds())));
dispatch(records);
- return records.count();
+ records.count();
}
/**
@@ -125,8 +122,6 @@
consumer = c;
c.subscribe(Collections.singletonList(properties.getTopic()));
while (running) pollOnce();
- } catch (WakeupException ignored) {
- if (running) errorHandler.onError(ignored);
} catch (Throwable e) {
if (running) errorHandler.onError(e);
} finally {
@@ -147,11 +142,23 @@
log.info("1、UDAN EMS可读字段: emsSystemId={}, data={}", record.key(), toReadableProtobuf(payload));
// 此处即为业务接入点:解析 protobuf 后调用保存服务,当前仅由回调方决定如何处理。
- messageHandler.onMessage(record.key(), payload);
+ messageHandler.onMessage(record.key(), payload, getEmsDeviceId(record));
} catch (Throwable e) {
errorHandler.onError(e);
}
}
+ }
+
+ /** 获取消息 Header 中的 EMS 设备 ID。优先使用 emsDeviceId,兼容单 Header 报文。 */
+ private String getEmsDeviceId(ConsumerRecord<String, byte[]> record) {
+ Header header = record.headers().lastHeader("ems_device_id");
+ if (header == null && !Objects.isNull(record.headers())) {
+ header = record.headers().iterator().next();
+ }
+ if (header == null || header.value() == null) {
+ return null;
+ }
+ return new String(header.value(), StandardCharsets.UTF_8);
}
/**
@@ -237,7 +244,6 @@
/** 判断长度字段是否是可直接阅读的 UTF-8 文本。 */
private static boolean isReadableString(byte[] data) {
- if (data.length == 0) return true;
for (byte value : data) {
int c = value & 0xFF;
if (c < 0x20 || c > 0x7E) return false;
@@ -304,8 +310,9 @@
*
* @param emsSystemId Kafka Key,即 EMS 系统 ID
* @param protobufBytes Kafka Value,即 protobuf3 原始字节
+ * @param emsDeviceId Kafka Header 中的 EMS 设备 ID
*/
- void onMessage(String emsSystemId, byte[] protobufBytes);
+ void onMessage(String emsSystemId, byte[] protobufBytes, String emsDeviceId);
}
/** 消费和消息处理异常回调。 */
diff --git a/ruoyi-service/ruoyi-other/src/main/resources/mapper/other/EmindStationMapper.xml b/ruoyi-service/ruoyi-other/src/main/resources/mapper/other/EmindStationMapper.xml
index 759e98e..dae23eb 100644
--- a/ruoyi-service/ruoyi-other/src/main/resources/mapper/other/EmindStationMapper.xml
+++ b/ruoyi-service/ruoyi-other/src/main/resources/mapper/other/EmindStationMapper.xml
@@ -54,7 +54,7 @@
<select id="selectManagementPage" resultMap="stationResultMap">
SELECT id, station_id, name, address, province, city, longitude, latitude,
max_power, capacity, commissioning_time, platform_name, source_type,
- management_deleted, del_flag, synced_at, create_time, update_time
+ management_deleted, del_flag, synced_at, create_time, update_time, station_code
FROM t_emind_station
WHERE del_flag = 0 AND management_deleted = 0
<if test="query.platformName != null and query.platformName != ''">
diff --git a/ruoyi-service/ruoyi-other/src/main/resources/sql/t_screen_content_alter.sql b/ruoyi-service/ruoyi-other/src/main/resources/sql/t_screen_content_alter.sql
index 342d894..2328ae1 100644
--- a/ruoyi-service/ruoyi-other/src/main/resources/sql/t_screen_content_alter.sql
+++ b/ruoyi-service/ruoyi-other/src/main/resources/sql/t_screen_content_alter.sql
@@ -1,4 +1,8 @@
-- ----------------------------
--- 增加大屏名称配置字段
+-- 1、增加大屏名称配置字段
alter table t_screen_content add column screen_name varchar(255) default 'NULL' null comment '大屏名称';
+-- 2、增加储能电站表设备编号
+alter table t_emind_station add column station_code varchar(255) default 'NULL' null comment '设备编号';
+
+
--
Gitblit v1.7.1