From 8fab4df47dcd0dc7efe4a13f2f31897ce5751963 Mon Sep 17 00:00:00 2001
From: yanderong <307678080@qq.com>
Date: 星期日, 27 九月 2026 14:14:12 +0800
Subject: [PATCH] feat: 储能数据同步-能量链修改,新增需求

---
 ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/service/EmindEnergyChainManagementService.java |   28 ++
 ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/ElectricMeterState.java                   |   32 +++
 ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/UdanEmsDeviceState.java                   |    5 
 ruoyi-service/ruoyi-other/src/main/resources/sql/t_screen_content_alter.sql                                  |   20 ++
 ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/service/EmindEnergyChainSyncService.java       |    8 
 ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/TStationCollectServiceImpl.java         |    7 
 ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/service/UdanEmsMessageProcessor.java            |  125 ++++++++++++
 ruoyi-service/ruoyi-other/src/main/resources/mapper/other/EmindEnergyChainMapper.xml                         |    3 
 ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/domain/EmindEnergyChain.java                   |    9 
 ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/task/ExchangeCodeExpireTask.java                     |    2 
 ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/service/EmindEnergyChainDetailService.java     |  111 +++++++++++
 ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/TmsState.java                             |   35 +++
 ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/PcsState.java                             |   59 +++++
 ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/domain/EmindEnergyChainDetail.java             |   56 +++++
 ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/BatteryStackState.java                    |   39 +++
 ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/mapper/EmindEnergyChainDetailMapper.java       |    8 
 16 files changed, 537 insertions(+), 10 deletions(-)

diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/domain/EmindEnergyChain.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/domain/EmindEnergyChain.java
index 0a4776f..8bb892a 100644
--- a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/domain/EmindEnergyChain.java
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/domain/EmindEnergyChain.java
@@ -125,4 +125,13 @@
     /** Data status, not persisted. */
     @ApiModelProperty("Data status, not persisted")
     private Integer dataStatus;
