learshaw 3 месяцев назад
Родитель
Сommit
1aa5e26a80
18 измененных файлов с 3073 добавлено и 309 удалено
  1. 426 0
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/handler/ChargingHandler.java
  2. 134 36
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/handler/ChargingPileMessageHandler.java
  3. 74 24
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/mock/config/MockPileConfig.java
  4. 51 26
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/mock/handler/MockClientDecoder.java
  5. 71 14
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/mock/handler/MockClientHandler.java
  6. 69 83
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/mock/simulator/MockPileClient.java
  7. 234 0
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/model/req/ErrorReportFrame.java
  8. 9 9
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/model/req/RealtimeDataFrame.java
  9. 188 0
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/model/req/RemoteStopRespFrame.java
  10. 526 0
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/model/req/TransactionRecordFrame.java
  11. 109 0
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/model/resp/RemoteStopFrame.java
  12. 117 0
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/model/resp/TransactionConfirmFrame.java
  13. 66 34
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/protocol/FrameParser.java
  14. 526 40
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/service/ChargingDataService.java
  15. 136 39
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/service/PowerControlService.java
  16. 130 0
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/controller/ChargingController.java
  17. 1 1
      ems/ems-cloud/ems-dev-adapter/src/test/java/com/huashe/test/ProtocolParserTest.java
  18. 206 3
      ems/sql/ems_init_data_ctfwq.sql

+ 426 - 0
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/handler/ChargingHandler.java

@@ -0,0 +1,426 @@
+/*
+ * 文 件 名:  ChargingHandler
+ * 版    权:  华设设计集团股份有限公司
+ * 描    述:  充电桩适配处理Handler
+ * 修 改 人:  lvwenbin
+ * 修改时间:  2025/12/25
+ * 修改内容:  新建充电桩能力调用处理器,继承BaseDevHandler
+ */
+package com.ruoyi.ems.charging.handler;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.ruoyi.ems.charging.core.ChargingPileSessionManager;
+import com.ruoyi.ems.charging.service.ChargingDataService;
+import com.ruoyi.ems.charging.service.PowerControlService;
+import com.ruoyi.ems.domain.EmsDevice;
+import com.ruoyi.ems.domain.EmsObjAttrValue;
+import com.ruoyi.ems.enums.DevObjType;
+import com.ruoyi.ems.enums.DevOnlineStatus;
+import com.ruoyi.ems.handle.BaseDevHandler;
+import com.ruoyi.ems.model.AbilityPayload;
+import com.ruoyi.ems.model.CallResponse;
+import com.ruoyi.ems.model.QueryDevice;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 充电桩适配处理Handler
+ * 处理充电桩系统、主机、充电枪的能力调用
+ *
+ * @author lvwenbin
+ * @version [版本号, 2025/12/25]
+ */
+@Service("chargingHandler")
+public class ChargingHandler extends BaseDevHandler {
+
+    private static final Logger log = LoggerFactory.getLogger(ChargingHandler.class);
+
+    /**
+     * 充电系统模型代码
+     */
+    private static final String MODEL_CODE_SYS = "M_W2_SYS_CHARGING";
+
+    /**
+     * 充电主机模型代码
+     */
+    private static final String MODEL_CODE_HOST = "M_W2_DEV_CHARGING_HOST";
+
+    /**
+     * 充电枪模型代码
+     */
+    private static final String MODEL_CODE_PILE = "M_W2_DEV_CHARGING_PILE";
+
+    /**
+     * 子系统代码
+     */
+    private static final String SUBSYSTEM_CODE = "SYS_CD";
+
+    @Autowired
+    private PowerControlService powerControlService;
+
+    @Autowired
+    private ChargingDataService chargingDataService;
+
+    @Autowired
+    private ChargingPileSessionManager sessionManager;
+
+    @Override
+    public CallResponse<Void> call(AbilityPayload abilityParam) {
+        CallResponse<Void> callResponse;
+
+        try {
+            log.info("充电桩能力调用: {}", abilityParam);
+
+            int objType = abilityParam.getObjType();
+            String modelCode = abilityParam.getModelCode();
+            String abilityKey = abilityParam.getAbilityKey();
+            String objCode = abilityParam.getObjCode();
+            String param = abilityParam.getAbilityParam();
+
+            // 根据对象类型分发处理
+            if (DevObjType.SYSTEM.getCode() == objType) {
+                // 系统级能力
+                callResponse = handleSystemAbility(abilityKey, param);
+            }
+            else if (DevObjType.DEVC.getCode() == objType) {
+                // 设备级能力
+                if (MODEL_CODE_HOST.equals(modelCode)) {
+                    callResponse = handleHostAbility(objCode, abilityKey, param);
+                }
+                else if (MODEL_CODE_PILE.equals(modelCode)) {
+                    callResponse = handlePileAbility(objCode, abilityKey, param);
+                }
+                else {
+                    callResponse = new CallResponse<>(-1, "不支持的模型代码: " + modelCode);
+                }
+            }
+            else {
+                callResponse = new CallResponse<>(-1, "不支持的对象类型: " + objType);
+            }
+
+            // 记录调用日志
+            saveCallLog(objCode, modelCode, abilityKey, callResponse.getCode() == 0 ? 0 : -1, param,
+                callResponse.getMessage());
+
+        }
+        catch (Exception e) {
+            log.error("充电桩能力调用异常", e);
+            callResponse = new CallResponse<>(-1, "内部错误: " + e.getMessage());
+        }
+
+        return callResponse;
+    }
+
+    /**
+     * 处理系统级能力
+     */
+    private CallResponse<Void> handleSystemAbility(String abilityKey, String param) {
+        switch (abilityKey) {
+            case "setAllPilesPowerLimit":
+                // 批量设置功率限制
+                int powerPercent = param != null ? Integer.parseInt(param) : 100;
+                if (powerPercent < 30 || powerPercent > 100) {
+                    return new CallResponse<>(-1, "功率百分比需在30-100之间");
+                }
+                powerControlService.setAllPilesPowerLimit(powerPercent);
+                return new CallResponse<>(0, "批量功率限制指令已发送");
+
+            case "stopAllCharging":
+                // 全部停止充电
+                int stopCount = powerControlService.stopAllCharging();
+                return new CallResponse<>(0, "已发送停止充电指令: " + stopCount + "条");
+
+            case "clearCache":
+                // 清除设备编码缓存
+                chargingDataService.clearDeviceCodeCache();
+                return new CallResponse<>(0, "缓存已清除");
+
+            case "getCacheStats":
+                // 获取缓存统计
+                return new CallResponse<>(0, JSON.toJSONString(chargingDataService.getCacheStats()));
+
+            default:
+                return new CallResponse<>(-1, "不支持的系统能力: " + abilityKey);
+        }
+    }
+
+    /**
+     * 处理充电主机能力
+     */
+    private CallResponse<Void> handleHostAbility(String objCode, String abilityKey, String param) {
+        // 获取桩编码
+        String pileCode = getPileCodeByDeviceCode(objCode);
+        if (StringUtils.isBlank(pileCode)) {
+            return new CallResponse<>(-1, "未找到设备的桩编码: " + objCode);
+        }
+
+        switch (abilityKey) {
+            case "setMaxOutputPower":
+                // 设置最大输出功率
+                int powerPercent = param != null ? Integer.parseInt(param) : 100;
+                if (powerPercent < 30 || powerPercent > 100) {
+                    return new CallResponse<>(-1, "功率百分比需在30-100之间");
+                }
+                boolean powerResult = powerControlService.setMaxOutputPower(pileCode, powerPercent);
+                return powerResult ?
+                    new CallResponse<>(0, "功率设置指令已发送") :
+                    new CallResponse<>(-1, "功率设置指令发送失败,请检查设备是否在线");
+
+            case "enablePile":
+                // 启用充电桩
+                boolean enableResult = powerControlService.enablePile(pileCode);
+                return enableResult ?
+                    new CallResponse<>(0, "启用指令已发送") :
+                    new CallResponse<>(-1, "启用指令发送失败");
+
+            case "disablePile":
+                // 禁用充电桩
+                boolean disableResult = powerControlService.disablePile(pileCode);
+                return disableResult ?
+                    new CallResponse<>(0, "禁用指令已发送") :
+                    new CallResponse<>(-1, "禁用指令发送失败");
+
+            default:
+                return new CallResponse<>(-1, "不支持的主机能力: " + abilityKey);
+        }
+    }
+
+    /**
+     * 处理充电枪能力
+     */
+    private CallResponse<Void> handlePileAbility(String objCode, String abilityKey, String param) {
+        // 获取桩编码和枪号
+        String pileCode = getGunPileCode(objCode);
+        String gunNo = getGunNo(objCode);
+
+        if (StringUtils.isBlank(pileCode) || StringUtils.isBlank(gunNo)) {
+            return new CallResponse<>(-1, "未找到设备的桩编码或枪号: " + objCode);
+        }
+
+        switch (abilityKey) {
+            case "remoteStop":
+                // 远程停止充电
+                boolean stopResult = powerControlService.remoteStop(pileCode, gunNo);
+                return stopResult ?
+                    new CallResponse<>(0, "远程停机指令已发送") :
+                    new CallResponse<>(-1, "远程停机指令发送失败,请检查设备是否在线");
+
+            case "readRealtimeData":
+                // 主动读取实时数据
+                boolean readResult = powerControlService.readRealtimeData(pileCode, gunNo);
+                return readResult ?
+                    new CallResponse<>(0, "读取数据指令已发送") :
+                    new CallResponse<>(-1, "读取数据指令发送失败");
+
+            default:
+                return new CallResponse<>(-1, "不支持的充电枪能力: " + abilityKey);
+        }
+    }
+
+    /**
+     * 根据设备编码获取桩编码(充电主机)
+     */
+    private String getPileCodeByDeviceCode(String deviceCode) {
+        EmsObjAttrValue attrValue = objAttrValueService.selectObjAttrValue(MODEL_CODE_HOST, deviceCode, "pileCode");
+        return attrValue != null ? attrValue.getAttrValue() : null;
+    }
+
+    /**
+     * 获取充电枪的桩编码
+     */
+    private String getGunPileCode(String deviceCode) {
+        EmsObjAttrValue attrValue = objAttrValueService.selectObjAttrValue(MODEL_CODE_PILE, deviceCode, "pileCode");
+        return attrValue != null ? attrValue.getAttrValue() : null;
+    }
+
+    /**
+     * 获取充电枪的枪号
+     */
+    private String getGunNo(String deviceCode) {
+        EmsObjAttrValue attrValue = objAttrValueService.selectObjAttrValue(MODEL_CODE_PILE, deviceCode, "gunNo");
+        return attrValue != null ? attrValue.getAttrValue() : null;
+    }
+
+    @Override
+    public List<EmsDevice> getDeviceList() {
+        // 返回所有充电主机设备
+        QueryDevice queryDevice = new QueryDevice();
+        queryDevice.setDeviceModel(MODEL_CODE_HOST);
+        queryDevice.setSubsystemCode(SUBSYSTEM_CODE);
+        return deviceService.selectList(queryDevice);
+    }
+
+    @Override
+    public void refreshOnline() {
+        log.info("开始刷新充电桩在线状态...");
+
+        try {
+            // 查询本地充电主机设备
+            QueryDevice queryHost = new QueryDevice();
+            queryHost.setDeviceModel(MODEL_CODE_HOST);
+            queryHost.setSubsystemCode(SUBSYSTEM_CODE);
+            List<EmsDevice> hostDevices = deviceService.selectList(queryHost);
+
+            for (EmsDevice device : hostDevices) {
+                try {
+                    // 获取设备的桩编码
+                    EmsObjAttrValue pileCodeAttr = objAttrValueService.selectObjAttrValue(MODEL_CODE_HOST,
+                        device.getDeviceCode(), "pileCode");
+
+                    if (pileCodeAttr == null || StringUtils.isBlank(pileCodeAttr.getAttrValue())) {
+                        continue;
+                    }
+
+                    String pileCode = pileCodeAttr.getAttrValue();
+
+                    // 通过SessionManager判断桩是否在线
+                    boolean isOnline = sessionManager.isOnline(pileCode);
+                    DevOnlineStatus newStatus = isOnline ? DevOnlineStatus.ONLINE : DevOnlineStatus.OFFLINE;
+
+                    // 更新状态
+                    refreshStatus(device, newStatus);
+
+                    // 同步更新该主机下所有充电枪的状态
+                    updateGunOnlineStatus(device.getDeviceCode(), isOnline);
+
+                }
+                catch (Exception e) {
+                    log.error("刷新充电主机 {} 状态异常", device.getDeviceCode(), e);
+                }
+            }
+
+            log.info("充电桩在线状态刷新完成");
+
+        }
+        catch (Exception e) {
+            log.error("刷新充电桩在线状态异常", e);
+        }
+    }
+
+    /**
+     * 更新充电枪在线状态
+     * 充电枪的在线状态跟随其所属主机
+     */
+    private void updateGunOnlineStatus(String hostDeviceCode, boolean isOnline) {
+        try {
+            // 获取主机的subDev属性,解析出充电枪列表
+            EmsObjAttrValue subDevAttr = objAttrValueService.selectObjAttrValue(MODEL_CODE_HOST, hostDeviceCode,
+                "subDev");
+
+            if (subDevAttr == null || StringUtils.isBlank(subDevAttr.getAttrValue())) {
+                return;
+            }
+
+            // 解析子设备列表 JSON
+            List<JSONObject> subDevList = JSON.parseArray(subDevAttr.getAttrValue(), JSONObject.class);
+
+            if (CollectionUtils.isEmpty(subDevList)) {
+                return;
+            }
+
+            for (JSONObject subDev : subDevList) {
+                String gunDeviceCode = subDev.getString("deviceCode");
+                String gunModelCode = subDev.getString("modelCode");
+
+                if (StringUtils.isBlank(gunDeviceCode) || !MODEL_CODE_PILE.equals(gunModelCode)) {
+                    continue;
+                }
+
+                // 查询枪设备
+                EmsDevice gunDevice = deviceService.selectByCode(gunDeviceCode);
+                if (gunDevice != null) {
+                    DevOnlineStatus newStatus = isOnline ? DevOnlineStatus.ONLINE : DevOnlineStatus.OFFLINE;
+                    refreshStatus(gunDevice, newStatus);
+                }
+            }
+
+        }
+        catch (Exception e) {
+            log.error("更新充电枪在线状态异常 - 主机: {}", hostDeviceCode, e);
+        }
+    }
+
+    /**
+     * 同步单个主机属性
+     * 从内存缓存或协议获取最新数据更新到数据库
+     */
+    public void syncHostAttr(String deviceCode) {
+        log.info("同步充电主机 {} 属性", deviceCode);
+
+        try {
+            // 获取桩编码
+            String pileCode = getPileCodeByDeviceCode(deviceCode);
+            if (StringUtils.isBlank(pileCode)) {
+                log.warn("充电主机 {} 无pileCode属性", deviceCode);
+                return;
+            }
+
+            // 检查桩是否在线
+            if (!sessionManager.isOnline(pileCode)) {
+                log.warn("充电桩 {} 不在线,无法同步属性", pileCode);
+                return;
+            }
+
+            // 获取会话信息
+            ChargingPileSessionManager.PileSession session = sessionManager.getSession(pileCode);
+            if (session == null) {
+                return;
+            }
+
+            // 获取现有属性
+            List<EmsObjAttrValue> existingAttrs = objAttrValueService.selectByObjCode(MODEL_CODE_HOST, deviceCode);
+            Map<String, EmsObjAttrValue> attrMap = existingAttrs.stream()
+                .collect(Collectors.toMap(EmsObjAttrValue::getAttrKey, Function.identity(), (k1, k2) -> k1));
+
+            // 更新枪数量
+            checkAndUpdate(attrMap, deviceCode, MODEL_CODE_HOST, "gunCount", String.valueOf(session.getGunCount()));
+
+            log.info("充电主机 {} 属性同步完成", deviceCode);
+
+        }
+        catch (Exception e) {
+            log.error("同步充电主机 {} 属性异常", deviceCode, e);
+        }
+    }
+
+    /**
+     * 同步单个充电枪属性
+     * 主动读取实时数据
+     */
+    public void syncGunAttr(String deviceCode) {
+        log.info("同步充电枪 {} 属性", deviceCode);
+
+        try {
+            String pileCode = getGunPileCode(deviceCode);
+            String gunNo = getGunNo(deviceCode);
+
+            if (StringUtils.isBlank(pileCode) || StringUtils.isBlank(gunNo)) {
+                log.warn("充电枪 {} 无pileCode或gunNo属性", deviceCode);
+                return;
+            }
+
+            // 发送读取实时数据指令
+            boolean result = powerControlService.readRealtimeData(pileCode, gunNo);
+            if (result) {
+                log.info("充电枪 {} 属性同步指令已发送", deviceCode);
+            }
+            else {
+                log.warn("充电枪 {} 属性同步指令发送失败", deviceCode);
+            }
+
+        }
+        catch (Exception e) {
+            log.error("同步充电枪 {} 属性异常", deviceCode, e);
+        }
+    }
+}

+ 134 - 36
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/handler/ChargingPileMessageHandler.java

@@ -3,7 +3,10 @@
  * 版    权:  华设设计集团股份有限公司
  * 描    述:  充电桩消息处理器
  * 修 改 人:  lvwenbin
- * 修改时间:  2025/12/22
+ * 修改时间:  2025/12/25
+ * 修改内容:
+ *   1. 添加错误报文、交易记录、远程停机应答的处理
+ *   2. 【新增】在channelInactive中调用ChargingDataService.onPileDisconnect更新设备离线状态
  */
 package com.ruoyi.ems.charging.handler;
 
@@ -11,12 +14,16 @@ import com.ruoyi.ems.charging.core.ChargingPileSessionManager;
 import com.ruoyi.ems.charging.model.BaseFrame;
 import com.ruoyi.ems.charging.model.req.ChargingEndFrame;
 import com.ruoyi.ems.charging.model.req.ChargingHandshakeFrame;
+import com.ruoyi.ems.charging.model.req.ErrorReportFrame;
 import com.ruoyi.ems.charging.model.req.HeartbeatReqFrame;
 import com.ruoyi.ems.charging.model.req.LoginReqFrame;
 import com.ruoyi.ems.charging.model.req.RealtimeDataFrame;
+import com.ruoyi.ems.charging.model.req.RemoteStopRespFrame;
+import com.ruoyi.ems.charging.model.req.TransactionRecordFrame;
 import com.ruoyi.ems.charging.model.req.WorkParamSetRespFrame;
 import com.ruoyi.ems.charging.model.resp.HeartbeatRespFrame;
 import com.ruoyi.ems.charging.model.resp.LoginRespFrame;
+import com.ruoyi.ems.charging.model.resp.TransactionConfirmFrame;
 import com.ruoyi.ems.charging.protocol.ProtocolConstants;
 import com.ruoyi.ems.charging.service.ChargingDataService;
 import io.netty.channel.ChannelHandler;
@@ -32,61 +39,88 @@ import org.springframework.stereotype.Component;
  * 处理充电桩上送的各类协议消息
  *
  * @author lvwenbin
- * @version [版本号, 2025/12/22]
+ * @version [版本号, 2025/12/25]
  */
 @Component
 @ChannelHandler.Sharable
 public class ChargingPileMessageHandler extends SimpleChannelInboundHandler<BaseFrame> {
-    
+
     private static final Logger log = LoggerFactory.getLogger(ChargingPileMessageHandler.class);
-    
+
     @Autowired
     private ChargingPileSessionManager sessionManager;
-    
+
     @Autowired
     private ChargingDataService chargingDataService;
-    
+
     @Override
     protected void channelRead0(ChannelHandlerContext ctx, BaseFrame frame) throws Exception {
         byte frameType = frame.getFrameType();
-        
+
         switch (frameType) {
+            // 登录认证 (0x01)
             case ProtocolConstants.FRAME_TYPE_LOGIN_REQ:
                 handleLoginRequest(ctx, (LoginReqFrame) frame);
                 break;
+
+            // 心跳请求 (0x03)
             case ProtocolConstants.FRAME_TYPE_HEARTBEAT_REQ:
                 handleHeartbeatRequest(ctx, (HeartbeatReqFrame) frame);
                 break;
+
+            // 实时监测数据 (0x13)
             case ProtocolConstants.FRAME_TYPE_REALTIME_DATA:
                 handleRealtimeData(ctx, (RealtimeDataFrame) frame);
                 break;
+
+            // 充电握手 (0x15)
             case ProtocolConstants.FRAME_TYPE_CHARGING_HANDSHAKE:
                 handleChargingHandshake(ctx, (ChargingHandshakeFrame) frame);
                 break;
+
+            // 充电结束 (0x19)
             case ProtocolConstants.FRAME_TYPE_CHARGING_END:
                 handleChargingEnd(ctx, (ChargingEndFrame) frame);
                 break;
+
+            // 错误报文 (0x1B) - 新增
+            case ProtocolConstants.FRAME_TYPE_ERROR:
+                handleErrorReport(ctx, (ErrorReportFrame) frame);
+                break;
+
+            // 远程停机命令回复 (0x35) - 新增
+            case ProtocolConstants.FRAME_TYPE_REMOTE_STOP_RESP:
+                handleRemoteStopResp(ctx, (RemoteStopRespFrame) frame);
+                break;
+
+            // 交易记录 (0x3B) - 新增
+            case ProtocolConstants.FRAME_TYPE_TRANSACTION:
+                handleTransactionRecord(ctx, (TransactionRecordFrame) frame);
+                break;
+
+            // 工作参数设置应答 (0x51)
             case ProtocolConstants.FRAME_TYPE_WORK_PARAM_SET_RESP:
                 handleWorkParamSetResp(ctx, (WorkParamSetRespFrame) frame);
                 break;
+
             default:
                 log.info("收到未处理的帧类型: 0x{}", String.format("%02X", frameType & 0xFF));
                 break;
         }
     }
-    
+
     /**
      * 处理登录认证请求 (0x01)
      */
     private void handleLoginRequest(ChannelHandlerContext ctx, LoginReqFrame req) {
         log.info("收到登录认证请求: {}", req);
-        
+
         String pileCode = req.getPileCode();
         int gunCount = req.getGunCount();
-        
+
         // 验证桩信息(这里简化处理,实际应查询数据库验证)
         boolean valid = validatePile(pileCode);
-        
+
         // 发送登录应答
         LoginRespFrame resp;
         if (valid) {
@@ -98,79 +132,126 @@ public class ChargingPileMessageHandler extends SimpleChannelInboundHandler<Base
             resp = LoginRespFrame.fail(pileCode, req.getSequenceNo());
             log.warn("充电桩[{}]登录失败,验证未通过", pileCode);
         }
-        
+
         ctx.writeAndFlush(resp);
-        
-        // 通知业务层
+
+        // 通知业务层(会更新主机和子设备的在线状态)
         chargingDataService.onPileLogin(req, valid);
     }
-    
+
     /**
      * 处理心跳请求 (0x03)
      */
     private void handleHeartbeatRequest(ChannelHandlerContext ctx, HeartbeatReqFrame req) {
         log.debug("收到心跳请求: {}", req);
-        
+
         String pileCode = req.getPileCode();
-        
+
         // 更新心跳时间
         sessionManager.updateHeartbeat(pileCode);
-        
+
         // 发送心跳应答
         HeartbeatRespFrame resp = HeartbeatRespFrame.create(pileCode, req.getGunNo(), req.getSequenceNo());
         ctx.writeAndFlush(resp);
-        
+
         // 如果枪状态异常,通知业务层
         if (!req.isNormal()) {
             log.warn("充电桩[{}]枪[{}]状态异常: 故障", pileCode, req.getGunNo());
             chargingDataService.onGunFault(pileCode, req.getGunNo());
         }
     }
-    
+
     /**
      * 处理实时监测数据 (0x13)
      * 这是能耗平台最核心的数据处理
      */
     private void handleRealtimeData(ChannelHandlerContext ctx, RealtimeDataFrame data) {
         log.info("收到实时监测数据: {}", data);
-        
+
         // 更新心跳时间
         sessionManager.updateHeartbeat(data.getPileCode());
-        
+
         // 处理实时数据
         chargingDataService.processRealtimeData(data);
     }
-    
+
     /**
      * 处理充电握手 (0x15)
      */
     private void handleChargingHandshake(ChannelHandlerContext ctx, ChargingHandshakeFrame handshake) {
         log.info("收到充电握手: {}", handshake);
-        
+
         // 通知业务层
         chargingDataService.onChargingHandshake(handshake);
     }
-    
+
     /**
      * 处理充电结束 (0x19)
      */
     private void handleChargingEnd(ChannelHandlerContext ctx, ChargingEndFrame end) {
         log.info("收到充电结束: {}", end);
-        
+
         // 通知业务层
         chargingDataService.onChargingEnd(end);
     }
-    
+
+    /**
+     * 处理错误报文 (0x1B) - 新增
+     * 用于接收充电过程中的通信异常信息
+     */
+    private void handleErrorReport(ChannelHandlerContext ctx, ErrorReportFrame error) {
+        if (error.hasError()) {
+            log.warn("收到错误报文: {}", error);
+        } else {
+            log.debug("收到错误报文(无错误): {}", error);
+        }
+
+        // 通知业务层
+        chargingDataService.onErrorReport(error);
+    }
+
+    /**
+     * 处理远程停机命令回复 (0x35) - 新增
+     */
+    private void handleRemoteStopResp(ChannelHandlerContext ctx, RemoteStopRespFrame resp) {
+        if (resp.isSuccess()) {
+            log.info("远程停机成功: {}", resp);
+        } else {
+            log.warn("远程停机失败: {}", resp);
+        }
+
+        // 通知业务层
+        chargingDataService.onRemoteStopResp(resp);
+    }
+
+    /**
+     * 处理交易记录 (0x3B)
+     * 这是能耗统计最重要的数据,包含分时电量
+     */
+    private void handleTransactionRecord(ChannelHandlerContext ctx, TransactionRecordFrame record) {
+        log.info("收到交易记录: {}", record);
+
+        // 发送确认应答
+        TransactionConfirmFrame confirmFrame = TransactionConfirmFrame.success(
+            record.getTransactionNo(), record.getSequenceNo());
+        ctx.writeAndFlush(confirmFrame);
+
+        log.info("已发送交易记录确认: transNo={}", record.getTransactionNo());
+
+        // 通知业务层处理交易记录
+        chargingDataService.onTransactionRecord(record);
+    }
+
     /**
      * 处理工作参数设置应答 (0x51)
      */
     private void handleWorkParamSetResp(ChannelHandlerContext ctx, WorkParamSetRespFrame resp) {
         log.info("收到工作参数设置应答: {}", resp);
-        
+
         // 通知业务层
         chargingDataService.onWorkParamSetResp(resp);
     }
-    
+
     /**
      * 验证充电桩
      * 实际应用中应查询数据库验证桩信息
@@ -179,26 +260,43 @@ public class ChargingPileMessageHandler extends SimpleChannelInboundHandler<Base
         // 简单验证:桩编号不为空且长度合法
         return pileCode != null && !pileCode.isEmpty() && pileCode.length() <= 14;
     }
-    
+
     @Override
     public void channelActive(ChannelHandlerContext ctx) throws Exception {
         log.info("充电桩连接建立: {}", ctx.channel().remoteAddress());
         super.channelActive(ctx);
     }
-    
+
+    /**
+     * 连接断开时的处理
+     * 1. 注销会话
+     * 2. 调用ChargingDataService更新设备离线状态
+     */
     @Override
     public void channelInactive(ChannelHandlerContext ctx) throws Exception {
         log.info("充电桩连接断开: {}", ctx.channel().remoteAddress());
-        
-        // 注销会话
+
+        // 1. 先获取桩编码(在注销前获取,否则就拿不到了)
+        String pileCode = sessionManager.getPileCodeByChannel(ctx.channel());
+
+        // 2. 注销会话
         sessionManager.unregisterSession(ctx.channel());
-        
+
+        // 3. 通知业务层处理设备离线
+        if (pileCode != null) {
+            try {
+                chargingDataService.onPileDisconnect(pileCode);
+            } catch (Exception e) {
+                log.error("处理充电桩断开连接时发生异常 - 桩号: {}", pileCode, e);
+            }
+        }
+
         super.channelInactive(ctx);
     }
-    
+
     @Override
     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
         log.error("消息处理异常: {} - {}", ctx.channel().remoteAddress(), cause.getMessage(), cause);
         ctx.close();
     }
