|
@@ -11,13 +11,11 @@
|
|
|
package com.ruoyi.ems.handle;
|
|
package com.ruoyi.ems.handle;
|
|
|
|
|
|
|
|
import com.huashe.common.exception.Assert;
|
|
import com.huashe.common.exception.Assert;
|
|
|
-import com.huashe.common.utils.DateUtils;
|
|
|
|
|
import com.ruoyi.ems.config.EmsConfig;
|
|
import com.ruoyi.ems.config.EmsConfig;
|
|
|
import com.ruoyi.ems.core.EmsApiTemplate;
|
|
import com.ruoyi.ems.core.EmsApiTemplate;
|
|
|
import com.ruoyi.ems.core.MqttTemplate;
|
|
import com.ruoyi.ems.core.MqttTemplate;
|
|
|
import com.ruoyi.ems.domain.EmsDevice;
|
|
import com.ruoyi.ems.domain.EmsDevice;
|
|
|
import com.ruoyi.ems.domain.EmsObjAttrValue;
|
|
import com.ruoyi.ems.domain.EmsObjAttrValue;
|
|
|
-import com.ruoyi.ems.enums.DevOnlineStatus;
|
|
|
|
|
import com.ruoyi.ems.model.AbilityPayload;
|
|
import com.ruoyi.ems.model.AbilityPayload;
|
|
|
import com.ruoyi.ems.model.CallResponse;
|
|
import com.ruoyi.ems.model.CallResponse;
|
|
|
import com.ruoyi.ems.model.ModbusCommand;
|
|
import com.ruoyi.ems.model.ModbusCommand;
|
|
@@ -33,9 +31,9 @@ import org.springframework.scheduling.annotation.Async;
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
|
|
import javax.annotation.Resource;
|
|
import javax.annotation.Resource;
|
|
|
-import java.util.Date;
|
|
|
|
|
import java.util.List;
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
import java.util.Map;
|
|
|
|
|
+import java.util.concurrent.ConcurrentHashMap;
|
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -58,31 +56,25 @@ public class Keka86BsHandler extends BaseDevHandler {
|
|
|
@Autowired
|
|
@Autowired
|
|
|
private EmsConfig emsConfig;
|
|
private EmsConfig emsConfig;
|
|
|
|
|
|
|
|
- // 主题前置
|
|
|
|
|
private static final String TOPIC_PREFIX = "/sc/dtu/ctl/";
|
|
private static final String TOPIC_PREFIX = "/sc/dtu/ctl/";
|
|
|
-
|
|
|
|
|
- // 设备模型代码
|
|
|
|
|
private static final String MODE_CODE = "M_W2_QS_KEKA_86";
|
|
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 FUNC_READ = 0x03;
|
|
|
|
|
+ private static final byte FUNC_WRITE = 0x06;
|
|
|
private static final byte DEVICE_ADDR = 0x01;
|
|
private static final byte DEVICE_ADDR = 0x01;
|
|
|
|
|
|
|
|
- // 灯控制寄存器地址 (44100=0x1004开启, 44101=0x1005关闭)
|
|
|
|
|
- // 这里保持0x1021以匹配实际测试指令,如需修改请改为0x1004
|
|
|
|
|
|
|
+ // 单按键控制寄存器基地址 (协议: 44129=0x1021)
|
|
|
private static final int LIGHT_BASE_ADDR = 0x1021;
|
|
private static final int LIGHT_BASE_ADDR = 0x1021;
|
|
|
|
|
|
|
|
- // 控制值
|
|
|
|
|
- private static final int VALUE_ON = 0x0001; // 开
|
|
|
|
|
|
|
+ private static final int VALUE_ON = 0x0001; // 闭合/开
|
|
|
|
|
+ private static final int VALUE_OFF = 0x0000; // 断开/关
|
|
|
|
|
|
|
|
- private static final int VALUE_OFF = 0x0000; // 关
|
|
|
|
|
|
|
+ // 修正:读取单个寄存器只需要1个
|
|
|
|
|
+ private static final int READ_COUNT = 0x0001;
|
|
|
|
|
|
|
|
- // 读取寄存器数量
|
|
|
|
|
- private static final int READ_COUNT = 0x0002; // 读2个寄存器
|
|
|
|
|
|
|
+ // ========== 新增:用于追踪待处理的读取请求 ==========
|
|
|
|
|
+ // Key: gatewayId, Value: 最近请求的buttonId
|
|
|
|
|
+ private final Map<String, Integer> pendingReadRequests = new ConcurrentHashMap<>();
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
public CallResponse<Void> call(AbilityPayload abilityParam) {
|
|
public CallResponse<Void> call(AbilityPayload abilityParam) {
|
|
@@ -99,16 +91,19 @@ public class Keka86BsHandler extends BaseDevHandler {
|
|
|
if (StringUtils.equals("on-off", abilityParam.getAbilityKey())) {
|
|
if (StringUtils.equals("on-off", abilityParam.getAbilityKey())) {
|
|
|
ModbusCommand command = buildControlCommand(Integer.parseInt(buttonId),
|
|
ModbusCommand command = buildControlCommand(Integer.parseInt(buttonId),
|
|
|
Integer.parseInt(abilityParam.getAbilityParam()));
|
|
Integer.parseInt(abilityParam.getAbilityParam()));
|
|
|
- // 发送消息到MQTT服务器
|
|
|
|
|
String topic = TOPIC_PREFIX + gatewayId;
|
|
String topic = TOPIC_PREFIX + gatewayId;
|
|
|
sendMqttHex(topic, command);
|
|
sendMqttHex(topic, command);
|
|
|
saveCallLog(abilityParam, command.getCommandHex(), System.currentTimeMillis(), 0);
|
|
saveCallLog(abilityParam, command.getCommandHex(), System.currentTimeMillis(), 0);
|
|
|
callResponse = new CallResponse<>(0, "执行成功!");
|
|
callResponse = new CallResponse<>(0, "执行成功!");
|
|
|
}
|
|
}
|
|
|
else if (StringUtils.equals("syncState", abilityParam.getAbilityKey())) {
|
|
else if (StringUtils.equals("syncState", abilityParam.getAbilityKey())) {
|
|
|
- ModbusCommand command = buildReadCommand(Integer.parseInt(buttonId));
|
|
|
|
|
- // 发送消息到MQTT服务器
|
|
|
|
|
|
|
+ int btnId = Integer.parseInt(buttonId);
|
|
|
|
|
+ ModbusCommand command = buildReadCommand(btnId);
|
|
|
String topic = TOPIC_PREFIX + gatewayId;
|
|
String topic = TOPIC_PREFIX + gatewayId;
|
|
|
|
|
+
|
|
|
|
|
+ // 记录待处理的读取请求
|
|
|
|
|
+ pendingReadRequests.put(gatewayId + "_" + getRegisterAddr(btnId), btnId);
|
|
|
|
|
+
|
|
|
sendMqttHex(topic, command);
|
|
sendMqttHex(topic, command);
|
|
|
saveCallLog(abilityParam, command.getCommandHex(), System.currentTimeMillis(), 0);
|
|
saveCallLog(abilityParam, command.getCommandHex(), System.currentTimeMillis(), 0);
|
|
|
callResponse = new CallResponse<>(0, "执行成功!");
|
|
callResponse = new CallResponse<>(0, "执行成功!");
|
|
@@ -136,32 +131,26 @@ public class Keka86BsHandler extends BaseDevHandler {
|
|
|
try {
|
|
try {
|
|
|
log.info("[Keka86] 网关:{}, 收到消息:{}", gatewayId, payload);
|
|
log.info("[Keka86] 网关:{}, 收到消息:{}", gatewayId, payload);
|
|
|
|
|
|
|
|
- // 检查消息长度
|
|
|
|
|
String[] hexBytes = payload.trim().split("\\s+");
|
|
String[] hexBytes = payload.trim().split("\\s+");
|
|
|
- if (hexBytes.length < 7) {
|
|
|
|
|
|
|
+ if (hexBytes.length < 5) {
|
|
|
log.warn("[Keka86] 消息长度不足,忽略: {}", payload);
|
|
log.warn("[Keka86] 消息长度不足,忽略: {}", payload);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 判断消息类型 (功能码在第2个字节,索引为1)
|
|
|
|
|
- String funcCodeStr = hexBytes[1].toUpperCase();
|
|
|
|
|
- int funcCode = Integer.parseInt(funcCodeStr, 16);
|
|
|
|
|
|
|
+ int funcCode = Integer.parseInt(hexBytes[1].toUpperCase(), 16);
|
|
|
|
|
|
|
|
if (funcCode == 0x03) {
|
|
if (funcCode == 0x03) {
|
|
|
- // 读取响应 (03H)
|
|
|
|
|
handleReadResponse(gatewayId, payload);
|
|
handleReadResponse(gatewayId, payload);
|
|
|
}
|
|
}
|
|
|
else if (funcCode == 0x06) {
|
|
else if (funcCode == 0x06) {
|
|
|
- // 写入确认响应 (06H) - 这就是你遇到的情况
|
|
|
|
|
handleWriteResponse(gatewayId, payload);
|
|
handleWriteResponse(gatewayId, payload);
|
|
|
}
|
|
}
|
|
|
else if ((funcCode & 0x80) != 0) {
|
|
else if ((funcCode & 0x80) != 0) {
|
|
|
- // 错误响应 (功能码最高位为1,如83H、86H)
|
|
|
|
|
byte originalFuncCode = (byte) (funcCode & 0x7F);
|
|
byte originalFuncCode = (byte) (funcCode & 0x7F);
|
|
|
handleErrorResponse(gatewayId, payload, originalFuncCode);
|
|
handleErrorResponse(gatewayId, payload, originalFuncCode);
|
|
|
}
|
|
}
|
|
|
else {
|
|
else {
|
|
|
- log.warn("[Keka86] 未知功能码: 0x{}, 消息: {}", funcCodeStr, payload);
|
|
|
|
|
|
|
+ log.warn("[Keka86] 未知功能码: 0x%02X, 消息: {}", funcCode, payload);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
}
|
|
}
|
|
@@ -171,95 +160,85 @@ public class Keka86BsHandler extends BaseDevHandler {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private String getDeviceCode(String gatewayId, int buttonId) {
|
|
private String getDeviceCode(String gatewayId, int buttonId) {
|
|
|
- List<EmsObjAttrValue> list = objAttrValueService.selectByAttrKeyValue(MODE_CODE, "gatewayId", gatewayId);
|
|
|
|
|
|
|
+ List<EmsObjAttrValue> list = objAttrValueService
|
|
|
|
|
+ .selectByAttrKeyValue(MODE_CODE, "gatewayId", gatewayId);
|
|
|
|
|
|
|
|
if (CollectionUtils.isNotEmpty(list)) {
|
|
if (CollectionUtils.isNotEmpty(list)) {
|
|
|
- for (EmsObjAttrValue gatewayIdValue : list) {
|
|
|
|
|
- EmsObjAttrValue buttonIdValue = objAttrValueService.selectObjAttrValue(MODE_CODE,
|
|
|
|
|
- gatewayIdValue.getObjCode(), "buttonId");
|
|
|
|
|
|
|
+ for (EmsObjAttrValue gatewayAttr : list) {
|
|
|
|
|
+ EmsObjAttrValue buttonAttr = objAttrValueService
|
|
|
|
|
+ .selectObjAttrValue(MODE_CODE, gatewayAttr.getObjCode(), "buttonId");
|
|
|
|
|
|
|
|
- if (null != buttonIdValue && StringUtils.equals(buttonIdValue.getObjCode(),
|
|
|
|
|
- gatewayIdValue.getObjCode())) {
|
|
|
|
|
- return gatewayIdValue.getObjCode();
|
|
|
|
|
|
|
+ if (buttonAttr != null
|
|
|
|
|
+ && StringUtils.equals(buttonAttr.getAttrValue(), String.valueOf(buttonId))) {
|
|
|
|
|
+ return gatewayAttr.getObjCode();
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 生成控制指令 (开/关) - 优化版本
|
|
|
|
|
- *
|
|
|
|
|
- * @param lightId 灯ID (1, 2, 3)
|
|
|
|
|
- * @param state 状态 (1=开, 0=关)
|
|
|
|
|
- * @return ModbusCommand 包含字节数组和十六进制字符串
|
|
|
|
|
|
|
+ * 生成控制指令 (开/关)
|
|
|
*/
|
|
*/
|
|
|
- public ModbusCommand buildControlCommand(int lightId, int state) {
|
|
|
|
|
- if (lightId < 1 || lightId > 3) {
|
|
|
|
|
- throw new IllegalArgumentException("灯ID必须是1-3");
|
|
|
|
|
|
|
+ public ModbusCommand buildControlCommand(int buttonId, int state) {
|
|
|
|
|
+ if (buttonId < 1 || buttonId > 6) {
|
|
|
|
|
+ throw new IllegalArgumentException("按键ID必须是1-6");
|
|
|
}
|
|
}
|
|
|
if (state != 0 && state != 1) {
|
|
if (state != 0 && state != 1) {
|
|
|
throw new IllegalArgumentException("状态必须是0(关)或1(开)");
|
|
throw new IllegalArgumentException("状态必须是0(关)或1(开)");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 计算寄存器地址
|
|
|
|
|
- int registerAddr = LIGHT_BASE_ADDR + (lightId - 1);
|
|
|
|
|
-
|
|
|
|
|
- // 控制值
|
|
|
|
|
|
|
+ int registerAddr = getRegisterAddr(buttonId);
|
|
|
int value = (state == 1) ? VALUE_ON : VALUE_OFF;
|
|
int value = (state == 1) ? VALUE_ON : VALUE_OFF;
|
|
|
|
|
|
|
|
- // 构建Modbus帧 (不含CRC)
|
|
|
|
|
byte[] frame = new byte[6];
|
|
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
|
|
|
|
|
|
|
+ frame[0] = DEVICE_ADDR;
|
|
|
|
|
+ frame[1] = FUNC_WRITE;
|
|
|
|
|
+ frame[2] = (byte) (registerAddr >> 8);
|
|
|
|
|
+ frame[3] = (byte) (registerAddr & 0xFF);
|
|
|
|
|
+ frame[4] = (byte) (value >> 8);
|
|
|
|
|
+ frame[5] = (byte) (value & 0xFF);
|
|
|
|
|
+
|
|
|
int crc = calculateCRC16(frame);
|
|
int crc = calculateCRC16(frame);
|
|
|
byte[] fullFrame = new byte[8];
|
|
byte[] fullFrame = new byte[8];
|
|
|
System.arraycopy(frame, 0, fullFrame, 0, 6);
|
|
System.arraycopy(frame, 0, fullFrame, 0, 6);
|
|
|
- fullFrame[6] = (byte) (crc & 0xFF); // CRC低字节在前
|
|
|
|
|
- fullFrame[7] = (byte) (crc >> 8); // CRC高字节在后
|
|
|
|
|
|
|
+ fullFrame[6] = (byte) (crc & 0xFF);
|
|
|
|
|
+ fullFrame[7] = (byte) (crc >> 8);
|
|
|
|
|
|
|
|
String hexString = bytesToHexString(fullFrame);
|
|
String hexString = bytesToHexString(fullFrame);
|
|
|
|
|
+ log.debug("[Keka86] 构建控制指令: 按键{}, 状态{}, 寄存器0x{}, 指令:{}",
|
|
|
|
|
+ buttonId, state == 1 ? "开" : "关", String.format("%04X", registerAddr), hexString);
|
|
|
return new ModbusCommand(fullFrame, hexString);
|
|
return new ModbusCommand(fullFrame, hexString);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 生成读取指令 - 优化版本
|
|
|
|
|
- *
|
|
|
|
|
- * @param lightId 灯ID (1, 2, 3)
|
|
|
|
|
- * @return ModbusCommand 包含字节数组和十六进制字符串
|
|
|
|
|
|
|
+ * 生成读取指令
|
|
|
|
|
+ * 读取单个按键状态,寄存器数量=1
|
|
|
*/
|
|
*/
|
|
|
- public ModbusCommand buildReadCommand(int lightId) {
|
|
|
|
|
- if (lightId < 1 || lightId > 3) {
|
|
|
|
|
- throw new IllegalArgumentException("灯ID必须是1-3");
|
|
|
|
|
|
|
+ public ModbusCommand buildReadCommand(int buttonId) {
|
|
|
|
|
+ if (buttonId < 1 || buttonId > 6) {
|
|
|
|
|
+ throw new IllegalArgumentException("按键ID必须是1-6");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 计算寄存器地址
|
|
|
|
|
- int registerAddr = LIGHT_BASE_ADDR + (lightId - 1);
|
|
|
|
|
|
|
+ int registerAddr = getRegisterAddr(buttonId);
|
|
|
|
|
|
|
|
- // 构建Modbus帧 (不含CRC)
|
|
|
|
|
byte[] frame = new byte[6];
|
|
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
|
|
|
|
|
|
|
+ frame[0] = DEVICE_ADDR;
|
|
|
|
|
+ frame[1] = FUNC_READ;
|
|
|
|
|
+ frame[2] = (byte) (registerAddr >> 8);
|
|
|
|
|
+ frame[3] = (byte) (registerAddr & 0xFF);
|
|
|
|
|
+ frame[4] = (byte) (READ_COUNT >> 8); // 0x00
|
|
|
|
|
+ frame[5] = (byte) (READ_COUNT & 0xFF); // 0x01 (读1个寄存器)
|
|
|
|
|
+
|
|
|
int crc = calculateCRC16(frame);
|
|
int crc = calculateCRC16(frame);
|
|
|
byte[] fullFrame = new byte[8];
|
|
byte[] fullFrame = new byte[8];
|
|
|
System.arraycopy(frame, 0, fullFrame, 0, 6);
|
|
System.arraycopy(frame, 0, fullFrame, 0, 6);
|
|
|
- fullFrame[6] = (byte) (crc & 0xFF); // CRC低字节
|
|
|
|
|
- fullFrame[7] = (byte) (crc >> 8); // CRC高字节
|
|
|
|
|
|
|
+ fullFrame[6] = (byte) (crc & 0xFF);
|
|
|
|
|
+ fullFrame[7] = (byte) (crc >> 8);
|
|
|
|
|
|
|
|
String hexString = bytesToHexString(fullFrame);
|
|
String hexString = bytesToHexString(fullFrame);
|
|
|
|
|
+ log.debug("[Keka86] 构建读取指令: 按键{}, 寄存器0x{}, 指令:{}",
|
|
|
|
|
+ buttonId, String.format("%04X", registerAddr), hexString);
|
|
|
return new ModbusCommand(fullFrame, hexString);
|
|
return new ModbusCommand(fullFrame, hexString);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -321,6 +300,20 @@ public class Keka86BsHandler extends BaseDevHandler {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
|
|
+ * 获取按键对应的寄存器地址
|
|
|
|
|
+ */
|
|
|
|
|
+ private int getRegisterAddr(int buttonId) {
|
|
|
|
|
+ return LIGHT_BASE_ADDR + (buttonId - 1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 根据寄存器地址反推按键ID
|
|
|
|
|
+ */
|
|
|
|
|
+ private int getButtonIdFromAddr(int registerAddr) {
|
|
|
|
|
+ return registerAddr - LIGHT_BASE_ADDR + 1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
* 获取Modbus错误信息
|
|
* 获取Modbus错误信息
|
|
|
*/
|
|
*/
|
|
|
private static String getModbusErrorMessage(byte errorCode) {
|
|
private static String getModbusErrorMessage(byte errorCode) {
|
|
@@ -401,34 +394,106 @@ public class Keka86BsHandler extends BaseDevHandler {
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 处理读取响应 (功能码 03H)
|
|
* 处理读取响应 (功能码 03H)
|
|
|
- * 响应格式: 01 03 04 00 01 00 00 FA 33
|
|
|
|
|
|
|
+ *
|
|
|
|
|
+ * 响应格式 (读取1个寄存器):
|
|
|
|
|
+ * [0] 设备地址: 01
|
|
|
|
|
+ * [1] 功能码: 03
|
|
|
|
|
+ * [2] 字节数: 02 (1个寄存器=2字节)
|
|
|
|
|
+ * [3] 数据高: 00
|
|
|
|
|
+ * [4] 数据低: 01 或 00
|
|
|
|
|
+ * [5] CRC低
|
|
|
|
|
+ * [6] CRC高
|
|
|
|
|
+ *
|
|
|
|
|
+ * 示例: 01 03 02 00 01 79 84 表示状态=开
|
|
|
|
|
+ * 01 03 02 00 00 B8 44 表示状态=关
|
|
|
*/
|
|
*/
|
|
|
private void handleReadResponse(String gatewayId, String hexMessage) {
|
|
private void handleReadResponse(String gatewayId, String hexMessage) {
|
|
|
try {
|
|
try {
|
|
|
- // 从响应中提取寄存器地址,推断是哪个按键
|
|
|
|
|
byte[] response = hexStringToBytes(hexMessage);
|
|
byte[] response = hexStringToBytes(hexMessage);
|
|
|
|
|
|
|
|
- // 方法1: 如果响应中没有地址信息,需要从上下文推断
|
|
|
|
|
- // 这里先假设返回的是第一个寄存器的数据
|
|
|
|
|
- // 实际需要根据之前发送的读取指令来匹配
|
|
|
|
|
|
|
+ // 最小长度: 地址(1) + 功能码(1) + 字节数(1) + 数据(2) + CRC(2) = 7
|
|
|
|
|
+ if (response.length < 7) {
|
|
|
|
|
+ log.warn("[Keka86-Read] 响应长度不足: {}", hexMessage);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 临时处理: 遍历所有可能的按键
|
|
|
|
|
- for (int buttonId = 1; buttonId <= 3; buttonId++) {
|
|
|
|
|
- DeviceStatus deviceStatus = parseReadResponse(hexMessage, buttonId);
|
|
|
|
|
- String deviceCode = getDeviceCode(gatewayId, buttonId);
|
|
|
|
|
|
|
+ // 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 (null == deviceCode) {
|
|
|
|
|
- log.debug("[Keka86-Read] 网关:{}, 按键{} 未注册", gatewayId, buttonId);
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (calculatedCrc != receivedCrc) {
|
|
|
|
|
+ log.error("[Keka86-Read] CRC校验失败, 计算:0x{}, 接收:0x{}, Hex:{}",
|
|
|
|
|
+ String.format("%04X", calculatedCrc),
|
|
|
|
|
+ String.format("%04X", receivedCrc), hexMessage);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 更新设备状态
|
|
|
|
|
- updateDeviceStatus(deviceCode, deviceStatus);
|
|
|
|
|
|
|
+ // 解析响应数据
|
|
|
|
|
+ int deviceAddr = response[0] & 0xFF;
|
|
|
|
|
+ int funcCode = response[1] & 0xFF;
|
|
|
|
|
+ int byteCount = response[2] & 0xFF;
|
|
|
|
|
+
|
|
|
|
|
+ if (funcCode != 0x03) {
|
|
|
|
|
+ log.warn("[Keka86-Read] 功能码不匹配: 0x{}", String.format("%02X", funcCode));
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 提取寄存器值 (2字节)
|
|
|
|
|
+ int dataValue = ((response[3] & 0xFF) << 8) | (response[4] & 0xFF);
|
|
|
|
|
+ int status = (dataValue == VALUE_ON) ? 1 : 0;
|
|
|
|
|
+
|
|
|
|
|
+ log.info("[Keka86-Read] 网关:{}, 设备地址:{}, 字节数:{}, 数据值:0x{}, 状态:{}",
|
|
|
|
|
+ gatewayId, deviceAddr, byteCount,
|
|
|
|
|
+ String.format("%04X", dataValue), status == 1 ? "开" : "关");
|
|
|
|
|
+
|
|
|
|
|
+ // ========== 关键修复:确定是哪个按键的响应 ==========
|
|
|
|
|
+ // 方案1: 从pendingReadRequests中查找 (需要记录发送时的按键)
|
|
|
|
|
+ // 方案2: 遍历所有按键配置,更新匹配的设备
|
|
|
|
|
+
|
|
|
|
|
+ // 这里采用方案2: 查找该网关下所有按键设备并更新
|
|
|
|
|
+ // 实际生产中建议用方案1,通过请求-响应匹配
|
|
|
|
|
+
|
|
|
|
|
+ List<EmsObjAttrValue> gatewayDevices = objAttrValueService
|
|
|
|
|
+ .selectByAttrKeyValue(MODE_CODE, "gatewayId", gatewayId);
|
|
|
|
|
+
|
|
|
|
|
+ if (CollectionUtils.isEmpty(gatewayDevices)) {
|
|
|
|
|
+ log.warn("[Keka86-Read] 网关:{} 无注册设备", gatewayId);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- log.info("[Keka86-Read] 网关:{}, 设备:{}, 按键{}, 状态:{}", gatewayId, deviceCode, buttonId,
|
|
|
|
|
- deviceStatus.getStatus() == 1 ? "开" : "关");
|
|
|
|
|
|
|
+ // 检查是否有匹配的待处理请求
|
|
|
|
|
+ for (EmsObjAttrValue gw : gatewayDevices) {
|
|
|
|
|
+ String deviceCode = gw.getObjCode();
|
|
|
|
|
+ EmsObjAttrValue btnAttr = objAttrValueService
|
|
|
|
|
+ .selectObjAttrValue(MODE_CODE, deviceCode, "buttonId");
|
|
|
|
|
+
|
|
|
|
|
+ if (btnAttr != null) {
|
|
|
|
|
+ int buttonId = Integer.parseInt(btnAttr.getAttrValue());
|
|
|
|
|
+ int registerAddr = getRegisterAddr(buttonId);
|
|
|
|
|
+ String requestKey = gatewayId + "_" + registerAddr;
|
|
|
|
|
+
|
|
|
|
|
+ // 检查是否是该按键的响应
|
|
|
|
|
+ if (pendingReadRequests.containsKey(requestKey)) {
|
|
|
|
|
+ pendingReadRequests.remove(requestKey);
|
|
|
|
|
+
|
|
|
|
|
+ DeviceStatus deviceStatus = new DeviceStatus(
|
|
|
|
|
+ deviceAddr, buttonId, status, hexMessage);
|
|
|
|
|
+ updateDeviceStatus(deviceCode, deviceStatus);
|
|
|
|
|
+
|
|
|
|
|
+ log.info("[Keka86-Read] 更新设备:{}, 按键:{}, 状态:{}",
|
|
|
|
|
+ deviceCode, buttonId, status == 1 ? "开" : "关");
|
|
|
|
|
+ return; // 找到匹配的请求,处理完毕
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // 如果没有待处理请求匹配,可能是设备主动上报
|
|
|
|
|
+ log.info("[Keka86-Read] 网关:{} 收到状态上报(可能是主动上报), 状态:{}",
|
|
|
|
|
+ gatewayId, status == 1 ? "开" : "关");
|
|
|
|
|
+
|
|
|
}
|
|
}
|
|
|
catch (Exception e) {
|
|
catch (Exception e) {
|
|
|
log.error("[Keka86-Read] 网关:{}, 解析失败, Hex:{}", gatewayId, hexMessage, e);
|
|
log.error("[Keka86-Read] 网关:{}, 解析失败, Hex:{}", gatewayId, hexMessage, e);
|
|
@@ -437,18 +502,33 @@ public class Keka86BsHandler extends BaseDevHandler {
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 处理写入响应 (功能码 06H)
|
|
* 处理写入响应 (功能码 06H)
|
|
|
- * 响应格式: 01 06 10 21 00 00 DD 00
|
|
|
|
|
- * 这是对写入指令的确认,表示设备已接收并执行
|
|
|
|
|
|
|
+ *
|
|
|
|
|
+ * 响应格式 (写入确认 - 回显请求):
|
|
|
|
|
+ * [0] 设备地址: 01
|
|
|
|
|
+ * [1] 功能码: 06
|
|
|
|
|
+ * [2] 寄存器高: 10
|
|
|
|
|
+ * [3] 寄存器低: 21 (0x1021 = 按键1)
|
|
|
|
|
+ * [4] 数据高: 00
|
|
|
|
|
+ * [5] 数据低: 01 或 00
|
|
|
|
|
+ * [6] CRC低
|
|
|
|
|
+ * [7] CRC高
|
|
|
|
|
+ *
|
|
|
|
|
+ * 示例: 01 06 10 21 00 01 DD 00 表示按键1设置为开
|
|
|
*/
|
|
*/
|
|
|
private void handleWriteResponse(String gatewayId, String hexMessage) {
|
|
private void handleWriteResponse(String gatewayId, String hexMessage) {
|
|
|
try {
|
|
try {
|
|
|
byte[] response = hexStringToBytes(hexMessage);
|
|
byte[] response = hexStringToBytes(hexMessage);
|
|
|
|
|
|
|
|
- // 验证CRC
|
|
|
|
|
- byte[] dataWithoutCrc = new byte[response.length - 2];
|
|
|
|
|
- System.arraycopy(response, 0, dataWithoutCrc, 0, response.length - 2);
|
|
|
|
|
|
|
+ if (response.length < 8) {
|
|
|
|
|
+ log.warn("[Keka86-Write] 响应长度不足: {}", hexMessage);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // CRC校验
|
|
|
|
|
+ byte[] dataWithoutCrc = new byte[6];
|
|
|
|
|
+ System.arraycopy(response, 0, dataWithoutCrc, 0, 6);
|
|
|
int calculatedCrc = calculateCRC16(dataWithoutCrc);
|
|
int calculatedCrc = calculateCRC16(dataWithoutCrc);
|
|
|
- int receivedCrc = (response[response.length - 2] & 0xFF) | ((response[response.length - 1] & 0xFF) << 8);
|
|
|
|
|
|
|
+ int receivedCrc = (response[6] & 0xFF) | ((response[7] & 0xFF) << 8);
|
|
|
|
|
|
|
|
if (calculatedCrc != receivedCrc) {
|
|
if (calculatedCrc != receivedCrc) {
|
|
|
log.error("[Keka86-Write] CRC校验失败, 网关:{}, Hex:{}", gatewayId, hexMessage);
|
|
log.error("[Keka86-Write] CRC校验失败, 网关:{}, Hex:{}", gatewayId, hexMessage);
|
|
@@ -457,21 +537,26 @@ public class Keka86BsHandler extends BaseDevHandler {
|
|
|
|
|
|
|
|
// 解析寄存器地址和值
|
|
// 解析寄存器地址和值
|
|
|
int registerAddr = ((response[2] & 0xFF) << 8) | (response[3] & 0xFF);
|
|
int registerAddr = ((response[2] & 0xFF) << 8) | (response[3] & 0xFF);
|
|
|
- int value = ((response[4] & 0xFF) << 8) | (response[5] & 0xFF);
|
|
|
|
|
|
|
+ int dataValue = ((response[4] & 0xFF) << 8) | (response[5] & 0xFF);
|
|
|
|
|
|
|
|
// 根据寄存器地址推断按键ID
|
|
// 根据寄存器地址推断按键ID
|
|
|
- int buttonId = registerAddr - LIGHT_BASE_ADDR + 1;
|
|
|
|
|
- int status = (value == VALUE_ON) ? 1 : 0;
|
|
|
|
|
|
|
+ int buttonId = getButtonIdFromAddr(registerAddr);
|
|
|
|
|
+ int status = (dataValue == VALUE_ON) ? 1 : 0;
|
|
|
|
|
|
|
|
- log.info("[Keka86-Write] 网关:{}, 写入确认成功, 寄存器:0x{}, 按键:{}, 状态:{}", gatewayId,
|
|
|
|
|
- String.format("%04X", registerAddr), buttonId, status == 1 ? "开" : "关");
|
|
|
|
|
|
|
+ log.info("[Keka86-Write] 网关:{}, 写入确认, 寄存器:0x{}, 按键:{}, 状态:{}",
|
|
|
|
|
+ gatewayId, String.format("%04X", registerAddr),
|
|
|
|
|
+ buttonId, status == 1 ? "开" : "关");
|
|
|
|
|
|
|
|
- // 写入成功后更新数据库状态
|
|
|
|
|
|
|
+ // 更新数据库状态
|
|
|
String deviceCode = getDeviceCode(gatewayId, buttonId);
|
|
String deviceCode = getDeviceCode(gatewayId, buttonId);
|
|
|
if (deviceCode != null) {
|
|
if (deviceCode != null) {
|
|
|
- DeviceStatus deviceStatus = new DeviceStatus(response[0] & 0xFF, buttonId, status, hexMessage);
|
|
|
|
|
|
|
+ DeviceStatus deviceStatus = new DeviceStatus(
|
|
|
|
|
+ response[0] & 0xFF, buttonId, status, hexMessage);
|
|
|
updateDeviceStatus(deviceCode, deviceStatus);
|
|
updateDeviceStatus(deviceCode, deviceStatus);
|
|
|
}
|
|
}
|
|
|
|
|
+ else {
|
|
|
|
|
+ log.warn("[Keka86-Write] 网关:{}, 按键{} 未找到对应设备", gatewayId, buttonId);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
}
|
|
}
|
|
|
catch (Exception e) {
|
|
catch (Exception e) {
|