+
+    /**
+     * 设备类型(平台为华为时展示)
+     */
+    @TableField("station_type")
+    private String stationType;
+
+    @TableField(exist = false)
+    private String detail;
 }
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/domain/EmindEnergyChainDetail.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/domain/EmindEnergyChainDetail.java
new file mode 100644
index 0000000..713e606
--- /dev/null
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/domain/EmindEnergyChainDetail.java
@@ -0,0 +1,56 @@
+package com.ruoyi.other.emind.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/** eMind 储能能量链本地冗余数据。 */
+@Data
+@ApiModel("EmindEnergyChainDetail")
+@TableName("t_emind_energy_chain_detail")
+public class EmindEnergyChainDetail {
+
+
+    @TableId(value = "id", type = IdType.AUTO)
+    @ApiModelProperty("Primary key")
+    private Long id;
+
+    @TableField("platform_name")
+    private String platformName;
+
+    @TableField("station_id")
+    private String stationId;
+
+    @TableField("station_code")
+    private String stationCode;
+
+    @TableField("station_type")
+    private String stationType;
+
+    @TableField("detail")
+    private String detail;
+
+    @TableField("data_source")
+    @ApiModelProperty("Data source: 1 remote, 2 manual")
+    private Integer dataSource;
+
+    @TableField("del_flag")
+    private Integer delFlag;
+
+    @TableField("sync_time")
+    private LocalDateTime syncTime;
+
+    @TableField("create_time")
+    private LocalDateTime createTime;
+
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+
+
+}
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/mapper/EmindEnergyChainDetailMapper.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/mapper/EmindEnergyChainDetailMapper.java
new file mode 100644
index 0000000..b5d2bc0
--- /dev/null
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/mapper/EmindEnergyChainDetailMapper.java
@@ -0,0 +1,8 @@
+package com.ruoyi.other.emind.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.other.emind.domain.EmindEnergyChainDetail;
+
+/** 能量链详情数据访问层。 */
+public interface EmindEnergyChainDetailMapper extends BaseMapper<EmindEnergyChainDetail> {
+}
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/service/EmindEnergyChainDetailService.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/service/EmindEnergyChainDetailService.java
new file mode 100644
index 0000000..2574abf
--- /dev/null
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/service/EmindEnergyChainDetailService.java
@@ -0,0 +1,111 @@
+package com.ruoyi.other.emind.service;
+
+import com.alibaba.fastjson2.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.ruoyi.other.emind.domain.EmindEnergyChainDetail;
+import com.ruoyi.other.emind.mapper.EmindEnergyChainDetailMapper;
+import com.ruoyi.other.udan.model.UdanEmsDeviceState;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.Clock;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/** 能量链详情服务。 */
+@Service
+public class EmindEnergyChainDetailService {
+    private final EmindEnergyChainDetailMapper mapper;
+    private final Clock clock;
+
+    @Autowired
+    public EmindEnergyChainDetailService(EmindEnergyChainDetailMapper mapper) {
+        this(mapper, Clock.systemDefaultZone());
+    }
+
+    EmindEnergyChainDetailService(EmindEnergyChainDetailMapper mapper, Clock clock) {
+        this.mapper = mapper;
+        this.clock = clock;
+    }
+
+    /** 新增能量链详情。 */
+    @Transactional(rollbackFor = Exception.class)
+    public EmindEnergyChainDetail add(EmindEnergyChainDetail input) {
+        LocalDateTime now = LocalDateTime.now(clock);
+        EmindEnergyChainDetail detail = new EmindEnergyChainDetail();
+        detail.setPlatformName(input.getPlatformName());
+        detail.setStationId(input.getStationId());
+        detail.setStationCode(input.getStationCode());
+        detail.setStationType(input.getStationType());
+        detail.setDetail(input.getDetail());
+        detail.setDataSource(input.getDataSource());
+        detail.setDelFlag(0);
+        detail.setSyncTime(now);
+        detail.setCreateTime(now);
+        detail.setUpdateTime(now);
+        mapper.insert(detail);
+        return detail;
+    }
+
+    /** 根据设备类型和设备编号查询有效的能量链详情。 */
+    public List<EmindEnergyChainDetail> getByStationTypes(List<String> stationTypes) {
+        return mapper.selectList(new LambdaQueryWrapper<EmindEnergyChainDetail>()
+                .eq(EmindEnergyChainDetail::getDelFlag, 0)
+                .in(EmindEnergyChainDetail::getStationType, stationTypes));
+    }
+
+    @Transactional
+    public void saveAll(UdanEmsDeviceState udanEmsDeviceState) {
+       List<EmindEnergyChainDetail> detailList = new ArrayList<>();
+        // 保存BMS
+        if(Objects.nonNull(udanEmsDeviceState.getTmsState())) {
+            EmindEnergyChainDetail detail = new EmindEnergyChainDetail();
+            detail.setPlatformName("欧力特");
+            detail.setDataSource(2);
+            detail.setStationCode(udanEmsDeviceState.getEmsSystemId());
+            detail.setDetail(JSON.toJSONString(udanEmsDeviceState.getTmsState()));
+            detail.setStationId(udanEmsDeviceState.getTmsState().getDeviceId());
+            detail.setStationType("TMS");
+            detailList.add(detail);
+        }
+        if(Objects.nonNull(udanEmsDeviceState.getPcsState())) {
+            EmindEnergyChainDetail detail = new EmindEnergyChainDetail();
+            detail.setPlatformName("欧力特");
+            detail.setDataSource(2);
+            detail.setStationCode(udanEmsDeviceState.getEmsSystemId());
+            detail.setDetail(JSON.toJSONString(udanEmsDeviceState.getPcsState()));
+            detail.setStationId(udanEmsDeviceState.getTmsState().getDeviceId());
+            detail.setStationType("PCS");
+            detailList.add(detail);
+        }
+        if(Objects.nonNull(udanEmsDeviceState.getElectricMeterState())) {
+            EmindEnergyChainDetail detail = new EmindEnergyChainDetail();
+            detail.setPlatformName("欧力特");
+            detail.setDataSource(2);
+            detail.setStationCode(udanEmsDeviceState.getEmsSystemId());
+            detail.setDetail(JSON.toJSONString(udanEmsDeviceState.getElectricMeterState()));
+            detail.setStationId(udanEmsDeviceState.getTmsState().getDeviceId());
+            detail.setStationType("电表");
+            detailList.add(detail);
+        }
+        if(Objects.nonNull(udanEmsDeviceState.getBatteryStackState())) {
+            EmindEnergyChainDetail detail = new EmindEnergyChainDetail();
+            detail.setPlatformName("欧力特");
+            detail.setDataSource(2);
+            detail.setStationCode(udanEmsDeviceState.getEmsSystemId());
+            detail.setDetail(JSON.toJSONString(udanEmsDeviceState.getBatteryStackState()));
+            detail.setStationId(udanEmsDeviceState.getTmsState().getDeviceId());
+            detail.setStationType("BMS");
+            detailList.add(detail);
+        }
+        detailList.forEach(d -> {
+            mapper.delete(new QueryWrapper<EmindEnergyChainDetail>().eq("station_code", d.getStationCode()).eq("station_type", d.getStationType()));
+            add(d);
+        });
+
+    }
+}
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/service/EmindEnergyChainManagementService.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/service/EmindEnergyChainManagementService.java
index 0299a0a..20fe6af 100644
--- a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/service/EmindEnergyChainManagementService.java
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/service/EmindEnergyChainManagementService.java
@@ -3,10 +3,12 @@
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ruoyi.other.emind.domain.EmindEnergyChain;
+import com.ruoyi.other.emind.domain.EmindEnergyChainDetail;
 import com.ruoyi.other.emind.domain.EmindStation;
 import com.ruoyi.other.emind.dto.EmindEnergyChainPageQuery;
 import com.ruoyi.other.emind.mapper.EmindEnergyChainMapper;
 import com.ruoyi.other.emind.mapper.EmindStationMapper;
