learshaw 1 Minggu lalu
induk
melakukan
9057e0dd82

+ 4 - 0
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/charging/mock/config/MockPileAutoConfiguration.java

@@ -89,6 +89,10 @@ public class MockPileAutoConfiguration {
 
         clientManager = new MockPileClientManager(config);
 
+        // 自动启动所有客户端
+        log.info("自动启动Mock充电桩客户端...");
+        clientManager.startAll();
+
         return clientManager;
     }
 

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

@@ -17,11 +17,11 @@ import java.util.List;
  * 编码规则 (符合PDF协议规范):
  *   桩编码(pileCode): 7字节BCD码 = 14位数字
  *   枪号(gunNo): 1字节BCD码 = 2位数字 (01-08)
- * 桩编码设计: 321283 + AA + BBBB + 01
- *   321283: 泰兴市行政区划码前6位
- *   AA: 服务区(01=北区, 02=南区)
- *   BBBB: 主机序号(0001, 0002)
- *   01: 校验位
+ * 桩编码与数据库adm_ems_obj_attr_value表中pileCode属性一致:
+ *   南区1号: 32128300070001 (8枪)
+ *   南区2号: 32128300070002 (7枪)
+ *   北区1号: 32128300060001 (8枪)
+ *   北区2号: 32128300060002 (7枪)
  *
  * @author lvwenbin
  * @version [版本号, 2025/12/23]
@@ -29,12 +29,12 @@ import java.util.List;
 public class MockPileConfig {
 
     /**
-     * 桩编码常量 - 与数据库保持一致
+     * 桩编码常量 - 与数据库adm_ems_obj_attr_value表保持一致
      */
-    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";
+    public static final String PILE_CODE_SOUTH_HOST_01 = "32128300070001";
+    public static final String PILE_CODE_SOUTH_HOST_02 = "32128300070002";
+    public static final String PILE_CODE_NORTH_HOST_01 = "32128300060001";
+    public static final String PILE_CODE_NORTH_HOST_02 = "32128300060002";
 
     /**
      * 服务器地址
@@ -89,78 +89,76 @@ public class MockPileConfig {
     /**
      * 创建默认的高速服务区配置
      * 南北两侧,每侧2台充电主机
-     * 主机1: 1台600kW水冷枪 + 6台250kW风冷枪 = 7枪
-     * 主机2: 1台600kW水冷枪 + 7台250kW风冷枪 = 8枪
+     * 桩编码与数据库adm_ems_obj_attr_value表保持一致
      *
-     * 桩编码使用14位BCD码格式,与数据库adm_ems_obj_attr_value表保持一致
+     * 南区1号: 32128300070001 (8枪)
+     * 南区2号: 32128300070002 (7枪)
+     * 北区1号: 32128300060001 (8枪)
+     * 北区2号: 32128300060002 (7枪)
      */
     public static MockPileConfig createHighwayServiceAreaConfig() {
         MockPileConfig config = new MockPileConfig();
 
-        // 北区-主机1: 7枪 (1*600kW + 6*250kW)
-        // 对应数据库: W2-B-CHARGING-HOST-01, pileCode=32128301000101
-        PileHostConfig northHost1 = new PileHostConfig();
-        northHost1.setPileCode(PILE_CODE_NORTH_HOST_01);  // 32128301000101
-        northHost1.setGunCount(7);
-        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.format("%02d", i), 250, GunConfig.GunType.AIR_COOLED));
-        }
-        config.getPileHosts().add(northHost1);
-
-        // 北区-主机2: 8枪 (1*600kW + 7*250kW)
-        // 对应数据库: W2-B-CHARGING-HOST-02, pileCode=32128301000201
-        PileHostConfig northHost2 = new PileHostConfig();
-        northHost2.setPileCode(PILE_CODE_NORTH_HOST_02);  // 32128301000201
-        northHost2.setGunCount(8);
-        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.format("%02d", i), 250, GunConfig.GunType.AIR_COOLED));
-        }
-        config.getPileHosts().add(northHost2);
-
-        // 南区-主机1: 7枪 (1*600kW + 6*250kW)
-        // 对应数据库: W2-N-CHARGING-HOST-01, pileCode=32128302000101
+        // 南区-主机1: 8枪
+        // 对应数据库: device_code=32128300070001, pileCode=32128300070001
         PileHostConfig southHost1 = new PileHostConfig();
-        southHost1.setPileCode(PILE_CODE_SOUTH_HOST_01);  // 32128302000101
-        southHost1.setGunCount(7);
+        southHost1.setPileCode(PILE_CODE_SOUTH_HOST_01);
+        southHost1.setGunCount(8);
         southHost1.setDescription("南区-主机1");
-        // 枪号使用2位格式: 01-07
         southHost1.addGun(new GunConfig("01", 600, GunConfig.GunType.WATER_COOLED));
