learshaw 4 месяцев назад
Родитель
Сommit
492fa743d9
21 измененных файлов с 1121 добавлено и 71 удалено
  1. 1 0
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/ClientInit.java
  2. 1 1
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/core/MqttMessageHandler.java
  3. 2 3
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/core/MqttTemplate.java
  4. 2 2
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/handle/BaseDevHandler.java
  5. 56 19
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/handle/GeekOpenCbHandler.java
  6. 423 24
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/handle/Keka86BsHandler.java
  7. 37 4
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/handle/RootMsgHandler.java
  8. 59 0
      ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/model/keka/DeviceStatus.java
  9. 1 1
      ems/ems-cloud/ems-dev-adapter/src/main/resources/application-local.yml
  10. 41 6
      ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/controller/OpEnergyStrategyController.java
  11. 10 0
      ems/ems-core/src/main/java/com/ruoyi/ems/mapper/EmsObjAttrValueMapper.java
  12. 69 0
      ems/ems-core/src/main/java/com/ruoyi/ems/mapper/OpEnergyStrategyContextMapper.java
  13. 10 0
      ems/ems-core/src/main/java/com/ruoyi/ems/service/IEmsObjAttrValueService.java
  14. 69 0
      ems/ems-core/src/main/java/com/ruoyi/ems/service/IOpEnergyStrategyContextService.java
  15. 6 0
      ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/EmsObjAttrValueServiceImpl.java
  16. 108 0
      ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/OpEnergyStrategyContextServiceImpl.java
  17. 2 2
      ems/ems-core/src/main/resources/mapper/ems/EmsObjAbilityCallLogMapper.xml
  18. 5 0
      ems/ems-core/src/main/resources/mapper/ems/EmsObjAttrValueMapper.xml
  19. 93 0
      ems/ems-core/src/main/resources/mapper/ems/OpEnergyStrategyContextMapper.xml
  20. 0 9
      ems/sql/ems_init_data.sql
  21. 126 0
      ems/sql/ems_init_data_test.sql

+ 1 - 0
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/ClientInit.java