-}
+}

+ 74 - 24
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/mock/config/MockPileConfig.java

@@ -4,6 +4,7 @@
  * 描    述:  Mock充电桩配置
  * 修 改 人:  lvwenbin
  * 修改时间:  2025/12/23
+ * 修改内容:  修正桩编码为14位BCD码格式,与数据库attr_value保持一致
  */
 package com.ruoyi.ems.charging.mock.config;
 
@@ -13,6 +14,14 @@ import java.util.List;
 /**
  * Mock充电桩配置
  * 定义模拟的充电桩主机和充电枪配置
+ * 编码规则 (符合PDF协议规范):
+ *   桩编码(pileCode): 7字节BCD码 = 14位数字
+ *   枪号(gunNo): 1字节BCD码 = 2位数字 (01-08)
+ * 桩编码设计: 321283 + AA + BBBB + 01
+ *   321283: 泰兴市行政区划码前6位
+ *   AA: 服务区(01=北区, 02=南区)
+ *   BBBB: 主机序号(0001, 0002)
+ *   01: 校验位
  *
  * @author lvwenbin
  * @version [版本号, 2025/12/23]
@@ -20,6 +29,14 @@ import java.util.List;
 public class MockPileConfig {
 
     /**
+     * 桩编码常量 - 与数据库保持一致
+     */
+    public static final String PILE_CODE_NORTH_HOST_01 = "32128301000101";
+    public static final String PILE_CODE_NORTH_HOST_02 = "32128301000201";
+    public static final String PILE_CODE_SOUTH_HOST_01 = "32128302000101";
+    public static final String PILE_CODE_SOUTH_HOST_02 = "32128302000201";
+
+    /**
      * 服务器地址
      */
     private String serverHost = "127.0.0.1";