-        for (int i = 2; i <= 7; i++) {
+        for (int i = 2; i <= 8; i++) {
             southHost1.addGun(new GunConfig(String.format("%02d", i), 250, GunConfig.GunType.AIR_COOLED));
         }
         config.getPileHosts().add(southHost1);
 
-        // 南区-主机2: 8枪 (1*600kW + 7*250kW)
-        // 对应数据库: W2-N-CHARGING-HOST-02, pileCode=32128302000201
+        // 南区-主机2: 7枪
+        // 对应数据库: device_code=32128300070002, pileCode=32128300070002
         PileHostConfig southHost2 = new PileHostConfig();
-        southHost2.setPileCode(PILE_CODE_SOUTH_HOST_02);  // 32128302000201
-        southHost2.setGunCount(8);
+        southHost2.setPileCode(PILE_CODE_SOUTH_HOST_02);
+        southHost2.setGunCount(7);
         southHost2.setDescription("南区-主机2");
-        // 枪号使用2位格式: 01-08
         southHost2.addGun(new GunConfig("01", 600, GunConfig.GunType.WATER_COOLED));
-        for (int i = 2; i <= 8; i++) {
+        for (int i = 2; i <= 7; i++) {
             southHost2.addGun(new GunConfig(String.format("%02d", i), 250, GunConfig.GunType.AIR_COOLED));
         }
         config.getPileHosts().add(southHost2);
 
+        // 北区-主机1: 8枪
+        // 对应数据库: device_code=32128300060001, pileCode=32128300060001
+        PileHostConfig northHost1 = new PileHostConfig();
+        northHost1.setPileCode(PILE_CODE_NORTH_HOST_01);
+        northHost1.setGunCount(8);
+        northHost1.setDescription("北区-主机1");
+        northHost1.addGun(new GunConfig("01", 600, GunConfig.GunType.WATER_COOLED));
+        for (int i = 2; i <= 8; i++) {
+            northHost1.addGun(new GunConfig(String.format("%02d", i), 250, GunConfig.GunType.AIR_COOLED));
+        }
+        config.getPileHosts().add(northHost1);
+
+        // 北区-主机2: 7枪
+        // 对应数据库: device_code=32128300060002, pileCode=32128300060002
+        PileHostConfig northHost2 = new PileHostConfig();
+        northHost2.setPileCode(PILE_CODE_NORTH_HOST_02);
+        northHost2.setGunCount(7);
+        northHost2.setDescription("北区-主机2");
+        northHost2.addGun(new GunConfig("01", 600, GunConfig.GunType.WATER_COOLED));
+        for (int i = 2; i <= 7; i++) {
+            northHost2.addGun(new GunConfig(String.format("%02d", i), 250, GunConfig.GunType.AIR_COOLED));
+        }
+        config.getPileHosts().add(northHost2);
+
         return config;
     }
 
     /**
      * 创建简单的测试配置(单主机单枪)
-     * 使用北区主机1的桩编码
+     * 使用区主机1的桩编码
      */
     public static MockPileConfig createSimpleTestConfig() {
         MockPileConfig config = new MockPileConfig();
 
         PileHostConfig host = new PileHostConfig();
-        host.setPileCode(PILE_CODE_NORTH_HOST_01);  // 32128301000101
+        host.setPileCode(PILE_CODE_SOUTH_HOST_01);
         host.setGunCount(1);
         host.setDescription("测试主机");
         host.addGun(new GunConfig("01", 120, GunConfig.GunType.AIR_COOLED));

+ 2 - 2
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/common/config/MqttConfig.java

@@ -30,10 +30,10 @@ import javax.annotation.Resource;
 @Configuration
 public class MqttConfig {
     @Resource
-    private MqttServerProperty mqttProperty;
+    private MqttServerProperty prop;
 
     @Bean(name = "mqttTemplate")
     public MqttTemplate getMqttTemplate() throws MqttException {
-        return new MqttTemplate(mqttProperty.getHost(), mqttProperty.getClientId());
+        return new MqttTemplate(prop.getHost(), prop.getClientId(), prop.getUserName(), prop.getPassword());
     }
 }

+ 20 - 0
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/common/config/MqttServerProperty.java

@@ -29,6 +29,10 @@ public class MqttServerProperty {
 
     private String clientId;
 
+    private String userName;
+
+    private String password;
+
     public String getHost() {
         return host;
     }
@@ -44,4 +48,20 @@ public class MqttServerProperty {
     public void setClientId(String clientId) {
         this.clientId = clientId;
     }
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public void setUserName(String userName) {
+        this.userName = userName;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
 }

+ 14 - 4
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/common/mqtt/MqttTemplate.java

@@ -10,6 +10,7 @@
  */
 package com.ruoyi.ems.common.mqtt;
 
+import org.apache.commons.lang3.StringUtils;
 import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
 import org.eclipse.paho.client.mqttv3.MqttCallback;
 import org.eclipse.paho.client.mqttv3.MqttClient;
@@ -42,7 +43,7 @@ public class MqttTemplate {
      * @param broker   服务地址
      * @param clientId 客户端编码
      */
-    public MqttTemplate(String broker, String clientId) throws MqttException {
+    public MqttTemplate(String broker, String clientId, String userName, String password) throws MqttException {
         MemoryPersistence persistence = new MemoryPersistence();
 
         mqttClient = new MqttClient(broker, clientId, persistence);
@@ -51,6 +52,15 @@ public class MqttTemplate {
         // 保留会话
         connOpts.setCleanSession(false);
         connOpts.setAutomaticReconnect(true);
+
+        if (StringUtils.isNotEmpty(userName)) {
+            connOpts.setUserName(userName);
+        }
+
+        if (StringUtils.isNotEmpty(password)) {
+            connOpts.setPassword(password.toCharArray());
+        }
+
         // 建立连接
         mqttClient.connect(connOpts);
     }
@@ -125,10 +135,10 @@ public class MqttTemplate {
     /**
      * 发送十六进制数据 - 优化版本,直接接收字节数组
      *
-     * @param topic       主题
+     * @param topic        主题
      * @param payloadBytes 十六进制字节数组
-     * @param qos         消息级别
-     * @param retained    回复
+     * @param qos          消息级别
+     * @param retained     回复
      */
     public void sendHex(String topic, byte[] payloadBytes, int qos, boolean retained) {
         try {

+ 50 - 16
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/growatt/retrofit/GrowattTemplate.java

@@ -188,24 +188,58 @@ public class GrowattTemplate extends BaseApiTemplate {
      * @return 设备列表
      */
     public JSONObject getDeviceData(int type, List<String> snList) {
+        return getDeviceData(type, snList, 3, 2000);
+    }
+
+    /**
+     * 获取设备数据(支持重试)
+     *
+     * @param type      设备类型
+     * @param snList    设备SN集合
+     * @param maxRetry  最大重试次数
+     * @param baseDelay 基础延迟(毫秒),每次重试翻倍
+     * @return 设备数据,失败返回null
+     */
+    public JSONObject getDeviceData(int type, List<String> snList, int maxRetry, long baseDelay) {
         JSONObject retJson = null;
 
-        try {
-            // 执行调用
-            String devSnStr = StringUtils.join(snList, ",");
-            Call<String> call = api.getDeviceData(token, type, devSnStr);
-            Response<String> response = call.execute();
-            log.debug("getDeviceData response:{}", response);
-
-            Assert.isTrue(response.isSuccessful(), response.code(), response.message());
-            JSONObject resJson = JSONObject.parseObject(response.body());
-            Assert.isTrue(resJson.getIntValue("error_code") == 0, resJson.getIntValue("error_code"),
-                resJson.getString("error_msg"));
-
-            retJson = resJson.getJSONObject("data");
-        }
-        catch (Exception e) {
-            log.error("getDeviceData fail!", e);
+        for (int attempt = 1; attempt <= maxRetry; attempt++) {
+            try {
+                // 执行调用
+                String devSnStr = StringUtils.join(snList, ",");
+                Call<String> call = api.getDeviceData(token, type, devSnStr);
+                Response<String> response = call.execute();
+                log.debug("getDeviceData response:{}", response);
+
+                Assert.isTrue(response.isSuccessful(), response.code(), response.message());
+                JSONObject resJson = JSONObject.parseObject(response.body());
+                int errorCode = resJson.getIntValue("error_code");
+                String errorMsg = resJson.getString("error_msg");
+
+                // 频率限制,等待后重试
+                if ("error_frequently_access".equals(errorMsg)) {
+                    if (attempt < maxRetry) {
+                        long delay = baseDelay * (1L << (attempt - 1)); // 指数退避: base, 2*base, 4*base
+                        log.warn("API频率限制,第{}次重试,等待{}ms", attempt, delay);
+                        Thread.sleep(delay);
+                        continue;
+                    }
+                    log.error("API频率限制,已达最大重试次数{}", maxRetry);
+                }
+
+                Assert.isTrue(errorCode == 0, errorCode, errorMsg);
+                retJson = resJson.getJSONObject("data");
+                break; // 成功,跳出重试循环
+            }
+            catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                log.error("getDeviceData重试被中断", e);
+                break;
+            }
+            catch (Exception e) {
+                log.error("getDeviceData fail!", e);
+                break;
+            }
         }
 
         return retJson;

+ 3 - 1
ems/ems-cloud/ems-dev-adapter/src/main/resources/application-local.yml

@@ -53,8 +53,10 @@ async:
 
 mqtt:
   server:
-    host: tcp://127.0.0.1:1883
+    host: tcp://xt.wenhq.top:8581
     client_id: ems-dev-adapter-ttttt
+    user-name: device_dlq_user
+    password: Huashe@123456
   executor:
     msgHandle:
       corePoolSize: 20