@@ -44,6 +44,7 @@ public class ClientInit implements CommandLineRunner {
     public void run(String... args) {
         try {
             template.subscribe("/server/dlq/#", 0, rootMsgHandler);
+            template.subscribe("/sc/dtu/rep/#", 0, rootMsgHandler);
         }
         catch (Exception e) {
             log.error("init fail!", e);

+ 1 - 1
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/core/MqttMessageHandler.java

@@ -24,5 +24,5 @@ public interface MqttMessageHandler {
      * 消息出来
      * @param payload 消息报文
      */
-    void handle(String topic, String payload);
+    void handle(String topic, byte[] payload);
 }

+ 2 - 3
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/core/MqttTemplate.java

@@ -20,7 +20,6 @@ import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.math.BigInteger;
 import java.nio.charset.StandardCharsets;
 
 /**
@@ -86,8 +85,8 @@ public class MqttTemplate {
                 // 消息接收处理
                 @Override
                 public void messageArrived(String topic, MqttMessage message) throws Exception {
-                    String content = new String(message.getPayload(), StandardCharsets.UTF_8);
-                    handler.handle(topic, content);
+                    byte[] payload = message.getPayload();
+                    handler.handle(topic, payload);
                 }
 
                 // 消息发送处理

+ 2 - 2
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/handle/BaseDevHandler.java

@@ -181,13 +181,13 @@ public abstract class BaseDevHandler {
         objEventLogService.insertLog(eventLog);
     }
 
-    protected EmsObjAbilityCallLog saveCallLog(AbilityPayload abilityParam, long sendTime, int callStatus) {
+    protected EmsObjAbilityCallLog saveCallLog(AbilityPayload abilityParam, String callPayload, long sendTime, int callStatus) {
         EmsObjAbilityCallLog objAbilityCallLog = new EmsObjAbilityCallLog();
         objAbilityCallLog.setObjCode(abilityParam.getObjCode());
         objAbilityCallLog.setModelCode(abilityParam.getModelCode());
         objAbilityCallLog.setAbilityKey(abilityParam.getAbilityKey());
         objAbilityCallLog.setCallTime(new Date(sendTime));
-        objAbilityCallLog.setCallPayload(abilityParam.getAbilityParam());
+        objAbilityCallLog.setCallPayload(callPayload);
         objAbilityCallLog.setCallStatus(callStatus);
         objAbilityCallLogService.addLog(objAbilityCallLog);
         return objAbilityCallLog;

+ 56 - 19
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/handle/GeekOpenCbHandler.java

@@ -11,6 +11,7 @@
 package com.ruoyi.ems.handle;
 
 import com.alibaba.fastjson2.JSONObject;
+import com.huashe.common.domain.JsonEntity;
 import com.huashe.common.exception.Assert;
 import com.huashe.common.exception.BusinessException;
 import com.huashe.common.utils.DateUtils;
@@ -76,23 +77,19 @@ public class GeekOpenCbHandler extends BaseMeterDevHandler {
     public CallResponse<Void> call(AbilityPayload abilityParam) {
         CallResponse<Void> callResponse = null;
 
-        JSONObject sendObject = JSONObject.parseObject(abilityParam.getAbilityParam());
-        String type = sendObject.getString("type");
+        JSONObject msgBody = buildSendMsg(abilityParam);
+        String messageId = msgBody.getString("messageId");
         String deviceCode = abilityParam.getObjCode();
-        String messageId = StringUtils.equals("statistic", type) ? "auto" : ("CALL-" + IdUtils.generateMessageId());
-        String msgBody = addMsgId(abilityParam.getAbilityParam(), "messageId", messageId);
 
         // 发送消息到MQTT服务器
         long sendTime = System.currentTimeMillis();
         String topic = TOPIC_PREFIX + deviceCode;
-        sendMqttMsg(topic, msgBody, 2, false);
+        sendMqttMsg(topic, msgBody.toString(), 2, false);
 
         // 需要监测回包的流程
-        if (StringUtils.equals(type, "event") || (StringUtils.equals(type, "setting") && (
-            sendObject.containsKey("keyLock") || sendObject.containsKey("timerEnable") || sendObject.containsKey(
-                "onState")))) {
+        if (StringUtils.startsWith(messageId, "CALL-")) {
             // 写入日志
-            EmsObjAbilityCallLog logItem = saveCallLog(abilityParam, sendTime, 1);
+            EmsObjAbilityCallLog logItem = saveCallLog(abilityParam, msgBody.toString(), sendTime, 1);
 
             while (true) {
                 MqttCacheMsg cacheMsg = messageCache.readDevResponse(messageId);
@@ -100,7 +97,7 @@ public class GeekOpenCbHandler extends BaseMeterDevHandler {
                 if (null != cacheMsg) {
                     String receiveParam = cacheMsg.getPayload();
                     JSONObject receiveObject = JSONObject.parseObject(receiveParam);
-                    callResponse = checkResult(sendObject, receiveObject);
+                    callResponse = checkResult(msgBody, receiveObject);
                     updateCallLog(logItem, cacheMsg, callResponse.getCode() == 0 ? 0 : 2);
 
                     break;
@@ -118,7 +115,7 @@ public class GeekOpenCbHandler extends BaseMeterDevHandler {
             }
         }
         else {
-            saveCallLog(abilityParam, sendTime, 0);
+            saveCallLog(abilityParam, msgBody.toString(), sendTime, 0);
             callResponse = new CallResponse<>(0, "执行成功!");
         }
 
@@ -137,7 +134,7 @@ public class GeekOpenCbHandler extends BaseMeterDevHandler {
                 // 自动上报数据: 1.更新属性值,2.更新电量计量数据
                 if (StringUtils.equals("auto", messageId)) {
                     updateAutoAttr(device, msgBody);
-                    saveCallLog(deviceCode, device.getDeviceModel(), "电量上报", 0, null, payload);
+                    saveCallLog(deviceCode, device.getDeviceModel(), "triggerSync", 0, null, payload);
                 }
                 // 前序调用的响应消息:1.写入消息队列,2.更新属性值
                 else if (StringUtils.isNotEmpty(messageId) && StringUtils.startsWith(messageId, "CALL-")) {
@@ -148,7 +145,7 @@ public class GeekOpenCbHandler extends BaseMeterDevHandler {
                 // 设备同步数据(INFO类,协议类):更新基础属性值
                 else {
                     updateBaseAttr(device, msgBody);
-                    saveCallLog(deviceCode, device.getDeviceModel(), "信息上报", 0, null, payload);
+                    saveCallLog(deviceCode, device.getDeviceModel(), "triggerSync", 0, null, payload);
                 }
 
                 // 设备消息抵达,将数据库离线状态改为在线
@@ -362,15 +359,55 @@ public class GeekOpenCbHandler extends BaseMeterDevHandler {
     }
 
     /**
-     * 添加消息ID到消息中
+     * 构造下发指令
      *
-     * @param msg 消息
+     * @param abilityParam 消息
      * @return msgBody
      */
-    public String addMsgId(String msg, String msgIdKey, String msgIdValue) {
-        JSONObject json = JSONObject.parseObject(msg);
-        json.put(msgIdKey, msgIdValue);
-        return json.toString();
+    public JSONObject buildSendMsg(AbilityPayload abilityParam) {
+        String abilityKey = abilityParam.getAbilityKey();
+        JSONObject jsonBody = null;
+
+        if (StringUtils.equals("OnOffCtl", abilityKey)) {
+            jsonBody = JsonEntity.objBuilder().putKv("type", "event")
+                .putKv("key", Integer.parseInt(abilityParam.getAbilityParam()))
+                .putKv("messageId", "CALL-" + IdUtils.generateMessageId()).build().getJsonObj();
+        }
+        else if (StringUtils.equals("KeyLockCtl", abilityKey)) {
+            jsonBody = JsonEntity.objBuilder().putKv("type", "setting")
+                .putKv("keyLock", Integer.parseInt(abilityParam.getAbilityParam()))
+                .putKv("messageId", "CALL-" + IdUtils.generateMessageId()).build().getJsonObj();
+        }
+        else if (StringUtils.equals("settingCtl", abilityKey)) {
+            jsonBody = JsonEntity.objBuilder().putKv("type", "setting").putKv("system", abilityParam.getAbilityParam())
+                .putKv("messageId", "auto").build().getJsonObj();
+        }
+        else if (StringUtils.equals("settAutoReport", abilityKey)) {
+            JsonEntity.ObjBuilder onOffJson = JsonEntity.objBuilder();
+            onOffJson.putKv("type", "setting").putKv("messageId", "auto");
+            Integer timerInterval = Integer.parseInt(abilityParam.getAbilityParam());
+
+            if (0 == timerInterval) {
+                onOffJson.putKv("timerEnable", 0);
+            }
+            else {
+                onOffJson.putKv("timerEnable", 1);
+                onOffJson.putKv("timerInterval", timerInterval);
+            }
+
+            jsonBody = onOffJson.build().getJsonObj();
+        }
+        else if (StringUtils.equals("setOnState", abilityKey)) {
+            jsonBody = JsonEntity.objBuilder().putKv("type", "setting")
+                .putKv("onState", Integer.parseInt(abilityParam.getAbilityParam()))
+                .putKv("messageId", "CALL-" + IdUtils.generateMessageId()).build().getJsonObj();
+        }
+        else if (StringUtils.equals("triggerSync", abilityKey)) {
+            jsonBody = JsonEntity.objBuilder().putKv("type", abilityParam.getAbilityParam()).putKv("messageId", "auto")
+                .build().getJsonObj();
+        }
+
+        return jsonBody;
     }
 
     /**

+ 423 - 24
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/handle/Keka86BsHandler.java

@@ -19,10 +19,13 @@ import com.ruoyi.ems.enums.DevOnlineStatus;
 import com.ruoyi.ems.model.AbilityPayload;
 import com.ruoyi.ems.model.CallResponse;
 import com.ruoyi.ems.model.QueryDevice;
+import com.ruoyi.ems.model.keka.DeviceStatus;
 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.Qualifier;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
@@ -54,23 +57,59 @@ public class Keka86BsHandler extends BaseDevHandler {
     // 设备模型代码
     private static final String MODE_CODE = "M_W2_QS_KEKA_86";
 
+    // Modbus功能码
+    private static final byte FUNC_READ = 0x03;   // 读保持寄存器
+
+    private static final byte FUNC_WRITE = 0x06;  // 写单个寄存器
+
+    // 设备地址
+    private static final byte DEVICE_ADDR = 0x01;
+
+    // 灯控制寄存器地址 (44100=0x1004开启, 44101=0x1005关闭)
+    // 这里保持0x1021以匹配实际测试指令,如需修改请改为0x1004
+    private static final int LIGHT_BASE_ADDR = 0x1021;
+
+    // 控制值
+    private static final int VALUE_ON = 0x0001;   // 开
+
+    private static final int VALUE_OFF = 0x0000;  // 关
+
+    // 读取寄存器数量
+    private static final int READ_COUNT = 0x0002; // 读2个寄存器
+
     @Override
     public CallResponse<Void> call(AbilityPayload abilityParam) {
         CallResponse<Void> callResponse = null;
 
-        // 获取协议属性
         List<EmsObjAttrValue> attrList = objAttrValueService.selectByObjCode(MODE_CODE, abilityParam.getObjCode());
         Assert.notEmpty(attrList, -1, "设备协议属性未配置!");
         Map<String, String> attrMap = attrList.stream()
             .collect(Collectors.toMap(EmsObjAttrValue::getAttrKey, EmsObjAttrValue::getAttrValue));
 
         String gatewayId = attrMap.get("gatewayId");
+        String buttonId = attrMap.get("buttonId");
+
+        if (StringUtils.equals("on-off", abilityParam.getAbilityKey())) {
+            String payload = buildControlCommand(Integer.parseInt(buttonId),
+                Integer.parseInt(abilityParam.getAbilityParam()));
+            // 发送消息到MQTT服务器
+            String topic = TOPIC_PREFIX + gatewayId;
+            sendMqttHex(topic, payload, 2, false);
+            saveCallLog(abilityParam, payload, System.currentTimeMillis(), 0);
+            callResponse = new CallResponse<>(0, "执行成功!");
+        }
+        else if (StringUtils.equals("syncState", abilityParam.getAbilityKey())) {
+            String payload = buildReadCommand(Integer.parseInt(buttonId));
+            // 发送消息到MQTT服务器
+            String topic = TOPIC_PREFIX + gatewayId;
+            sendMqttHex(topic, payload, 2, false);
+            saveCallLog(abilityParam, payload, System.currentTimeMillis(), 0);
+            callResponse = new CallResponse<>(0, "执行成功!");
+        }
+        else {
+            callResponse = new CallResponse<>(-1, "不支持的能力键!");
+        }
 
-        // 发送消息到MQTT服务器
-        String topic = TOPIC_PREFIX + gatewayId;
-        sendMqttHex(topic, abilityParam.getAbilityParam(), 2, false);
-        saveCallLog(abilityParam, System.currentTimeMillis(), 0);
-        callResponse = new CallResponse<>(0, "执行成功!");
         return callResponse;
     }
 
@@ -81,34 +120,262 @@ public class Keka86BsHandler extends BaseDevHandler {
         return deviceService.selectList(queryDevice);
     }
 
-    /**
-     * 定时刷新在线状态
-     * <br/>每小时执行一次,扫描2个小时无消息的设备,标记为离线状态
-     */
     @Override
     public void refreshOnline() {
-        long threshold = 2 * 60 * 60 * 1000;
-        long currentTime = new Date().getTime();
+    }
+
+    @Async("msgHandleExecutor")
+    public void msgHandle(String gatewayId, String payload) {
+        try {
+            log.info("[Keka86] 网关:{}, 收到消息:{}", gatewayId, payload);
+
+            // 检查消息长度
+            String[] hexBytes = payload.trim().split("\\s+");
+            if (hexBytes.length < 7) {
+                log.warn("[Keka86] 消息长度不足,忽略: {}", payload);
+                return;
+            }
+
+            // 判断消息类型 (功能码在第2个字节,索引为1)
+            String funcCodeStr = hexBytes[1].toUpperCase();
+            int funcCode = Integer.parseInt(funcCodeStr, 16);
+
+            if (funcCode == 0x03) {
+                // 读取响应 (03H)
+                handleReadResponse(gatewayId, payload);
+            }
+            else if (funcCode == 0x06) {
+                // 写入确认响应 (06H) - 这就是你遇到的情况
+                handleWriteResponse(gatewayId, payload);
+            }
+            else if ((funcCode & 0x80) != 0) {
+                // 错误响应 (功能码最高位为1,如83H、86H)
+                byte originalFuncCode = (byte)(funcCode & 0x7F);
+                handleErrorResponse(gatewayId, payload, originalFuncCode);
+            }
+            else {
+                log.warn("[Keka86] 未知功能码: 0x{}, 消息: {}", funcCodeStr, payload);
+            }
+
+        } catch (Exception e) {
+            log.error("[Keka86] 网关:{}, 消息处理异常", gatewayId, e);
+        }
+    }
+
+    private String getDeviceCode(String gatewayId, int buttonId) {
+        List<EmsObjAttrValue> list = objAttrValueService.selectByAttrKeyValue(MODE_CODE, "gatewayId", gatewayId);
+
+        if (CollectionUtils.isNotEmpty(list)) {
+            for (EmsObjAttrValue gatewayIdValue : list) {
+                EmsObjAttrValue buttonIdValue = objAttrValueService.selectObjAttrValue(MODE_CODE,
+                    gatewayIdValue.getObjCode(), "buttonId");
+
+                if (null != buttonIdValue && StringUtils.equals(buttonIdValue.getObjCode(),
+                    gatewayIdValue.getObjCode())) {
+                    return gatewayIdValue.getObjCode();
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * 生成控制指令 (开/关)
+     *
+     * @param lightId 灯ID (1, 2, 3)
+     * @param state   状态 (1=开, 0=关)
+     * @return 十六进制字符串 (如: "01 06 10 21 00 01 1C C0")
+     */
+    public String buildControlCommand(int lightId, int state) {
+        if (lightId < 1 || lightId > 3) {
+            throw new IllegalArgumentException("灯ID必须是1-3");
+        }
+        if (state != 0 && state != 1) {
+            throw new IllegalArgumentException("状态必须是0(关)或1(开)");
+        }
+
+        // 计算寄存器地址
+        int registerAddr = LIGHT_BASE_ADDR + (lightId - 1);
+
+        // 控制值
+        int value = (state == 1) ? VALUE_ON : VALUE_OFF;
+
+        // 构建Modbus帧 (不含CRC)
+        byte[] frame = new byte[6];
+        frame[0] = DEVICE_ADDR;                          // 设备地址
+        frame[1] = FUNC_WRITE;                           // 功能码 06H
+        frame[2] = (byte) (registerAddr >> 8);           // 寄存器地址高字节
+        frame[3] = (byte) (registerAddr & 0xFF);         // 寄存器地址低字节
+        frame[4] = (byte) (value >> 8);                  // 数据高字节
+        frame[5] = (byte) (value & 0xFF);                // 数据低字节
+
+        // 计算并添加CRC
+        int crc = calculateCRC16(frame);
+        byte[] fullFrame = new byte[8];
+        System.arraycopy(frame, 0, fullFrame, 0, 6);
+        fullFrame[6] = (byte) (crc & 0xFF);              // CRC低字节在前
+        fullFrame[7] = (byte) (crc >> 8);                // CRC高字节在后
+
+        return bytesToHexString(fullFrame);
+    }
+
+    /**
+     * 生成读取指令
+     *
+     * @param lightId 灯ID (1, 2, 3)
+     * @return 十六进制字符串 (如: "01 03 10 21 00 02 90 C1")
+     */
+    public String buildReadCommand(int lightId) {
+        if (lightId < 1 || lightId > 3) {
+            throw new IllegalArgumentException("灯ID必须是1-3");
+        }
+
+        // 计算寄存器地址
+        int registerAddr = LIGHT_BASE_ADDR + (lightId - 1);
+
+        // 构建Modbus帧 (不含CRC)
+        byte[] frame = new byte[6];
+        frame[0] = DEVICE_ADDR;                          // 设备地址
+        frame[1] = FUNC_READ;                            // 功能码 03H
+        frame[2] = (byte) (registerAddr >> 8);           // 寄存器地址高字节
+        frame[3] = (byte) (registerAddr & 0xFF);         // 寄存器地址低字节
+        frame[4] = (byte) (READ_COUNT >> 8);             // 寄存器数量高字节
+        frame[5] = (byte) (READ_COUNT & 0xFF);           // 寄存器数量低字节
+
+        // 计算并添加CRC
+        int crc = calculateCRC16(frame);
+        byte[] fullFrame = new byte[8];
+        System.arraycopy(frame, 0, fullFrame, 0, 6);
+        fullFrame[6] = (byte) (crc & 0xFF);              // CRC低字节
+        fullFrame[7] = (byte) (crc >> 8);                // CRC高字节
+
+        return bytesToHexString(fullFrame);
+    }
+
+    /**
+     * 解析读取响应
+     * 响应格式: [设备地址][功能码][字节数][数据...][CRC_L][CRC_H]
+     * 根据协议文档2.1节:
+     * - 字节1: 设备地址
+     * - 字节2: 03H (功能码)
+     * - 字节3: 返回数据字节个数
+     * - 字节4-5: 返回第一个寄存器数据高字节+低字节
+     * - 字节6-7: 返回第二个寄存器数据高字节+低字节
+     * - 字节8-9: CRC低+CRC高
+     *
+     * @param hexResponse 十六进制响应字符串
+     * @param lightId     灯ID (用于返回完整状态信息)
+     * @return DeviceStatus 设备状态对象
+     */
+    public DeviceStatus parseReadResponse(String hexResponse, int lightId) {
+        byte[] response = hexStringToBytes(hexResponse);
+
+        if (response.length < 7) {
+            throw new IllegalArgumentException("响应数据长度不足");
+        }
+
+        // 验证CRC
+        byte[] dataWithoutCrc = new byte[response.length - 2];
+        System.arraycopy(response, 0, dataWithoutCrc, 0, response.length - 2);
+        int calculatedCrc = calculateCRC16(dataWithoutCrc);
+        int receivedCrc = (response[response.length - 2] & 0xFF) | ((response[response.length - 1] & 0xFF) << 8);
+
+        if (calculatedCrc != receivedCrc) {
+            throw new IllegalArgumentException(
+                String.format("CRC校验失败: 计算值=0x%04X, 接收值=0x%04X", calculatedCrc, receivedCrc));
+        }
 
-        List<EmsDevice> deviceList = getDeviceList();
+        // 解析数据
+        byte deviceAddr = response[0];
+        byte funcCode = response[1];
+        byte byteCount = response[2];
 
-        if (CollectionUtils.isNotEmpty(deviceList)) {
-            for (EmsDevice device : deviceList) {
-                Object cacheTimeObj = redisService.getCacheMapValue(device.getDeviceCode(), MQTT_LAST_TIME);
+        if (funcCode != FUNC_READ) {
+            // 检查是否是错误响应 (功能码最高位为1表示错误)
+            if ((funcCode & 0x80) != 0) {
+                byte errorCode = response[2];
+                String errorMsg = getModbusErrorMessage(errorCode);
+                throw new IllegalArgumentException("Modbus错误响应: " + errorMsg);
+            }
+            throw new IllegalArgumentException("功能码错误: 0x" + String.format("%02X", funcCode & 0xFF));
+        }
+
+        // 提取状态数据 (第一个寄存器的值)
+        int value = ((response[3] & 0xFF) << 8) | (response[4] & 0xFF);
 
-                if (null != cacheTimeObj) {
-                    String lastMsgTime = cacheTimeObj.toString();
+        // 解析状态: 0x0001=开, 0x0000=关
+        int status = (value == VALUE_ON) ? 1 : 0;
+
+        return new DeviceStatus(deviceAddr & 0xFF, lightId, status, hexResponse);
+    }
 
-                    // 计算最后一次消息至今的时间差
-                    long time =
-                        currentTime - DateUtils.stringToDate(lastMsgTime, DateUtils.YYYY_MM_DD_HH_MM_SS).getTime();
+    /**
+     * 获取Modbus错误信息
+     */
+    private static String getModbusErrorMessage(byte errorCode) {
+        switch (errorCode) {
+            case 0x01:
+                return "01H - 功能码错误";
+            case 0x02:
+                return "02H - 非法数据地址";
+            case 0x03:
+                return "03H - 非法数据值";
+            case 0x04:
+                return "04H - 设备故障";
+            case 0x05:
+                return "05H - 查询帧正确,但设备正在处理";
+            case 0x06:
+                return "06H - 设备忙";
+            default:
+                return String.format("未知错误码: 0x%02X", errorCode & 0xFF);
+        }
+    }
 
-                    if (time > threshold) {
-                        refreshStatus(device, DevOnlineStatus.OFFLINE);
-                    }
+    /**
+     * 计算Modbus CRC-16
+     * 多项式: 0xA001 (反向)
+     */
+    private static 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 >>= 1;
+                    crc ^= 0xA001;
+                }
+                else {
+                    crc >>= 1;
                 }
             }
         }
+        return crc;
+    }
+
+    /**
+     * 字节数组转十六进制字符串
+     */
+    private static String bytesToHexString(byte[] bytes) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < bytes.length; i++) {
+            if (i > 0)
+                sb.append(" ");
+            sb.append(String.format("%02X", bytes[i] & 0xFF));
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 十六进制字符串转字节数组
+     */
+    private static byte[] hexStringToBytes(String hexString) {
+        String[] hexArray = hexString.trim().split("\\s+");
+        byte[] bytes = new byte[hexArray.length];
+        for (int i = 0; i < hexArray.length; i++) {
+            bytes[i] = (byte) Integer.parseInt(hexArray[i], 16);
+        }
+        return bytes;
     }
 
     /**
@@ -123,4 +390,136 @@ public class Keka86BsHandler extends BaseDevHandler {
         log.info("[Send] Topic:{}, message:{}, qos:{}, retained:{}", topic, payload, qos, retained);
         mqttTemplate.sendHex(topic, payload, 2, false);
     }
+
+    /**
+     * 处理读取响应 (功能码 03H)
+     * 响应格式: 01 03 04 00 01 00 00 FA 33
+     */
+    private void handleReadResponse(String gatewayId, String hexMessage) {
+        try {
+            // 从响应中提取寄存器地址,推断是哪个按键
+            byte[] response = hexStringToBytes(hexMessage);
+
+            // 方法1: 如果响应中没有地址信息,需要从上下文推断
+            // 这里先假设返回的是第一个寄存器的数据
+            // 实际需要根据之前发送的读取指令来匹配
+
+            // 临时处理: 遍历所有可能的按键
+            for (int buttonId = 1; buttonId <= 3; buttonId++) {
+                DeviceStatus deviceStatus = parseReadResponse(hexMessage, buttonId);
+                String deviceCode = getDeviceCode(gatewayId, buttonId);
+
+                if (null == deviceCode) {
+                    log.debug("[Keka86-Read] 网关:{}, 按键{} 未注册", gatewayId, buttonId);
+                    continue;
+                }
+
+                // 更新设备状态
+                updateDeviceStatus(deviceCode, deviceStatus);
+
+                log.info("[Keka86-Read] 网关:{}, 设备:{}, 按键{}, 状态:{}",
+                    gatewayId, deviceCode, buttonId,
+                    deviceStatus.getStatus() == 1 ? "开" : "关");
+            }
+
+        } catch (Exception e) {
+            log.error("[Keka86-Read] 网关:{}, 解析失败, Hex:{}", gatewayId, hexMessage, e);
+        }
+    }
+
+    /**
+     * 处理写入响应 (功能码 06H)
+     * 响应格式: 01 06 10 21 00 00 DD 00
+     * 这是对写入指令的确认,表示设备已接收并执行
+     */
+    private void handleWriteResponse(String gatewayId, String hexMessage) {
+        try {
+            byte[] response = hexStringToBytes(hexMessage);
+
+            // 验证CRC
+            byte[] dataWithoutCrc = new byte[response.length - 2];
+            System.arraycopy(response, 0, dataWithoutCrc, 0, response.length - 2);
+            int calculatedCrc = calculateCRC16(dataWithoutCrc);
+            int receivedCrc = (response[response.length - 2] & 0xFF) |
+                ((response[response.length - 1] & 0xFF) << 8);
+
+            if (calculatedCrc != receivedCrc) {
+                log.error("[Keka86-Write] CRC校验失败, 网关:{}, Hex:{}", gatewayId, hexMessage);
+                return;
+            }
+
+            // 解析寄存器地址和值
+            int registerAddr = ((response[2] & 0xFF) << 8) | (response[3] & 0xFF);
+            int value = ((response[4] & 0xFF) << 8) | (response[5] & 0xFF);
+
+            // 根据寄存器地址推断按键ID
+            int buttonId = registerAddr - LIGHT_BASE_ADDR + 1;
+            int status = (value == VALUE_ON) ? 1 : 0;
+
+            log.info("[Keka86-Write] 网关:{}, 写入确认成功, 寄存器:0x{}, 按键:{}, 状态:{}",
+                gatewayId,
+                String.format("%04X", registerAddr),
+                buttonId,
+                status == 1 ? "开" : "关");
+
+            // 写入成功后更新数据库状态
+            String deviceCode = getDeviceCode(gatewayId, buttonId);
+            if (deviceCode != null) {
+                DeviceStatus deviceStatus = new DeviceStatus(
+                    response[0] & 0xFF, buttonId, status, hexMessage);
+                updateDeviceStatus(deviceCode, deviceStatus);
+            }
+
+        } catch (Exception e) {
+            log.error("[Keka86-Write] 网关:{}, 解析失败, Hex:{}", gatewayId, hexMessage, e);
+        }
+    }
+
+    /**
+     * 处理错误响应
+     * 响应格式: 01 83 01 CRC_L CRC_H (读取错误)
+     *          01 86 01 CRC_L CRC_H (写入错误)
+     */
+    private void handleErrorResponse(String gatewayId, String hexMessage, byte originalFuncCode) {
+        try {
+            byte[] response = hexStringToBytes(hexMessage);
+            if (response.length >= 3) {
+                byte errorCode = response[2];
+                String errorMsg = getModbusErrorMessage(errorCode);
+
+                String funcName = (originalFuncCode == 0x03) ? "读取" :
+                    (originalFuncCode == 0x06) ? "写入" : "未知";
+
+                log.error("[Keka86-Error] 网关:{}, {}操作失败, 错误:{}, Hex:{}",
+                    gatewayId, funcName, errorMsg, hexMessage);
+            }
+        } catch (Exception e) {
+            log.error("[Keka86-Error] 网关:{}, 错误响应解析失败, Hex:{}",
+                gatewayId, hexMessage, e);
+        }
+    }
+
+    /**
+     * 更新设备状态到数据库
+     */
+    private void updateDeviceStatus(String deviceCode, DeviceStatus deviceStatus) {
+        try {
+            EmsObjAttrValue value = objAttrValueService.selectObjAttrValue(
+                MODE_CODE, deviceCode, "Switch");
+            String newValue = String.valueOf(deviceStatus.getStatus());
+
+            if (null != value && !StringUtils.equals(value.getAttrValue(), newValue)) {
+                objAttrValueService.updateObjAttrValue(MODE_CODE, deviceCode, "Switch", newValue);
+                log.info("[Keka86-Update] 设备:{}, 状态更新: {} -> {}",
+                    deviceCode, value.getAttrValue(), newValue);
+            }
+            else if (null == value) {
+                value = new EmsObjAttrValue(deviceCode, MODE_CODE, "Switch", newValue);
+                objAttrValueService.mergeObjAttrValue(value);
+                log.info("[Keka86-Update] 设备:{}, 状态新增: {}", deviceCode, newValue);
+            }
+        } catch (Exception e) {
+            log.error("[Keka86-Update] 设备:{}, 状态更新失败", deviceCode, e);
+        }
+    }
 }

+ 37 - 4
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/handle/RootMsgHandler.java

@@ -17,6 +17,8 @@ import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.nio.charset.StandardCharsets;
+
 /**
  * 消息处理handle
  * <功能详细描述>
@@ -33,18 +35,49 @@ public class RootMsgHandler implements MqttMessageHandler {
     @Autowired
     private GeekOpenCbHandler geekOpenCbHandler;
 
+    @Autowired
+    private Keka86BsHandler keka86BsHandler;
+
     @Override
-    public void handle(String topic, String payload) {
+    public void handle(String topic, byte[] message) {
         try {
-            log.info("[Receive] Topic:{}, message:{}", topic, payload);
-
             if (StringUtils.startsWith(topic, "/server/dlq/")) {
+                String payload = convertToString(message);
+                log.info("[Receive] Topic:{}, message:{}", topic, payload);
+
                 String deviceCode = StringUtils.substringAfter(topic, "/server/dlq/");
                 geekOpenCbHandler.msgHandle(deviceCode, payload);
             }
+            else if (StringUtils.startsWith(topic, "/sc/dtu/rep/")) {
+                String payload = convertToHexString(message);
+                log.info("[Receive] Topic:{}, message:{}", topic, payload);
+                String deviceCode = StringUtils.substringAfter(topic, "/sc/dtu/rep/");
+                keka86BsHandler.msgHandle(deviceCode, payload);
+            }
         }
         catch (Exception e) {
-            log.error("[Handle]Topic:{}, message:{}", topic, payload, e);
+            log.error("[Handle]Topic:{}, message:{}", topic, message, e);
+        }
+    }
+
+    /**
+     * 将payload转换为字符串 (用于文本类消息)
+     */
+    private String convertToString(byte[] message) {
+        return new String(message, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * 将payload转换为十六进制字符串 (用于Modbus二进制消息)
+     */
+    private String convertToHexString(byte[] message) {
+        StringBuilder sb = new StringBuilder();
+
+        for (int i = 0; i < message.length; i++) {
+            if (i > 0)
+                sb.append(" ");
+            sb.append(String.format("%02X", message[i] & 0xFF));
         }
+        return sb.toString();
     }
 }

+ 59 - 0
ems/ems-cloud/ems-dev-adapter/src/main/java/com/ruoyi/ems/model/keka/DeviceStatus.java

@@ -0,0 +1,59 @@
+/*
+ * 文 件 名:  DeviceStatus
+ * 版    权:  华设设计集团股份有限公司
+ * 描    述:  <描述>
+ * 修 改 人:  lvwenbin
+ * 修改时间:  2025/12/4
+ * 跟踪单号:  <跟踪单号>
+ * 修改单号:  <修改单号>
+ * 修改内容:  <修改内容>
+ */
+package com.ruoyi.ems.model.keka;
+
+/**
+ * 设备状态类
+ * <功能详细描述>
+ *
+ * @author lvwenbin
+ * @version [版本号, 2025/12/4]
+ * @see [相关类/方法]
+ * @since [产品/模块版本]
+ */
+public class DeviceStatus {
+    private int deviceAddr;
+
+    private int buttonId;     // 灯ID (1/2/3)
+
+    private int status;      // 1=开, 0=关
+
+    private String rawResponse;
+
+    public DeviceStatus(int deviceAddr, int buttonId, int status, String rawResponse) {
+        this.deviceAddr = deviceAddr;
+        this.buttonId = buttonId;
+        this.status = status;
+        this.rawResponse = rawResponse;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("设备地址: %d, 灯%d状态: %s, 原始响应: %s", deviceAddr, buttonId, status == 1 ? "开" : "关",
+            rawResponse);
+    }
+
+    public int getDeviceAddr() {
+        return deviceAddr;
+    }
+
+    public int getButtonId() {
+        return buttonId;
+    }
+
+    public int getStatus() {
+        return status;
+    }
+
+    public String getRawResponse() {
+        return rawResponse;
+    }
+}

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

@@ -46,7 +46,7 @@ spring:
 mqtt:
   server:
     host: tcp://xt.wenhq.top:8581
-    client_id: ems-dev-adapter1
+    client_id: ems-dev-adapter-local
   executor:
     msgHandle:
       corePoolSize: 20

+ 41 - 6
ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/controller/OpEnergyStrategyController.java

@@ -9,12 +9,14 @@ import com.ruoyi.common.log.annotation.Log;
 import com.ruoyi.common.log.enums.BusinessType;
 import com.ruoyi.common.security.annotation.RequiresPermissions;
 import com.ruoyi.ems.domain.OpEnergyStrategy;
+import com.ruoyi.ems.domain.OpEnergyStrategyContext;
 import com.ruoyi.ems.domain.OpEnergyStrategyExecLog;
 import com.ruoyi.ems.domain.OpEnergyStrategyParam;
 import com.ruoyi.ems.domain.OpEnergyStrategyStep;
 import com.ruoyi.ems.domain.OpEnergyStrategyStepLog;
 import com.ruoyi.ems.domain.OpEnergyStrategyTemplate;
 import com.ruoyi.ems.domain.OpEnergyStrategyTrigger;
+import com.ruoyi.ems.service.IOpEnergyStrategyContextService;
 import com.ruoyi.ems.service.IOpEnergyStrategyExecLogService;
 import com.ruoyi.ems.service.IOpEnergyStrategyParamService;
 import com.ruoyi.ems.service.IOpEnergyStrategyService;
@@ -70,8 +72,10 @@ public class OpEnergyStrategyController extends BaseController {
     private IOpEnergyStrategyExecLogService execLogService;
 
     @Autowired
-    private StrategyExecutor strategyExecutor;
+    private IOpEnergyStrategyContextService contextService;
 
+    @Autowired
+    private StrategyExecutor strategyExecutor;
 
     /**
      * 查询能源策略列表
@@ -173,7 +177,8 @@ public class OpEnergyStrategyController extends BaseController {
         try {
             int updateCnt = paramService.updateParamValue(strategyParam);
             return toAjax(updateCnt);
-        } catch (BusinessException e) {
+        }
+        catch (BusinessException e) {
             return error(e.getMessage());
         }
     }
@@ -284,7 +289,8 @@ public class OpEnergyStrategyController extends BaseController {
     public AjaxResult saveTrigger(@RequestBody OpEnergyStrategyTrigger trigger) {
         if (trigger.getId() == null) {
             return toAjax(triggerService.insertTrigger(trigger));
-        } else {
+        }
+        else {
             return toAjax(triggerService.updateTrigger(trigger));
         }
     }
@@ -300,16 +306,44 @@ public class OpEnergyStrategyController extends BaseController {
 
     /**
      * 策略模板
+     *
      * @param template
      * @return
      */
     @GetMapping("/template/list")
-    public AjaxResult listTemplate(OpEnergyStrategyTemplate template)
-    {
+    public AjaxResult listTemplate(OpEnergyStrategyTemplate template) {
         List<OpEnergyStrategyTemplate> list = templateService.selectTemplateList(template);
         return success(list);
     }
 
+    /**
+     * 查询上下文参数
+     *
+     * @param strategyCode
+     * @return
+     */
+    @GetMapping("/context")
+    public AjaxResult getStrategyContext(@RequestParam String strategyCode) {
+        OpEnergyStrategyContext context = contextService.selectContextByStrategyCode(strategyCode);
+        return success(context);
+    }
+
+    /**
+     * 保存上下文参数
+     *
+     * @param context
+     * @return
+     */
+    @PostMapping("/context")
+    public AjaxResult saveStrategyContext(@RequestBody OpEnergyStrategyContext context) {
+        int cnt = contextService.insertContext(context);
+        return success(cnt);
+    }
+
+    @DeleteMapping("/context/{id}")
+    public AjaxResult deleteStrategyContext(@PathVariable Long id) {
+        return toAjax(contextService.deleteContextById(id));
+    }
 
     /**
      * 手动执行策略
@@ -332,7 +366,8 @@ public class OpEnergyStrategyController extends BaseController {
             result.put("message", "策略执行已启动");
 
             return success(result);
-        } catch (Exception e) {
+        }
+        catch (Exception e) {
             return error("策略执行失败: " + e.getMessage());
         }
     }

+ 10 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/mapper/EmsObjAttrValueMapper.java

@@ -55,6 +55,16 @@ public interface EmsObjAttrValueMapper {
      * @param attrKey   对象代码
      * @return 能源对象属性值集合
      */
+    List<EmsObjAttrValue> selectByAttrKeyValue(@Param("modelCode") String modelCode, @Param("attrKey") String attrKey,
+        @Param("attrValue") String attrValue);
+
+    /**
+     * 查询能源对象属性值列alue
+     *
+     * @param modelCode 对象类型
+     * @param attrKey   对象代码
+     * @return 能源对象属性值集合
+     */
     List<EmsObjAttrValue> selectByAttrKey(@Param("modelCode") String modelCode, @Param("attrKey") String attrKey);
 
     /**

+ 69 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/mapper/OpEnergyStrategyContextMapper.java

@@ -0,0 +1,69 @@
+package com.ruoyi.ems.mapper;
+
+import java.util.List;
+import com.ruoyi.ems.domain.OpEnergyStrategyContext;
+
+/**
+ * 策略上下文变量Mapper接口
+ * 
+ * @author ruoyi
+ * @date 2025-12-02
+ */
+public interface OpEnergyStrategyContextMapper 
+{
+    /**
+     * 查询策略上下文变量
+     * 
+     * @param id 策略上下文变量主键
+     * @return 策略上下文变量
+     */
+     OpEnergyStrategyContext selectContextById(Long id);
+
+    /**
+     * 查询策略上下文变量
+     *
+     * @param strategyCode 策略上下文变量主键
+     * @return 策略上下文变量
+     */
+    OpEnergyStrategyContext selectContextByStrategyCode(String strategyCode);
+
+    /**
+     * 查询策略上下文变量列表
+     * 
+     * @param context 策略上下文变量
+     * @return 策略上下文变量集合
+     */
+     List<OpEnergyStrategyContext> selectContextList(OpEnergyStrategyContext context);
+
+    /**
+     * 新增策略上下文变量
+     * 
+     * @param context 策略上下文变量
+     * @return 结果
+     */
+     int insertContext(OpEnergyStrategyContext context);
+
+    /**
+     * 修改策略上下文变量
+     * 
+     * @param context 策略上下文变量
+     * @return 结果
+     */
+     int updateContext(OpEnergyStrategyContext context);
+
+    /**
+     * 删除策略上下文变量
+     * 
+     * @param id 策略上下文变量主键
+     * @return 结果
+     */
+     int deleteContextById(Long id);
+
+    /**
+     * 批量删除策略上下文变量
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+     int deleteContextByIds(Long[] ids);
+}

+ 10 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/service/IEmsObjAttrValueService.java

@@ -57,6 +57,16 @@ public interface IEmsObjAttrValueService {
     List<EmsObjAttrValue> selectByAttrKey(String modelCode, String attrKey);
 
     /**
+     * 查询能源对象属性值
+     *
+     * @param modelCode 对象类型
+     * @param attrKey   对象代码
+     * @param attrValue 对象值
+     * @return 能源对象属性值集合
+     */
+    List<EmsObjAttrValue> selectByAttrKeyValue(String modelCode, String attrKey, String attrValue);
+
+    /**
      * 新增能源对象属性值
      *
      * @param objAttrValue 能源对象属性值

+ 69 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/service/IOpEnergyStrategyContextService.java

@@ -0,0 +1,69 @@
+package com.ruoyi.ems.service;
+
+import java.util.List;
+
+import com.ruoyi.ems.domain.OpEnergyStrategyContext;
+
+/**
+ * 策略上下文变量Service接口
+ *
+ * @author ruoyi
+ * @date 2025-12-02
+ */
+public interface IOpEnergyStrategyContextService {
+    /**
+     * 查询策略上下文变量
+     *
+     * @param id 策略上下文变量主键
+     * @return 策略上下文变量
+     */
+    OpEnergyStrategyContext selectContextById(Long id);
+
+    /**
+     * 查询策略上下文变量
+     *
+     * @param strategyCode 策略上下文变量主键
+     * @return 策略上下文变量
+     */
+    OpEnergyStrategyContext selectContextByStrategyCode(String strategyCode);
+
+    /**
+     * 查询策略上下文变量列表
+     *
+     * @param context 策略上下文变量
+     * @return 策略上下文变量集合
+     */
+    List<OpEnergyStrategyContext> selectContextList(OpEnergyStrategyContext context);
+
+    /**
+     * 新增策略上下文变量
+     *
+     * @param context 策略上下文变量
+     * @return 结果
+     */
+    int insertContext(OpEnergyStrategyContext context);
+
+    /**
+     * 修改策略上下文变量
+     *
+     * @param context 策略上下文变量
+     * @return 结果
+     */
+    int updateContext(OpEnergyStrategyContext context);
+
+    /**
+     * 批量删除策略上下文变量
+     *
+     * @param ids 需要删除的策略上下文变量主键集合
+     * @return 结果
+     */
+    int deleteContextByIds(Long[] ids);
+
+    /**
+     * 删除策略上下文变量信息
+     *
+     * @param id 策略上下文变量主键
+     * @return 结果
+     */
+    int deleteContextById(Long id);
+}

+ 6 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/EmsObjAttrValueServiceImpl.java

@@ -3,6 +3,7 @@ package com.ruoyi.ems.service.impl;
 import com.ruoyi.ems.domain.EmsObjAttrValue;
 import com.ruoyi.ems.mapper.EmsObjAttrValueMapper;
 import com.ruoyi.ems.service.IEmsObjAttrValueService;
+import org.apache.ibatis.annotations.Param;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -56,6 +57,11 @@ public class EmsObjAttrValueServiceImpl implements IEmsObjAttrValueService {
         return objAttrValueMapper.selectByAttrKey(modelCode, attrKey);
     }
 
+    @Override
+    public List<EmsObjAttrValue> selectByAttrKeyValue(String modelCode, String attrKey, String attrValue) {
+        return objAttrValueMapper.selectByAttrKeyValue(modelCode, attrKey, attrValue);
+    }
+
     /**
      * 新增/更新能源对象属性值
      *

+ 108 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/OpEnergyStrategyContextServiceImpl.java

@@ -0,0 +1,108 @@
+package com.ruoyi.ems.service.impl;
+
+import java.util.List;
+
+import com.huashe.common.utils.DateUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.ems.mapper.OpEnergyStrategyContextMapper;
+import com.ruoyi.ems.domain.OpEnergyStrategyContext;
+import com.ruoyi.ems.service.IOpEnergyStrategyContextService;
+
+/**
+ * 策略上下文变量Service业务层处理
+ * 
+ * @author ruoyi
+ * @date 2025-12-02
+ */
+@Service
+public class OpEnergyStrategyContextServiceImpl implements IOpEnergyStrategyContextService
+{
+    @Autowired
+    private OpEnergyStrategyContextMapper contextMapper;
+
+    /**
+     * 查询策略上下文变量
+     * 
+     * @param id 策略上下文变量主键
+     * @return 策略上下文变量
+     */
+    @Override
+    public OpEnergyStrategyContext selectContextById(Long id)
+    {
+        return contextMapper.selectContextById(id);
+    }
+
+    /**
+     * 查询策略上下文变量
+     *
+     * @param strategyCode 策略上下文变量主键
+     * @return 策略上下文变量
+     */
+    @Override
+    public OpEnergyStrategyContext selectContextByStrategyCode(String strategyCode)
+    {
+        return contextMapper.selectContextByStrategyCode(strategyCode);
+    }
+
+    /**
+     * 查询策略上下文变量列表
+     * 
+     * @param context 策略上下文变量
+     * @return 策略上下文变量
+     */
+    @Override
+    public List<OpEnergyStrategyContext> selectContextList(OpEnergyStrategyContext context)
+    {
+        return contextMapper.selectContextList(context);
+    }
+
+    /**
+     * 新增策略上下文变量
+     * 
+     * @param context 策略上下文变量
+     * @return 结果
+     */
+    @Override
+    public int insertContext(OpEnergyStrategyContext context)
+    {
+        context.setCreateTime(DateUtils.getNowDate());
+        return contextMapper.insertContext(context);
+    }
+
+    /**
+     * 修改策略上下文变量
+     * 
+     * @param context 策略上下文变量
+     * @return 结果
+     */
+    @Override
+    public int updateContext(OpEnergyStrategyContext context)
+    {
+        return contextMapper.updateContext(context);
+    }
+
+    /**
+     * 批量删除策略上下文变量
+     * 
+     * @param ids 需要删除的策略上下文变量主键
+     * @return 结果
+     */
+    @Override
+    public int deleteContextByIds(Long[] ids)
+    {
+        return contextMapper.deleteContextByIds(ids);
+    }
+
+    /**
+     * 删除策略上下文变量信息
+     * 
+     * @param id 策略上下文变量主键
+     * @return 结果
+     */
+    @Override
+    public int deleteContextById(Long id)
+    {
+        return contextMapper.deleteContextById(id);
+    }
+}

+ 2 - 2
ems/ems-core/src/main/resources/mapper/ems/EmsObjAbilityCallLogMapper.xml

@@ -29,7 +29,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         left join adm_ems_obj_model m on l.model_code = m.model_code
         left join adm_ems_device d on l.obj_code = d.device_code and m.obj_type = 2
         left join adm_ems_subsystem s on l.obj_code = s.system_code and m.obj_type = 3
-        left join adm_ems_obj_ability a on l.ability_key = a.ability_key
+        left join adm_ems_obj_ability a on l.model_code = a.model_code and l.ability_key = a.ability_key
     </sql>
 
     <sql id="selectDetail">
@@ -42,7 +42,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         left join adm_ems_obj_model m on l.model_code = m.model_code
         left join adm_ems_device d on l.obj_code = d.device_code and m.obj_type = 2
         left join adm_ems_subsystem s on l.obj_code = s.system_code and m.obj_type = 3
-        left join adm_ems_obj_ability a on l.ability_key = a.ability_key
+        left join adm_ems_obj_ability a on l.model_code = a.model_code and l.ability_key = a.ability_key
     </sql>
 
     <select id="selectObjAbilityCallLogById" parameterType="java.lang.Long" resultMap="objAbilityCallLogResult">

+ 5 - 0
ems/ems-core/src/main/resources/mapper/ems/EmsObjAttrValueMapper.xml

@@ -41,6 +41,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         where model_code = #{modelCode} and attr_key = #{attrKey}
     </select>
 
+    <select id="selectByAttrKeyValue" resultMap="objAttrValueResult">
+        <include refid="selectObjAttrValueVo"/>
+        where model_code = #{modelCode} and attr_key = #{attrKey} and attr_value = #{attrValue}
+    </select>
+
     <select id="selectObjAttrValueById" parameterType="Long" resultMap="objAttrValueResult">
         <include refid="selectObjAttrValueVo"/>
         where id = #{id}

+ 93 - 0
ems/ems-core/src/main/resources/mapper/ems/OpEnergyStrategyContextMapper.xml

@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.ems.mapper.OpEnergyStrategyContextMapper">
+    
+    <resultMap type="com.ruoyi.ems.domain.OpEnergyStrategyContext" id="OpEnergyStrategyContextResult">
+        <result property="id"    column="id"    />
+        <result property="strategyCode"    column="strategy_code"    />
+        <result property="varKey"    column="var_key"    />
+        <result property="varName"    column="var_name"    />
+        <result property="varType"    column="var_type"    />
+        <result property="dataType"    column="data_type"    />
+        <result property="defaultValue"    column="default_value"    />
+        <result property="valueSource"    column="value_source"    />
+        <result property="description"    column="description"    />
+        <result property="createTime"    column="create_time"    />
+    </resultMap>
+
+    <sql id="selectContextVo">
+        select id, strategy_code, var_key, var_name, var_type, data_type, default_value, value_source, description, create_time from adm_op_energy_strategy_context
+    </sql>
+
+    <select id="selectContextList" parameterType="com.ruoyi.ems.domain.OpEnergyStrategyContext" resultMap="OpEnergyStrategyContextResult">
+        <include refid="selectContextVo"/>
+        <where>  
+            <if test="strategyCode != null  and strategyCode != ''"> and strategy_code = #{strategyCode}</if>
+        </where>
+    </select>
+
+    <select id="selectContextById" parameterType="Long" resultMap="OpEnergyStrategyContextResult">
+        <include refid="selectContextVo"/>
+        where id = #{id}
+    </select>
+
+    <select id="selectContextByStrategyCode" parameterType="String" resultMap="OpEnergyStrategyContextResult">
+        <include refid="selectContextVo"/>
+        where strategy_code = #{strategyCode}
+    </select>
+        
+    <insert id="insertContext" parameterType="com.ruoyi.ems.domain.OpEnergyStrategyContext" useGeneratedKeys="true" keyProperty="id">
+        insert into adm_op_energy_strategy_context
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="strategyCode != null and strategyCode != ''">strategy_code,</if>
+            <if test="varKey != null and varKey != ''">var_key,</if>
+            <if test="varName != null and varName != ''">var_name,</if>
+            <if test="varType != null and varType != ''">var_type,</if>
+            <if test="dataType != null">data_type,</if>
+            <if test="defaultValue != null">default_value,</if>
+            <if test="valueSource != null">value_source,</if>
+            <if test="description != null">description,</if>
+            <if test="createTime != null">create_time,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="strategyCode != null and strategyCode != ''">#{strategyCode},</if>
+            <if test="varKey != null and varKey != ''">#{varKey},</if>
+            <if test="varName != null and varName != ''">#{varName},</if>
+            <if test="varType != null and varType != ''">#{varType},</if>
+            <if test="dataType != null">#{dataType},</if>
+            <if test="defaultValue != null">#{defaultValue},</if>
+            <if test="valueSource != null">#{valueSource},</if>
+            <if test="description != null">#{description},</if>
+            <if test="createTime != null">#{createTime},</if>
+         </trim>
+    </insert>
+
+    <update id="updateContext" parameterType="com.ruoyi.ems.domain.OpEnergyStrategyContext">
+        update adm_op_energy_strategy_context
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="strategyCode != null and strategyCode != ''">strategy_code = #{strategyCode},</if>
+            <if test="varKey != null and varKey != ''">var_key = #{varKey},</if>
+            <if test="varName != null and varName != ''">var_name = #{varName},</if>
+            <if test="varType != null and varType != ''">var_type = #{varType},</if>
+            <if test="dataType != null">data_type = #{dataType},</if>
+            <if test="defaultValue != null">default_value = #{defaultValue},</if>
+            <if test="valueSource != null">value_source = #{valueSource},</if>
+            <if test="description != null">`description` = #{description},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteContextById" parameterType="Long">
+        delete from adm_op_energy_strategy_context where id = #{id}
+    </delete>
+
+    <delete id="deleteContextByIds" parameterType="String">
+        delete from adm_op_energy_strategy_context where id in 
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 0 - 9
ems/sql/ems_init_data.sql

@@ -264,12 +264,6 @@ INSERT INTO `adm_ems_device` (`device_code`, `device_name`, `device_brand`, `dev
 
 
 -- 策略初始数据
-INSERT INTO `adm_op_energy_strategy` (`area_code`, `strategy_code`, `strategy_name`, `strategy_type`, `strategy_state`, `strategy_desc`, `exec_mode`, `exec_rule`) VALUES ('321283124S3001', 'CL_YW_01', '北区-默认策略', 1, 1, '默认执行', 0, NULL);
-INSERT INTO `adm_op_energy_strategy` (`area_code`, `strategy_code`, `strategy_name`, `strategy_type`, `strategy_state`, `strategy_desc`, `exec_mode`, `exec_rule`) VALUES ('321283124S3002', 'CL_YW_02', '南区-默认策略', 1, 1, '默认执行', 0, NULL);
-INSERT INTO `adm_op_energy_strategy` (`area_code`, `strategy_code`, `strategy_name`, `strategy_type`, `strategy_state`, `strategy_desc`, `exec_mode`, `exec_rule`) VALUES ('321283124S3001', 'CL_YH_01', '北区-默认策略', 2, 1, '默认执行', 0, NULL);
-INSERT INTO `adm_op_energy_strategy` (`area_code`, `strategy_code`, `strategy_name`, `strategy_type`, `strategy_state`, `strategy_desc`, `exec_mode`, `exec_rule`) VALUES ('321283124S3002', 'CL_YH_02', '南区-默认策略', 2, 1, '默认执行', 0, NULL);
-INSERT INTO `adm_op_energy_strategy` (`area_code`, `strategy_code`, `strategy_name`, `strategy_type`, `strategy_state`, `strategy_desc`, `exec_mode`, `exec_rule`) VALUES ('321283124S3001', 'CL_WC_01', '北区-默认策略', 3, 1, '默认执行', 0, NULL);
-INSERT INTO `adm_op_energy_strategy` (`area_code`, `strategy_code`, `strategy_name`, `strategy_type`, `strategy_state`, `strategy_desc`, `exec_mode`, `exec_rule`) VALUES ('321283124S3002', 'CL_WC_02', '南区-默认策略', 3, 1, '默认执行', 0, NULL);
 
 -- 策略参数数据
 -- 源网 控制模式:
@@ -308,8 +302,6 @@ 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_Z010_DEV_SQUARE_CONCENTRATOR', '照明集中器', 2, 'http://172.17.60.27:9203/ems-dev-adapter/square-light-ctl/ct/abilityCall', NULL);
 INSERT INTO `adm_ems_obj_model` (`model_code`, `model_name`, `obj_type`, `ability_handler`, `event_handler`) VALUES ('M_W4_DEV_ELEC_MONITOR_BHZZ', '电力保护装置', 2, 'http://172.17.60.27:9203/ems-dev-adapter/elec-monitor-acrel/ct/abilityCall', NULL);
 INSERT INTO `adm_ems_obj_model` (`model_code`, `model_name`, `obj_type`, `ability_handler`, `event_handler`) VALUES ('M_W4_DEV_ELEC_MONITOR_DB', '多功能电表', 2, 'http://172.17.60.27:9203/ems-dev-adapter/elec-monitor-acrel/ct/abilityCall', NULL);
-INSERT INTO `adm_ems_obj_model` (`model_code`, `model_name`, `obj_type`, `ability_handler`, `event_handler`) VALUES ('M_W2_QF_GEEKOPEN', 'GeekOpen断路器', 2, 'http://172.17.60.27:9203/ems-dev-adapter/circuit-breaker/GeekOpen/abilityCall', NULL);
-INSERT INTO `adm_ems_obj_model` (`model_code`, `model_name`, `obj_type`, `ability_handler`, `event_handler`) VALUES ('M_W2_QS_KEKA_86', 'KEKA开关(86型)', 2, 'http://172.17.60.27:9203/ems-dev-adapter/button-switch/keka/86ButtonSwitchCall', NULL);
 
 -- 对象属性数据
 -- 能耗监测属性
@@ -1435,7 +1427,6 @@ INSERT INTO `adm_meter_device` (`area_code`, `device_code`, `device_model`, `dev
 INSERT INTO `adm_meter_device` (`area_code`, `device_code`, `device_model`, `device_name`, `device_enable`, `location_ref`, `location`, `meter_cls`, `col_cycle`, `col_mode`, `magnification`, `spec_desc`) VALUES ('321283124S3002', 'C_1005_AV_0600','M_W4_DEV_BA_METER_W', '司机之家水表', 1, '321283124S300202', '司机之家',   70, 300, 0, 1, null);
 
 -- 测试
-INSERT INTO `adm_meter_device` (`area_code`, `device_code`, `device_model`, `device_name`, `device_enable`, `location_ref`, `location`, `meter_cls`, `col_cycle`, `col_mode`, `magnification`, `spec_desc`) VALUES ('321283124S3001', '864142073640059', 'M_W2_QF_GEEKOPEN', '智能断路器', 1, '321283124S300101', '综合楼C3-茶水间', 45, 3600, 0, 1, '智能表');
 INSERT INTO `adm_meter_device` (`area_code`, `device_code`, `device_model`, `device_name`, `device_enable`, `location_ref`, `location`, `meter_cls`, `col_cycle`, `col_mode`, `magnification`, `spec_desc`) VALUES ('321283124S3001', 'J-E-B-101', 'test', '综合楼B-101', 1, '321283124S300101', '一楼设备间', 45, NULL, 1, 1, '智能表');
 INSERT INTO `adm_meter_device` (`area_code`, `device_code`, `device_model`, `device_name`, `device_enable`, `location_ref`, `location`, `meter_cls`, `col_cycle`, `col_mode`, `magnification`, `spec_desc`) VALUES ('321283124S3001', 'J-W-B-102', 'test', '综合楼B-102', 1, '321283124S300101', '一楼设备间', 70, NULL, 1, 1, '智能表');
 INSERT INTO `adm_meter_device` (`area_code`, `device_code`, `device_model`, `device_name`, `device_enable`, `location_ref`, `location`, `meter_cls`, `col_cycle`, `col_mode`, `magnification`, `spec_desc`) VALUES ('321283124S3002', 'J-E-N-101', 'test', '综合楼N-101',  1,'321283124S300201', '一楼设备间', 45, NULL, 1, 1, '智能表');

+ 126 - 0
ems/sql/ems_init_data_test.sql

@@ -0,0 +1,126 @@
+-- 测试区域
+INSERT INTO `adm_area` (`area_code`, `parent_code`, `ancestors`, `area_name`, `short_name`, `desc`, `order_num`, `status`) VALUES ('320100', '0', '0', '测试区域', '测试区域', '测试区域', 4, 0);
+INSERT INTO `adm_area` (`area_code`, `parent_code`, `ancestors`, `area_name`, `short_name`, `desc`, `order_num`, `status`) VALUES ('32010010', '320100', '0,320100', '1号楼', '1号楼', '1号楼', 1, 0);
+
+INSERT INTO `adm_area_attr` (`area_code`, `attr_org`, `mgr_org`, `leader`, `phone`, `open_date`, `floor_area`, `usable_area`, `floor`, `longitude`, `latitude`) VALUES ('320100', '狄诺尼', '狄诺尼', 'xxx', '13000000000', '2025-01-01', NULL, NULL, NULL, 118.91,32.02);
+INSERT INTO `adm_area_attr` (`area_code`, `attr_org`, `mgr_org`, `leader`, `phone`, `open_date`, `floor_area`, `usable_area`, `floor`, `longitude`, `latitude`) VALUES ('32010010', '狄诺尼', '狄诺尼', 'xxx', '13000000000', '2025-01-01', NULL, NULL, NULL, 118.91,32.02);
+
+INSERT INTO `adm_ems_facs` (`facs_code`, `facs_name`, `facs_category`, `facs_subcategory`, `enable`, `ref_area`) VALUES ('W203', '测试-供电网', 'W', 'W2', 1, '320100');
+
+-- 对象模型表
+INSERT INTO `adm_ems_obj_model` (`model_code`, `model_name`, `obj_type`, `ability_handler`, `event_handler`) VALUES ('M_W2_QF_GEEKOPEN', 'GeekOpen断路器', 2, 'http://127.0.0.1:9203/ems-dev-adapter/circuit-breaker/GeekOpen/abilityCall', NULL);
+INSERT INTO `adm_ems_obj_model` (`model_code`, `model_name`, `obj_type`, `ability_handler`, `event_handler`) VALUES ('M_W2_QS_KEKA_86', 'KEKA开关(86型)', 2, 'http://127.0.0.1:9203/ems-dev-adapter/button-switch/keka/86ButtonSwitchCall', NULL);
+
+-- 测试能源设备
+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 ('864142073640059', '智能断路器', 'GEEK-OPEN', '30A-4G', 1, '综合楼茶水间', '32010010', '320100', 'M_W2_QF_GEEKOPEN', 'W203', 'QF', '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 ('D-B-QS-10000001', '照明控制-灯1', '德力西', '485开关', 1, '综合楼大厅', '32010010', '321283124S3001', 'M_W2_QS_KEKA_86', 'W203', 'QS', '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 ('D-B-QS-10000002', '照明控制-灯2', '德力西', '485开关', 1, '综合楼大厅', '32010010', '321283124S3001', 'M_W2_QS_KEKA_86', 'W203', 'QS', '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 ('D-B-QS-10000003', '照明控制-锁定', '德力西', '485开关', 1, '综合楼大厅', '32010010', '321283124S3001', 'M_W2_QS_KEKA_86', 'W203', 'QS', 'SYS_BA');
+
+
+-- 测试计量设备
+INSERT INTO `adm_meter_device` (`area_code`, `device_code`, `device_model`, `device_name`, `device_enable`, `location_ref`, `location`, `meter_cls`, `col_cycle`, `col_mode`, `magnification`, `spec_desc`) VALUES ('321283124S3001', '864142073640059', 'M_W2_QF_GEEKOPEN', '智能断路器', 1, '321283124S300101', '综合楼C3-茶水间', 45, 3600, 0, 1, '智能表');
+
+
+-- 对象属性DEMO数据
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QF_GEEKOPEN', 'Base', 'version', '固件版本号', NULL, 'Value');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QF_GEEKOPEN', 'Base', 'iccid', '物联网卡ICCID号', NULL, 'Value');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QF_GEEKOPEN', 'Base', 'imei', '设备imei', NULL, 'Value');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QF_GEEKOPEN', 'State', 'timerEnable', '上报定时开关', '', 'Enum');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QF_GEEKOPEN', 'State', 'timerInterval', '上报时间间隔', '秒(S)', 'Value');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QF_GEEKOPEN', 'State', 'signal', '信号强度', NULL, 'Value');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QF_GEEKOPEN', 'State', 'keyLock', '状态-按键锁定', NULL, 'Enum');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QF_GEEKOPEN', 'State', 'key', '状态-通断', NULL, 'Enum');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QF_GEEKOPEN', 'State', 'resetLock', '重置锁', NULL, 'Enum');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QF_GEEKOPEN', 'State', 'onState', '设备上电的默认状态', NULL, 'Enum');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QF_GEEKOPEN', 'Measure', 'power', '实时功率', '瓦(W)', 'Value');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QF_GEEKOPEN', 'Measure', 'voltage', '实时电压', '伏特(V)', 'Value');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QF_GEEKOPEN', 'Measure', 'current', '实时电流', '安培(A)', 'Value');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QF_GEEKOPEN', 'Measure', 'energy', '累计电量值', '千瓦时(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_QF_GEEKOPEN', 'Protocol', 'protocol', '协议', NULL, 'Value');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QF_GEEKOPEN', 'Protocol', 'server', 'MQTT地址', NULL, 'Value');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QF_GEEKOPEN', 'Protocol', 'port', 'MQTT端口', NULL, 'Value');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QF_GEEKOPEN', 'Protocol', 'clientId', '设备ID', NULL, 'Value');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QF_GEEKOPEN', 'Protocol', 'username', '用户名', NULL, 'Value');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QF_GEEKOPEN', 'Protocol', 'subcribe', '订阅主题', NULL, 'Value');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QF_GEEKOPEN', 'Protocol', 'publish', '发布主题', NULL, 'Value');
+
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QS_KEKA_86', 'Base', 'gatewayId', '网关编号', NULL, 'String');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QS_KEKA_86', 'Base', 'buttonId', '按键编号', NULL, 'String');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QS_KEKA_86', 'Protocol', 'protocol', '协议', NULL, 'String');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QS_KEKA_86', 'Protocol', 'server', 'MQTT地址', NULL, 'String');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QS_KEKA_86', 'Protocol', 'port', 'MQTT端口', NULL, 'String');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QS_KEKA_86', 'Protocol', 'publish', '发布主题', NULL, 'String');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QS_KEKA_86', 'Protocol', 'subcribe', '订阅主题', NULL, 'String');
+INSERT INTO `adm_ems_obj_attr` (`model_code`, `attr_group`, `attr_key`, `attr_name`, `attr_unit`, `attr_value_type`) VALUES ('M_W2_QS_KEKA_86', 'State', 'Switch', '开关状态', NULL, 'Enum');
+
+-- 对象属性DEMO数据
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('864142073640059', 'M_W2_QF_GEEKOPEN', 'current', '0.050', '2025-03-05 16:07:36');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('864142073640059', 'M_W2_QF_GEEKOPEN', 'energy', '421.787', '2025-03-05 16:02:36');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('864142073640059', 'M_W2_QF_GEEKOPEN', 'iccid', '898604E6192390306801', '2025-03-05 15:51:00');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('864142073640059', 'M_W2_QF_GEEKOPEN', 'key', '1', '2025-03-05 15:57:59');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('864142073640059', 'M_W2_QF_GEEKOPEN', 'keyLock', '0', '2025-03-05 15:51:00');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('864142073640059', 'M_W2_QF_GEEKOPEN', 'onState', '2', '2025-03-05 15:51:00');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('864142073640059', 'M_W2_QF_GEEKOPEN', 'power', '5.108', '2025-03-05 16:07:36');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('864142073640059', 'M_W2_QF_GEEKOPEN', 'resetLock', '0', '2025-03-05 15:51:00');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('864142073640059', 'M_W2_QF_GEEKOPEN', 'signal', '31', '2025-03-05 15:51:00');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('864142073640059', 'M_W2_QF_GEEKOPEN', 'timerEnable', '1', '2025-03-05 15:51:00');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('864142073640059', 'M_W2_QF_GEEKOPEN', 'timerInterval', '300', '2025-03-05 15:51:00');
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('864142073640059', 'M_W2_QF_GEEKOPEN', 'version', '1.0.1', '2025-03-05 15:51:00');
+
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('D-B-QS-10000001', 'M_W2_QS_KEKA_86', 'gatewayId', '864814074089037', null);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('D-B-QS-10000001', 'M_W2_QS_KEKA_86', 'buttonId', '1', null);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('D-B-QS-10000001', 'M_W2_QS_KEKA_86', 'protocol', 'mqtt', null);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('D-B-QS-10000001', 'M_W2_QS_KEKA_86', 'server', 'xt.wenhq.top', null);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('D-B-QS-10000001', 'M_W2_QS_KEKA_86', 'port', '8581', null);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('D-B-QS-10000001', 'M_W2_QS_KEKA_86', 'publish', '/sc/dtu/rep/864814074089037', null);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('D-B-QS-10000001', 'M_W2_QS_KEKA_86', 'subcribe', '/sc/dtu/ctl/864814074089037', null);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('D-B-QS-10000001', 'M_W2_QS_KEKA_86', 'Switch', '0', null);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('D-B-QS-10000002', 'M_W2_QS_KEKA_86', 'gatewayId', '864814074089037', null);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('D-B-QS-10000002', 'M_W2_QS_KEKA_86', 'buttonId', '2', null);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('D-B-QS-10000002', 'M_W2_QS_KEKA_86', 'protocol', 'mqtt', null);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('D-B-QS-10000002', 'M_W2_QS_KEKA_86', 'server', 'xt.wenhq.top', null);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('D-B-QS-10000002', 'M_W2_QS_KEKA_86', 'port', '8581', null);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('D-B-QS-10000002', 'M_W2_QS_KEKA_86', 'publish', '/sc/dtu/rep/864814074089037', null);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('D-B-QS-10000002', 'M_W2_QS_KEKA_86', 'subcribe', '/sc/dtu/ctl/864814074089037', null);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('D-B-QS-10000002', 'M_W2_QS_KEKA_86', 'Switch', '0', null);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('D-B-QS-10000003', 'M_W2_QS_KEKA_86', 'gatewayId', '864814074089037', null);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('D-B-QS-10000003', 'M_W2_QS_KEKA_86', 'buttonId', '3', null);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('D-B-QS-10000003', 'M_W2_QS_KEKA_86', 'protocol', 'mqtt', null);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('D-B-QS-10000003', 'M_W2_QS_KEKA_86', 'server', 'xt.wenhq.top', null);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('D-B-QS-10000003', 'M_W2_QS_KEKA_86', 'port', '8581', null);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('D-B-QS-10000003', 'M_W2_QS_KEKA_86', 'publish', '/sc/dtu/rep/864814074089037', null);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('D-B-QS-10000003', 'M_W2_QS_KEKA_86', 'subcribe', '/sc/dtu/ctl/864814074089037', null);
+INSERT INTO `adm_ems_obj_attr_value` (`obj_code`, `model_code`, `attr_key`, `attr_value`, `update_time`) VALUES ('D-B-QS-10000003', 'M_W2_QS_KEKA_86', 'Switch', '0', null);
+
+
+-- 对象属性值枚举
+INSERT INTO `adm_ems_obj_attr_enum` (`model_code`, `attr_key`, `attr_value`, `attr_value_name`) VALUES ('M_W2_QF_GEEKOPEN', 'onState', '0', '记忆');
+INSERT INTO `adm_ems_obj_attr_enum` (`model_code`, `attr_key`, `attr_value`, `attr_value_name`) VALUES ('M_W2_QF_GEEKOPEN', 'onState', '1', '分闸');
+INSERT INTO `adm_ems_obj_attr_enum` (`model_code`, `attr_key`, `attr_value`, `attr_value_name`) VALUES ('M_W2_QF_GEEKOPEN', 'onState', '2', '合闸');
+INSERT INTO `adm_ems_obj_attr_enum` (`model_code`, `attr_key`, `attr_value`, `attr_value_name`) VALUES ('M_W2_QF_GEEKOPEN', 'key', '1', '合闸');
+INSERT INTO `adm_ems_obj_attr_enum` (`model_code`, `attr_key`, `attr_value`, `attr_value_name`) VALUES ('M_W2_QF_GEEKOPEN', 'key', '0', '分闸');
+INSERT INTO `adm_ems_obj_attr_enum` (`model_code`, `attr_key`, `attr_value`, `attr_value_name`) VALUES ('M_W2_QF_GEEKOPEN', 'timerEnable', '0', '关闭');
+INSERT INTO `adm_ems_obj_attr_enum` (`model_code`, `attr_key`, `attr_value`, `attr_value_name`) VALUES ('M_W2_QF_GEEKOPEN', 'timerEnable', '1', '开启');
+INSERT INTO `adm_ems_obj_attr_enum` (`model_code`, `attr_key`, `attr_value`, `attr_value_name`) VALUES ('M_W2_QF_GEEKOPEN', 'keyLock', '0', '关闭');
+INSERT INTO `adm_ems_obj_attr_enum` (`model_code`, `attr_key`, `attr_value`, `attr_value_name`) VALUES ('M_W2_QF_GEEKOPEN', 'keyLock', '1', '开启');
+INSERT INTO `adm_ems_obj_attr_enum` (`model_code`, `attr_key`, `attr_value`, `attr_value_name`) VALUES ('M_W2_QF_GEEKOPEN', 'resetLock', '0', '关闭');
+INSERT INTO `adm_ems_obj_attr_enum` (`model_code`, `attr_key`, `attr_value`, `attr_value_name`) VALUES ('M_W2_QF_GEEKOPEN', 'resetLock', '1', '开启');
+INSERT INTO `adm_ems_obj_attr_enum` (`model_code`, `attr_key`, `attr_value`, `attr_value_name`) VALUES ('M_W2_QS_KEKA_86', 'Switch', '0', '关闭');
+INSERT INTO `adm_ems_obj_attr_enum` (`model_code`, `attr_key`, `attr_value`, `attr_value_name`) VALUES ('M_W2_QS_KEKA_86', 'Switch', '1', '开启');
+
+-- 对象能力DEMO数据
+INSERT INTO `adm_ems_obj_ability` (`model_code`, `ability_key`, `ability_name`, `ability_desc`, `param_definition`, `hidden_flag`) VALUES ('M_W2_QF_GEEKOPEN', 'OnOffCtl', '通断控制', '控制线路通断', '{"type":"Options", "list":[{"key":"断电", "value":"0"},{"key":"通电", "value":"1"}]}', 1);
+INSERT INTO `adm_ems_obj_ability` (`model_code`, `ability_key`, `ability_name`, `ability_desc`, `param_definition`, `hidden_flag`) VALUES ('M_W2_QF_GEEKOPEN', 'KeyLockCtl', '按键控制', '设置按键控制锁定/解锁', '{"type":"Options", "list":[{"key":"解锁按钮", "value":"0"},{"key":"锁定按钮", "value":"1"}]}', 1);
+INSERT INTO `adm_ems_obj_ability` (`model_code`, `ability_key`, `ability_name`, `ability_desc`, `param_definition`, `hidden_flag`) VALUES ('M_W2_QF_GEEKOPEN', 'settingCtl', '设备设置', '设备软重启/重置', '{"type":"Options", "list":[{"key":"软重启", "value":"restart"},{"key":"重置/恢复出厂", "value":"reset"}]}', 1);
+INSERT INTO `adm_ems_obj_ability` (`model_code`, `ability_key`, `ability_name`, `ability_desc`, `param_definition`, `hidden_flag`) VALUES ('M_W2_QF_GEEKOPEN', 'settAutoReport', '电量上报设置', '设置电量信息定时上报', '{"type":"Options", "list":[{"key":"关闭上报", "value":"0"},{"key":"15分钟", "value":"900"},{"key":"30分钟", "value":"1800"},{"key":"1小时", "value":"3600"}]}', 1);
+INSERT INTO `adm_ems_obj_ability` (`model_code`, `ability_key`, `ability_name`, `ability_desc`, `param_definition`, `hidden_flag`) VALUES ('M_W2_QF_GEEKOPEN', 'setOnState', '上电状态设置', '设置上电通断(默认关闭)', '{"type":"Options", "list":[{"key":"记忆", "value":"0"},{"key":"断开", "value":"1"},{"key":"开启", "value":"2"}]}', 1);
+INSERT INTO `adm_ems_obj_ability` (`model_code`, `ability_key`, `ability_name`, `ability_desc`, `param_definition`, `hidden_flag`) VALUES ('M_W2_QF_GEEKOPEN', 'triggerSync', '数据同步', '触发设备信息同步', '{"type":"Options", "list":[{"key":"通讯同步", "value":"protocol"},{"key":"电量同步", "value":"statistic"},{"key":"状态同步", "value":"info"}]}', 1);
+
+INSERT INTO `adm_ems_obj_ability` (`model_code`, `ability_key`, `ability_name`, `ability_desc`, `param_definition`, `hidden_flag`) VALUES ('M_W2_QS_KEKA_86', 'on-off', '开关', '开启/关闭', '{"type":"Options", "list":[{"key":"开启", "value":"1"},{"key":"关闭", "value":"0"}]}', 1);
+INSERT INTO `adm_ems_obj_ability` (`model_code`, `ability_key`, `ability_name`, `ability_desc`, `param_definition`, `hidden_flag`) VALUES ('M_W2_QS_KEKA_86', 'syncState', '同步状态', '同步状态', null, 1);
+
+
+-- 对象事件DEMO数据
+INSERT INTO `adm_ems_obj_event` (`model_code`, `event_type`, `event_key`, `event_name`, `event_desc`, `event_code`, `ext_event_code`) VALUES ('M_W2', 2, 'overload', '过载', '功率过载', 'e-gy-0001', '0x0001');
+INSERT INTO `adm_ems_obj_event` (`model_code`, `event_type`, `event_key`, `event_name`, `event_desc`, `event_code`, `ext_event_code`) VALUES ('M_W2', 2, 'undervoltage', '欠压', '电压不足', 'e-gy-0002', '0x0002');