@@ -74,51 +91,61 @@ public class MockPileConfig {
      * 南北两侧,每侧2台充电主机
      * 主机1: 1台600kW水冷枪 + 6台250kW风冷枪 = 7枪
      * 主机2: 1台600kW水冷枪 + 7台250kW风冷枪 = 8枪
+     *
+     * 桩编码使用14位BCD码格式,与数据库adm_ems_obj_attr_value表保持一致
      */
     public static MockPileConfig createHighwayServiceAreaConfig() {
         MockPileConfig config = new MockPileConfig();
 
-        // 北侧-主机1: 7枪 (1*600kW + 6*250kW)
+        // 北区-主机1: 7枪 (1*600kW + 6*250kW)
+        // 对应数据库: W2-B-CHARGING-HOST-01, pileCode=32128301000101
         PileHostConfig northHost1 = new PileHostConfig();
-        northHost1.setPileCode("32010600000001");
+        northHost1.setPileCode(PILE_CODE_NORTH_HOST_01);  // 32128301000101
         northHost1.setGunCount(7);
-        northHost1.setDescription("北侧-主机1");
-        northHost1.addGun(new GunConfig("1", 600, GunConfig.GunType.WATER_COOLED));
+        northHost1.setDescription("北区-主机1");
+        // 枪号使用2位格式: 01-07
+        northHost1.addGun(new GunConfig("01", 600, GunConfig.GunType.WATER_COOLED));
         for (int i = 2; i <= 7; i++) {
-            northHost1.addGun(new GunConfig(String.valueOf(i), 250, GunConfig.GunType.AIR_COOLED));
+            northHost1.addGun(new GunConfig(String.format("%02d", i), 250, GunConfig.GunType.AIR_COOLED));
         }
         config.getPileHosts().add(northHost1);
 
-        // 北侧-主机2: 8枪 (1*600kW + 7*250kW)
+        // 北区-主机2: 8枪 (1*600kW + 7*250kW)
+        // 对应数据库: W2-B-CHARGING-HOST-02, pileCode=32128301000201
         PileHostConfig northHost2 = new PileHostConfig();
-        northHost2.setPileCode("32010600000002");
+        northHost2.setPileCode(PILE_CODE_NORTH_HOST_02);  // 32128301000201
         northHost2.setGunCount(8);
-        northHost2.setDescription("北侧-主机2");
-        northHost2.addGun(new GunConfig("1", 600, GunConfig.GunType.WATER_COOLED));
+        northHost2.setDescription("北区-主机2");
+        // 枪号使用2位格式: 01-08
+        northHost2.addGun(new GunConfig("01", 600, GunConfig.GunType.WATER_COOLED));
         for (int i = 2; i <= 8; i++) {
-            northHost2.addGun(new GunConfig(String.valueOf(i), 250, GunConfig.GunType.AIR_COOLED));
+            northHost2.addGun(new GunConfig(String.format("%02d", i), 250, GunConfig.GunType.AIR_COOLED));
         }
         config.getPileHosts().add(northHost2);
 
-        // 南侧-主机1: 7枪 (1*600kW + 6*250kW)
+        // 南区-主机1: 7枪 (1*600kW + 6*250kW)
+        // 对应数据库: W2-N-CHARGING-HOST-01, pileCode=32128302000101
         PileHostConfig southHost1 = new PileHostConfig();
-        southHost1.setPileCode("32010600000003");
+        southHost1.setPileCode(PILE_CODE_SOUTH_HOST_01);  // 32128302000101
         southHost1.setGunCount(7);
-        southHost1.setDescription("南侧-主机1");
-        southHost1.addGun(new GunConfig("1", 600, GunConfig.GunType.WATER_COOLED));
+        southHost1.setDescription("南区-主机1");
+        // 枪号使用2位格式: 01-07
+        southHost1.addGun(new GunConfig("01", 600, GunConfig.GunType.WATER_COOLED));
         for (int i = 2; i <= 7; i++) {
-            southHost1.addGun(new GunConfig(String.valueOf(i), 250, GunConfig.GunType.AIR_COOLED));
+            southHost1.addGun(new GunConfig(String.format("%02d", i), 250, GunConfig.GunType.AIR_COOLED));
         }
         config.getPileHosts().add(southHost1);
 
-        // 南侧-主机2: 8枪 (1*600kW + 7*250kW)
+        // 南区-主机2: 8枪 (1*600kW + 7*250kW)
+        // 对应数据库: W2-N-CHARGING-HOST-02, pileCode=32128302000201
         PileHostConfig southHost2 = new PileHostConfig();
-        southHost2.setPileCode("32010600000004");
+        southHost2.setPileCode(PILE_CODE_SOUTH_HOST_02);  // 32128302000201
         southHost2.setGunCount(8);
-        southHost2.setDescription("南侧-主机2");
-        southHost2.addGun(new GunConfig("1", 600, GunConfig.GunType.WATER_COOLED));
+        southHost2.setDescription("南区-主机2");
+        // 枪号使用2位格式: 01-08
+        southHost2.addGun(new GunConfig("01", 600, GunConfig.GunType.WATER_COOLED));
         for (int i = 2; i <= 8; i++) {
-            southHost2.addGun(new GunConfig(String.valueOf(i), 250, GunConfig.GunType.AIR_COOLED));
+            southHost2.addGun(new GunConfig(String.format("%02d", i), 250, GunConfig.GunType.AIR_COOLED));
         }
         config.getPileHosts().add(southHost2);
 
@@ -127,20 +154,43 @@ public class MockPileConfig {
 
     /**
      * 创建简单的测试配置(单主机单枪)
+     * 使用北区主机1的桩编码
      */
     public static MockPileConfig createSimpleTestConfig() {
         MockPileConfig config = new MockPileConfig();
 
         PileHostConfig host = new PileHostConfig();
-        host.setPileCode("32010600000001");
+        host.setPileCode(PILE_CODE_NORTH_HOST_01);  // 32128301000101
         host.setGunCount(1);
         host.setDescription("测试主机");
-        host.addGun(new GunConfig("1", 120, GunConfig.GunType.AIR_COOLED));
+        host.addGun(new GunConfig("01", 120, GunConfig.GunType.AIR_COOLED));
         config.getPileHosts().add(host);
 
         return config;
     }
 
+    /**
+     * 格式化枪号为2位BCD格式
+     * @param gunNo 枪号 (1-99)
+     * @return 2位格式枪号 (01-99)
+     */
+    public static String formatGunNo(int gunNo) {
+        return String.format("%02d", gunNo);
+    }
+
+    /**
+     * 格式化枪号为2位BCD格式
+     * @param gunNo 枪号字符串
+     * @return 2位格式枪号
+     */
+    public static String formatGunNo(String gunNo) {
+        try {
+            return String.format("%02d", Integer.parseInt(gunNo));
+        } catch (NumberFormatException e) {
+            return gunNo;
+        }
+    }
+
     // Getters and Setters
     public String getServerHost() {
         return serverHost;
@@ -341,7 +391,7 @@ public class MockPileConfig {
      */
     public static class GunConfig {
         /**
-         * 枪号 (1-N)
+         * 枪号 (2位BCD格式: 01-99)
          */
         private String gunNo;
 
@@ -400,4 +450,4 @@ public class MockPileConfig {
             this.gunType = gunType;
         }
     }
-}
+}

+ 51 - 26
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/mock/handler/MockClientDecoder.java

@@ -3,7 +3,8 @@
  * 版    权:  华设设计集团股份有限公司
  * 描    述:  Mock客户端协议解码器
  * 修 改 人:  lvwenbin
- * 修改时间:  2025/12/23
+ * 修改时间:  2025/01/07
+ * 修改内容:  修复解码时索引越界问题,添加远程停止充电(0x36)支持
  */
 package com.ruoyi.ems.charging.mock.handler;
 
@@ -11,10 +12,9 @@ import com.ruoyi.ems.charging.model.BaseFrame;
 import com.ruoyi.ems.charging.model.resp.HeartbeatRespFrame;
 import com.ruoyi.ems.charging.model.resp.LoginRespFrame;
 import com.ruoyi.ems.charging.model.resp.ReadRealtimeDataFrame;
+import com.ruoyi.ems.charging.model.resp.RemoteStopFrame;
 import com.ruoyi.ems.charging.model.resp.WorkParamSetFrame;
 import com.ruoyi.ems.charging.protocol.ProtocolConstants;
-import com.ruoyi.ems.charging.utils.ByteUtils;
-import com.ruoyi.ems.charging.utils.CRC16Utils;
 import io.netty.buffer.ByteBuf;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.handler.codec.ByteToMessageDecoder;
@@ -29,7 +29,7 @@ import java.util.List;
  * 服务端下发的帧类型码为偶数
  *
  * @author lvwenbin
- * @version [版本号, 2025/12/23]
+ * @version [版本号, 2025/01/07]
  */
 public class MockClientDecoder extends ByteToMessageDecoder {
 
@@ -51,46 +51,46 @@ public class MockClientDecoder extends ByteToMessageDecoder {
             // 读取数据长度
             int dataLength = in.readByte() & 0xFF;
 
-            // 检查数据完整性
+            // 检查数据完整性: 需要 dataLength + 2(CRC) 字节
             if (in.readableBytes() < dataLength + 2) {
                 // 数据不完整,等待更多数据
                 in.resetReaderIndex();
                 return;
             }
 
-            // 记录CRC校验起始位置
-            int crcStartIndex = in.readerIndex();
+            // 记录数据域起始位置(用于CRC计算和帧重构)
+            int dataStartIndex = in.readerIndex();
 
             // 读取序列号、加密标志、帧类型
             int sequenceNo = in.readShortLE() & 0xFFFF;
             byte encryptFlag = in.readByte();
             byte frameType = in.readByte();
 
-            // 计算消息体长度
+            // 计算消息体长度 = 数据域长度 - 序列号(2) - 加密标志(1) - 帧类型(1)
             int bodyLength = dataLength - 4;
 
-            // 读取消息体
-            byte[] bodyData = new byte[bodyLength];
+            // 跳过消息体
             if (bodyLength > 0) {
-                in.readBytes(bodyData);
+                in.skipBytes(bodyLength);
             }
 
             // 读取CRC
             int receivedCrc = in.readShortLE() & 0xFFFF;
 
-            // 验证CRC
+            // 计算CRC(对数据域进行校验)
             byte[] crcData = new byte[dataLength];
-            in.readerIndex(crcStartIndex);
+            in.readerIndex(dataStartIndex);
             in.readBytes(crcData);
-            int calculatedCrc = CRC16Utils.calculateCRC(crcData);
+            int calculatedCrc = calculateCRC16(crcData);
 
             // 跳过CRC字节
             in.skipBytes(2);
 
             if (calculatedCrc != receivedCrc) {
-                log.warn("CRC校验失败, 计算值: 0x{}, 接收值: 0x{}",
-                        String.format("%04X", calculatedCrc),
-                        String.format("%04X", receivedCrc));
+                log.warn("CRC校验失败, 计算值: 0x{}, 接收值: 0x{}, 帧类型: 0x{}",
+                    String.format("%04X", calculatedCrc),
+                    String.format("%04X", receivedCrc),
+                    String.format("%02X", frameType & 0xFF));
                 continue;
             }
 
@@ -101,9 +101,13 @@ public class MockClientDecoder extends ByteToMessageDecoder {
                 continue;
             }
 
-            // 解码帧 (回退到起始位置重新解码完整帧)
-            in.readerIndex(crcStartIndex - 2);
-            byte[] fullFrame = new byte[2 + dataLength + 2];
+            // 构建完整帧数据进行解码
+            // 完整帧 = 起始标志(1) + 数据长度(1) + 数据域(dataLength) + CRC(2)
+            int totalFrameLength = 2 + dataLength + 2;
+            byte[] fullFrame = new byte[totalFrameLength];
+
+            // 回到帧起始位置(起始标志位置)
+            in.readerIndex(dataStartIndex - 2);
             in.readBytes(fullFrame);
 
             if (frame.decode(fullFrame)) {
@@ -118,22 +122,43 @@ public class MockClientDecoder extends ByteToMessageDecoder {
     }
 
     /**
+     * 计算CRC16校验值 (Modbus CRC16)
+     */
+    private int calculateCRC16(byte[] data) {
+        int crc = 0xFFFF;
+        for (byte b : data) {
+            crc ^= (b & 0xFF);
+            for (int i = 0; i < 8; i++) {
+                if ((crc & 0x0001) != 0) {
+                    crc = (crc >> 1) ^ 0xA001;
+                } else {
+                    crc = crc >> 1;
+                }
+            }
+        }
+        return crc & 0xFFFF;
+    }
+
+    /**
      * 根据帧类型创建对应的帧对象
      * 这里只处理服务端下发的帧类型(偶数)
      */
     private BaseFrame createFrame(byte frameType) {
         switch (frameType) {
             // 服务端下发的帧(偶数)
-            case ProtocolConstants.FRAME_TYPE_LOGIN_RESP:
+            case ProtocolConstants.FRAME_TYPE_LOGIN_RESP:           // 0x02
                 return new LoginRespFrame();
-            case ProtocolConstants.FRAME_TYPE_HEARTBEAT_RESP:
+            case ProtocolConstants.FRAME_TYPE_HEARTBEAT_RESP:       // 0x04
                 return new HeartbeatRespFrame();
-            case ProtocolConstants.FRAME_TYPE_READ_REALTIME_DATA:
+            case ProtocolConstants.FRAME_TYPE_READ_REALTIME_DATA:   // 0x12
                 return new ReadRealtimeDataFrame();
-            case ProtocolConstants.FRAME_TYPE_WORK_PARAM_SET:
+            case ProtocolConstants.FRAME_TYPE_REMOTE_STOP:          // 0x36 远程停止充电
+                return new RemoteStopFrame();
+            case ProtocolConstants.FRAME_TYPE_WORK_PARAM_SET:       // 0x52
                 return new WorkParamSetFrame();
             default:
                 // 使用通用帧处理
+                log.debug("使用通用帧处理, 帧类型: 0x{}", String.format("%02X", frameType & 0xFF));
                 return new GenericFrame(frameType);
         }
     }
@@ -172,7 +197,7 @@ public class MockClientDecoder extends ByteToMessageDecoder {
         @Override
         public String toString() {
             return String.format("GenericFrame[type=0x%02X, bodyLen=%d]",
-                    type & 0xFF, bodyData != null ? bodyData.length : 0);
+                type & 0xFF, bodyData != null ? bodyData.length : 0);
         }
     }
-}
+}

+ 71 - 14
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/mock/handler/MockClientHandler.java

@@ -3,7 +3,8 @@
  * 版    权:  华设设计集团股份有限公司
  * 描    述:  Mock充电桩客户端消息处理器
  * 修 改 人:  lvwenbin
- * 修改时间:  2025/12/23
+ * 修改时间:  2025/01/07
+ * 修改内容:  添加远程停止充电(0x36)处理
  */
 package com.ruoyi.ems.charging.mock.handler;
 
@@ -11,10 +12,12 @@ import com.ruoyi.ems.charging.mock.model.MockGunState;
 import com.ruoyi.ems.charging.mock.simulator.MockPileClient;
 import com.ruoyi.ems.charging.model.BaseFrame;
 import com.ruoyi.ems.charging.model.req.RealtimeDataFrame;
+import com.ruoyi.ems.charging.model.req.RemoteStopRespFrame;
 import com.ruoyi.ems.charging.model.req.WorkParamSetRespFrame;
 import com.ruoyi.ems.charging.model.resp.HeartbeatRespFrame;
 import com.ruoyi.ems.charging.model.resp.LoginRespFrame;
 import com.ruoyi.ems.charging.model.resp.ReadRealtimeDataFrame;
+import com.ruoyi.ems.charging.model.resp.RemoteStopFrame;
 import com.ruoyi.ems.charging.model.resp.WorkParamSetFrame;
 import com.ruoyi.ems.charging.protocol.ProtocolConstants;
 import io.netty.channel.ChannelHandlerContext;
@@ -27,7 +30,7 @@ import org.slf4j.LoggerFactory;
  * 处理服务端下发的消息并进行响应
  *
  * @author lvwenbin
- * @version [版本号, 2025/12/23]
+ * @version [版本号, 2025/01/07]
  */
 public class MockClientHandler extends SimpleChannelInboundHandler<BaseFrame> {
 
@@ -44,20 +47,23 @@ public class MockClientHandler extends SimpleChannelInboundHandler<BaseFrame> {
         byte frameType = frame.getFrameType();
 
         switch (frameType) {
-            case ProtocolConstants.FRAME_TYPE_LOGIN_RESP:
+            case ProtocolConstants.FRAME_TYPE_LOGIN_RESP:           // 0x02
                 handleLoginResponse(ctx, (LoginRespFrame) frame);
                 break;
-            case ProtocolConstants.FRAME_TYPE_HEARTBEAT_RESP:
+            case ProtocolConstants.FRAME_TYPE_HEARTBEAT_RESP:       // 0x04
                 handleHeartbeatResponse(ctx, (HeartbeatRespFrame) frame);
                 break;
-            case ProtocolConstants.FRAME_TYPE_READ_REALTIME_DATA:
+            case ProtocolConstants.FRAME_TYPE_READ_REALTIME_DATA:   // 0x12
                 handleReadRealtimeData(ctx, (ReadRealtimeDataFrame) frame);
                 break;
-            case ProtocolConstants.FRAME_TYPE_WORK_PARAM_SET:
+            case ProtocolConstants.FRAME_TYPE_REMOTE_STOP:          // 0x36
+                handleRemoteStop(ctx, (RemoteStopFrame) frame);
+                break;
+            case ProtocolConstants.FRAME_TYPE_WORK_PARAM_SET:       // 0x52
                 handleWorkParamSet(ctx, (WorkParamSetFrame) frame);
                 break;
             default:
-                log.debug("[{}] 收到未处理的帧类型: 0x{}", 
+                log.debug("[{}] 收到未处理的帧类型: 0x{}",
                     pileClient.getPileCode(), String.format("%02X", frameType & 0xFF));
                 break;
         }
@@ -103,7 +109,56 @@ public class MockClientHandler extends SimpleChannelInboundHandler<BaseFrame> {
         RealtimeDataFrame dataFrame = buildRealtimeDataFrame(gunState, req.getSequenceNo());
         ctx.writeAndFlush(dataFrame);
 
-        log.info("[{}] 响应实时数据查询: {}", pileClient.getPileCode(), dataFrame);
+        log.info("[{}] 响应实时数据查询: gun={}, status={}",
+            pileClient.getPileCode(), gunNo, gunState.isCharging() ? "充电中" : "空闲");
+    }
+
+    /**
+     * 处理远程停止充电 (0x36)
+     * 服务端下发停止充电命令
+     */
+    private void handleRemoteStop(ChannelHandlerContext ctx, RemoteStopFrame req) {
+        String gunNo = req.getGunNo();
+        log.info("[{}] 收到远程停止充电命令, 枪号: {}", pileClient.getPileCode(), gunNo);
+
+        // 查找对应枪的状态
+        MockGunState gunState = pileClient.getGunState(gunNo);
+
+        // 构建应答
+        RemoteStopRespFrame resp = new RemoteStopRespFrame();
+        resp.setPileCode(req.getPileCode());
+        resp.setGunNo(gunNo);
+        resp.setSequenceNo(req.getSequenceNo());
+
+        if (gunState == null) {
+            // 枪号不存在
+            log.warn("[{}] 远程停止充电失败: 未找到枪号 {}", pileClient.getPileCode(), gunNo);
+            resp.setStopResult(RemoteStopRespFrame.RESULT_FAIL);
+            resp.setFailReason(RemoteStopRespFrame.FAIL_REASON_DEVICE_MISMATCH);
+        } else if (!gunState.isCharging()) {
+            // 枪未在充电
+            log.warn("[{}] 远程停止充电失败: 枪[{}]未在充电", pileClient.getPileCode(), gunNo);
+            resp.setStopResult(RemoteStopRespFrame.RESULT_FAIL);
+            resp.setFailReason(RemoteStopRespFrame.FAIL_REASON_NOT_CHARGING);
+        } else {
+            // 停止充电成功
+            log.info("[{}] 远程停止充电成功: 枪[{}], 已充电量: {}kWh",
+                pileClient.getPileCode(), gunNo,
+                String.format("%.2f", gunState.getChargingEnergy()));
+
+            // 先发送充电结束帧
+            pileClient.sendChargingEndFrame(gunState);
+
+            // 停止充电
+            gunState.stopCharging();
+
+            resp.setStopResult(RemoteStopRespFrame.RESULT_SUCCESS);
+            resp.setFailReason(RemoteStopRespFrame.FAIL_REASON_NONE);
+        }
+
+        // 发送应答
+        ctx.writeAndFlush(resp);
+        log.info("[{}] 发送远程停止充电应答: {}", pileClient.getPileCode(), resp);
     }
 
     /**
@@ -112,7 +167,7 @@ public class MockClientHandler extends SimpleChannelInboundHandler<BaseFrame> {
      */
     private void handleWorkParamSet(ChannelHandlerContext ctx, WorkParamSetFrame req) {
         log.info("[{}] 收到工作参数设置: allowWork={}, maxPower={}%",
-                pileClient.getPileCode(), req.isAllowWork(), req.getMaxOutputPowerPercent());
+            pileClient.getPileCode(), req.isAllowWork(), req.getMaxOutputPowerPercent());
 
         // 更新所有枪的功率限制
         boolean success = true;
@@ -126,6 +181,8 @@ public class MockClientHandler extends SimpleChannelInboundHandler<BaseFrame> {
             if (!req.isAllowWork()) {
                 for (MockGunState gunState : pileClient.getAllGunStates()) {
                     if (gunState.isCharging()) {
+                        // 发送充电结束帧
+                        pileClient.sendChargingEndFrame(gunState);
                         gunState.stopCharging();
                         log.info("[{}] 枪[{}] 因禁用而停止充电", pileClient.getPileCode(), gunState.getGunNo());
                     }
@@ -155,16 +212,16 @@ public class MockClientHandler extends SimpleChannelInboundHandler<BaseFrame> {
         frame.setSequenceNo(sequenceNo);
 
         // 基础信息
-        frame.setTransactionNo(gunState.getTransactionNo() != null ? 
-                gunState.getTransactionNo() : "00000000000000000000000000000000");
+        frame.setTransactionNo(gunState.getTransactionNo() != null ?
+            gunState.getTransactionNo() : "00000000000000000000000000000000");
         frame.setPileCode(gunState.getPileCode());
         frame.setGunNo(String.format("%02d", Integer.parseInt(gunState.getGunNo())));
 
         // 状态信息
         frame.setStatus(gunState.getStatus());
         frame.setGunReturned(gunState.getGunReturned());
-        frame.setGunConnected(gunState.isGunConnected() ? 
-                ProtocolConstants.GUN_CONNECTED : ProtocolConstants.GUN_NOT_CONNECTED);
+        frame.setGunConnected(gunState.isGunConnected() ?
+            ProtocolConstants.GUN_CONNECTED : ProtocolConstants.GUN_NOT_CONNECTED);
 
         // 输出参数 (保留一位小数)
         frame.setOutputVoltage((int) (gunState.getOutputVoltage() * 10));
@@ -214,4 +271,4 @@ public class MockClientHandler extends SimpleChannelInboundHandler<BaseFrame> {
         log.error("[{}] 连接异常: {}", pileClient.getPileCode(), cause.getMessage());
         ctx.close();
     }
-}
+}

+ 69 - 83
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/mock/simulator/MockPileClient.java

@@ -136,7 +136,8 @@ public class MockPileClient {
         if (eventLoopGroup != null) {
             this.eventLoopGroup = eventLoopGroup;
             this.sharedEventLoopGroup = true;
-        } else {
+        }
+        else {
             this.eventLoopGroup = new NioEventLoopGroup(1);
             this.sharedEventLoopGroup = false;
         }
@@ -150,11 +151,8 @@ public class MockPileClient {
      */
     private void initGunStates() {
         for (GunConfig gunConfig : config.getGuns()) {
-            MockGunState gunState = new MockGunState(
-                    config.getPileCode(),
-                    gunConfig.getGunNo(),
-                    gunConfig.getRatedPower()
-            );
+            MockGunState gunState = new MockGunState(config.getPileCode(), gunConfig.getGunNo(),
+                gunConfig.getRatedPower());
             gunStateMap.put(gunConfig.getGunNo(), gunState);
         }
 
@@ -191,44 +189,41 @@ public class MockPileClient {
             return;
         }
 
-        log.info("[{}] 正在连接服务器 {}:{}", getPileCode(),
-                globalConfig.getServerHost(), globalConfig.getServerPort());
+        log.info("[{}] 正在连接服务器 {}:{}", getPileCode(), globalConfig.getServerHost(),
+            globalConfig.getServerPort());
 
         Bootstrap bootstrap = new Bootstrap();
-        bootstrap.group(eventLoopGroup)
-                .channel(NioSocketChannel.class)
-                .option(ChannelOption.SO_KEEPALIVE, true)
-                .option(ChannelOption.TCP_NODELAY, true)
-                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
-                .handler(new ChannelInitializer<SocketChannel>() {
-                    @Override
-                    protected void initChannel(SocketChannel ch) {
-                        ChannelPipeline pipeline = ch.pipeline();
-
-                        // 空闲检测
-                        pipeline.addLast("idleStateHandler",
-                                new IdleStateHandler(0, 30, 0, TimeUnit.SECONDS));
-
-                        // 解码器
-                        pipeline.addLast("decoder", new MockClientDecoder());
-
-                        // 编码器
-                        pipeline.addLast("encoder", new ChargingPileEncoder());
-
-                        // 业务处理器
-                        pipeline.addLast("handler", new MockClientHandler(MockPileClient.this));
-                    }
-                });
+        bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).option(ChannelOption.SO_KEEPALIVE, true)
+            .option(ChannelOption.TCP_NODELAY, true).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
+            .handler(new ChannelInitializer<SocketChannel>() {
+                @Override
+                protected void initChannel(SocketChannel ch) {
+                    ChannelPipeline pipeline = ch.pipeline();
+
+                    // 空闲检测
+                    pipeline.addLast("idleStateHandler", new IdleStateHandler(0, 30, 0, TimeUnit.SECONDS));
+
+                    // 解码器
+                    pipeline.addLast("decoder", new MockClientDecoder());
+
+                    // 编码器
+                    pipeline.addLast("encoder", new ChargingPileEncoder());
+
+                    // 业务处理器
+                    pipeline.addLast("handler", new MockClientHandler(MockPileClient.this));
+                }
+            });
 
         bootstrap.connect(globalConfig.getServerHost(), globalConfig.getServerPort())
-                .addListener((ChannelFutureListener) future -> {
-                    if (future.isSuccess()) {
-                        log.info("[{}] 连接服务器成功", getPileCode());
-                    } else {
-                        log.warn("[{}] 连接服务器失败: {}", getPileCode(), future.cause().getMessage());
-                        scheduleReconnect();
-                    }
-                });
+            .addListener((ChannelFutureListener) future -> {
+                if (future.isSuccess()) {
+                    log.info("[{}] 连接服务器成功", getPileCode());
+                }
+                else {
+                    log.warn("[{}] 连接服务器失败: {}", getPileCode(), future.cause().getMessage());
+                    scheduleReconnect();
+                }
+            });
     }
 
     /**
@@ -352,10 +347,9 @@ public class MockPileClient {
             RealtimeDataFrame frame = buildRealtimeDataFrame(gunState);
             sendFrame(frame);
 
-            log.debug("[{}] 发送实时数据: gun={}, status={}, power={:.1f}kW, energy={:.4f}kWh",
-                    getPileCode(), gunState.getGunNo(),
-                    gunState.isCharging() ? "充电中" : "空闲",
-                    gunState.getOutputPower(), gunState.getChargingEnergy());
+            log.debug("[{}] 发送实时数据: gun={}, status={}, power={:.1f}kW, energy={:.4f}kWh", getPileCode(),
+                gunState.getGunNo(), gunState.isCharging() ? "充电中" : "空闲", gunState.getOutputPower(),
+                gunState.getChargingEnergy());
         }
     }
 
@@ -368,11 +362,11 @@ public class MockPileClient {
         frame.setTransactionNo(gunState.getTransactionNo());
         frame.setPileCode(gunState.getPileCode());
         frame.setGunNo(String.format("%02d", Integer.parseInt(gunState.getGunNo())));
-        frame.setBmsProtocolVersion(new byte[]{0x01, 0x01, 0x00});
+        frame.setBmsProtocolVersion(new byte[] { 0x01, 0x01, 0x00 });
         frame.setBmsBatteryType(gunState.getBatteryType());
         frame.setBmsRatedCapacity((int) (gunState.getRatedCapacity() * 10));
         frame.setBmsRatedVoltage((int) (gunState.getRatedVoltage() * 10));
-        frame.setBmsManufacturer(new byte[]{0x54, 0x45, 0x53, 0x54}); // TEST
+        frame.setBmsManufacturer(new byte[] { 0x54, 0x45, 0x53, 0x54 }); // TEST
         frame.setBmsBatterySerialNo(new byte[4]);
         frame.setBmsProductionYear(2023 - 1985);
         frame.setBmsProductionMonth(6);
@@ -388,6 +382,17 @@ public class MockPileClient {
     }
 
     /**
+     * 发送充电结束帧 (供外部调用,如远程停止充电)
+     * @param gunState 枪状态
+     */
+    public void sendChargingEndFrame(MockGunState gunState) {
+        if (gunState == null || !gunState.isCharging()) {
+            return;
+        }
+        sendChargingEnd(gunState);
+    }
+
+    /**
      * 发送充电结束
      */
     private void sendChargingEnd(MockGunState gunState) {
@@ -406,9 +411,8 @@ public class MockPileClient {
         frame.setChargerNo(1);
 
         sendFrame(frame);
-        log.info("[{}] 发送充电结束: gun={}, energy={:.2f}kWh, time={}min, soc={}%",
-                getPileCode(), gunState.getGunNo(),
-                gunState.getChargingEnergy(), gunState.getChargingTime(), gunState.getSoc());
+        log.info("[{}] 发送充电结束: gun={}, energy={:.2f}kWh, time={}min, soc={}%", getPileCode(), gunState.getGunNo(),
+            gunState.getChargingEnergy(), gunState.getChargingTime(), gunState.getSoc());
     }
 
     /**
@@ -419,16 +423,16 @@ public class MockPileClient {
         frame.setSequenceNo(nextSequenceNo());
 
         // 基础信息
-        frame.setTransactionNo(gunState.getTransactionNo() != null ?
-                gunState.getTransactionNo() : "00000000000000000000000000000000");
+        frame.setTransactionNo(
+            gunState.getTransactionNo() != null ? gunState.getTransactionNo() : "00000000000000000000000000000000");
         frame.setPileCode(gunState.getPileCode());
         frame.setGunNo(String.format("%02d", Integer.parseInt(gunState.getGunNo())));
 
         // 状态信息
         frame.setStatus(gunState.getStatus());
         frame.setGunReturned(gunState.getGunReturned());
-        frame.setGunConnected(gunState.isGunConnected() ?
-                ProtocolConstants.GUN_CONNECTED : ProtocolConstants.GUN_NOT_CONNECTED);
+        frame.setGunConnected(
+            gunState.isGunConnected() ? ProtocolConstants.GUN_CONNECTED : ProtocolConstants.GUN_NOT_CONNECTED);
 
         // 输出参数 (保留一位小数)
         frame.setOutputVoltage((int) (gunState.getOutputVoltage() * 10));
@@ -464,44 +468,25 @@ public class MockPileClient {
      */
     private void startScheduledTasks() {
         // 心跳任务
-        heartbeatTask = scheduler.scheduleAtFixedRate(
-                this::sendHeartbeat,
-                0,
-                globalConfig.getHeartbeatIntervalSeconds(),
-                TimeUnit.SECONDS
-        );
+        heartbeatTask = scheduler.scheduleAtFixedRate(this::sendHeartbeat, 0,
+            globalConfig.getHeartbeatIntervalSeconds(), TimeUnit.SECONDS);
 
         // 数据上报任务 (使用充电时的上报间隔)
-        reportTask = scheduler.scheduleAtFixedRate(
-                this::sendRealtimeData,
-                1,
-                globalConfig.getChargingReportIntervalSeconds(),
-                TimeUnit.SECONDS
-        );
+        reportTask = scheduler.scheduleAtFixedRate(this::sendRealtimeData, 1,
+            globalConfig.getChargingReportIntervalSeconds(), TimeUnit.SECONDS);
 
         // 充电更新任务 (每秒更新一次充电状态)
-        chargingUpdateTask = scheduler.scheduleAtFixedRate(
-                this::updateChargingStates,
-                1,
-                1,
-                TimeUnit.SECONDS
-        );
+        chargingUpdateTask = scheduler.scheduleAtFixedRate(this::updateChargingStates, 1, 1, TimeUnit.SECONDS);
 
         // 充电模拟任务
         if (globalConfig.isAutoChargingEnabled()) {
-            chargingSimulatorTask = scheduler.scheduleAtFixedRate(
-                    this::simulateChargingCycle,
-                    10, // 10秒后开始第一次
-                    globalConfig.getChargingCycleSeconds(),
-                    TimeUnit.SECONDS
-            );
+            chargingSimulatorTask = scheduler.scheduleAtFixedRate(this::simulateChargingCycle, 10, // 10秒后开始第一次
+                globalConfig.getChargingCycleSeconds(), TimeUnit.SECONDS);
         }
 
-        log.info("[{}] 定时任务已启动: 心跳间隔={}s, 数据上报间隔={}s, 充电周期={}s",
-                getPileCode(),
-                globalConfig.getHeartbeatIntervalSeconds(),
-                globalConfig.getChargingReportIntervalSeconds(),
-                globalConfig.getChargingCycleSeconds());
+        log.info("[{}] 定时任务已启动: 心跳间隔={}s, 数据上报间隔={}s, 充电周期={}s", getPileCode(),
+            globalConfig.getHeartbeatIntervalSeconds(), globalConfig.getChargingReportIntervalSeconds(),
+            globalConfig.getChargingCycleSeconds());
     }
 
     /**
@@ -686,7 +671,8 @@ public class MockPileClient {
         try {
             int gunNoInt = Integer.parseInt(gunNo);
             return gunStateMap.get(String.valueOf(gunNoInt));
-        } catch (NumberFormatException e) {
+        }
+        catch (NumberFormatException e) {
             return null;
         }
     }

+ 234 - 0
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/model/req/ErrorReportFrame.java

@@ -0,0 +1,234 @@
+/*
+ * 文 件 名:  ErrorReportFrame
+ * 版    权:  华设设计集团股份有限公司
+ * 描    述:  错误报文帧
+ * 修 改 人:  lvwenbin
+ * 修改时间:  2025/12/25
+ */
+package com.ruoyi.ems.charging.model.req;
+
+import com.ruoyi.ems.charging.model.BaseFrame;
+import com.ruoyi.ems.charging.protocol.ProtocolConstants;
+import com.ruoyi.ems.charging.utils.ByteUtils;
+import io.netty.buffer.ByteBuf;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 错误报文帧 (0x1B)
+ * GBT-27930 充电桩与BMS充电错误报文
+ * 用于上报充电过程中的通信超时等异常情况
+ *
+ * @author lvwenbin
+ * @version [版本号, 2025/12/25]
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ErrorReportFrame extends BaseFrame {
+
+    /**
+     * 交易流水号 (BCD码 16字节)
+     */
+    private String transactionNo;
+
+    /**
+     * 桩编号 (BCD码 7字节)
+     */
+    private String pileCode;
+
+    /**
+     * 枪号 (BCD码 1字节)
+     */
+    private String gunNo;
+
+    /**
+     * 错误字节1 - BMS侧超时错误
+     * Bit0-1: 接收SPN2560=0x00的充电机辨识报文超时
+     * Bit2-3: 接收SPN2560=0xAA的充电机辨识报文超时
+     * Bit4-7: 预留位
+     */
+    private int errorByte1;
+
+    /**
+     * 错误字节2 - BMS侧超时错误
+     * Bit0-1: 接收充电机的时间同步和充电机最大输出能力报文超时
+     * Bit2-3: 接收充电机完成充电准备报文超时
+     * Bit4-7: 预留位
+     */
+    private int errorByte2;
+
+    /**
+     * 错误字节3 - BMS侧超时错误
+     * Bit0-1: 接收充电机充电状态报文超时
+     * Bit2-3: 接收充电机中止充电报文超时
+     * Bit4-7: 预留位
+     */
+    private int errorByte3;
+
+    /**
+     * 错误字节4 - BMS侧超时错误
+     * Bit0-1: 接收充电机充电统计报文超时
+     * Bit2-7: BMS其他
+     */
+    private int errorByte4;
+
+    /**
+     * 错误字节5 - 充电机侧超时错误
+     * Bit0-1: 接收BMS和车辆的辨识报文超时
+     * Bit2-7: 预留位
+     */
+    private int errorByte5;
+
+    /**
+     * 错误字节6 - 充电机侧超时错误
+     * Bit0-1: 接收电池充电参数报文超时
+     * Bit2-3: 接收BMS完成充电准备报文超时
+     * Bit4-7: 预留位
+     */
+    private int errorByte6;
+
+    /**
+     * 错误字节7 - 充电机侧超时错误
+     * Bit0-1: 接收电池充电总状态报文超时
+     * Bit2-3: 接收电池充电要求报文超时
+     * Bit4-5: 接收BMS中止充电报文超时
+     * Bit6-7: 预留位
+     */
+    private int errorByte7;
+
+    /**
+     * 错误字节8 - 充电机侧超时错误
+     * Bit0-1: 接收BMS充电统计报文超时
+     * Bit2-7: 充电机其他
+     */
+    private int errorByte8;
+
+    @Override
+    public byte getFrameType() {
+        return ProtocolConstants.FRAME_TYPE_ERROR;
+    }
+
+    @Override
+    protected void encodeBody(ByteBuf buf) {
+        ByteUtils.writeBcd(buf, transactionNo, 16);
+        ByteUtils.writeBcd(buf, pileCode, 7);
+        ByteUtils.writeBcd(buf, gunNo, 1);
+        buf.writeByte(errorByte1);
+        buf.writeByte(errorByte2);
+        buf.writeByte(errorByte3);
+        buf.writeByte(errorByte4);
+        buf.writeByte(errorByte5);
+        buf.writeByte(errorByte6);
+        buf.writeByte(errorByte7);
+        buf.writeByte(errorByte8);
+    }
+
+    @Override
+    protected void decodeBody(ByteBuf buf) {
+        this.transactionNo = ByteUtils.readBcd(buf, 16);
+        this.pileCode = ByteUtils.readBcd(buf, 7);
+        this.gunNo = ByteUtils.readBcd(buf, 1);
+        this.errorByte1 = buf.readByte() & 0xFF;
+        this.errorByte2 = buf.readByte() & 0xFF;
+        this.errorByte3 = buf.readByte() & 0xFF;
+        this.errorByte4 = buf.readByte() & 0xFF;
+        this.errorByte5 = buf.readByte() & 0xFF;
+        this.errorByte6 = buf.readByte() & 0xFF;
+        this.errorByte7 = buf.readByte() & 0xFF;
+        this.errorByte8 = buf.readByte() & 0xFF;
+    }
+
+    /**
+     * 获取完整枪号
+     */
+    public String getFullGunNo() {
+        return pileCode + gunNo;
+    }
+
+    /**
+     * 是否有错误
+     */
+    public boolean hasError() {
+        return errorByte1 != 0 || errorByte2 != 0 || errorByte3 != 0 || errorByte4 != 0
+            || errorByte5 != 0 || errorByte6 != 0 || errorByte7 != 0 || errorByte8 != 0;
+    }
+
+    /**
+     * 获取错误描述列表
+     * 对于能源管理平台,简化处理,只需要知道是否有通信异常
+     */
+    public List<String> getErrorDescriptions() {
+        List<String> errors = new ArrayList<>();
+
+        // BMS侧错误
+        if (getBitValue(errorByte1, 0, 2) == 1) {
+            errors.add("BMS:充电机辨识报文超时");
+        }
+        if (getBitValue(errorByte2, 0, 2) == 1) {
+            errors.add("BMS:充电机时间同步报文超时");
+        }
+        if (getBitValue(errorByte2, 2, 2) == 1) {
+            errors.add("BMS:充电机准备报文超时");
+        }
+        if (getBitValue(errorByte3, 0, 2) == 1) {
+            errors.add("BMS:充电机状态报文超时");
+        }
+        if (getBitValue(errorByte3, 2, 2) == 1) {
+            errors.add("BMS:充电机中止报文超时");
+        }
+
+        // 充电机侧错误
+        if (getBitValue(errorByte5, 0, 2) == 1) {
+            errors.add("充电机:BMS辨识报文超时");
+        }
+        if (getBitValue(errorByte6, 0, 2) == 1) {
+            errors.add("充电机:电池参数报文超时");
+        }
+        if (getBitValue(errorByte6, 2, 2) == 1) {
+            errors.add("充电机:BMS准备报文超时");
+        }
+        if (getBitValue(errorByte7, 0, 2) == 1) {
+            errors.add("充电机:电池状态报文超时");
+        }
+        if (getBitValue(errorByte7, 2, 2) == 1) {
+            errors.add("充电机:电池要求报文超时");
+        }
+        if (getBitValue(errorByte7, 4, 2) == 1) {
+            errors.add("充电机:BMS中止报文超时");
+        }
+
+        return errors;
+    }
+
+    /**
+     * 获取错误摘要(简化版,适合能源管理场景)
+     */
+    public String getErrorSummary() {
+        if (!hasError()) {
+            return "无错误";
+        }
+        List<String> errors = getErrorDescriptions();
+        if (errors.isEmpty()) {
+            return "通信异常";
+        }
+        return String.join("; ", errors);
+    }
+
+    /**
+     * 从字节中提取指定位的值
+     */
+    private int getBitValue(int byteValue, int startBit, int bitCount) {
+        int mask = (1 << bitCount) - 1;
+        return (byteValue >> startBit) & mask;
+    }
+
+    @Override
+    public String toString() {
+        return String.format(
+            "ErrorReportFrame[transNo=%s, pile=%s, gun=%s, hasError=%s, summary=%s]",
+            transactionNo, pileCode, gunNo, hasError(), getErrorSummary());
+    }
+}

+ 9 - 9
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/model/req/RealtimeDataFrame.java

@@ -256,22 +256,22 @@ public class RealtimeDataFrame extends BaseFrame {
     /**
      * 获取状态描述
      */
-    public String getStatusDesc() {
+    public String getStatusEnum() {
         switch (status) {
             case ProtocolConstants.GUN_STATUS_OFFLINE:
-                return "离线";
+                return "0"; // 离线
             case ProtocolConstants.GUN_STATUS_FAULT:
-                return "故障";
+                return "1"; // 故障
             case ProtocolConstants.GUN_STATUS_IDLE:
-                return "空闲";
+                return "2"; // 空闲
             case ProtocolConstants.GUN_STATUS_CHARGING:
-                return "充电中";
+                return "3"; // 充电中
             case ProtocolConstants.GUN_STATUS_IDLE_PULSE:
-                return "空闲-脉冲静置";
+                return "4"; // 空闲-脉冲静置
             case ProtocolConstants.GUN_STATUS_CHARGING_PULSE:
-                return "充电中-脉冲检测";
+                return "5"; // 充电中-脉冲检测
             default:
-                return "未知";
+                return "-1"; // 未知
         }
     }
     
@@ -326,7 +326,7 @@ public class RealtimeDataFrame extends BaseFrame {
     public String toString() {
         return String.format(
             "RealtimeDataFrame[transNo=%s, pile=%s, gun=%s, status=%s, V=%.1f, A=%.1f, P=%.2fkW, energy=%.4fkWh, soc=%d%%, time=%dmin]",
-            transactionNo, pileCode, gunNo, getStatusDesc(),
+            transactionNo, pileCode, gunNo, status,
             getOutputVoltageValue(), getOutputCurrentValue(), getOutputPower(),
             getChargingEnergyValue(), soc, chargingTime);
     }

+ 188 - 0
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/model/req/RemoteStopRespFrame.java

@@ -0,0 +1,188 @@
+/*
+ * 文 件 名:  RemoteStopRespFrame
+ * 版    权:  华设设计集团股份有限公司
+ * 描    述:  远程停止充电应答帧 (充电桩上报 0x35)
+ * 修 改 人:  lvwenbin
+ * 修改时间:  2025/01/07
+ */
+package com.ruoyi.ems.charging.model.req;
+
+import com.ruoyi.ems.charging.model.BaseFrame;
+import com.ruoyi.ems.charging.protocol.ProtocolConstants;
+import com.ruoyi.ems.charging.utils.ByteUtils;
+import io.netty.buffer.ByteBuf;
+
+/**
+ * 远程停止充电应答帧 (充电桩上报)
+ * 帧类型: 0x35
+ *
+ * 消息体格式:
+ * - 桩编号: 7字节 BCD码
+ * - 枪号: 1字节 BCD码
+ * - 停止结果: 1字节 (0x00=失败, 0x01=成功)
+ * - 失败原因: 1字节
+ *
+ * @author lvwenbin
+ * @version [版本号, 2025/01/07]
+ */
+public class RemoteStopRespFrame extends BaseFrame {
+
+    /**
+     * 停止结果: 成功
+     */
+    public static final byte RESULT_SUCCESS = 0x01;
+
+    /**
+     * 停止结果: 失败
+     */
+    public static final byte RESULT_FAIL = 0x00;
+
+    /**
+     * 失败原因: 无
+     */
+    public static final byte FAIL_REASON_NONE = 0x00;
+
+    /**
+     * 失败原因: 设备编号不匹配
+     */
+    public static final byte FAIL_REASON_DEVICE_MISMATCH = 0x01;
+
+    /**
+     * 失败原因: 枪未在充电
+     */
+    public static final byte FAIL_REASON_NOT_CHARGING = 0x02;
+
+    /**
+     * 失败原因: 其他
+     */
+    public static final byte FAIL_REASON_OTHER = (byte) 0xFF;
+
+    /**
+     * 桩编号 (7字节BCD码, 14位数字)
+     */
+    private String pileCode;
+
+    /**
+     * 枪号 (1字节BCD码, 2位数字)
+     */
+    private String gunNo;
+
+    /**
+     * 停止结果 (0x00=失败, 0x01=成功)
+     */
+    private byte stopResult;
+
+    /**
+     * 失败原因
+     */
+    private byte failReason;
+
+    @Override
+    public byte getFrameType() {
+        return ProtocolConstants.FRAME_TYPE_REMOTE_STOP_RESP;
+    }
+
+    @Override
+    protected void encodeBody(ByteBuf buf) {
+        // 桩编号 7字节
+        buf.writeBytes(ByteUtils.strToBcd(pileCode, 7));
+        // 枪号 1字节
+        buf.writeBytes(ByteUtils.strToBcd(gunNo, 1));
+        // 停止结果 1字节
+        buf.writeByte(stopResult);
+        // 失败原因 1字节
+        buf.writeByte(failReason);
+    }
+
+    @Override
+    protected void decodeBody(ByteBuf buf) {
+        // 桩编号 7字节
+        byte[] pileCodeBytes = new byte[7];
+        buf.readBytes(pileCodeBytes);
+        this.pileCode = ByteUtils.bcdToStr(pileCodeBytes);
+
+        // 枪号 1字节
+        byte[] gunNoBytes = new byte[1];
+        buf.readBytes(gunNoBytes);
+        this.gunNo = ByteUtils.bcdToStr(gunNoBytes);
+
+        // 停止结果 1字节
+        this.stopResult = buf.readByte();
+
+        // 失败原因 1字节
+        this.failReason = buf.readByte();
+    }
+
+    /**
+     * 是否成功
+     */
+    public boolean isSuccess() {
+        return stopResult == RESULT_SUCCESS;
+    }
+
+    /**
+     * 获取完整枪号 (桩编码 + 枪号)
+     * @return 完整枪号,如 "3212830100010101"
+     */
+    public String getFullGunNo() {
+        return pileCode + gunNo;
+    }
+
+    /**
+     * 获取失败原因描述
+     * @return 失败原因描述文字
+     */
+    public String getFailReasonDesc() {
+        switch (failReason) {
+            case FAIL_REASON_NONE:
+                return "无";
+            case FAIL_REASON_DEVICE_MISMATCH:
+                return "设备编号不匹配";
+            case FAIL_REASON_NOT_CHARGING:
+                return "枪未在充电";
+            case FAIL_REASON_OTHER:
+                return "其他原因";
+            default:
+                return "未知原因(0x" + String.format("%02X", failReason) + ")";
+        }
+    }
+
+    // Getters and Setters
+    public String getPileCode() {
+        return pileCode;
+    }
+
+    public void setPileCode(String pileCode) {
+        this.pileCode = pileCode;
+    }
+
+    public String getGunNo() {
+        return gunNo;
+    }
+
+    public void setGunNo(String gunNo) {
+        this.gunNo = gunNo;
+    }
+
+    public byte getStopResult() {
+        return stopResult;
+    }
+
+    public void setStopResult(byte stopResult) {
+        this.stopResult = stopResult;
+    }
+
+    public byte getFailReason() {
+        return failReason;
+    }
+
+    public void setFailReason(byte failReason) {
+        this.failReason = failReason;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("RemoteStopRespFrame[pileCode=%s, gunNo=%s, result=%s, failReason=0x%02X, seq=%d]",
+            pileCode, gunNo, isSuccess() ? "成功" : "失败", failReason, getSequenceNo());
+    }
+}

+ 526 - 0
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/model/req/TransactionRecordFrame.java

@@ -0,0 +1,526 @@
+/*
+ * 文 件 名:  TransactionRecordFrame
+ * 版    权:  华设设计集团股份有限公司
+ * 描    述:  交易记录帧
+ * 修 改 人:  lvwenbin
+ * 修改时间:  2025/12/25
+ */
+package com.ruoyi.ems.charging.model.req;
+
+import com.ruoyi.ems.charging.model.BaseFrame;
+import com.ruoyi.ems.charging.protocol.ProtocolConstants;
+import com.ruoyi.ems.charging.utils.ByteUtils;
+import io.netty.buffer.ByteBuf;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.time.LocalDateTime;
+
+/**
+ * 交易记录帧 (0x3B)
+ * 充电结束后上送的完整交易记录,包含分时电量统计
+ * 
+ * 【重要】这是能源管理平台最核心的数据帧之一:
+ * - 包含尖/峰/平/谷分时电量,用于能耗分析
+ * - 包含电表起止值,用于电量核对
+ * - 包含充电起止时间,用于负荷分析
+ *
+ * @author lvwenbin
+ * @version [版本号, 2025/12/25]
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class TransactionRecordFrame extends BaseFrame {
+
+    /**
+     * 交易流水号 (BCD码 16字节)
+     */
+    private String transactionNo;
+
+    /**
+     * 桩编号 (BCD码 7字节)
+     */
+    private String pileCode;
+
+    /**
+     * 枪号 (BCD码 1字节)
+     */
+    private String gunNo;
+
+    /**
+     * 开始时间 (CP56Time2a 7字节)
+     */
+    private LocalDateTime startTime;
+
+    /**
+     * 结束时间 (CP56Time2a 7字节)
+     */
+    private LocalDateTime endTime;
+
+    // ==================== 尖时段电量 ====================
+    
+    /**
+     * 尖单价 (BIN 4字节) - 精确到五位小数 (电费+服务费)
+     */
+    private long sharpPrice;
+
+    /**
+     * 尖电量 (BIN 4字节) - 精确到四位小数 (kWh)
+     */
+    private long sharpEnergy;
+
+    /**
+     * 计损尖电量 (BIN 4字节) - 精确到四位小数 (kWh)
+     */
+    private long sharpLossEnergy;
+
+    /**
+     * 尖金额 (BIN 4字节) - 精确到四位小数 (元)
+     */
+    private long sharpAmount;
+
+    // ==================== 峰时段电量 ====================
+    
+    /**
+     * 峰单价 (BIN 4字节) - 精确到五位小数
+     */
+    private long peakPrice;
+
+    /**
+     * 峰电量 (BIN 4字节) - 精确到四位小数 (kWh)
+     */
+    private long peakEnergy;
+
+    /**
+     * 计损峰电量 (BIN 4字节) - 精确到四位小数 (kWh)
+     */
+    private long peakLossEnergy;
+
+    /**
+     * 峰金额 (BIN 4字节) - 精确到四位小数 (元)
+     */
+    private long peakAmount;
+
+    // ==================== 平时段电量 ====================
+    
+    /**
+     * 平单价 (BIN 4字节) - 精确到五位小数
+     */
+    private long flatPrice;
+
+    /**
+     * 平电量 (BIN 4字节) - 精确到四位小数 (kWh)
+     */
+    private long flatEnergy;
+
+    /**
+     * 计损平电量 (BIN 4字节) - 精确到四位小数 (kWh)
+     */
+    private long flatLossEnergy;
+
+    /**
+     * 平金额 (BIN 4字节) - 精确到四位小数 (元)
+     */
+    private long flatAmount;
+
+    // ==================== 谷时段电量 ====================
+    
+    /**
+     * 谷单价 (BIN 4字节) - 精确到五位小数
+     */
+    private long valleyPrice;
+
+    /**
+     * 谷电量 (BIN 4字节) - 精确到四位小数 (kWh)
+     */
+    private long valleyEnergy;
+
+    /**
+     * 计损谷电量 (BIN 4字节) - 精确到四位小数 (kWh)
+     */
+    private long valleyLossEnergy;
+
+    /**
+     * 谷金额 (BIN 4字节) - 精确到四位小数 (元)
+     */
+    private long valleyAmount;
+
+    // ==================== 电表读数 ====================
+    
+    /**
+     * 电表总起值 (BIN 5字节) - 精确到四位小数 (kWh)
+     */
+    private long meterStartValue;
+
+    /**
+     * 电表总止值 (BIN 5字节) - 精确到四位小数 (kWh)
+     */
+    private long meterEndValue;
+
+    // ==================== 汇总数据 ====================
+    
+    /**
+     * 总电量 (BIN 4字节) - 精确到四位小数 (kWh)
+     */
+    private long totalEnergy;
+
+    /**
+     * 计损总电量 (BIN 4字节) - 精确到四位小数 (kWh)
+     */
+    private long totalLossEnergy;
+
+    /**
+     * 消费金额 (BIN 4字节) - 精确到四位小数 (元)
+     * 包含电费+服务费
+     */
+    private long totalAmount;
+
+    /**
+     * 电动汽车唯一标识VIN (ASCII 17字节)
+     */
+    private String vin;
+
+    /**
+     * 交易标识 (BIN 1字节)
+     * 0x01:app启动, 0x02:卡启动, 0x04:离线卡启动, 0x05:vin码启动
+     */
+    private byte transactionType;
+
+    /**
+     * 交易日期时间 (CP56Time2a 7字节)
+     */
+    private LocalDateTime transactionTime;
+
+    /**
+     * 停止原因 (BIN 1字节)
+     */
+    private int stopReason;
+
+    /**
+     * 物理卡号 (BIN 8字节)
+     */
+    private String physicalCardNo;
+
+    /**
+     * 桩厂家停止原因 (BIN 1字节) - 可选字段
+     */
+    private int manufacturerStopReason;
+
+    @Override
+    public byte getFrameType() {
+        return ProtocolConstants.FRAME_TYPE_TRANSACTION;
+    }
+
+    @Override
+    protected void encodeBody(ByteBuf buf) {
+        ByteUtils.writeBcd(buf, transactionNo, 16);
+        ByteUtils.writeBcd(buf, pileCode, 7);
+        ByteUtils.writeBcd(buf, gunNo, 1);
+        ByteUtils.writeCp56Time2a(buf, startTime);
+        ByteUtils.writeCp56Time2a(buf, endTime);
+        
+        // 尖时段
+        buf.writeIntLE((int) sharpPrice);
+        buf.writeIntLE((int) sharpEnergy);
+        buf.writeIntLE((int) sharpLossEnergy);
+        buf.writeIntLE((int) sharpAmount);
+        
+        // 峰时段
+        buf.writeIntLE((int) peakPrice);
+        buf.writeIntLE((int) peakEnergy);
+        buf.writeIntLE((int) peakLossEnergy);
+        buf.writeIntLE((int) peakAmount);
+        
+        // 平时段
+        buf.writeIntLE((int) flatPrice);
+        buf.writeIntLE((int) flatEnergy);
+        buf.writeIntLE((int) flatLossEnergy);
+        buf.writeIntLE((int) flatAmount);
+        
+        // 谷时段
+        buf.writeIntLE((int) valleyPrice);
+        buf.writeIntLE((int) valleyEnergy);
+        buf.writeIntLE((int) valleyLossEnergy);
+        buf.writeIntLE((int) valleyAmount);
+        
+        // 电表读数 (5字节)
+        ByteUtils.writeUnsignedInt5LE(buf, meterStartValue);
+        ByteUtils.writeUnsignedInt5LE(buf, meterEndValue);
+        
+        // 汇总
+        buf.writeIntLE((int) totalEnergy);
+        buf.writeIntLE((int) totalLossEnergy);
+        buf.writeIntLE((int) totalAmount);
+        
+        ByteUtils.writeAscii(buf, vin, 17);
+        buf.writeByte(transactionType);
+        ByteUtils.writeCp56Time2a(buf, transactionTime);
+        buf.writeByte(stopReason);
+        
+        // 物理卡号8字节
+        byte[] cardBytes = ByteUtils.hexToBytes(physicalCardNo != null ? physicalCardNo : "");
+        byte[] paddedCard = new byte[8];
+        if (cardBytes.length > 0) {
+            System.arraycopy(cardBytes, 0, paddedCard, 0, Math.min(cardBytes.length, 8));
+        }
+        buf.writeBytes(paddedCard);
+    }
+
+    @Override
+    protected void decodeBody(ByteBuf buf) {
+        this.transactionNo = ByteUtils.readBcd(buf, 16);
+        this.pileCode = ByteUtils.readBcd(buf, 7);
+        this.gunNo = ByteUtils.readBcd(buf, 1);
+        this.startTime = ByteUtils.readCp56Time2a(buf);
+        this.endTime = ByteUtils.readCp56Time2a(buf);
+        
+        // 尖时段
+        this.sharpPrice = buf.readIntLE() & 0xFFFFFFFFL;
+        this.sharpEnergy = buf.readIntLE() & 0xFFFFFFFFL;
+        this.sharpLossEnergy = buf.readIntLE() & 0xFFFFFFFFL;
+        this.sharpAmount = buf.readIntLE() & 0xFFFFFFFFL;
+        
+        // 峰时段
+        this.peakPrice = buf.readIntLE() & 0xFFFFFFFFL;
+        this.peakEnergy = buf.readIntLE() & 0xFFFFFFFFL;
+        this.peakLossEnergy = buf.readIntLE() & 0xFFFFFFFFL;
+        this.peakAmount = buf.readIntLE() & 0xFFFFFFFFL;
+        
+        // 平时段
+        this.flatPrice = buf.readIntLE() & 0xFFFFFFFFL;
+        this.flatEnergy = buf.readIntLE() & 0xFFFFFFFFL;
+        this.flatLossEnergy = buf.readIntLE() & 0xFFFFFFFFL;
+        this.flatAmount = buf.readIntLE() & 0xFFFFFFFFL;
+        
+        // 谷时段
+        this.valleyPrice = buf.readIntLE() & 0xFFFFFFFFL;
+        this.valleyEnergy = buf.readIntLE() & 0xFFFFFFFFL;
+        this.valleyLossEnergy = buf.readIntLE() & 0xFFFFFFFFL;
+        this.valleyAmount = buf.readIntLE() & 0xFFFFFFFFL;
+        
+        // 电表读数 (5字节)
+        this.meterStartValue = ByteUtils.readUnsignedInt5LE(buf);
+        this.meterEndValue = ByteUtils.readUnsignedInt5LE(buf);
+        
+        // 汇总
+        this.totalEnergy = buf.readIntLE() & 0xFFFFFFFFL;
+        this.totalLossEnergy = buf.readIntLE() & 0xFFFFFFFFL;
+        this.totalAmount = buf.readIntLE() & 0xFFFFFFFFL;
+        
+        this.vin = ByteUtils.readAscii(buf, 17);
+        this.transactionType = buf.readByte();
+        this.transactionTime = ByteUtils.readCp56Time2a(buf);
+        this.stopReason = buf.readByte() & 0xFF;
+        
+        // 物理卡号8字节
+        byte[] cardBytes = new byte[8];
+        buf.readBytes(cardBytes);
+        this.physicalCardNo = ByteUtils.bytesToHex(cardBytes).replace(" ", "");
+        
+        // 可选字段:桩厂家停止原因
+        if (buf.readableBytes() >= 1) {
+            this.manufacturerStopReason = buf.readByte() & 0xFF;
+        }
+    }
+
+    // ==================== 能源管理核心方法 ====================
+
+    /**
+     * 获取完整枪号
+     */
+    public String getFullGunNo() {
+        return pileCode + gunNo;
+    }
+
+    /**
+     * 获取尖电量(kWh)
+     */
+    public double getSharpEnergyValue() {
+        return sharpEnergy / 10000.0;
+    }
+
+    /**
+     * 获取峰电量(kWh)
+     */
+    public double getPeakEnergyValue() {
+        return peakEnergy / 10000.0;
+    }
+
+    /**
+     * 获取平电量(kWh)
+     */
+    public double getFlatEnergyValue() {
+        return flatEnergy / 10000.0;
+    }
+
+    /**
+     * 获取谷电量(kWh)
+     */
+    public double getValleyEnergyValue() {
+        return valleyEnergy / 10000.0;
+    }
+
+    /**
+     * 获取总电量(kWh)
+     */
+    public double getTotalEnergyValue() {
+        return totalEnergy / 10000.0;
+    }
+
+    /**
+     * 获取计损总电量(kWh)
+     */
+    public double getTotalLossEnergyValue() {
+        return totalLossEnergy / 10000.0;
+    }
+
+    /**
+     * 获取电表起值(kWh)
+     */
+    public double getMeterStartValueKwh() {
+        return meterStartValue / 10000.0;
+    }
+
+    /**
+     * 获取电表止值(kWh)
+     */
+    public double getMeterEndValueKwh() {
+        return meterEndValue / 10000.0;
+    }
+
+    /**
+     * 获取电表差值(kWh) - 用于核对电量
+     */
+    public double getMeterDeltaKwh() {
+        return getMeterEndValueKwh() - getMeterStartValueKwh();
+    }
+
+    /**
+     * 获取充电时长(分钟)
+     */
+    public long getChargingDurationMinutes() {
+        if (startTime != null && endTime != null) {
+            return java.time.Duration.between(startTime, endTime).toMinutes();
+        }
+        return 0;
+    }
+
+    /**
+     * 获取交易类型描述
+     */
+    public String getTransactionTypeDesc() {
+        switch (transactionType) {
+            case 0x01: return "APP启动";
+            case 0x02: return "刷卡启动";
+            case 0x04: return "离线卡启动";
+            case 0x05: return "VIN码启动";
+            default: return "未知";
+        }
+    }
+
+    /**
+     * 获取停止原因描述
+     */
+    public String getStopReasonDesc() {
+        return StopReasonCode.getDescription(stopReason);
+    }
+
+    /**
+     * 是否正常结束
+     */
+    public boolean isNormalStop() {
+        // 0x40-0x49 为正常充电完成
+        return stopReason >= 0x40 && stopReason <= 0x49;
+    }
+
+    /**
+     * 是否启动失败
+     */
+    public boolean isStartFailed() {
+        // 0x4A-0x69 为充电启动失败
+        return stopReason >= 0x4A && stopReason <= 0x69;
+    }
+
+    /**
+     * 是否异常中止
+     */
+    public boolean isAbnormalStop() {
+        // 0x6A-0x8F 为充电异常中止
+        return stopReason >= 0x6A && stopReason <= 0x8F;
+    }
+
+    @Override
+    public String toString() {
+        return String.format(
+            "TransactionRecordFrame[transNo=%s, pile=%s, gun=%s, " +
+            "energy=%.4fkWh(尖:%.4f,峰:%.4f,平:%.4f,谷:%.4f), " +
+            "duration=%dmin, stopReason=%s]",
+            transactionNo, pileCode, gunNo,
+            getTotalEnergyValue(), getSharpEnergyValue(), getPeakEnergyValue(),
+            getFlatEnergyValue(), getValleyEnergyValue(),
+            getChargingDurationMinutes(), getStopReasonDesc());
+    }
+
+    /**
+     * 停止原因代码枚举
+     */
+    public static class StopReasonCode {
+        
+        public static String getDescription(int code) {
+            // 正常充电完成 0x40-0x49
+            if (code == 0x40) return "APP远程停止";
+            if (code == 0x41) return "SOC达到100%";
+            if (code == 0x42) return "充电电量满足设定";
+            if (code == 0x43) return "充电金额满足设定";
+            if (code == 0x44) return "充电时间满足设定";
+            if (code == 0x45) return "手动停止充电";
+            if (code >= 0x46 && code <= 0x49) return "其他正常结束";
+            
+            // 充电启动失败 0x4A-0x69
+            if (code == 0x4A) return "启动失败:控制系统故障";
+            if (code == 0x4B) return "启动失败:控制导引断开";
+            if (code == 0x4C) return "启动失败:断路器跳位";
+            if (code == 0x4D) return "启动失败:电表通信中断";
+            if (code == 0x4E) return "启动失败:余额不足";
+            if (code == 0x4F) return "启动失败:充电模块故障";
+            if (code == 0x50) return "启动失败:急停开入";
+            if (code == 0x51) return "启动失败:防雷器异常";
+            if (code == 0x52) return "启动失败:BMS未就绪";
+            if (code == 0x53) return "启动失败:温度异常";
+            if (code == 0x54) return "启动失败:电池反接";
+            if (code == 0x55) return "启动失败:电子锁异常";
+            if (code == 0x56) return "启动失败:合闸失败";
+            if (code == 0x57) return "启动失败:绝缘异常";
+            if (code >= 0x59 && code <= 0x63) return "启动失败:BMS通信超时";
+            if (code == 0x64) return "启动失败:充电机未就绪";
+            if (code >= 0x4A && code <= 0x69) return "启动失败:其他原因";
+            
+            // 充电异常中止 0x6A-0x8F
+            if (code == 0x6A) return "异常中止:系统闭锁";
+            if (code == 0x6B) return "异常中止:导引断开";
+            if (code == 0x6C) return "异常中止:断路器跳位";
+            if (code == 0x6D) return "异常中止:电表通信中断";
+            if (code == 0x6E) return "异常中止:余额不足";
+            if (code == 0x6F) return "异常中止:交流保护动作";
+            if (code == 0x70) return "异常中止:直流保护动作";
+            if (code == 0x71) return "异常中止:充电模块故障";
+            if (code == 0x72) return "异常中止:急停开入";
+            if (code == 0x73) return "异常中止:防雷器异常";
+            if (code == 0x74) return "异常中止:温度异常";
+            if (code == 0x75) return "异常中止:输出异常";
+            if (code == 0x76) return "异常中止:充电无流";
+            if (code == 0x77) return "异常中止:电子锁异常";
+            if (code == 0x79) return "异常中止:总电压异常";
+            if (code == 0x7A) return "异常中止:总电流异常";
+            if (code == 0x7B) return "异常中止:单体电压异常";
+            if (code == 0x7C) return "异常中止:电池组过温";
+            if (code == 0x83) return "异常中止:充电桩断电";
+            if (code >= 0x6A && code <= 0x8F) return "异常中止:其他原因";
+            
+            // 未知原因
+            if (code == 0x90) return "未知原因停止";
+            
+            return "未知停止原因(0x" + Integer.toHexString(code) + ")";
+        }
+    }
+}

+ 109 - 0
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/model/resp/RemoteStopFrame.java

@@ -0,0 +1,109 @@
+/*
+ * 文 件 名:  RemoteStopFrame
+ * 版    权:  华设设计集团股份有限公司
+ * 描    述:  远程停止充电帧 (服务端下发 0x36)
+ * 修 改 人:  lvwenbin
+ * 修改时间:  2025/01/07
+ */
+package com.ruoyi.ems.charging.model.resp;
+
+import com.ruoyi.ems.charging.model.BaseFrame;
+import com.ruoyi.ems.charging.protocol.ProtocolConstants;
+import com.ruoyi.ems.charging.utils.ByteUtils;
+import io.netty.buffer.ByteBuf;
+
+/**
+ * 远程停止充电帧 (服务端下发)
+ * 帧类型: 0x36
+ *
+ * 消息体格式:
+ * - 桩编号: 7字节 BCD码
+ * - 枪号: 1字节 BCD码
+ *
+ * @author lvwenbin
+ * @version [版本号, 2025/01/07]
+ */
+public class RemoteStopFrame extends BaseFrame {
+
+    /**
+     * 桩编号 (7字节BCD码, 14位数字)
+     */
+    private String pileCode;
+
+    /**
+     * 枪号 (1字节BCD码, 2位数字)
+     */
+    private String gunNo;
+
+    @Override
+    public byte getFrameType() {
+        return ProtocolConstants.FRAME_TYPE_REMOTE_STOP;
+    }
+
+    /**
+     * 创建远程停止充电帧
+     * @param pileCode 桩编码 (14位)
+     * @param gunNo 枪号 (2位)
+     * @param sequenceNo 序列号
+     * @return RemoteStopFrame实例
+     */
+    public static RemoteStopFrame create(String pileCode, String gunNo, int sequenceNo) {
+        RemoteStopFrame frame = new RemoteStopFrame();
+        frame.setPileCode(pileCode);
+        frame.setGunNo(gunNo);
+        frame.setSequenceNo(sequenceNo);
+        return frame;
+    }
+
+    /**
+     * 获取完整枪号 (桩编码 + 枪号)
+     * @return 完整枪号,如 "3212830100010101"
+     */
+    public String getFullGunNo() {
+        return pileCode + gunNo;
+    }
+
+    @Override
+    protected void encodeBody(ByteBuf buf) {
+        // 桩编号 7字节
+        buf.writeBytes(ByteUtils.strToBcd(pileCode, 7));
+        // 枪号 1字节
+        buf.writeBytes(ByteUtils.strToBcd(gunNo, 1));
+    }
+
+    @Override
+    protected void decodeBody(ByteBuf buf) {
+        // 桩编号 7字节
+        byte[] pileCodeBytes = new byte[7];
+        buf.readBytes(pileCodeBytes);
+        this.pileCode = ByteUtils.bcdToStr(pileCodeBytes);
+
+        // 枪号 1字节
+        byte[] gunNoBytes = new byte[1];
+        buf.readBytes(gunNoBytes);
+        this.gunNo = ByteUtils.bcdToStr(gunNoBytes);
+    }
+
+    // Getters and Setters
+    public String getPileCode() {
+        return pileCode;
+    }
+
+    public void setPileCode(String pileCode) {
+        this.pileCode = pileCode;
+    }
+
+    public String getGunNo() {
+        return gunNo;
+    }
+
+    public void setGunNo(String gunNo) {
+        this.gunNo = gunNo;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("RemoteStopFrame[pileCode=%s, gunNo=%s, seq=%d]",
+            pileCode, gunNo, getSequenceNo());
+    }
+}

+ 117 - 0
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/model/resp/TransactionConfirmFrame.java

@@ -0,0 +1,117 @@
+/*
+ * 文 件 名:  TransactionConfirmFrame
+ * 版    权:  华设设计集团股份有限公司
+ * 描    述:  交易记录确认帧
+ * 修 改 人:  lvwenbin
+ * 修改时间:  2025/12/25
+ */
+package com.ruoyi.ems.charging.model.resp;
+
+import com.ruoyi.ems.charging.model.BaseFrame;
+import com.ruoyi.ems.charging.protocol.ProtocolConstants;
+import com.ruoyi.ems.charging.utils.ByteUtils;
+import io.netty.buffer.ByteBuf;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 交易记录确认帧 (0x40)
+ * 运营平台接收到结算账单上传后,回复此确认信息
+ * 
+ * 注意:
+ * 1. 若桩未收到回复帧,则5分钟后继续上送一次交易记录
+ * 2. 此帧仅表示平台成功接收到交易记录报文,并不代表交易订单成功结算
+ *
+ * @author lvwenbin
+ * @version [版本号, 2025/12/25]
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class TransactionConfirmFrame extends BaseFrame {
+
+    /**
+     * 交易流水号 (BCD码 16字节)
+     */
+    private String transactionNo;
+
+    /**
+     * 确认结果 (BIN码 1字节)
+     * 0x00:上传成功
+     * 0x01:非法账单
+     */
+    private byte confirmResult;
+
+    /**
+     * 上传成功
+     */
+    public static final byte RESULT_SUCCESS = 0x00;
+
+    /**
+     * 非法账单
+     */
+    public static final byte RESULT_INVALID = 0x01;
+
+    public TransactionConfirmFrame() {
+    }
+
+    public TransactionConfirmFrame(String transactionNo, boolean success) {
+        this.transactionNo = transactionNo;
+        this.confirmResult = success ? RESULT_SUCCESS : RESULT_INVALID;
+    }
+
+    @Override
+    public byte getFrameType() {
+        return ProtocolConstants.FRAME_TYPE_TRANSACTION_CONFIRM;
+    }
+
+    @Override
+    protected void encodeBody(ByteBuf buf) {
+        ByteUtils.writeBcd(buf, transactionNo, 16);
+        buf.writeByte(confirmResult);
+    }
+
+    @Override
+    protected void decodeBody(ByteBuf buf) {
+        this.transactionNo = ByteUtils.readBcd(buf, 16);
+        this.confirmResult = buf.readByte();
+    }
+
+    /**
+     * 是否确认成功
+     */
+    public boolean isSuccess() {
+        return confirmResult == RESULT_SUCCESS;
+    }
+
+    /**
+     * 创建成功确认
+     *
+     * @param transactionNo 交易流水号
+     * @param sequenceNo    序列号
+     * @return 确认帧
+     */
+    public static TransactionConfirmFrame success(String transactionNo, int sequenceNo) {
+        TransactionConfirmFrame frame = new TransactionConfirmFrame(transactionNo, true);
+        frame.setSequenceNo(sequenceNo);
+        return frame;
+    }
+
+    /**
+     * 创建失败确认(非法账单)
+     *
+     * @param transactionNo 交易流水号
+     * @param sequenceNo    序列号
+     * @return 确认帧
+     */
+    public static TransactionConfirmFrame invalid(String transactionNo, int sequenceNo) {
+        TransactionConfirmFrame frame = new TransactionConfirmFrame(transactionNo, false);
+        frame.setSequenceNo(sequenceNo);
+        return frame;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("TransactionConfirmFrame[transNo=%s, result=%s]",
+            transactionNo, isSuccess() ? "成功" : "非法账单");
+    }
+}

+ 66 - 34
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/protocol/FrameParser.java

@@ -3,13 +3,21 @@
  * 版    权:  华设设计集团股份有限公司
  * 描    述:  协议帧解析器
  * 修 改 人:  lvwenbin
- * 修改时间:  2025/12/22
+ * 修改时间:  2025/12/25
+ * 修改内容:  添加错误报文、交易记录、远程停机应答的解析支持
  */
 package com.ruoyi.ems.charging.protocol;
 
 import com.ruoyi.ems.charging.model.BaseFrame;
-import com.ruoyi.ems.charging.model.req.*;
-import com.ruoyi.ems.charging.utils.ByteUtils;
+import com.ruoyi.ems.charging.model.req.ChargingEndFrame;
+import com.ruoyi.ems.charging.model.req.ChargingHandshakeFrame;
+import com.ruoyi.ems.charging.model.req.ErrorReportFrame;
+import com.ruoyi.ems.charging.model.req.HeartbeatReqFrame;
+import com.ruoyi.ems.charging.model.req.LoginReqFrame;
+import com.ruoyi.ems.charging.model.req.RealtimeDataFrame;
+import com.ruoyi.ems.charging.model.req.RemoteStopRespFrame;
+import com.ruoyi.ems.charging.model.req.TransactionRecordFrame;
+import com.ruoyi.ems.charging.model.req.WorkParamSetRespFrame;
 import com.ruoyi.ems.charging.utils.CRC16Utils;
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.Unpooled;
@@ -21,12 +29,12 @@ import org.slf4j.LoggerFactory;
  * 负责解析充电桩上送的各类消息帧
  *
  * @author lvwenbin
- * @version [版本号, 2025/12/22]
+ * @version [版本号, 2025/12/25]
  */
 public class FrameParser {
-    
+
     private static final Logger log = LoggerFactory.getLogger(FrameParser.class);
-    
+
     /**
      * 解析帧数据
      *
@@ -38,7 +46,7 @@ public class FrameParser {
             log.warn("数据长度不足,无法解析");
             return null;
         }
-        
+
         ByteBuf buf = Unpooled.wrappedBuffer(data);
         try {
             return parse(buf);
@@ -46,7 +54,7 @@ public class FrameParser {
             buf.release();
         }
     }
-    
+
     /**
      * 解析帧数据
      *
@@ -58,10 +66,10 @@ public class FrameParser {
             log.warn("数据长度不足,无法解析");
             return null;
         }
-        
+
         // 标记读取位置
         buf.markReaderIndex();
-        
+
         // 读取并验证起始标志
         byte startFlag = buf.readByte();
         if (startFlag != ProtocolConstants.START_FLAG) {
@@ -69,39 +77,39 @@ public class FrameParser {
             buf.resetReaderIndex();
             return null;
         }
-        
+
         // 读取数据长度
         int dataLength = buf.readByte() & 0xFF;
-        
+
         // 检查数据完整性
         if (buf.readableBytes() < dataLength + 2) { // 数据域 + CRC
             log.warn("数据不完整,期望长度: {}, 实际可读: {}", dataLength + 2, buf.readableBytes());
             buf.resetReaderIndex();
             return null;
         }
-        
+
         // 验证CRC
         int crcStartIndex = buf.readerIndex();
         byte[] crcData = new byte[dataLength];
         buf.getBytes(crcStartIndex, crcData);
         int calculatedCrc = CRC16Utils.calculateCRC(crcData);
-        
+
         // 读取接收到的CRC
         int receivedCrc = buf.getShortLE(crcStartIndex + dataLength) & 0xFFFF;
-        
+
         if (calculatedCrc != receivedCrc) {
-            log.warn("CRC校验失败,计算值: 0x{}, 接收值: 0x{}", 
+            log.warn("CRC校验失败,计算值: 0x{}, 接收值: 0x{}",
                 String.format("%04X", calculatedCrc),
                 String.format("%04X", receivedCrc));
             buf.resetReaderIndex();
             return null;
         }
-        
+
         // 读取序列号、加密标志、帧类型
         int sequenceNo = buf.readShortLE() & 0xFFFF;
         byte encryptFlag = buf.readByte();
         byte frameType = buf.readByte();
-        
+
         // 根据帧类型创建对应的帧对象
         BaseFrame frame = createFrame(frameType);
         if (frame == null) {
@@ -109,20 +117,20 @@ public class FrameParser {
             buf.resetReaderIndex();
             return null;
         }
-        
+
         // 重置读取位置到起始标志
         buf.resetReaderIndex();
-        
+
         // 解码完整帧
         if (!frame.decode(buf)) {
             log.warn("帧解码失败: {}", frame.getClass().getSimpleName());
             return null;
         }
-        
+
         log.debug("解析帧成功: {}", frame);
         return frame;
     }
-    
+
     /**
      * 根据帧类型创建对应的帧对象
      *
@@ -131,26 +139,50 @@ public class FrameParser {
      */
     private static BaseFrame createFrame(byte frameType) {
         switch (frameType) {
-            // 充电桩发起的帧(奇数)
+            // ==================== 充电桩发起的帧(奇数) ====================
+            
+            // 登录认证 (0x01)
             case ProtocolConstants.FRAME_TYPE_LOGIN_REQ:
                 return new LoginReqFrame();
+            
+            // 心跳请求 (0x03)
             case ProtocolConstants.FRAME_TYPE_HEARTBEAT_REQ:
                 return new HeartbeatReqFrame();
+            
+            // 实时监测数据 (0x13)
             case ProtocolConstants.FRAME_TYPE_REALTIME_DATA:
                 return new RealtimeDataFrame();
+            
+            // 充电握手 (0x15)
             case ProtocolConstants.FRAME_TYPE_CHARGING_HANDSHAKE:
                 return new ChargingHandshakeFrame();
+            
+            // 充电结束 (0x19)
             case ProtocolConstants.FRAME_TYPE_CHARGING_END:
                 return new ChargingEndFrame();
+            
+            // 错误报文 (0x1B) - 新增
+            case ProtocolConstants.FRAME_TYPE_ERROR:
+                return new ErrorReportFrame();
+            
+            // 远程停机命令回复 (0x35) - 新增
+            case ProtocolConstants.FRAME_TYPE_REMOTE_STOP_RESP:
+                return new RemoteStopRespFrame();
+            
+            // 交易记录 (0x3B) - 新增
+            case ProtocolConstants.FRAME_TYPE_TRANSACTION:
+                return new TransactionRecordFrame();
+            
+            // 工作参数设置应答 (0x51)
             case ProtocolConstants.FRAME_TYPE_WORK_PARAM_SET_RESP:
                 return new WorkParamSetRespFrame();
-            // 可以根据需要继续添加其他帧类型
+            
             default:
                 // 对于未实现的帧类型,使用通用帧处理
                 return new GenericFrame(frameType);
         }
     }
-    
+
     /**
      * 从ByteBuf中查找帧起始位置
      *
@@ -170,7 +202,7 @@ public class FrameParser {
         buf.readerIndex(readerIndex);
         return -1;
     }
-    
+
     /**
      * 获取完整帧长度
      *
@@ -185,34 +217,34 @@ public class FrameParser {
         buf.skipBytes(1); // 跳过起始标志
         int dataLength = buf.readByte() & 0xFF;
         buf.resetReaderIndex();
-        
+
         // 完整帧长度 = 起始标志(1) + 数据长度(1) + 数据域(dataLength) + CRC(2)
         return 1 + 1 + dataLength + 2;
     }
-    
+
     /**
      * 通用帧 - 用于处理未实现的帧类型
      */
     private static class GenericFrame extends BaseFrame {
         private byte type;
         private byte[] bodyData;
-        
+
         public GenericFrame(byte type) {
             this.type = type;
         }
-        
+
         @Override
         public byte getFrameType() {
             return type;
         }
-        
+
         @Override
         protected void encodeBody(ByteBuf buf) {
             if (bodyData != null) {
                 buf.writeBytes(bodyData);
             }
         }
-        
+
         @Override
         protected void decodeBody(ByteBuf buf) {
             if (buf.readableBytes() > 0) {
@@ -220,10 +252,10 @@ public class FrameParser {
                 buf.readBytes(bodyData);
             }
         }
-        
+
         @Override
         public String toString() {
-            return String.format("GenericFrame[type=0x%02X, bodyLen=%d]", 
+            return String.format("GenericFrame[type=0x%02X, bodyLen=%d]",
                 type & 0xFF, bodyData != null ? bodyData.length : 0);
         }
     }

+ 526 - 40
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/service/ChargingDataService.java

@@ -3,32 +3,64 @@
  * 版    权:  华设设计集团股份有限公司
  * 描    述:  充电数据服务
  * 修 改 人:  lvwenbin
- * 修改时间:  2025/12/23
- * 修改内容:  修复日志格式化问题和内存泄漏
+ * 修改时间:  2025/12/25
+ * 修改内容:
+ *   1. 添加物模型属性同步功能,复用已有服务接口
+ *   2. 新增登录时子设备(充电枪)在线状态同步
+ *   3. 新增断开连接时主机和子设备离线状态处理
  */
 package com.ruoyi.ems.charging.service;
 
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
+import com.huashe.common.exception.Assert;
+import com.huashe.common.exception.BusinessException;
 import com.ruoyi.ems.charging.model.ChargingSession;
 import com.ruoyi.ems.charging.model.RealtimeDataCache;
-import com.ruoyi.ems.charging.model.req.*;
+import com.ruoyi.ems.charging.model.req.ChargingEndFrame;
+import com.ruoyi.ems.charging.model.req.ChargingHandshakeFrame;
+import com.ruoyi.ems.charging.model.req.ErrorReportFrame;
+import com.ruoyi.ems.charging.model.req.LoginReqFrame;
+import com.ruoyi.ems.charging.model.req.RealtimeDataFrame;
+import com.ruoyi.ems.charging.model.req.RemoteStopRespFrame;
+import com.ruoyi.ems.charging.model.req.TransactionRecordFrame;
+import com.ruoyi.ems.charging.model.req.WorkParamSetRespFrame;
+import com.ruoyi.ems.domain.EmsDevice;
+import com.ruoyi.ems.domain.EmsObjAttrValue;
+import com.ruoyi.ems.enums.DevOnlineStatus;
+import com.ruoyi.ems.service.IEmsDeviceService;
+import com.ruoyi.ems.service.IEmsObjAttrValueService;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.time.LocalDateTime;
 import java.time.temporal.ChronoUnit;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 /**
  * 充电数据服务
  * 处理充电桩上报的各类数据,转化为能耗数据存储
+ * 【能源管理平台核心关注点】
+ * 1. 桩的在线状态
+ * 2. 充电枪的工作状态
+ * 3. 实时功率和电量
+ * 4. 分时电量统计(尖/峰/平/谷)
  *
  * @author lvwenbin
- * @version [版本号, 2025/12/23]
+ * @version [版本号, 2025/12/25]
  */
 @Service
 public class ChargingDataService {
@@ -36,6 +68,31 @@ public class ChargingDataService {
     private static final Logger log = LoggerFactory.getLogger(ChargingDataService.class);
 
     /**
+     * 充电主机模型代码
+     */
+    private static final String MODEL_CODE_HOST = "M_W2_DEV_CHARGING_HOST";
+
+    /**
+     * 充电枪模型代码
+     */
+    private static final String MODEL_CODE_PILE = "M_W2_DEV_CHARGING_PILE";
+
+    /**
+     * 充电系统模型代码
+     */
+    private static final String MODEL_CODE_SYS = "M_W2_SYS_CHARGING";
+
+    /**
+     * 子设备属性键
+     */
+    private static final String ATTR_KEY_SUB_DEV = "subDev";
+
+    /**
+     * 桩编码属性键
+     */
+    private static final String ATTR_KEY_PILE_CODE = "pileCode";
+
+    /**
      * 缓存过期时间(小时) - 超过此时间未更新的缓存将被清理
      */
     private static final int CACHE_EXPIRE_HOURS = 24;
@@ -51,14 +108,190 @@ public class ChargingDataService {
     private final Map<String, ChargingSession> chargingSessionCache = new ConcurrentHashMap<>();
 
     /**
+     * 远程停机请求回调 - 枪号 -> 回调时间(用于超时检测)
+     */
+    private final Map<String, LocalDateTime> pendingStopRequests = new ConcurrentHashMap<>();
+
+    /**
+     * 桩编码 -> 设备编码 缓存
+     */
+    private final Map<String, String> pileCodeToDeviceCodeCache = new ConcurrentHashMap<>();
+
+    /**
+     * 桩编码+枪号 -> 设备编码 缓存
+     */
+    private final Map<String, String> gunNoToDeviceCodeCache = new ConcurrentHashMap<>();
+
+    @Autowired
+    private IEmsObjAttrValueService objAttrValueService;
+
+    @Autowired
+    protected IEmsDeviceService deviceService;
+
+    // ==================== 消息处理方法 ====================
+
+    /**
      * 处理充电桩登录
+     * 根据桩编码更新充电主机的物模型属性
+     * 【新增】同时更新子设备(充电枪)的在线状态
      */
     @Async("msgExecExecutor")
+    @Transactional(rollbackFor = Exception.class)
     public void onPileLogin(LoginReqFrame req, boolean valid) {
-        if (valid) {
-            log.info("充电桩登录成功 - 桩号: {}, 类型: {}, 枪数: {}, 协议版本: {}, 软件版本: {}",
-                req.getPileCode(), req.getPileTypeDesc(), req.getGunCount(),
-                req.getProtocolVersionDesc(), req.getSoftwareVersion());
+        if (!valid) {
+            log.warn("充电桩登录验证失败,不进行属性更新 - 桩号: {}", req.getPileCode());
+            return;
+        }
+
+        log.info("充电桩登录成功,开始同步属性 - 桩号: {}, 类型: {}, 枪数: {}, 协议版本: {}, 软件版本: {}",
+            req.getPileCode(), req.getPileTypeDesc(), req.getGunCount(), req.getProtocolVersionDesc(),
+            req.getSoftwareVersion());
+
+        try {
+            // 根据桩编码查找设备编码
+            String deviceCode = findHostDeviceCodeByPileCode(req.getPileCode());
+            Assert.notEmpty(deviceCode, -1, String.format("未找到桩编码 %s 对应的充电主机设备", req.getPileCode()));
+
+            log.info("找到充电主机设备: {} -> {}", req.getPileCode(), deviceCode);
+
+            // 1. 更新充电主机在线状态
+            EmsDevice emsDevice = deviceService.selectByCode(deviceCode);
+            Assert.notNull(emsDevice, -1, String.format("未找到设备编码 %s 对应的充电主机设备", deviceCode));
+
+            if (emsDevice.getDeviceStatus() == null ||
+                emsDevice.getDeviceStatus() == DevOnlineStatus.OFFLINE.getCode()) {
+                emsDevice.setDeviceStatus(DevOnlineStatus.ONLINE.getCode());
+                deviceService.updateEmsDevice(emsDevice);
+                log.info("充电主机 {} 状态更新为在线", deviceCode);
+            }
+
+            // 2. 更新子设备(充电枪)在线状态
+            updateSubDevicesOnlineStatus(deviceCode, DevOnlineStatus.ONLINE);
+
+            // 3. 获取现有属性,构建属性Map
+            List<EmsObjAttrValue> existingAttrs = objAttrValueService.selectByObjCode(MODEL_CODE_HOST, deviceCode);
+            Map<String, EmsObjAttrValue> attrMap = existingAttrs.stream()
+                .collect(Collectors.toMap(EmsObjAttrValue::getAttrKey, Function.identity()));
+
+            // 4. 更新属性值
+            checkAndUpdate(attrMap, deviceCode, MODEL_CODE_HOST, "gunCount", String.valueOf(req.getGunCount()));
+            checkAndUpdate(attrMap, deviceCode, MODEL_CODE_HOST, "protocolVersion", req.getProtocolVersionDesc());
+            checkAndUpdate(attrMap, deviceCode, MODEL_CODE_HOST, "softwareVersion", req.getSoftwareVersion());
+            checkAndUpdate(attrMap, deviceCode, MODEL_CODE_HOST, "networkType", req.getNetworkTypeDesc());
+            checkAndUpdate(attrMap, deviceCode, MODEL_CODE_HOST, "simCard", req.getSimCard());
+            checkAndUpdate(attrMap, deviceCode, MODEL_CODE_HOST, "carrier", req.getCarrierDesc());
+
+            log.info("充电主机 {} 属性同步完成", deviceCode);
+        }
+        catch (BusinessException e) {
+            log.warn(e.getMessage());
+        }
+        catch (Exception e) {
+            log.error("充电桩登录属性同步异常 - 桩号: {}", req.getPileCode(), e);
+        }
+    }
+
+    /**
+     * 【新增】处理充电桩断开连接
+     * 将充电主机和其下所有充电枪标记为离线
+     *
+     * @param pileCode 桩编码
+     */
+    @Async("msgExecExecutor")
+    @Transactional(rollbackFor = Exception.class)
+    public void onPileDisconnect(String pileCode) {
+        if (StringUtils.isBlank(pileCode)) {
+            log.warn("充电桩断开连接,但桩编码为空");
+            return;
+        }
+
+        log.info("充电桩断开连接,开始更新设备状态 - 桩号: {}", pileCode);
+
+        try {
+            // 1. 根据桩编码查找设备编码
+            String deviceCode = findHostDeviceCodeByPileCode(pileCode);
+            if (StringUtils.isBlank(deviceCode)) {
+                log.warn("充电桩断开连接,但未找到对应的设备编码 - 桩号: {}", pileCode);
+                return;
+            }
+
+            // 2. 更新充电主机离线状态
+            EmsDevice hostDevice = deviceService.selectByCode(deviceCode);
+            if (hostDevice != null) {
+                hostDevice.setDeviceStatus(DevOnlineStatus.OFFLINE.getCode());
+                deviceService.updateEmsDevice(hostDevice);
+                log.info("充电主机 {} 状态更新为离线", deviceCode);
+            }
+
+            // 3. 更新子设备(充电枪)离线状态
+            updateSubDevicesOnlineStatus(deviceCode, DevOnlineStatus.OFFLINE);
+
+            // 4. 清理桩下线缓存
+            onPileOffline(pileCode);
+
+            log.info("充电桩 {} 断开连接处理完成", pileCode);
+        }
+        catch (Exception e) {
+            log.error("充电桩断开连接处理异常 - 桩号: {}", pileCode, e);
+        }
+    }
+
+    /**
+     * 【新增】更新子设备(充电枪)的在线状态
+     *
+     * @param hostDeviceCode 充电主机设备编码
+     * @param status         目标状态
+     */
+    private void updateSubDevicesOnlineStatus(String hostDeviceCode, DevOnlineStatus status) {
+        // 查询充电主机的subDev属性值
+        EmsObjAttrValue subDevAttr = objAttrValueService.selectObjAttrValue(
+            MODEL_CODE_HOST, hostDeviceCode, ATTR_KEY_SUB_DEV);
+
+        if (subDevAttr == null || StringUtils.isBlank(subDevAttr.getAttrValue())) {
+            log.warn("充电主机 {} 未配置子设备列表(subDev)", hostDeviceCode);
+            return;
+        }
+
+        try {
+            // 解析subDev JSON数组
+            // 格式: [{"deviceCode":"W2-B-CHARGING-PILE-0101","modelCode":"M_W2_DEV_CHARGING_PILE"},...]
+            JSONArray subDevArray = JSON.parseArray(subDevAttr.getAttrValue());
+            int updateCount = 0;
+
+            for (int i = 0; i < subDevArray.size(); i++) {
+                JSONObject subDevObj = subDevArray.getJSONObject(i);
+                String subDeviceCode = subDevObj.getString("deviceCode");
+                String subModelCode = subDevObj.getString("modelCode");
+
+                if (StringUtils.isBlank(subDeviceCode)) {
+                    continue;
+                }
+
+                // 只处理充电枪类型的子设备
+                if (!MODEL_CODE_PILE.equals(subModelCode)) {
+                    continue;
+                }
+
+                EmsDevice pileDevice = deviceService.selectByCode(subDeviceCode);
+                if (pileDevice != null) {
+                    // 仅在状态不同时才更新,避免不必要的数据库操作
+                    if (pileDevice.getDeviceStatus() == null ||
+                        pileDevice.getDeviceStatus() != status.getCode()) {
+                        pileDevice.setDeviceStatus(status.getCode());
+                        deviceService.updateEmsDevice(pileDevice);
+                        updateCount++;
+                        log.debug("充电枪 {} 状态更新为 {}", subDeviceCode, status.name());
+                    }
+                } else {
+                    log.warn("未找到充电枪设备: {}", subDeviceCode);
+                }
+            }
+
+            log.info("充电主机 {} 的 {} 个子设备状态已更新为 {}",
+                hostDeviceCode, updateCount, status.name());
+
+        } catch (Exception e) {
+            log.error("解析充电主机 {} 的子设备列表失败: {}", hostDeviceCode, e.getMessage(), e);
         }
     }
 
@@ -69,18 +302,35 @@ public class ChargingDataService {
     public void onGunFault(String pileCode, String gunNo) {
         String fullGunNo = pileCode + gunNo;
         log.warn("充电枪故障告警 - 枪号: {}", fullGunNo);
+
+        try {
+            // 查找枪设备编码
+            String deviceCode = findGunDeviceCode(pileCode, gunNo);
+            if (StringUtils.isBlank(deviceCode)) {
+                log.warn("未找到充电枪设备 - 桩号: {}, 枪号: {}", pileCode, gunNo);
+                return;
+            }
+
+            // 更新故障状态
+            objAttrValueService.updateObjAttrValue(MODEL_CODE_PILE, deviceCode, "status", "1");
+
+        }
+        catch (Exception e) {
+            log.error("充电枪故障状态更新异常 - 枪号: {}", fullGunNo, e);
+        }
     }
 
     /**
      * 处理实时监测数据 - 能耗数据核心处理
+     * 同步更新充电枪的物模型属性
      */
     @Async("msgExecExecutor")
     public void processRealtimeData(RealtimeDataFrame data) {
         String fullGunNo = data.getFullGunNo();
         LocalDateTime now = LocalDateTime.now();
 
+        // 更新内存缓存
         RealtimeDataCache cache = realtimeDataCache.computeIfAbsent(fullGunNo, k -> new RealtimeDataCache());
-
         cache.setLastData(data);
         cache.setUpdateTime(now);
 
@@ -88,15 +338,159 @@ public class ChargingDataService {
             processChargingData(data, cache, now);
         }
 
+        // 状态变化日志
         if (cache.getLastStatus() != data.getStatus()) {
-            log.info("充电枪状态变化 - 枪号: {}, 状态: {} -> {}",
-                fullGunNo, getStatusDesc(cache.getLastStatus()), data.getStatusDesc());
+            log.info("充电枪状态变化 - 枪号: {}, 状态: {} -> {}", fullGunNo, getStatusDesc(cache.getLastStatus()),
+                getStatusDesc(data.getStatus()));
             cache.setLastStatus(data.getStatus());
         }
 
+        // 同步物模型属性
+        syncGunRealtimeAttr(data);
+
+        // 保存实时数据到数据库
         saveRealtimeData(data);
     }
 
+    /**
+     * 同步充电枪实时数据到物模型属性表
+     */
+    private void syncGunRealtimeAttr(RealtimeDataFrame data) {
+        try {
+            // 根据桩编码和枪号查找设备编码
+            String deviceCode = findGunDeviceCode(data.getPileCode(), data.getGunNo());
+            if (StringUtils.isBlank(deviceCode)) {
+                log.debug("未找到充电枪设备 - 桩号: {}, 枪号: {}", data.getPileCode(), data.getGunNo());
+                return;
+            }
+
+            // 获取现有属性,构建属性Map
+            List<EmsObjAttrValue> existingAttrs = objAttrValueService.selectByObjCode(MODEL_CODE_PILE, deviceCode);
+            Map<String, EmsObjAttrValue> attrMap = existingAttrs.stream()
+                .collect(Collectors.toMap(EmsObjAttrValue::getAttrKey, Function.identity(), (k1, k2) -> k1));
+
+            // 更新State分组属性
+            checkAndUpdate(attrMap, deviceCode, MODEL_CODE_PILE, "transactionNo", data.getTransactionNo());
+            checkAndUpdate(attrMap, deviceCode, MODEL_CODE_PILE, "status", data.getStatusEnum());
+            checkAndUpdate(attrMap, deviceCode, MODEL_CODE_PILE, "gunReturned", String.valueOf(data.getGunReturned()));
+            checkAndUpdate(attrMap, deviceCode, MODEL_CODE_PILE, "gunConnected",
+                String.valueOf(data.getGunConnected()));
+
+            // 更新Measure分组属性
+            checkAndUpdate(attrMap, deviceCode, MODEL_CODE_PILE, "outputVoltage",
+                String.format("%.1f", data.getOutputVoltageValue()));
+            checkAndUpdate(attrMap, deviceCode, MODEL_CODE_PILE, "outputCurrent",
+                String.format("%.1f", data.getOutputCurrentValue()));
+            checkAndUpdate(attrMap, deviceCode, MODEL_CODE_PILE, "gunTemperature",
+                String.valueOf(data.getGunTemperatureValue()));
+            checkAndUpdate(attrMap, deviceCode, MODEL_CODE_PILE, "chargingTime",
+                String.valueOf(data.getChargingTime()));
+            checkAndUpdate(attrMap, deviceCode, MODEL_CODE_PILE, "remainingTime",
+                String.valueOf(data.getRemainingTime()));
+            checkAndUpdate(attrMap, deviceCode, MODEL_CODE_PILE, "chargingEnergy",
+                String.format("%.4f", data.getChargingEnergyValue()));
+            checkAndUpdate(attrMap, deviceCode, MODEL_CODE_PILE, "lossCorrectedEnergy",
+                String.format("%.4f", data.getLossCorrectedEnergyValue()));
+            checkAndUpdate(attrMap, deviceCode, MODEL_CODE_PILE, "chargedAmount",
+                String.format("%.4f", data.getChargedAmountValue()));
+            checkAndUpdate(attrMap, deviceCode, MODEL_CODE_PILE, "hardwareFault", data.getHardwareFaultDesc());
+
+            log.debug("充电枪 {} 实时属性同步完成", deviceCode);
+
+        }
+        catch (Exception e) {
+            log.error("同步充电枪实时属性异常 - 枪号: {}", data.getFullGunNo(), e);
+        }
+    }
+
+    // ==================== 设备查找方法 ====================
+
+    /**
+     * 根据桩编码查找充电主机设备编码
+     * 查询 model_code=M_W2_DEV_CHARGING_HOST, attr_key=pileCode, attr_value=桩编码
+     */
+    private String findHostDeviceCodeByPileCode(String pileCode) {
+        // 先查缓存
+        String cachedDeviceCode = pileCodeToDeviceCodeCache.get(pileCode);
+        if (StringUtils.isNotBlank(cachedDeviceCode)) {
+            return cachedDeviceCode;
+        }
+
+        // 使用现有接口查询:selectByAttrKeyValue
+        List<EmsObjAttrValue> attrValues = objAttrValueService.selectByAttrKeyValue(MODEL_CODE_HOST, ATTR_KEY_PILE_CODE,
+            pileCode);
+
+        if (CollectionUtils.isNotEmpty(attrValues)) {
+            String deviceCode = attrValues.get(0).getObjCode();
+            // 放入缓存
+            pileCodeToDeviceCodeCache.put(pileCode, deviceCode);
+            return deviceCode;
+        }
+
+        return null;
+    }
+
+    /**
+     * 根据桩编码和枪号查找充电枪设备编码
+     * 通过 pileCode + gunNo 组合查询
+     */
+    private String findGunDeviceCode(String pileCode, String gunNo) {
+        String cacheKey = pileCode + "_" + gunNo;
+
+        // 先查缓存
+        String cachedDeviceCode = gunNoToDeviceCodeCache.get(cacheKey);
+        if (StringUtils.isNotBlank(cachedDeviceCode)) {
+            return cachedDeviceCode;
+        }
+
+        // 查询所有 model_code=M_W2_DEV_CHARGING_PILE, attr_key=pileCode, attr_value=pileCode 的记录
+        List<EmsObjAttrValue> pileCodeAttrs = objAttrValueService.selectByAttrKeyValue(MODEL_CODE_PILE, ATTR_KEY_PILE_CODE,
+            pileCode);
+
+        for (EmsObjAttrValue pileCodeAttr : pileCodeAttrs) {
+            String objCode = pileCodeAttr.getObjCode();
+            // 验证枪号是否匹配
+            EmsObjAttrValue gunNoAttr = objAttrValueService.selectObjAttrValue(MODEL_CODE_PILE, objCode, "gunNo");
+            if (gunNoAttr != null && gunNo.equals(gunNoAttr.getAttrValue())) {
+                // 放入缓存
+                gunNoToDeviceCodeCache.put(cacheKey, objCode);
+                return objCode;
+            }
+        }
+
+        log.debug("未找到充电枪设备 - 桩号: {}, 枪号: {}", pileCode, gunNo);
+        return null;
+    }
+
+    // ==================== 属性更新方法(复用BaseDevHandler逻辑) ====================
+
+    /**
+     * 检查并更新属性值
+     * 复用 BaseDevHandler.checkAndUpdate 的逻辑
+     */
+    private void checkAndUpdate(Map<String, EmsObjAttrValue> objAttrValueMap, String objCode, String modelCode,
+        String key, String newValue) {
+        if (StringUtils.isBlank(newValue)) {
+            return;
+        }
+
+        EmsObjAttrValue objAttrValue = objAttrValueMap.get(key);
+
+        if (null != objAttrValue) {
+            // 值有变化才更新
+            if (!StringUtils.equals(objAttrValue.getAttrValue(), newValue)) {
+                objAttrValueService.updateObjAttrValue(modelCode, objCode, key, newValue);
+            }
+        }
+        else {
+            // 新增属性值
+            objAttrValue = new EmsObjAttrValue(objCode, modelCode, key, newValue);
+            objAttrValueService.mergeObjAttrValue(objAttrValue);
+        }
+    }
+
+    // ==================== 充电会话处理 ====================
+
     private void processChargingData(RealtimeDataFrame data, RealtimeDataCache cache, LocalDateTime now) {
         String transactionNo = data.getTransactionNo();
 
@@ -125,15 +519,13 @@ public class ChargingDataService {
     }
 
     private void saveRealtimeData(RealtimeDataFrame data) {
-        // 修复: SLF4J使用{}占位符,需要预先格式化浮点数
         if (log.isDebugEnabled()) {
-            log.debug("保存实时数据 - 枪号: {}, 状态: {}, 功率: {}kW, 电量: {}kWh",
-                data.getFullGunNo(), data.getStatusDesc(),
-                String.format("%.2f", data.getOutputPower()),
+            log.debug("保存实时数据 - 枪号: {}, 状态: {}, 功率: {}kW, 电量: {}kWh", data.getFullGunNo(),
+                getStatusDesc(data.getStatus()), String.format("%.2f", data.getOutputPower()),
                 String.format("%.4f", data.getChargingEnergyValue()));
         }
 
-        // TODO: 实际保存到数据库的逻辑
+        // TODO: 实际保存到数据库的逻辑(如需保存历史记录)
     }
 
     /**
@@ -141,10 +533,8 @@ public class ChargingDataService {
      */
     @Async("msgExecExecutor")
     public void onChargingHandshake(ChargingHandshakeFrame handshake) {
-        // 修复: 使用String.format格式化浮点数
-        log.info("充电握手 - 流水号: {}, 枪号: {}, VIN: {}, 电池类型: {}, 容量: {}Ah",
-            handshake.getTransactionNo(), handshake.getFullGunNo(),
-            handshake.getVin(), handshake.getBatteryTypeDesc(),
+        log.info("充电握手 - 流水号: {}, 枪号: {}, VIN: {}, 电池类型: {}, 容量: {}Ah", handshake.getTransactionNo(),
+            handshake.getFullGunNo(), handshake.getVin(), handshake.getBatteryTypeDesc(),
             String.format("%.1f", handshake.getRatedCapacityValue()));
 
         ChargingSession session = chargingSessionCache.get(handshake.getTransactionNo());
@@ -162,26 +552,100 @@ public class ChargingDataService {
     public void onChargingEnd(ChargingEndFrame end) {
         String transactionNo = end.getTransactionNo();
 
-        // 修复: 使用String.format格式化浮点数
-        log.info("充电结束 - 流水号: {}, 枪号: {}, SOC: {}%, 能量: {}kWh, 时长: {}min",
-            transactionNo, end.getFullGunNo(), end.getBmsFinalSoc(),
-            String.format("%.1f", end.getOutputEnergyValue()),
+        log.info("充电结束 - 流水号: {}, 枪号: {}, SOC: {}%, 能量: {}kWh, 时长: {}min", transactionNo,
+            end.getFullGunNo(), end.getBmsFinalSoc(), String.format("%.1f", end.getOutputEnergyValue()),
             end.getTotalChargingTime());
 
-        ChargingSession session = chargingSessionCache.remove(transactionNo);
+        ChargingSession session = chargingSessionCache.get(transactionNo);
         if (session != null) {
             session.setEndTime(LocalDateTime.now());
             session.setFinalSoc(end.getBmsFinalSoc());
             session.setTotalEnergy(end.getOutputEnergyValue());
             session.setChargingTime(end.getTotalChargingTime());
-
-            // TODO: 保存充电会话记录到数据库
         }
 
         RealtimeDataCache cache = realtimeDataCache.get(end.getFullGunNo());
         if (cache != null) {
             cache.setLastEnergy(0);
         }
+
+        // 更新枪状态为空闲
+        try {
+            String deviceCode = findGunDeviceCode(end.getPileCode(), end.getGunNo());
+            if (StringUtils.isNotBlank(deviceCode)) {
+                objAttrValueService.updateObjAttrValue(MODEL_CODE_PILE, deviceCode, "status", "2");
+            }
+        }
+        catch (Exception e) {
+            log.error("更新充电结束状态异常", e);
+        }
+    }
+
+    /**
+     * 处理错误报文
+     */
+    @Async("msgExecExecutor")
+    public void onErrorReport(ErrorReportFrame error) {
+        if (error.hasError()) {
+            log.warn("充电错误报文 - 流水号: {}, 枪号: {}, 错误: {}", error.getTransactionNo(), error.getFullGunNo(),
+                error.getErrorSummary());
+        }
+    }
+
+    /**
+     * 处理远程停机应答
+     */
+    @Async("msgExecExecutor")
+    public void onRemoteStopResp(RemoteStopRespFrame resp) {
+        String fullGunNo = resp.getFullGunNo();
+        pendingStopRequests.remove(fullGunNo);
+
+        if (resp.isSuccess()) {
+            log.info("远程停机成功 - 枪号: {}", fullGunNo);
+        }
+        else {
+            log.warn("远程停机失败 - 枪号: {}, 原因: {}", fullGunNo, resp.getFailReasonDesc());
+        }
+    }
+
+    /**
+     * 处理交易记录
+     */
+    @Async("msgExecExecutor")
+    public void onTransactionRecord(TransactionRecordFrame record) {
+        String transactionNo = record.getTransactionNo();
+        String fullGunNo = record.getFullGunNo();
+
+        log.info("处理交易记录 - 流水号: {}, 枪号: {}", transactionNo, fullGunNo);
+
+        log.info("分时电量统计 - 尖: {}kWh, 峰: {}kWh, 平: {}kWh, 谷: {}kWh, 总计: {}kWh",
+            String.format("%.4f", record.getSharpEnergyValue()), String.format("%.4f", record.getPeakEnergyValue()),
+            String.format("%.4f", record.getFlatEnergyValue()), String.format("%.4f", record.getValleyEnergyValue()),
+            String.format("%.4f", record.getTotalEnergyValue()));
+
+        log.info("电表读数 - 起值: {}kWh, 止值: {}kWh, 差值: {}kWh",
+            String.format("%.4f", record.getMeterStartValueKwh()), String.format("%.4f", record.getMeterEndValueKwh()),
+            String.format("%.4f", record.getMeterDeltaKwh()));
+
+        log.info("充电时段 - 开始: {}, 结束: {}, 时长: {}分钟", record.getStartTime(), record.getEndTime(),
+            record.getChargingDurationMinutes());
+
+        if (!record.isNormalStop()) {
+            log.warn("充电非正常结束 - 流水号: {}, 原因: {}", transactionNo, record.getStopReasonDesc());
+        }
+
+        saveTransactionRecord(record);
+
+        ChargingSession session = chargingSessionCache.remove(transactionNo);
+        if (session != null) {
+            log.debug("清理充电会话缓存 - 流水号: {}", transactionNo);
+        }
+    }
+
+    private void saveTransactionRecord(TransactionRecordFrame record) {
+        log.info("保存交易记录到数据库 - 流水号: {}, 总电量: {}kWh", record.getTransactionNo(),
+            String.format("%.4f", record.getTotalEnergyValue()));
+        // TODO: 实际保存到数据库的逻辑
     }
 
     /**
@@ -193,16 +657,23 @@ public class ChargingDataService {
     }
 
     /**
-     * 定期清理过期缓存 - 每小时执行一次
-     * 防止内存泄漏
+     * 记录远程停机请求
      */
-    @Scheduled(fixedRate = 3600000) // 1小时
+    public void recordStopRequest(String fullGunNo) {
+        pendingStopRequests.put(fullGunNo, LocalDateTime.now());
+    }
+
+    // ==================== 缓存管理 ====================
+
+    /**
+     * 定期清理过期缓存
+     */
+    @Scheduled(fixedRate = 3600000)
     public void cleanExpiredCache() {
         LocalDateTime expireTime = LocalDateTime.now().minus(CACHE_EXPIRE_HOURS, ChronoUnit.HOURS);
         int cleanedRealtimeCount = 0;
         int cleanedSessionCount = 0;
 
-        // 清理过期的实时数据缓存
         Iterator<Map.Entry<String, RealtimeDataCache>> realtimeIterator = realtimeDataCache.entrySet().iterator();
         while (realtimeIterator.hasNext()) {
             Map.Entry<String, RealtimeDataCache> entry = realtimeIterator.next();
@@ -213,7 +684,6 @@ public class ChargingDataService {
             }
         }
 
-        // 清理过期的充电会话缓存(超时未结束的会话)
         Iterator<Map.Entry<String, ChargingSession>> sessionIterator = chargingSessionCache.entrySet().iterator();
         while (sessionIterator.hasNext()) {
             Map.Entry<String, ChargingSession> entry = sessionIterator.next();
@@ -225,20 +695,21 @@ public class ChargingDataService {
             }
         }
 
+        LocalDateTime stopExpireTime = LocalDateTime.now().minus(5, ChronoUnit.MINUTES);
+        pendingStopRequests.entrySet().removeIf(entry -> entry.getValue().isBefore(stopExpireTime));
+
         if (cleanedRealtimeCount > 0 || cleanedSessionCount > 0) {
-            log.info("缓存清理完成 - 清理实时数据缓存: {}条, 充电会话缓存: {}条, 剩余实时缓存: {}条, 剩余会话缓存: {}条",
-                cleanedRealtimeCount, cleanedSessionCount,
-                realtimeDataCache.size(), chargingSessionCache.size());
+            log.info("缓存清理完成 - 清理实时数据缓存: {}条, 充电会话缓存: {}条", cleanedRealtimeCount,
+                cleanedSessionCount);
         }
     }
 
     /**
-     * 清理指定桩的缓存(桩下线时调用)
+     * 桩下线清理缓存
      */
     public void onPileOffline(String pileCode) {
         int cleanedCount = 0;
 
-        // 清理该桩所有枪的实时数据缓存
         Iterator<Map.Entry<String, RealtimeDataCache>> iterator = realtimeDataCache.entrySet().iterator();
         while (iterator.hasNext()) {
             Map.Entry<String, RealtimeDataCache> entry = iterator.next();
@@ -248,11 +719,24 @@ public class ChargingDataService {
             }
         }
 
+        // 清除缓存
+        pileCodeToDeviceCodeCache.remove(pileCode);
+        gunNoToDeviceCodeCache.entrySet().removeIf(entry -> entry.getKey().startsWith(pileCode));
+
         if (cleanedCount > 0) {
             log.info("桩下线清理缓存 - 桩号: {}, 清理枪缓存: {}条", pileCode, cleanedCount);
         }
     }
 
+    /**
+     * 清除设备编码缓存(设备配置变更时调用)
+     */
+    public void clearDeviceCodeCache() {
+        pileCodeToDeviceCodeCache.clear();
+        gunNoToDeviceCodeCache.clear();
+        log.info("设备编码缓存已清除");
+    }
+
     private String getStatusDesc(byte status) {
         switch (status) {
             case 0x00:
@@ -272,6 +756,8 @@ public class ChargingDataService {
         }
     }
 
+    // ==================== 查询方法 ====================
+
     public RealtimeDataCache getRealtimeDataCache(String fullGunNo) {
         return realtimeDataCache.get(fullGunNo);
     }
@@ -280,13 +766,13 @@ public class ChargingDataService {
         return chargingSessionCache.get(transactionNo);
     }
 
-    /**
-     * 获取缓存统计信息(用于监控)
-     */
     public Map<String, Integer> getCacheStats() {
         Map<String, Integer> stats = new ConcurrentHashMap<>();
         stats.put("realtimeDataCacheSize", realtimeDataCache.size());
         stats.put("chargingSessionCacheSize", chargingSessionCache.size());
+        stats.put("pendingStopRequestsSize", pendingStopRequests.size());
+        stats.put("pileCodeCacheSize", pileCodeToDeviceCodeCache.size());
+        stats.put("gunNoCacheSize", gunNoToDeviceCodeCache.size());
         return stats;
     }
 }

+ 136 - 39
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/service/PowerControlService.java

@@ -3,12 +3,14 @@
  * 版    权:  华设设计集团股份有限公司
  * 描    述:  功率控制服务
  * 修 改 人:  lvwenbin
- * 修改时间:  2025/12/22
+ * 修改时间:  2025/12/25
+ * 修改内容:  添加远程停机功能
  */
 package com.ruoyi.ems.charging.service;
 
 import com.ruoyi.ems.charging.core.ChargingPileSessionManager;
 import com.ruoyi.ems.charging.model.resp.ReadRealtimeDataFrame;
+import com.ruoyi.ems.charging.model.resp.RemoteStopFrame;
 import com.ruoyi.ems.charging.model.resp.WorkParamSetFrame;
 import io.netty.channel.Channel;
 import io.netty.channel.ChannelFuture;
@@ -17,34 +19,30 @@ import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
 
 /**
  * 功率控制服务
- * 用于能耗平台对充电桩进行功率调控
+ * 用于能耗平台对充电桩进行功率调控和远程控制
  *
  * @author lvwenbin
- * @version [版本号, 2025/12/22]
+ * @version [版本号, 2025/12/25]
  */
 @Service
 public class PowerControlService {
-    
+
     private static final Logger log = LoggerFactory.getLogger(PowerControlService.class);
-    
+
     @Autowired
     private ChargingPileSessionManager sessionManager;
-    
-    /**
-     * 待处理的设置请求回调 - 序列号 -> Future
-     */
-    private final ConcurrentHashMap<Integer, CompletableFuture<Boolean>> pendingRequests = new ConcurrentHashMap<>();
-    
+
+    @Autowired
+    private ChargingDataService chargingDataService;
+
     /**
      * 设置充电桩最大输出功率百分比
      *
-     * @param pileCode             桩编号
+     * @param pileCode              桩编号
      * @param maxOutputPowerPercent 最大输出功率百分比(30-100)
      * @return 是否发送成功
      */
@@ -53,20 +51,20 @@ public class PowerControlService {
             log.warn("充电桩[{}]不在线,无法设置功率", pileCode);
             return false;
         }
-        
+
         Channel channel = sessionManager.getChannel(pileCode);
         if (channel == null || !channel.isActive()) {
             log.warn("充电桩[{}]通道无效", pileCode);
             return false;
         }
-        
+
         ChargingPileSessionManager.PileSession session = sessionManager.getSession(pileCode);
         int sequenceNo = session.nextSequenceNo();
-        
+
         WorkParamSetFrame frame = WorkParamSetFrame.createPowerLimit(pileCode, maxOutputPowerPercent, sequenceNo);
-        
+
         ChannelFuture future = channel.writeAndFlush(frame);
-        
+
         try {
             future.await(5, TimeUnit.SECONDS);
             if (future.isSuccess()) {
@@ -82,7 +80,7 @@ public class PowerControlService {
             return false;
         }
     }
-    
+
     /**
      * 启用充电桩
      *
@@ -94,19 +92,19 @@ public class PowerControlService {
             log.warn("充电桩[{}]不在线,无法启用", pileCode);
             return false;
         }
-        
+
         Channel channel = sessionManager.getChannel(pileCode);
         if (channel == null || !channel.isActive()) {
             return false;
         }
-        
+
         ChargingPileSessionManager.PileSession session = sessionManager.getSession(pileCode);
         int sequenceNo = session.nextSequenceNo();
-        
+
         WorkParamSetFrame frame = WorkParamSetFrame.createEnable(pileCode, sequenceNo);
-        
+
         ChannelFuture future = channel.writeAndFlush(frame);
-        
+
         try {
             future.await(5, TimeUnit.SECONDS);
             if (future.isSuccess()) {
@@ -118,7 +116,7 @@ public class PowerControlService {
         }
         return false;
     }
-    
+
     /**
      * 禁用充电桩
      *
@@ -130,19 +128,19 @@ public class PowerControlService {
             log.warn("充电桩[{}]不在线,无法禁用", pileCode);
             return false;
         }
-        
+
         Channel channel = sessionManager.getChannel(pileCode);
         if (channel == null || !channel.isActive()) {
             return false;
         }
-        
+
         ChargingPileSessionManager.PileSession session = sessionManager.getSession(pileCode);
         int sequenceNo = session.nextSequenceNo();
-        
+
         WorkParamSetFrame frame = WorkParamSetFrame.createDisable(pileCode, sequenceNo);
-        
+
         ChannelFuture future = channel.writeAndFlush(frame);
-        
+
         try {
             future.await(5, TimeUnit.SECONDS);
             if (future.isSuccess()) {
@@ -154,7 +152,73 @@ public class PowerControlService {
         }
         return false;
     }
-    
+
+    /**
+     * 远程停止充电
+     * 
+     * 使用场景:
+     * 1. 紧急情况下远程停止充电
+     * 2. 电网负荷调控需要时停止充电
+     *
+     * @param pileCode 桩编号
+     * @param gunNo    枪号
+     * @return 是否发送成功
+     */
+    public boolean remoteStop(String pileCode, String gunNo) {
+        if (!sessionManager.isOnline(pileCode)) {
+            log.warn("充电桩[{}]不在线,无法远程停机", pileCode);
+            return false;
+        }
+
+        Channel channel = sessionManager.getChannel(pileCode);
+        if (channel == null || !channel.isActive()) {
+            log.warn("充电桩[{}]通道无效", pileCode);
+            return false;
+        }
+
+        ChargingPileSessionManager.PileSession session = sessionManager.getSession(pileCode);
+        int sequenceNo = session.nextSequenceNo();
+
+        RemoteStopFrame frame = RemoteStopFrame.create(pileCode, gunNo, sequenceNo);
+
+        ChannelFuture future = channel.writeAndFlush(frame);
+
+        try {
+            future.await(5, TimeUnit.SECONDS);
+            if (future.isSuccess()) {
+                String fullGunNo = pileCode + gunNo;
+                log.info("远程停机指令发送成功 - 枪号: {}", fullGunNo);
+                
+                // 记录待处理请求,用于超时检测
+                chargingDataService.recordStopRequest(fullGunNo);
+                return true;
+            } else {
+                log.error("远程停机指令发送失败 - 桩号: {}, 枪号: {}", pileCode, gunNo, future.cause());
+                return false;
+            }
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            log.error("远程停机指令发送超时 - 桩号: {}, 枪号: {}", pileCode, gunNo);
+            return false;
+        }
+    }
+
+    /**
+     * 远程停止充电(通过完整枪号)
+     *
+     * @param fullGunNo 完整枪号(桩编号14位+枪号)
+     * @return 是否发送成功
+     */
+    public boolean remoteStopByFullGunNo(String fullGunNo) {
+        if (fullGunNo == null || fullGunNo.length() < 15) {
+            log.warn("完整枪号格式错误: {}", fullGunNo);
+            return false;
+        }
+        String pileCode = fullGunNo.substring(0, 14);
+        String gunNo = fullGunNo.substring(14);
+        return remoteStop(pileCode, gunNo);
+    }
+
     /**
      * 主动读取实时数据
      *
@@ -167,19 +231,19 @@ public class PowerControlService {
             log.warn("充电桩[{}]不在线,无法读取数据", pileCode);
             return false;
         }
-        
+
         Channel channel = sessionManager.getChannel(pileCode);
         if (channel == null || !channel.isActive()) {
             return false;
         }
-        
+
         ChargingPileSessionManager.PileSession session = sessionManager.getSession(pileCode);
         int sequenceNo = session.nextSequenceNo();
-        
+
         ReadRealtimeDataFrame frame = ReadRealtimeDataFrame.create(pileCode, gunNo, sequenceNo);
-        
+
         ChannelFuture future = channel.writeAndFlush(frame);
-        
+
         try {
             future.await(5, TimeUnit.SECONDS);
             if (future.isSuccess()) {
@@ -191,7 +255,7 @@ public class PowerControlService {
         }
         return false;
     }
-    
+
     /**
      * 批量设置功率限制
      * 用于电网功率调控场景
@@ -200,7 +264,7 @@ public class PowerControlService {
      */
     public void setAllPilesPowerLimit(int powerPercent) {
         log.info("批量设置所有充电桩功率限制: {}%", powerPercent);
-        
+
         for (ChargingPileSessionManager.PileSession session : sessionManager.getAllOnlineSessions()) {
             if (session.isOnline() && session.getChannel() != null && session.getChannel().isActive()) {
                 try {
@@ -211,4 +275,37 @@ public class PowerControlService {
             }
         }
     }
+
+    /**
+     * 批量停止所有正在充电的枪
+     * 用于紧急情况
+     *
+     * @return 发送成功的数量
+     */
+    public int stopAllCharging() {
+        log.warn("执行紧急停止所有充电操作");
+        int successCount = 0;
+
+        for (ChargingPileSessionManager.PileSession session : sessionManager.getAllOnlineSessions()) {
+            if (session.isOnline() && session.getChannel() != null && session.getChannel().isActive()) {
+                String pileCode = session.getPileCode();
+                int gunCount = session.getGunCount();
+                
+                // 对每个枪发送停机命令
+                for (int i = 1; i <= gunCount; i++) {
+                    String gunNo = String.format("%02d", i);
+                    try {
+                        if (remoteStop(pileCode, gunNo)) {
+                            successCount++;
+                        }
+                    } catch (Exception e) {
+                        log.error("停止充电桩[{}]枪[{}]失败", pileCode, gunNo, e);
+                    }
+                }
+            }
+        }
+
+        log.warn("紧急停止充电完成 - 成功发送: {}条", successCount);
+        return successCount;
+    }
 }

+ 130 - 0
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/controller/ChargingController.java

@@ -0,0 +1,130 @@
+/*
+ * 文 件 名:  ChargingController
+ * 版    权:  华设设计集团股份有限公司
+ * 描    述:  充电桩适配控制器
+ * 修 改 人:  lvwenbin
+ * 修改时间:  2025/12/25
+ * 修改内容:  新建充电桩能力调用API入口
+ */
+package com.ruoyi.ems.controller;
+
+import com.ruoyi.ems.charging.handler.ChargingHandler;
+import com.ruoyi.ems.charging.service.ChargingDataService;
+import com.ruoyi.ems.model.AbilityPayload;
+import com.ruoyi.ems.model.CallResponse;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+/**
+ * 充电桩适配控制器
+ * 提供充电桩系统、主机、充电枪的能力调用API
+ * <h3>支持的能力列表:</h3>
+ * <h4>系统级能力 (M_W2_SYS_CHARGING, objType=3):</h4>
+ * <ul>
+ *   <li>setAllPilesPowerLimit - 批量设置功率限制,参数: 30-100</li>
+ *   <li>stopAllCharging - 全部停止充电</li>
+ *   <li>clearCache - 清除设备编码缓存</li>
+ *   <li>getCacheStats - 获取缓存统计信息</li>
+ * </ul>
+ * <h4>充电主机能力 (M_W2_DEV_CHARGING_HOST, objType=2):</h4>
+ * <ul>
+ *   <li>setMaxOutputPower - 设置最大输出功率,参数: 30-100</li>
+ *   <li>enablePile - 启用充电桩</li>
+ *   <li>disablePile - 禁用充电桩</li>
+ * </ul>
+ * <h4>充电枪能力 (M_W2_DEV_CHARGING_PILE, objType=2):</h4>
+ * <ul>
+ *   <li>remoteStop - 远程停止充电</li>
+ *   <li>readRealtimeData - 主动读取实时数据</li>
+ * </ul>
+ *
+ * @author lvwenbin
+ * @version [版本号, 2025/12/25]
+ */
+@RestController
+@CrossOrigin(allowedHeaders = "*", allowCredentials = "false")
+@RequestMapping("/charging")
+@Api(value = "ChargingController", description = "充电桩适配接口")
+public class ChargingController {
+
+    private static final Logger log = LoggerFactory.getLogger(ChargingController.class);
+
+    @Resource
+    private ChargingHandler chargingHandler;
+
+    @Resource
+    private ChargingDataService chargingDataService;
+
+    /**
+     * 充电桩能力调用入口
+     *
+     * @param abilityPayload 能力调用参数
+     * @return 调用结果
+     */
+    @PostMapping("/ct/abilityCall")
+    @ApiOperation(value = "充电桩能力调用", notes = "统一的充电桩能力调用入口")
+    @ApiResponses({ @ApiResponse(code = 200, message = "success"),
+        @ApiResponse(code = 400, message = "{code:****,message:'fail'}")
+    })
+    public CallResponse<Void> abilityCall(@RequestBody AbilityPayload abilityPayload) {
+        CallResponse<Void> res;
+
+        try {
+            log.info("收到充电桩能力调用请求: {}", abilityPayload);
+            res = chargingHandler.call(abilityPayload);
+        }
+        catch (Exception e) {
+            log.error("充电桩能力调用失败!", e);
+            res = new CallResponse<>(501, "内部错误:" + e.getMessage());
+        }
+
+        return res;
+    }
+
+    /**
+     * 清除设备编码缓存
+     *
+     * @return 操作结果
+     */
+    @PostMapping("/cache/clear")
+    @ApiOperation(value = "清除缓存", notes = "清除设备编码缓存,设备配置变更后需要调用")
+    public CallResponse<Void> clearCache() {
+        try {
+            chargingDataService.clearDeviceCodeCache();
+            return new CallResponse<>(0, "缓存已清除");
+        }
+        catch (Exception e) {
+            log.error("清除缓存失败", e);
+            return new CallResponse<>(-1, e.getMessage());
+        }
+    }
+
+    /**
+     * 刷新充电桩在线状态
+     *
+     * @return 操作结果
+     */
+    @PostMapping("/system/refreshOnline")
+    @ApiOperation(value = "刷新在线状态", notes = "刷新所有充电桩的在线状态")
+    public CallResponse<Void> refreshOnline() {
+        try {
+            chargingHandler.refreshOnline();
+            return new CallResponse<>(0, "在线状态刷新完成");
+        }
+        catch (Exception e) {
+            log.error("刷新在线状态失败", e);
+            return new CallResponse<>(-1, e.getMessage());
+        }
+    }
+}

+ 1 - 1
ems/ems-cloud/ems-dev-adapter/src/test/java/com/huashe/test/ProtocolParserTest.java

@@ -196,7 +196,7 @@ public class ProtocolParserTest {
             System.out.println("  电量: " + String.format("%.4f", rd.getChargingEnergyValue()) + "kWh");
             System.out.println("  金额: " + String.format("%.4f", rd.getChargedAmountValue()) + "元");
             System.out.println("  SOC: " + rd.getSoc() + "%");
-            System.out.println("  状态: " + rd.getStatusDesc());
+            System.out.println("  状态: " + rd.getStatus());
         }
         System.out.println();
     }

+ 206 - 3
ems/sql/ems_init_data_ctfwq.sql

@@ -320,7 +320,41 @@ INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `dev
 INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'Z020-N-LIGHT-13', '(照明)卫生间', '-', '-', '1', '综合楼', 'N-10101', '321283124S3002', 'M_Z020_DEV_BA_LIGHT', 'Z-ZM-02', NULL, 'SYS_BA');
 INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'Z020-N-LIGHT-14', '(照明)走廊', '-', '-', '1', '综合楼', 'N-10101', '321283124S3002', 'M_Z020_DEV_BA_LIGHT', 'Z-ZM-02', NULL, 'SYS_BA');
 
-
+-- 充电桩设备
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-B-CHARGING-HOST-01', '充电桩群控箱01', '科士达', 'CDS66', '0', '北区广场', '321283124S300170', '321283124S3001', 'M_W2_DEV_CHARGING_HOST', 'Charge01', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-B-CHARGING-HOST-02', '充电桩群控箱02', '科士达', 'CDS66', '0', '北区广场', '321283124S300170', '321283124S3001', 'M_W2_DEV_CHARGING_HOST', 'Charge01', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-N-CHARGING-HOST-01', '充电桩群控箱03', '科士达', 'CDS66', '0', '南区广场', '321283124S300270', '321283124S3002', 'M_W2_DEV_CHARGING_HOST', 'Charge02', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-N-CHARGING-HOST-02', '充电桩群控箱04', '科士达', 'CDS66', '0', '南区广场', '321283124S300270', '321283124S3002', 'M_W2_DEV_CHARGING_HOST', 'Charge02', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-B-CHARGING-PILE-0101', '(北)充电枪0101', '科士达', 'CDSIS', '0', '北区广场', '321283124S300170', '321283124S3001', 'M_W2_DEV_CHARGING_PILE', 'Charge01', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-B-CHARGING-PILE-0102', '(北)充电枪0102', '科士达', 'CDS1', '0', '北区广场', '321283124S300170', '321283124S3001', 'M_W2_DEV_CHARGING_PILE', 'Charge01', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-B-CHARGING-PILE-0103', '(北)充电枪0103', '科士达', 'CDS1', '0', '北区广场', '321283124S300170', '321283124S3001', 'M_W2_DEV_CHARGING_PILE', 'Charge01', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-B-CHARGING-PILE-0104', '(北)充电枪0104', '科士达', 'CDS1', '0', '北区广场', '321283124S300170', '321283124S3001', 'M_W2_DEV_CHARGING_PILE', 'Charge01', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-B-CHARGING-PILE-0105', '(北)充电枪0105', '科士达', 'CDS1', '0', '北区广场', '321283124S300170', '321283124S3001', 'M_W2_DEV_CHARGING_PILE', 'Charge01', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-B-CHARGING-PILE-0106', '(北)充电枪0106', '科士达', 'CDS1', '0', '北区广场', '321283124S300170', '321283124S3001', 'M_W2_DEV_CHARGING_PILE', 'Charge01', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-B-CHARGING-PILE-0107', '(北)充电枪0107', '科士达', 'CDS1', '0', '北区广场', '321283124S300170', '321283124S3001', 'M_W2_DEV_CHARGING_PILE', 'Charge01', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-B-CHARGING-PILE-0201', '(北)充电枪0201', '科士达', 'CDSIS', '0', '北区广场', '321283124S300170', '321283124S3001', 'M_W2_DEV_CHARGING_PILE', 'Charge01', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-B-CHARGING-PILE-0202', '(北)充电枪0202', '科士达', 'CDS1', '0', '北区广场', '321283124S300170', '321283124S3001', 'M_W2_DEV_CHARGING_PILE', 'Charge01', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-B-CHARGING-PILE-0203', '(北)充电枪0203', '科士达', 'CDS1', '0', '北区广场', '321283124S300170', '321283124S3001', 'M_W2_DEV_CHARGING_PILE', 'Charge01', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-B-CHARGING-PILE-0204', '(北)充电枪0204', '科士达', 'CDS1', '0', '北区广场', '321283124S300170', '321283124S3001', 'M_W2_DEV_CHARGING_PILE', 'Charge01', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-B-CHARGING-PILE-0205', '(北)充电枪0205', '科士达', 'CDS1', '0', '北区广场', '321283124S300170', '321283124S3001', 'M_W2_DEV_CHARGING_PILE', 'Charge01', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-B-CHARGING-PILE-0206', '(北)充电枪0206', '科士达', 'CDS1', '0', '北区广场', '321283124S300170', '321283124S3001', 'M_W2_DEV_CHARGING_PILE', 'Charge01', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-B-CHARGING-PILE-0207', '(北)充电枪0207', '科士达', 'CDS1', '0', '北区广场', '321283124S300170', '321283124S3001', 'M_W2_DEV_CHARGING_PILE', 'Charge01', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-B-CHARGING-PILE-0208', '(北)充电枪0208', '科士达', 'CDS1', '0', '北区广场', '321283124S300170', '321283124S3001', 'M_W2_DEV_CHARGING_PILE', 'Charge01', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-N-CHARGING-PILE-0101', '(南)充电枪0101', '科士达', 'CDSIS', '0', '南区广场', '321283124S300270', '321283124S3002', 'M_W2_DEV_CHARGING_PILE', 'Charge02', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-N-CHARGING-PILE-0102', '(南)充电枪0102', '科士达', 'CDS1', '0', '南区广场', '321283124S300270', '321283124S3002', 'M_W2_DEV_CHARGING_PILE', 'Charge02', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-N-CHARGING-PILE-0103', '(南)充电枪0103', '科士达', 'CDS1', '0', '南区广场', '321283124S300270', '321283124S3002', 'M_W2_DEV_CHARGING_PILE', 'Charge02', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-N-CHARGING-PILE-0104', '(南)充电枪0104', '科士达', 'CDS1', '0', '南区广场', '321283124S300270', '321283124S3002', 'M_W2_DEV_CHARGING_PILE', 'Charge02', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-N-CHARGING-PILE-0105', '(南)充电枪0105', '科士达', 'CDS1', '0', '南区广场', '321283124S300270', '321283124S3002', 'M_W2_DEV_CHARGING_PILE', 'Charge02', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-N-CHARGING-PILE-0106', '(南)充电枪0106', '科士达', 'CDS1', '0', '南区广场', '321283124S300270', '321283124S3002', 'M_W2_DEV_CHARGING_PILE', 'Charge02', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-N-CHARGING-PILE-0107', '(南)充电枪0107', '科士达', 'CDS1', '0', '南区广场', '321283124S300270', '321283124S3002', 'M_W2_DEV_CHARGING_PILE', 'Charge02', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-N-CHARGING-PILE-0201', '(南)充电枪0201', '科士达', 'CDSIS', '0', '南区广场', '321283124S300270', '321283124S3002', 'M_W2_DEV_CHARGING_PILE', 'Charge02', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-N-CHARGING-PILE-0202', '(南)充电枪0202', '科士达', 'CDS1', '0', '南区广场', '321283124S300270', '321283124S3002', 'M_W2_DEV_CHARGING_PILE', 'Charge02', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-N-CHARGING-PILE-0203', '(南)充电枪0203', '科士达', 'CDS1', '0', '南区广场', '321283124S300270', '321283124S3002', 'M_W2_DEV_CHARGING_PILE', 'Charge02', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-N-CHARGING-PILE-0204', '(南)充电枪0204', '科士达', 'CDS1', '0', '南区广场', '321283124S300270', '321283124S3002', 'M_W2_DEV_CHARGING_PILE', 'Charge02', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-N-CHARGING-PILE-0205', '(南)充电枪0205', '科士达', 'CDS1', '0', '南区广场', '321283124S300270', '321283124S3002', 'M_W2_DEV_CHARGING_PILE', 'Charge02', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-N-CHARGING-PILE-0206', '(南)充电枪0206', '科士达', 'CDS1', '0', '南区广场', '321283124S300270', '321283124S3002', 'M_W2_DEV_CHARGING_PILE', 'Charge02', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-N-CHARGING-PILE-0207', '(南)充电枪0207', '科士达', 'CDS1', '0', '南区广场', '321283124S300270', '321283124S3002', 'M_W2_DEV_CHARGING_PILE', 'Charge02', NULL, 'SYS_CD');
+INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `device_spec`, `device_status`, `location`, `location_ref`, `area_code`, `device_model`, `ref_facs`, `ps_code`, `subsystem_code`) VALUES ( 'W2-N-CHARGING-PILE-0208', '(南)充电枪0208', '科士达', 'CDS1', '0', '南区广场', '321283124S300270', '321283124S3002', 'M_W2_DEV_CHARGING_PILE', 'Charge02', NULL, 'SYS_CD');
 
 -- 策略初始数据
 
@@ -366,6 +400,11 @@ INSERT INTO `adm_ems_obj_model` (`model_code`, `model_name`, `obj_type`, `abilit
 INSERT INTO `adm_ems_obj_model` (`model_code`, `model_name`, `obj_type`, `ability_handler`, `event_handler`) VALUES ('M_W2_DEV_PHOTOVOLTAIC_COL', '光伏采集器', 2, 'http://172.17.60.27:9203/ems-dev-adapter/growatt/ct/abilityCall', NULL);
 INSERT INTO `adm_ems_obj_model` (`model_code`, `model_name`, `obj_type`, `ability_handler`, `event_handler`) VALUES ('M_E5_DEV_PHOTOVOLTAIC_INVERTER', '光伏逆变器', 2, 'http://172.17.60.27:9203/ems-dev-adapter/growatt/ct/abilityCall', NULL);
 
+INSERT INTO `adm_ems_obj_model` (`model_code`, `model_name`, `obj_type`, `ability_handler`, `event_handler`) VALUES ('M_W2_SYS_CHARGING', '充电系统', 3, 'http://172.17.60.27:9203/ems-dev-adapter/charging/ct/abilityCall', NULL);
+INSERT INTO `adm_ems_obj_model` (`model_code`, `model_name`, `obj_type`, `ability_handler`, `event_handler`) VALUES ('M_W2_DEV_CHARGING_HOST', '充电主机', 2, 'http://172.17.60.27:9203/ems-dev-adapter/charging/ct/abilityCall', NULL);
+INSERT INTO `adm_ems_obj_model` (`model_code`, `model_name`, `obj_type`, `ability_handler`, `event_handler`) VALUES ('M_W2_DEV_CHARGING_PILE', '充电枪', 2, 'http://172.17.60.27:9203/ems-dev-adapter/charging/ct/abilityCall', NULL);
+
+
 -- 对象属性数据
 -- 能耗监测属性
 INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W4_SYS_BA', 'Protocol', 'interfaceType', '协议类型', NULL, 'String');
@@ -439,7 +478,6 @@ INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_na
 INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_Z020_DEV_BA_WP', 'State', 'faultState', '故障状态', NULL, 'Enum');
 INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_Z020_DEV_BA_LIGHT', 'State', 'Switch', '开关', NULL, 'Enum');
 
-
 -- 广场灯控属性
 INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_Z010_SYS_SQUARE_LIGHT', 'Protocol', 'interfaceType', '协议类型', NULL, 'String');
 INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_Z010_SYS_SQUARE_LIGHT', 'Protocol', 'url', '服务地址', NULL, 'String');
@@ -636,6 +674,42 @@ INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_na
 INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_E5_DEV_PHOTOVOLTAIC_INVERTER', 'Measure', 'eacTotal', '累计发电量', 'kW·h', 'Value');
 INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_E5_DEV_PHOTOVOLTAIC_INVERTER', 'Measure', 'epvTotal', 'PV累计发电量', 'kW·h', 'Value');
 
+-- 充电桩属性
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_SYS_CHARGING', 'Protocol', 'interfaceType', '协议类型', NULL, 'String');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_SYS_CHARGING', 'Protocol', 'address', '服务地址', NULL, 'String');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_SYS_CHARGING', 'Protocol', 'port', '服务端口', NULL, 'String');
+
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_HOST', 'Base', 'pileCode', '桩编码', NULL, 'String');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_HOST', 'Base', 'pileType', '桩类型', NULL, 'Enum');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_HOST', 'Base', 'ratedPower', '额定功率', 'kW', 'Value');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_HOST', 'Base', 'inputVoltage', '输入电压', 'V', 'String');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_HOST', 'Base', 'outputCurrent', '输出电流', 'A', 'String');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_HOST', 'Base', 'outputVoltage', '输出电压', 'V', 'String');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_HOST', 'Base', 'gunCount', '充电枪数量', NULL, 'Value');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_HOST', 'Base', 'protocolVersion', '通讯协议版本', NULL, 'String');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_HOST', 'Base', 'softwareVersion', 'on序版本', NULL, 'String');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_HOST', 'Base', 'networkType', '网络链接类型', NULL, 'String');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_HOST', 'Base', 'simCard', 'Sim卡', NULL, 'String');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_HOST', 'Base', 'carrier', '运营商', NULL, 'String');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_HOST', 'Base', 'subDev', '充电枪列表', NULL, 'String');
+
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_PILE', 'Base', 'pileCode', '桩编码', NULL, 'String');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_PILE', 'Base', 'gunNo', '枪号', NULL, 'String');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_PILE', 'State', 'transactionNo', '交易流水号', NULL, 'String');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_PILE', 'State', 'status', '状态', NULL, 'Enum');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_PILE', 'State', 'gunReturned', '枪是否归位', NULL, 'String');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_PILE', 'State', 'gunConnected', '是否插枪', NULL, 'String');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_PILE', 'Measure', 'outputVoltage', '输出电压', 'V', 'Value');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_PILE', 'Measure', 'outputCurrent', '输出电流', 'A', 'Value');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_PILE', 'Measure', 'gunTemperature', '枪线温度', '℃', 'Value');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_PILE', 'Measure', 'chargingTime', '累计充电时间', 'min', 'Value');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_PILE', 'Measure', 'remainingTime', '剩余时间', 'min', 'Valueg');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_PILE', 'Measure', 'chargingEnergy', '充电度数', 'kW·h', 'Value');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_PILE', 'Measure', 'lossCorrectedEnergy', '计损充电度数', 'kW·h', 'Value');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_PILE', 'Measure', 'chargedAmount', '已充金额', NULL, 'Value');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_DEV_CHARGING_PILE', 'Measure', 'hardwareFault', '硬件故障', NULL, 'String');
+
+
 
 INSERT INTO `adm_ems_obj_attr_enum` (`model_code`, `attr_key`, `attr_value`, `attr_value_name`) VALUES ('M_W4_SYS_BA', 'interfaceStatus', '1', '正常');
 INSERT INTO `adm_ems_obj_attr_enum` (`model_code`, `attr_key`, `attr_value`, `attr_value_name`) VALUES ('M_W4_SYS_BA', 'interfaceStatus', '0', '断开');
@@ -732,6 +806,17 @@ INSERT INTO `adm_ems_obj_attr_enum` (`model_code`, `attr_key`, `attr_value`, `at
 INSERT INTO `adm_ems_obj_attr_enum` (`model_code`, `attr_key`, `attr_value`, `attr_value_name`) VALUES ('M_E5_DEV_PHOTOVOLTAIC_INVERTER', 'deratingMode', '1', '开启');
 INSERT INTO `adm_ems_obj_attr_enum` (`model_code`, `attr_key`, `attr_value`, `attr_value_name`) VALUES ('M_E5_DEV_PHOTOVOLTAIC_INVERTER', 'deratingMode', '0', '关闭');
 
+INSERT INTO `adm_ems_obj_attr_enum` (`model_code`, `attr_key`, `attr_value`, `attr_value_name`) VALUES ('M_W2_DEV_CHARGING_HOST', 'pileType', '0', '直流桩');
+INSERT INTO `adm_ems_obj_attr_enum` (`model_code`, `attr_key`, `attr_value`, `attr_value_name`) VALUES ('M_W2_DEV_CHARGING_HOST', 'pileType', '1', '交流桩');
+
+INSERT INTO `adm_ems_obj_attr_enum` (`model_code`, `attr_key`, `attr_value`, `attr_value_name`) VALUES ('M_W2_DEV_CHARGING_PILE', 'status', '0', '离线');
+INSERT INTO `adm_ems_obj_attr_enum` (`model_code`, `attr_key`, `attr_value`, `attr_value_name`) VALUES ('M_W2_DEV_CHARGING_PILE', 'status', '1', '故障');
+INSERT INTO `adm_ems_obj_attr_enum` (`model_code`, `attr_key`, `attr_value`, `attr_value_name`) VALUES ('M_W2_DEV_CHARGING_PILE', 'status', '2', '空闲');
+INSERT INTO `adm_ems_obj_attr_enum` (`model_code`, `attr_key`, `attr_value`, `attr_value_name`) VALUES ('M_W2_DEV_CHARGING_PILE', 'status', '3', '充电中');
+INSERT INTO `adm_ems_obj_attr_enum` (`model_code`, `attr_key`, `attr_value`, `attr_value_name`) VALUES ('M_W2_DEV_CHARGING_PILE', 'status', '4', '空闲-脉冲静置');
+INSERT INTO `adm_ems_obj_attr_enum` (`model_code`, `attr_key`, `attr_value`, `attr_value_name`) VALUES ('M_W2_DEV_CHARGING_PILE', 'status', '5', '充电中-脉冲检测');
+INSERT INTO `adm_ems_obj_attr_enum` (`model_code`, `attr_key`, `attr_value`, `attr_value_name`) VALUES ('M_W2_DEV_CHARGING_PILE', 'status', '-1', '未知');
+
 -- 对象属性
 INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('SYS_BA', 'M_W4_SYS_BA', 'interfaceType', 'http', NULL);
 INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('SYS_BA', 'M_W4_SYS_BA', 'url', 'http://172.17.50.186:80', NULL);
@@ -1402,6 +1487,124 @@ INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `att
 INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('E5-ZX-INV-GF-6', 'M_E5_DEV_PHOTOVOLTAIC_INVERTER', 'sn', 'GCN5EX902G', NULL);
 INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('E5-ZX-INV-GF-6', 'M_E5_DEV_PHOTOVOLTAIC_INVERTER', 'dataloggerSn', 'ZBK0E9L0MV', NULL);
 
+-- 充电桩数据
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('SYS_CD', 'M_W2_SYS_CHARGING', 'interfaceType', 'Socket', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('SYS_CD', 'M_W2_SYS_CHARGING', 'address', '172.17.60.27', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('SYS_CD', 'M_W2_SYS_CHARGING', 'port', '8234', NULL);
+
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-B-CHARGING-HOST-01', 'M_W2_DEV_CHARGING_HOST', 'pileCode', '32128301000101', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-B-CHARGING-HOST-01', 'M_W2_DEV_CHARGING_HOST', 'pileType', '0', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-B-CHARGING-HOST-01', 'M_W2_DEV_CHARGING_HOST', 'ratedPower', '640', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-B-CHARGING-HOST-01', 'M_W2_DEV_CHARGING_HOST', 'inputVoltage', 'AC380', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-B-CHARGING-HOST-01', 'M_W2_DEV_CHARGING_HOST', 'outputCurrent', '640', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-B-CHARGING-HOST-01', 'M_W2_DEV_CHARGING_HOST', 'outputVoltage', '200~1000', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-B-CHARGING-HOST-01', 'M_W2_DEV_CHARGING_HOST', 'gunCount', '7', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-B-CHARGING-HOST-01', 'M_W2_DEV_CHARGING_HOST', 'protocolVersion', NULL, NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-B-CHARGING-HOST-01', 'M_W2_DEV_CHARGING_HOST', 'softwareVersion', NULL, NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-B-CHARGING-HOST-01', 'M_W2_DEV_CHARGING_HOST', 'networkType', 'SIM', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-B-CHARGING-HOST-01', 'M_W2_DEV_CHARGING_HOST', 'simCard', NULL, NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-B-CHARGING-HOST-01', 'M_W2_DEV_CHARGING_HOST', 'carrier', '移动', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-B-CHARGING-HOST-01', 'M_W2_DEV_CHARGING_HOST', 'subDev', '[{"deviceCode":"W2-B-CHARGING-PILE-0101","modelCode":"M_W2_DEV_CHARGING_PILE"},{"deviceCode":"W2-B-CHARGING-PILE-0102","modelCode":"M_W2_DEV_CHARGING_PILE"},{"deviceCode":"W2-B-CHARGING-PILE-0103","modelCode":"M_W2_DEV_CHARGING_PILE"},{"deviceCode":"W2-B-CHARGING-PILE-0104","modelCode":"M_W2_DEV_CHARGING_PILE"},{"deviceCode":"W2-B-CHARGING-PILE-0105","modelCode":"M_W2_DEV_CHARGING_PILE"},{"deviceCode":"W2-B-CHARGING-PILE-0106","modelCode":"M_W2_DEV_CHARGING_PILE"},{"deviceCode":"W2-B-CHARGING-PILE-0107","modelCode":"M_W2_DEV_CHARGING_PILE"}]', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-B-CHARGING-HOST-02', 'M_W2_DEV_CHARGING_HOST', 'pileCode', '32128301000201', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-B-CHARGING-HOST-02', 'M_W2_DEV_CHARGING_HOST', 'pileType', '0', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-B-CHARGING-HOST-02', 'M_W2_DEV_CHARGING_HOST', 'ratedPower', '640', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-B-CHARGING-HOST-02', 'M_W2_DEV_CHARGING_HOST', 'inputVoltage', 'AC380', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-B-CHARGING-HOST-02', 'M_W2_DEV_CHARGING_HOST', 'outputCurrent', '640', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-B-CHARGING-HOST-02', 'M_W2_DEV_CHARGING_HOST', 'outputVoltage', '200~1000', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-B-CHARGING-HOST-02', 'M_W2_DEV_CHARGING_HOST', 'gunCount', '7', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-B-CHARGING-HOST-02', 'M_W2_DEV_CHARGING_HOST', 'protocolVersion', NULL, NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-B-CHARGING-HOST-02', 'M_W2_DEV_CHARGING_HOST', 'softwareVersion', NULL, NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-B-CHARGING-HOST-02', 'M_W2_DEV_CHARGING_HOST', 'networkType', 'SIM', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-B-CHARGING-HOST-02', 'M_W2_DEV_CHARGING_HOST', 'simCard', NULL, NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-B-CHARGING-HOST-02', 'M_W2_DEV_CHARGING_HOST', 'carrier', '移动', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-B-CHARGING-HOST-02', 'M_W2_DEV_CHARGING_HOST', 'subDev', '[{"deviceCode":"W2-B-CHARGING-PILE-0201","modelCode":"M_W2_DEV_CHARGING_PILE"},{"deviceCode":"W2-B-CHARGING-PILE-0202","modelCode":"M_W2_DEV_CHARGING_PILE"},{"deviceCode":"W2-B-CHARGING-PILE-0203","modelCode":"M_W2_DEV_CHARGING_PILE"},{"deviceCode":"W2-B-CHARGING-PILE-0204","modelCode":"M_W2_DEV_CHARGING_PILE"},{"deviceCode":"W2-B-CHARGING-PILE-0205","modelCode":"M_W2_DEV_CHARGING_PILE"},{"deviceCode":"W2-B-CHARGING-PILE-0206","modelCode":"M_W2_DEV_CHARGING_PILE"},{"deviceCode":"W2-B-CHARGING-PILE-0207","modelCode":"M_W2_DEV_CHARGING_PILE"},{"deviceCode":"W2-B-CHARGING-PILE-0208","modelCode":"M_W2_DEV_CHARGING_PILE"}]', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-N-CHARGING-HOST-01', 'M_W2_DEV_CHARGING_HOST', 'pileCode', '32128302000101', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-N-CHARGING-HOST-01', 'M_W2_DEV_CHARGING_HOST', 'pileType', '0', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-N-CHARGING-HOST-01', 'M_W2_DEV_CHARGING_HOST', 'ratedPower', '640', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-N-CHARGING-HOST-01', 'M_W2_DEV_CHARGING_HOST', 'inputVoltage', 'AC380', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-N-CHARGING-HOST-01', 'M_W2_DEV_CHARGING_HOST', 'outputCurrent', '640', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-N-CHARGING-HOST-01', 'M_W2_DEV_CHARGING_HOST', 'outputVoltage', '200~1000', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-N-CHARGING-HOST-01', 'M_W2_DEV_CHARGING_HOST', 'gunCount', '7', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-N-CHARGING-HOST-01', 'M_W2_DEV_CHARGING_HOST', 'protocolVersion', NULL, NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-N-CHARGING-HOST-01', 'M_W2_DEV_CHARGING_HOST', 'softwareVersion', NULL, NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-N-CHARGING-HOST-01', 'M_W2_DEV_CHARGING_HOST', 'networkType', 'SIM', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-N-CHARGING-HOST-01', 'M_W2_DEV_CHARGING_HOST', 'simCard', NULL, NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-N-CHARGING-HOST-01', 'M_W2_DEV_CHARGING_HOST', 'carrier', '移动', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-N-CHARGING-HOST-01', 'M_W2_DEV_CHARGING_HOST', 'subDev', '[{"deviceCode":"W2-N-CHARGING-PILE-0101","modelCode":"M_W2_DEV_CHARGING_PILE"},{"deviceCode":"W2-N-CHARGING-PILE-0102","modelCode":"M_W2_DEV_CHARGING_PILE"},{"deviceCode":"W2-N-CHARGING-PILE-0103","modelCode":"M_W2_DEV_CHARGING_PILE"},{"deviceCode":"W2-N-CHARGING-PILE-0104","modelCode":"M_W2_DEV_CHARGING_PILE"},{"deviceCode":"W2-N-CHARGING-PILE-0105","modelCode":"M_W2_DEV_CHARGING_PILE"},{"deviceCode":"W2-N-CHARGING-PILE-0106","modelCode":"M_W2_DEV_CHARGING_PILE"},{"deviceCode":"W2-N-CHARGING-PILE-0107","modelCode":"M_W2_DEV_CHARGING_PILE"}]', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-N-CHARGING-HOST-02', 'M_W2_DEV_CHARGING_HOST', 'pileCode', '32128302000201', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-N-CHARGING-HOST-02', 'M_W2_DEV_CHARGING_HOST', 'pileType', '0', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-N-CHARGING-HOST-02', 'M_W2_DEV_CHARGING_HOST', 'ratedPower', '640', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-N-CHARGING-HOST-02', 'M_W2_DEV_CHARGING_HOST', 'inputVoltage', 'AC380', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-N-CHARGING-HOST-02', 'M_W2_DEV_CHARGING_HOST', 'outputCurrent', '640', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-N-CHARGING-HOST-02', 'M_W2_DEV_CHARGING_HOST', 'outputVoltage', '200~1000', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-N-CHARGING-HOST-02', 'M_W2_DEV_CHARGING_HOST', 'gunCount', '7', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-N-CHARGING-HOST-02', 'M_W2_DEV_CHARGING_HOST', 'protocolVersion', NULL, NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-N-CHARGING-HOST-02', 'M_W2_DEV_CHARGING_HOST', 'softwareVersion', NULL, NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-N-CHARGING-HOST-02', 'M_W2_DEV_CHARGING_HOST', 'networkType', 'SIM', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-N-CHARGING-HOST-02', 'M_W2_DEV_CHARGING_HOST', 'simCard', NULL, NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-N-CHARGING-HOST-02', 'M_W2_DEV_CHARGING_HOST', 'carrier', '移动', NULL);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('W2-N-CHARGING-HOST-02', 'M_W2_DEV_CHARGING_HOST', 'subDev', '[{"deviceCode":"W2-N-CHARGING-PILE-0201","modelCode":"M_W2_DEV_CHARGING_PILE"},{"deviceCode":"W2-N-CHARGING-PILE-0202","modelCode":"M_W2_DEV_CHARGING_PILE"},{"deviceCode":"W2-N-CHARGING-PILE-0203","modelCode":"M_W2_DEV_CHARGING_PILE"},{"deviceCode":"W2-N-CHARGING-PILE-0204","modelCode":"M_W2_DEV_CHARGING_PILE"},{"deviceCode":"W2-N-CHARGING-PILE-0205","modelCode":"M_W2_DEV_CHARGING_PILE"},{"deviceCode":"W2-N-CHARGING-PILE-0206","modelCode":"M_W2_DEV_CHARGING_PILE"},{"deviceCode":"W2-N-CHARGING-PILE-0207","modelCode":"M_W2_DEV_CHARGING_PILE"},{"deviceCode":"W2-N-CHARGING-PILE-0208","modelCode":"M_W2_DEV_CHARGING_PILE"}]', NULL);
+
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0101', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128301000101');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0101', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '01');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0102', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128301000101');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0102', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '02');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0103', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128301000101');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0103', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '03');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0104', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128301000101');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0104', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '04');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0105', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128301000101');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0105', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '05');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0106', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128301000101');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0106', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '06');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0107', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128301000101');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0107', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '07');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0201', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128301000201');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0201', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '01');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0202', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128301000201');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0202', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '02');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0203', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128301000201');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0203', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '03');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0204', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128301000201');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0204', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '04');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0205', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128301000201');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0205', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '05');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0206', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128301000201');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0206', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '06');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0207', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128301000201');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0207', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '07');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0208', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128301000201');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-B-CHARGING-PILE-0208', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '08');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0101', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128302000101');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0101', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '01');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0102', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128302000101');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0102', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '02');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0103', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128302000101');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0103', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '03');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0104', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128302000101');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0104', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '04');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0105', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128302000101');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0105', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '05');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0106', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128302000101');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0106', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '06');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0107', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128302000101');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0107', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '07');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0201', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128302000201');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0201', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '01');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0202', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128302000201');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0202', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '02');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0203', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128302000201');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0203', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '03');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0204', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128302000201');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0204', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '04');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0205', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128302000201');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0205', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '05');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0206', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128302000201');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0206', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '06');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0207', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128302000201');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0207', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '07');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0208', 'M_W2_DEV_CHARGING_PILE', 'pileCode', '32128302000201');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`) VALUES ('W2-N-CHARGING-PILE-0208', 'M_W2_DEV_CHARGING_PILE', 'gunNo', '08');
 
 -- 对象能力数据
 INSERT INTO `adm_ems_obj_ability` (`model_code`, `ability_key`, `ability_name`, `ability_desc`, `param_definition`, `hidden_flag`) VALUES ('M_W4_SYS_BA', 'MeterReadingTotal', '全量抄报-网关', '网关-测点批量抄报', null, 1);
@@ -2009,7 +2212,7 @@ INSERT INTO `adm_ems_subsystem` (`system_code`, `system_name`, `short_name`, `mo
 INSERT INTO `adm_ems_subsystem` (`system_code`, `system_name`, `short_name`, `model_code`, `man_facturer`, `contact_person`, `contact_number`, `maintainer_person`, `maintainer_number`, `descr`) VALUES ('SYS_DLJK', '电力监控系统', '电力监控', 'M_W4_SYS_ELEC_MONITOR', '安科瑞', 'gzl', '1212121121', 'gzl', '1212221111', NULL);
 INSERT INTO `adm_ems_subsystem` (`system_code`, `system_name`, `short_name`, `model_code`, `man_facturer`, `contact_person`, `contact_number`, `maintainer_person`, `maintainer_number`, `descr`) VALUES ('SYS_GF', '光伏系统', '光伏', 'M_E5_SYS_PHOTOVOLTAIC', '古瑞瓦特', '张', '1380000', '李工', '123123', NULL);
 INSERT INTO `adm_ems_subsystem` (`system_code`, `system_name`, `short_name`, `model_code`, `man_facturer`, `contact_person`, `contact_number`, `maintainer_person`, `maintainer_number`, `descr`) VALUES ('SYS_CN', '储能系统', '储能', 'M_TEST','储能厂商', '王', '122112', '陈', '21212', NULL);