+import org.apache.commons.collections4.CollectionUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -14,32 +16,49 @@
 import java.time.Clock;
 import java.time.LocalDateTime;
 import java.util.List;
+import java.util.stream.Collectors;
 
 @Service
 public class EmindEnergyChainManagementService {
     private final EmindEnergyChainMapper mapper;
     private final EmindStationMapper stationMapper;
     private final Clock clock;
+    private final EmindEnergyChainDetailService emindEnergyChainDetailService;
 
     @Autowired
     public EmindEnergyChainManagementService(EmindEnergyChainMapper mapper,
-                                             EmindStationMapper stationMapper) {
-        this(mapper, stationMapper, Clock.systemDefaultZone());
+                                             EmindStationMapper stationMapper, EmindEnergyChainDetailService emindEnergyChainDetailService) {
+        this(mapper, stationMapper, Clock.systemDefaultZone(), emindEnergyChainDetailService);
     }
 
     EmindEnergyChainManagementService(EmindEnergyChainMapper mapper,
                                       EmindStationMapper stationMapper,
-                                      Clock clock) {
+                                      Clock clock, EmindEnergyChainDetailService emindEnergyChainDetailService) {
         this.mapper = mapper;
         this.stationMapper = stationMapper;
         this.clock = clock;
+        this.emindEnergyChainDetailService = emindEnergyChainDetailService;
     }
 
     public IPage<EmindEnergyChain> page(EmindEnergyChainPageQuery query) {
         int pageNumber = query.getPageCurr() == null || query.getPageCurr() < 1 ? 1 : query.getPageCurr();
         int pageSize = query.getPageSize() == null || query.getPageSize() < 1
                 ? 10 : Math.min(query.getPageSize(), 100);
-        return mapper.selectManagementPage(Page.of(pageNumber, pageSize), query);
+        IPage<EmindEnergyChain> pageData = mapper.selectManagementPage(Page.of(pageNumber, pageSize), query);
+        if(CollectionUtils.isNotEmpty(pageData.getRecords())) {
+            List<String> stationTypeList = pageData.getRecords().stream().filter(r -> "欧力特".equals(r.getPlatformName())).map(EmindEnergyChain::getStationType).collect(Collectors.toList());
+            if(CollectionUtils.isNotEmpty(stationTypeList)) {
+                List<EmindEnergyChainDetail> chainDetails = emindEnergyChainDetailService.getByStationTypes(stationTypeList);
+                if(CollectionUtils.isNotEmpty(chainDetails)) {
+                    pageData.getRecords().forEach(rd -> chainDetails.forEach(cd -> {
+                        if(cd.getStationType().equals(rd.getStationType())) {
+                            rd.setDetail(cd.getDetail());
+                        }
+                    }));
+                }
+            }
+        }
+        return pageData;
     }
 
     @Transactional(rollbackFor = Exception.class)
@@ -97,5 +116,6 @@
         target.setElinkId(input.getElinkId());
         target.setUuid(input.getUuid().trim());
         target.setElinkName(input.getElinkName().trim());
+        target.setStationType(input.getStationType());
     }
 }
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/service/EmindEnergyChainSyncService.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/service/EmindEnergyChainSyncService.java
index 32f18d4..a4a9680 100644
--- a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/service/EmindEnergyChainSyncService.java
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/emind/service/EmindEnergyChainSyncService.java
@@ -49,9 +49,11 @@
         LocalDateTime syncedAt = LocalDateTime.now(clock);
         List<EmindEnergyChain> chains = new ArrayList<>();
         for (EmindStation station : stations) {
-            List<EmindEnergyChainDto> remoteChains = api.getEnergyChains(station.getStationId());
-            for (EmindEnergyChainDto remote : remoteChains) {
-                chains.add(toEntity(station, remote, syncedAt));
+            if(station.getSourceType() == 1) {
+                List<EmindEnergyChainDto> remoteChains = api.getEnergyChains(station.getStationId());
+                for (EmindEnergyChainDto remote : remoteChains) {
+                    chains.add(toEntity(station, remote, syncedAt));
+                }
             }
         }
         chainMapper.markAllDeleted(syncedAt);
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/TStationCollectServiceImpl.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/TStationCollectServiceImpl.java
index 541eb91..d4ef6ca 100644
--- a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/TStationCollectServiceImpl.java
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/TStationCollectServiceImpl.java
@@ -349,6 +349,13 @@
             fillDefaults(collect);
             insertList.add(collect);
         }