-INSERT INTO `adm_ems_subsystem` (`system_code`, `system_name`, `short_name`, `model_code`, `man_facturer`, `contact_person`, `contact_number`, `maintainer_person`, `maintainer_number`, `descr`) VALUES ('SYS_CD', '充电桩系统', '充电桩', 'M_TEST', '特来电', '张三', '1212121121', '刘工', '1212221111', NULL);
+INSERT INTO `adm_ems_subsystem` (`system_code`, `system_name`, `short_name`, `model_code`, `man_facturer`, `contact_person`, `contact_number`, `maintainer_person`, `maintainer_number`, `descr`) VALUES ('SYS_CD', '充电桩系统', '充电桩', 'M_W2_SYS_CHARGING', '科士达', NULL, NULL, NULL, NULL, NULL);
 INSERT INTO `adm_ems_subsystem` (`system_code`, `system_name`, `short_name`, `model_code`, `man_facturer`, `contact_person`, `contact_number`, `maintainer_person`, `maintainer_number`, `descr`) VALUES ('SYS_GCC', '光储充系统', '光储充', 'M_TEST', '光储充厂商', '张三', '1212121121', '刘工', '1212221111', NULL);
 INSERT INTO `adm_ems_subsystem` (`system_code`, `system_name`, `short_name`, `model_code`, `man_facturer`, `contact_person`, `contact_number`, `maintainer_person`, `maintainer_number`, `descr`) VALUES ('SYS_GCZR', '光储直柔系统', '光储直柔', 'M_TEST', '光储直柔厂商', '张三', '1212121121', '刘工', '1212221111', NULL);
 INSERT INTO `adm_ems_subsystem` (`system_code`, `system_name`, `short_name`, `model_code`, `man_facturer`, `contact_person`, `contact_number`, `maintainer_person`, `maintainer_number`, `descr`) VALUES ('SYS_ZHHM', '智慧海绵系统', '智慧海绵', 'M_TEST', '智慧海绵厂商', '张三', '1212121121', '刘工', '1212221111', NULL);