+        // 设置电站名称
+        List<TStationInfo> infoList = stationInfoMapper.selectList(new LambdaQueryWrapper<>());
+        insertList.forEach(s -> infoList.forEach(info -> {
+            if(StringUtils.isNotEmpty(s.getStationId()) && s.getStationId().equals(info.getStationId())) {
+                s.setStationName(info.getStationName());
+            }
+        }));
         return CollectionUtils.isNotEmpty(insertList) && saveBatch(insertList);
     }
 }
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/task/ExchangeCodeExpireTask.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/task/ExchangeCodeExpireTask.java
index 437a87f..1f978fa 100644
--- a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/task/ExchangeCodeExpireTask.java
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/task/ExchangeCodeExpireTask.java
@@ -30,7 +30,7 @@
     }
 
     /**
-     * 每6小时执行科士达的数据同步
+     * 每6小时执行华为光伏的数据同步
      */
     @Scheduled(cron = "0 0 */6 * * ?")
     public void syncHuaWeiDataTask() {
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/BatteryStackState.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/BatteryStackState.java
new file mode 100644
index 0000000..964667a
--- /dev/null
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/BatteryStackState.java
@@ -0,0 +1,39 @@
+package com.ruoyi.other.udan.model;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 优旦储能-BMS 实时数据
+ *
+ * @author Lux
+ * @version 1.0.0
+ * @since 2026/9/26 16:23
+ */
+@Data
+public class BatteryStackState implements Serializable {
+
+    /**
+     * 设备id
+     */
+    private String deviceId;
+
+    // 故障状态
+    private String errorStatus;
+    private String  workStatus; // 工作状态
+    private String  warningStatus; // 告警状态
+    private Double tv; // 总电压  单位V
+    private Double tc; // 总电流  单位A
+    private Double soh; // soh  单位%
+    private Double soc; // soc  单位%
+    private Double rechargeableCapacityKwh; // 可充电量  单位kWh
+    private Double dischargeableCapacityKwh; // 可放电量  单位kWh
+    private Double cumulativeDischargeKwh; // 累计放电量  单位kWh
+    private Double cumulativeChargeKwh; // 累计充电量  单位kWh
+    private Double minCellVoltMv; // 单体最低电压  单位mV
+    private Double maxCellVoltMv; // 单体最高电压  单位mV
+    private Double avgCellTemp; // 电芯平均温度  保存需减40偏移量
+
+
+}
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/ElectricMeterState.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/ElectricMeterState.java
new file mode 100644
index 0000000..684b0af
--- /dev/null
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/ElectricMeterState.java
@@ -0,0 +1,32 @@
+package com.ruoyi.other.udan.model;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 优旦储能-电表实时数据
+ *
+ * @author Lux
+ * @version 1.0.0
+ * @since 2026/9/26 16:23
+ */
+@Data
+public class ElectricMeterState implements Serializable {
+
+    /**
+     * 设备id
+     */
+    private String deviceId;
+
+    private Double aPhaseVolt; // A相电压 单位V
+    private Double bPhaseVolt; // B相电压 单位V
+    private Double cPhaseVolt; // C相电压 单位V
+    private Double aPhaseCurrent; // A相电流 单位A
+    private Double bPhaseCurrent; // B相电流 单位A
+    private Double cPhaseCurrent; // C相电流 单位A
+    private Double acTotalActivePower; // 总有功功率		单位KW
+    private Double frequency; // 频率	  	单位Hz
+    private Double acTotalPowerFactor; // 总交流功率因数
+
+}
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/PcsState.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/PcsState.java
new file mode 100644
index 0000000..bd4dd59
--- /dev/null
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/PcsState.java
@@ -0,0 +1,59 @@
+package com.ruoyi.other.udan.model;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 优旦储能-pcs实时数据
+ *
+ * @author Lux
+ * @version 1.0.0
+ * @since 2026/9/26 16:23
+ */
+@Data
+public class PcsState implements Serializable {
+
+    /**
+     * 设备id
+     */
+    private String deviceId;
+
+    // 故障状态
+    private String errorStatus;
+
+    // 告警状态
+    private String alarmStatus;
+
+    // 运行状态
+    private String runStatus;
+
+    // 并网状态
+    private String onGridStatus;
+
+    // 有功功率A相
+    private Double aPhaseActivePower;
+
+    // 有功功率B相
+    private Double bPhaseActivePower;
+
+    // 有功功率C相
+    private Double cPhaseActivePower;
+
+    // 总交流有功功率
+    private Double acTotalActivePower;
+
+    // 日充电量  单位kWh
+    private Double dailyChargeEnergyKwh;
+
+    // 日放电量  单位kWh
+    private Double dailyDischargeEnergyKwh;
+
+    // 总充电量  单位kWh
+    private Double totalChargeEnergyKwh;
+
+    // 总放电量  单位kWh
+    private Double totalDischargeEnergyKwh;
+
+
+}
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/TmsState.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/TmsState.java
new file mode 100644
index 0000000..89325d7
--- /dev/null
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/udan/model/TmsState.java
@@ -0,0 +1,35 @@
+package com.ruoyi.other.udan.model;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 优旦储能-TNS 实时数据
+ *
+ * @author Lux
+ * @version 1.0.0
+ * @since 2026/9/26 16:23
+ */
+@Data
+public class TmsState implements Serializable {
+
+    /**
+     * 设备id
+     */
+    private String deviceId;
+
+    private String run_status; // 运行状态
+    private String  error_status; // 故障状态 0 预留 1 故障 2 正常
+    private String compress_status; // 压缩机状态
+    private String compress_heat_status; // 压缩机加热带状态
+    private String electric_heat_status; // 电加热运行状态
+    private String pump_status; // 水泵状态
+    private String fan_status; // 风机运行状态
+
+    private Double supply_temp; // 供液温度
+    private Double return_temp; // 回液温度
+    private Double env_temp; // 环境温度
+    private Double backwater_press; // 回水压力 回液压力
+    private Double out_water_press; // 出水压力 供液压力
+}
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 2119556..25b3996 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
@@ -21,6 +21,11 @@
     private EmsState emsState;
     private PowerMeterState powerMeterState;
 
+    private TmsState tmsState;
+    private PcsState pcsState;
+    private ElectricMeterState electricMeterState;
+    private BatteryStackState batteryStackState;
+
     public List<EmindIncomeHistoryDto> convert(Long stationId) {
         List<EmindIncomeHistoryDto> dailyList = new ArrayList<>();
 
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 eaa1613..08ea393 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
@@ -7,8 +7,13 @@
 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.emind.service.EmindEnergyChainDetailService;
 import com.ruoyi.other.udan.model.EmsState;
+import com.ruoyi.other.udan.model.BatteryStackState;
+import com.ruoyi.other.udan.model.ElectricMeterState;
+import com.ruoyi.other.udan.model.PcsState;
 import com.ruoyi.other.udan.model.PowerMeterState;
+import com.ruoyi.other.udan.model.TmsState;
 import com.ruoyi.other.udan.model.UdanEmsDeviceState;
 import com.ruoyi.other.udan.util.UdanEmsKafkaUtil;
 import lombok.extern.slf4j.Slf4j;
@@ -34,11 +39,13 @@
     private final UdanEmsKafkaUtil kafkaUtil;
     private final EmindStorageDashboardMapper dashboardMapper;
     private final EmindStationMapper stationMapper;
+    private final EmindEnergyChainDetailService emindEnergyChainDetailService;
 
-    public UdanEmsMessageProcessor(UdanEmsKafkaUtil kafkaUtil, EmindStorageDashboardMapper dashboardMapper, EmindStationMapper stationMapper) {
+    public UdanEmsMessageProcessor(UdanEmsKafkaUtil kafkaUtil, EmindStorageDashboardMapper dashboardMapper, EmindStationMapper stationMapper, EmindEnergyChainDetailService emindEnergyChainDetailService) {
         this.kafkaUtil = kafkaUtil;
         this.dashboardMapper = dashboardMapper;
         this.stationMapper = stationMapper;
+        this.emindEnergyChainDetailService = emindEnergyChainDetailService;
     }
 
     @PostConstruct
@@ -67,6 +74,8 @@
                 }else{
                     log.error("UDAN EMS数据同步错误-电站信息不存在!");
                 }
+                // 保存设备其它信息
+                emindEnergyChainDetailService.saveAll(udanEmsDeviceState);
             }
 
         } catch (IOException e) {
@@ -87,7 +96,19 @@
         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 == 4 && state.getPcsState() == null) {
+                state.setPcsState(parsePcs(in.readBytes().toByteArray()));
+            }
+            else if (type == 2 && field == 5 && state.getBatteryStackState() == null) {
+                state.setBatteryStackState(parseBatteryStack(in.readBytes().toByteArray()));
+            }
+            else if (type == 2 && field == 7 && state.getElectricMeterState() == null) {
+                state.setElectricMeterState(parseElectricMeter(in.readBytes().toByteArray()));
+            }
             else if (type == 2 && field == 8) state.setPowerMeterState(parsePowerMeter(in.readBytes().toByteArray()));
+            else if (type == 2 && field == 14 && state.getTmsState() == null) {
+                state.setTmsState(parseTms(in.readBytes().toByteArray()));
+            }
             else skip(in, type);
         });
         return state;
@@ -200,6 +221,96 @@
         return value;
     }
 
+    private PcsState parsePcs(byte[] bytes) throws IOException {
+        PcsState value = new PcsState();
+        read(bytes, (field, type, input) -> {
+            switch (field) {
+                case 2: value.setDeviceId(str(input, type)); break;
+                case 3: value.setErrorStatus(enumName(input, type, "ErrorStatus")); break;
+                case 4: value.setAlarmStatus(enumName(input, type, "AlarmStatus")); break;
+                case 5: value.setRunStatus(enumName(input, type, "RunStatus")); break;
+                case 6: value.setOnGridStatus(enumName(input, type, "GridStatus")); break;
+                case 13: value.setAcTotalActivePower((double) f32(input, type)); break;
+                case 17: value.setDailyChargeEnergyKwh((double) f32(input, type)); break;
+                case 18: value.setDailyDischargeEnergyKwh((double) f32(input, type)); break;
+                case 19: value.setTotalChargeEnergyKwh(f64(input, type)); break;
+                case 20: value.setTotalDischargeEnergyKwh(f64(input, type)); break;
+                case 31: value.setAPhaseActivePower((double) f32(input, type)); break;
+                case 34: value.setBPhaseActivePower((double) f32(input, type)); break;
+                case 37: value.setCPhaseActivePower((double) f32(input, type)); break;
+                default: skip(input, type);
+            }
+        });
+        return value;
+    }
+
+    private BatteryStackState parseBatteryStack(byte[] bytes) throws IOException {
+        BatteryStackState value = new BatteryStackState();
+        read(bytes, (field, type, input) -> {
+            switch (field) {
+                case 2: value.setDeviceId(str(input, type)); break;
+                case 4: value.setWorkStatus(enumName(input, type, "BatteryWorkStatus")); break;
+                case 5: value.setErrorStatus(enumName(input, type, "ErrorStatus")); break;
+                case 6: value.setWarningStatus(enumName(input, type, "AlarmStatus")); break;
+                case 7: value.setTv((double) f32(input, type)); break;
+                case 8: value.setTc((double) f32(input, type)); break;
+                case 9: value.setSoh((double) f32(input, type)); break;
+                case 10: value.setSoc((double) f32(input, type)); break;
+                case 15: value.setMinCellVoltMv((double) u32(input, type)); break;
+                case 23: value.setMaxCellVoltMv((double) u32(input, type)); break;
+                case 31: value.setRechargeableCapacityKwh((double) f32(input, type)); break;
+                case 32: value.setDischargeableCapacityKwh((double) f32(input, type)); break;
+                case 33: value.setCumulativeDischargeKwh(f64(input, type)); break;
+                case 34: value.setCumulativeChargeKwh(f64(input, type)); break;
+                case 36: value.setAvgCellTemp((double) u32(input, type) - 40D); break;
+                default: skip(input, type);
+            }
+        });
+        return value;
+    }
+
+    private ElectricMeterState parseElectricMeter(byte[] bytes) throws IOException {
+        ElectricMeterState value = new ElectricMeterState();
+        read(bytes, (field, type, input) -> {
+            switch (field) {
+                case 2: value.setDeviceId(str(input, type)); break;
+                case 48: value.setAPhaseVolt((double) f32(input, type)); break;
+                case 49: value.setBPhaseVolt((double) f32(input, type)); break;
+                case 50: value.setCPhaseVolt((double) f32(input, type)); break;
+                case 51: value.setAPhaseCurrent((double) f32(input, type)); break;
+                case 52: value.setBPhaseCurrent((double) f32(input, type)); break;
+                case 53: value.setCPhaseCurrent((double) f32(input, type)); break;
+                case 54: value.setAcTotalActivePower((double) f32(input, type)); break;
+                case 69: value.setFrequency((double) f32(input, type)); break;
+                case 74: value.setAcTotalPowerFactor((double) f32(input, type)); break;
+                default: skip(input, type);
+            }
+        });
+        return value;
+    }
+
+    private TmsState parseTms(byte[] bytes) throws IOException {
+        TmsState value = new TmsState();
+        read(bytes, (field, type, input) -> {
+            switch (field) {
+                case 1: value.setDeviceId(str(input, type)); break;
+                case 2: value.setRun_status(enumName(input, type, "TmsRunStatus")); break;
+                case 3: value.setError_status(enumName(input, type, "ErrorStatus")); break;
+                case 4: value.setSupply_temp((double) f32(input, type)); break;
+                case 5: value.setReturn_temp((double) f32(input, type)); break;
+                case 8: value.setEnv_temp((double) f32(input, type)); break;
+                case 9: value.setBackwater_press((double) f32(input, type)); break;
+                case 10: value.setOut_water_press((double) f32(input, type)); break;
+                case 13: value.setCompress_status(enumName(input, type, "RunStatus")); break;
+                case 14: value.setCompress_heat_status(enumName(input, type, "RunStatus")); break;
+                case 15: value.setElectric_heat_status(enumName(input, type, "RunStatus")); break;
+                case 16: value.setPump_status(enumName(input, type, "RunStatus")); break;
+                default: skip(input, type);
+            }
+        });
+        return value;
+    }
+
     private void read(byte[] bytes, Reader reader) throws IOException {
         CodedInputStream input = CodedInputStream.newInstance(bytes == null ? new byte[0] : bytes);
         while (!input.isAtEnd()) {
@@ -261,6 +372,18 @@
         if ("CommStatus".equals(prefix)) {
             return new String[]{"COMM_STATUS_UNKNOWN", "COMM_STATUS_DISCONNECTED", "COMM_STATUS_CONNECTED"}[number < 0 || number > 2 ? 0 : number];
         }
+        if ("ErrorStatus".equals(prefix)) {
+            return new String[]{"ERROR_STATUS_UNKNOWN", "ERROR_STATUS_OCCURRING", "ERROR_STATUS_NONE"}[number < 0 || number > 2 ? 0 : number];
+        }
+        if ("AlarmStatus".equals(prefix)) {
+            return new String[]{"ALARM_STATUS_UNKNOWN", "ALARM_STATUS_OCCURRING", "ALARM_STATUS_NONE"}[number < 0 || number > 2 ? 0 : number];
+        }
+        if ("BatteryWorkStatus".equals(prefix)) {
+            return new String[]{"BATTERY_WORK_STATUS_UNKNOWN", "BATTERY_WORK_STATUS_CHARGING", "BATTERY_WORK_STATUS_DISCHARGING", "BATTERY_WORK_STATUS_IDLE"}[number < 0 || number > 3 ? 0 : number];
+        }
+        if ("TmsRunStatus".equals(prefix)) {
+            return new String[]{"TMS_RUN_STATUS_UNKNOWN", "TMS_RUN_STATUS_IDLE", "TMS_RUN_STATUS_READY_TO_START", "TMS_RUN_STATUS_RUNNING", "TMS_RUN_STATUS_DELAYED_SHUTDOWN", "TMS_RUN_STATUS_SELF_TEST", "TMS_RUN_STATUS_ALARMING"}[number < 0 || number > 6 ? 0 : number];
+        }
         return prefix + "_" + number;
     }
     private static <T> T enumAt(T[] values, CodedInputStream input, int type) throws IOException {
diff --git a/ruoyi-service/ruoyi-other/src/main/resources/mapper/other/EmindEnergyChainMapper.xml b/ruoyi-service/ruoyi-other/src/main/resources/mapper/other/EmindEnergyChainMapper.xml
index 7237b1a..d4ace99 100644
--- a/ruoyi-service/ruoyi-other/src/main/resources/mapper/other/EmindEnergyChainMapper.xml
+++ b/ruoyi-service/ruoyi-other/src/main/resources/mapper/other/EmindEnergyChainMapper.xml
@@ -26,6 +26,7 @@
         <result column="synced_at" property="syncedAt"/>
         <result column="platform_name" property="platformName"/>
         <result column="data_status" property="dataStatus"/>
+        <result column="station_type" property="stationType"/>
     </resultMap>
 
     <update id="markAllDeleted">
@@ -77,7 +78,7 @@
                chain.phase_a_current, chain.phase_b_current, chain.phase_c_current,
                chain.dischargeable_energy, chain.soc, chain.charged_energy_today,
                chain.discharged_energy_today, chain.record_time, chain.synced_at,
-               chain.source_type, chain.management_deleted,
+               chain.source_type, chain.management_deleted, chain.station_type,
                station.platform_name,
                CASE WHEN chain.record_time IS NULL OR chain.record_time = '' THEN 0 ELSE 1 END AS data_status
         FROM t_emind_energy_chain chain
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 25b618f..6592cc3 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
@@ -7,4 +7,24 @@
 
 ALTER TABLE t_emind_dashboard_daily MODIFY COLUMN id BIGINT NOT NULL AUTO_INCREMENT comment '主键ID';
 
+-- 3、储能能量连表增加(设备类型)
+alter table t_emind_energy_chain add column station_type varchar(255)   default ''  null comment '设备类型';
+
+-- 增加能量链详情表
+create table t_emind_energy_chain_detail
+(
+    id                 bigint auto_increment comment '主键ID'
+        primary key,
+    platform_name        varchar(100)         null comment '所属平台(利普芯、欧力特)',
+    station_code       varchar(255)                   null comment '设备编号',
+    station_id       varchar(255)                   null comment '设备ID',
+    station_type       varchar(255)                   null comment '设备类型(与能量链里面的设备类型进行关联)',
+    detail             text                 null comment '设备详情json字段',
+    data_source        int                           null comment '数据来源(1系统创建,2第三方接口)',
+    create_time        datetime                      null comment '创建时间',
+    sync_time          datetime                      null comment '同步时间',
+    update_time        datetime                      null comment '更新时间',
+    del_flag           char           default '0'    not null comment '删除标志(0存在 2删除)'
+)
+    comment '能量链详情表' collate = utf8mb4_bin;
 

--
Gitblit v1.7.1