diff --git a/.claude/settings.local.json b/.claude/settings.local.json index f2095108..f1bfdf4a 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -13,7 +13,10 @@ "Bash(python3 -c \"import fitz; print\\(''fitz ok''\\)\")", "Bash(python3:*)", "Bash(grep -A 5 \"INSERT INTO \\\\`sys_menu\\\\`\" /Users/admin/IdeaProjects/fastbee/sql/yack.sql)", - "Bash(mvn compile:*)" + "Bash(mvn compile:*)", + "Bash(mvn test:*)", + "Bash(mysql -hlocalhost -uroot -pfastbee fastbee -e \"SELECT id, serial_number, platform_id, device_type, enable FROM platform_device_mapping LIMIT 5;\")", + "Bash(mvn test-compile:*)" ] } } diff --git a/bin/send_modbus_and_register.py b/bin/send_modbus_and_register.py index 1b6215a0..a0bfe35f 100644 --- a/bin/send_modbus_and_register.py +++ b/bin/send_modbus_and_register.py @@ -3,19 +3,26 @@ import time from binascii import unhexlify # 目标服务器地址和端口 -TARGET_HOST = 'localhost' # 请根据实际情况修改 +TARGET_HOST = 'localhost' # 请根据实际情况修改 TARGET_PORT = 8888 # 请根据实际情况修改 -# 你的注册指令数据 -REGISTER_DATA_HEX = "7e80D2DA725711FB7e" +# 元数组 modbus 指令 +MODBUS_DATA_HEX = ( +"7e80D55746B29F687e", +"010310c07ffffe45217c3d41dfe9964140686641f4" +) +# "01031c4268442f41d30000418b6685000000004026f4cac00d1fd3c2ad2a9d6311", +# "02031c42488fd041c5800041865ab7000000003f364458c03f765ec2add9722da5", +# "03031c42ffdad541c78000421529b8000000003d5f38f94082080dc2abdf4f1f94" +# 01 03 1c 4268 442f 41d3 0000 418b 6685 0000 0000 4026 f4ca c00d 1fd3 c2ad 2a9d 6311 +# 从机地址 功能码 字节长度 2个字节 CRC校验 + +# 在计算机中一个字节1Byte=8bit 而16进制表示法中,刚好 2位数表示一个字节 2^4=16 +# Modbus 协议传输数据的基本单位是 16位的寄存器(2个字节)。 +# 但是,像温度、压力这种带有小数点的数值,用 16 位(2个字节)来表示精度不够。因此,工业上通常会把 2 个连续的寄存器(即 4 个字节,32 bit) 捆绑在一起,用来表示一个 32位浮点数(Float32) -# 你的Modbus指令数据 01034040fb6f6141bd5555bfc82e00bd7d2000c2b0e1063e90c9983c9df308be91110041d9e3753fedf7df44789610c1d60000bf555555c477900041e1058342e1b9874f50 -MODBUS_DATA_HEX = "01034040fb6f6141bd5555bfc82e00bd7d2000c2b0e1063e90c9983c9df308be91110041d9e3753fedf7df44789610c1d60000bf555555c477900041e1058342e1b9874f50" def main(): - # 将十六进制字符串转换为字节 - register_bytes = unhexlify(REGISTER_DATA_HEX) - modbus_bytes = unhexlify(MODBUS_DATA_HEX) try: # 创建TCP套接字 @@ -26,19 +33,19 @@ def main(): sock.connect((TARGET_HOST, TARGET_PORT)) print("已连接。") - # 1. 发送注册包 - print("发送注册包...") - sock.sendall(register_bytes) - print("注册包已发送。") + for hex_bytes in MODBUS_DATA_HEX: - # 2. 等待0.5秒 - print("等待 0.5 秒...") - time.sleep(0.5) + # 0.将十六进制字符串转换为字节 + modbus_bytes = unhexlify(hex_bytes) - # 3. 发送Modbus数据包 - print("发送Modbus数据包...") - sock.sendall(modbus_bytes) - print("Modbus数据包已发送。") + # 1. 发送注册包 + print("发送注册包...") + sock.sendall(modbus_bytes) + print("注册包已发送。") + + # 2. 等待0.5秒 + print("等待 0.1 秒...") + time.sleep(0.5) # (可选)接收最终响应 try: diff --git a/fastbee-common/src/main/java/com/fastbee/common/enums/ModbusDataType.java b/fastbee-common/src/main/java/com/fastbee/common/enums/ModbusDataType.java index 8f881b61..e2833a64 100644 --- a/fastbee-common/src/main/java/com/fastbee/common/enums/ModbusDataType.java +++ b/fastbee-common/src/main/java/com/fastbee/common/enums/ModbusDataType.java @@ -24,8 +24,8 @@ public enum ModbusDataType { FLOAT_CDAB("float-CDAB", "32位 浮点数(CDAB)"), BIT("bit", "位"); - String type; - String msg; + final String type; + final String msg; public static ModbusDataType convert(String type) { for (ModbusDataType value : ModbusDataType.values()) { diff --git a/fastbee-common/src/main/java/com/fastbee/common/utils/CalculateUtils.java b/fastbee-common/src/main/java/com/fastbee/common/utils/CalculateUtils.java index b43e116b..5e1f0760 100644 --- a/fastbee-common/src/main/java/com/fastbee/common/utils/CalculateUtils.java +++ b/fastbee-common/src/main/java/com/fastbee/common/utils/CalculateUtils.java @@ -343,13 +343,19 @@ public class CalculateUtils { /** * 转32位浮点数 CDAB - * + * 转32位浮点数 CDAB模式 (寄存器交换,字节不交换) + * 例如: 传输顺序 [Reg1: 0x4268][Reg2: 0x442F] -> 实际内存 [0x442F4268] * @param bytes * @return */ - public static Float toFloat32_DCBA(byte[] bytes) { - int intValue = ((bytes[3] & 0xFF) << 24) | ((bytes[2] & 0xFF) << 16) | ((bytes[1] & 0xFF) << 8) - | ((bytes[0] & 0xFF)); + public static float toFloat32_CDAB(byte[] bytes) { + if (bytes.length < 4) throw new IllegalArgumentException("字节数组长度不足4"); + // 假设 bytes[0-1] 是第一个寄存器,bytes[2-3] 是第二个寄存器 + // CDAB: 将第二个寄存器放到高16位,第一个寄存器放到低16位 + int intValue = ((bytes[2] & 0xFF) << 24) | + ((bytes[3] & 0xFF) << 16) | + ((bytes[0] & 0xFF) << 8) | + ((bytes[1] & 0xFF)); return Float.intBitsToFloat(intValue); } @@ -386,8 +392,8 @@ public class CalculateUtils { case FLOAT_ABCD: value = CalculateUtils.toFloat32_ABCD(bytes) + ""; break; - case FLOAT_CDAB: // todo:待改枚举页面枚举 - value = CalculateUtils.toFloat32_DCBA(bytes) + ""; + case FLOAT_CDAB: + value = CalculateUtils.toFloat32_CDAB(bytes) + ""; break; } } @@ -478,6 +484,8 @@ public class CalculateUtils { byte[] bytes2 = intToBytes2(val1); int i1 = bytesToInt2(bytes2); System.out.println(i1);*/ + + System.out.println("parseValue = " + parseValue("float-ABCD", "4268442f")); } } diff --git a/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/CRC16Utils.java b/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/CRC16Utils.java index 4d98989a..51525bcc 100644 --- a/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/CRC16Utils.java +++ b/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/CRC16Utils.java @@ -141,7 +141,7 @@ public class CRC16Utils { byte[] byte8 = ByteUtils.hexToByte(crc8); int b = CRC8(byte8); System.out.println(b); - float v = CalculateUtils.toFloat32_DCBA(bytes); + float v = CalculateUtils.toFloat32_CDAB(bytes); System.out.println(v); } diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/adapter/dcp/DcpPlatformAdapter.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/adapter/dcp/DcpPlatformAdapter.java index 95f9a5f3..635167e4 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/adapter/dcp/DcpPlatformAdapter.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/adapter/dcp/DcpPlatformAdapter.java @@ -34,7 +34,6 @@ import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.Date; import java.util.HashMap; -import java.util.List; import java.util.Map; /** @@ -140,6 +139,14 @@ public class DcpPlatformAdapter { return; } + // 无数组字段 → 简单推送,使用子设备 platformDeviceId 和主表 fieldMapping + if (!hasArrayField(requestSchema)) { + // 如果只有一个设备完全可以走 + handleSubDeviceSimplePush(platform, mapping, subMapping, reportSerialNumber, + payload, pushPath, interfaceCode, requestSchema); + return; + } + JSONArray fieldMappings = parseJsonArray(mapping.getFieldMapping()); String baseUrl = platform.getBaseUrl() + pushPath; @@ -220,6 +227,22 @@ public class DcpPlatformAdapter { } } + /** + * 请求 schema 是否包含数组字段 + */ + private boolean hasArrayField(JSONArray requestSchema) { + if (requestSchema == null || requestSchema.isEmpty()) { + return false; + } + for (int i = 0; i < requestSchema.size(); i++) { + JSONObject field = requestSchema.getJSONObject(i); + if ("array".equalsIgnoreCase(field.getString("type"))) { + return true; + } + } + return false; + } + /** * 从子映射的 onboard_config 解析基准点序列号 */ @@ -266,12 +289,12 @@ public class DcpPlatformAdapter { JSONArray fieldMappings = parseJsonArray(mapping.getFieldMapping()); if (requestSchema == null || requestSchema.isEmpty()) { - return buildLegacyPushBody(mapping, fieldMappings, reportData); + return buildLegacyPushBody(mapping.getPlatformDeviceId(), fieldMappings, reportData); } JSONObject dcpData = buildObjectBySchema(platform, mapping, requestSchema, reportData, fieldMappings); - if (dcpData.isEmpty() || !hasPayloadDrivenValue(dcpData, requestSchema)) { - return buildLegacyPushBody(mapping, fieldMappings, reportData); + if (dcpData.isEmpty() || hasPayloadDrivenValue(dcpData, requestSchema)) { + return buildLegacyPushBody(mapping.getPlatformDeviceId(), fieldMappings, reportData); } JSONArray arr = new JSONArray(); @@ -279,13 +302,13 @@ public class DcpPlatformAdapter { return arr; } - private Object buildLegacyPushBody(PlatformDeviceMapping mapping, JSONArray fieldMappings, JSONObject reportData) { + private Object buildLegacyPushBody(String platformDeviceId, JSONArray fieldMappings, JSONObject reportData) { if (fieldMappings == null || fieldMappings.isEmpty()) { return null; } JSONObject dcpData = new JSONObject(); - dcpData.put("deviceId", mapping.getPlatformDeviceId()); + dcpData.put("deviceId", platformDeviceId); dcpData.put("epoch", nowIsoTimestamp()); for (int i = 0; i < fieldMappings.size(); i++) { @@ -310,6 +333,64 @@ public class DcpPlatformAdapter { return arr; } + /** + * 网关子设备简单推送(推送接口无数组字段时走此路径) + *
+ * 使用子设备 platformDeviceId 和主表 fieldMapping 组装请求体 + */ + private void handleSubDeviceSimplePush(PlatformInstance platform, PlatformDeviceMapping mapping, + PlatformDeviceSubMapping subMapping, + String reportSerialNumber, String payload, + String pushPath, String interfaceCode, + JSONArray requestSchema) { + Object body = buildSubDevicePushBody(platform, mapping, subMapping, payload, requestSchema); + if (body == null) { + log.warn("[DCP] 网关子设备 {} 字段映射结果为空,跳过推送", reportSerialNumber); + return; + } + PlatformPushLog logEntry = createLogEntry(platform, mapping, interfaceCode, reportSerialNumber); + doHttpPost(platform, platform.getBaseUrl() + pushPath, JSON.toJSONString(body), logEntry); + pushLogService.asyncSave(logEntry); + } + + /** + * 构建网关子设备的简单请求体 + *
+ * 复用主表 fieldMapping 进行物模型字段映射,deviceId 取自子映射行 + */ + private Object buildSubDevicePushBody(PlatformInstance platform, PlatformDeviceMapping mapping, + PlatformDeviceSubMapping subMapping, String payload, + JSONArray requestSchema) { + JSONObject reportData = parsePayloadToMap(payload); + if (reportData == null || reportData.isEmpty()) { + return null; + } + + // 平台设备ID + String platformDeviceId = StringUtils.hasText(subMapping.getPlatformDeviceId()) + ? subMapping.getPlatformDeviceId() : mapping.getPlatformDeviceId(); + + JSONArray fieldMappings = parseJsonArray(mapping.getFieldMapping()); + if (requestSchema == null || requestSchema.isEmpty()) { + return buildLegacyPushBody(platformDeviceId, fieldMappings, reportData); + } + + JSONObject dcpData = buildObjectBySchema(platform, mapping, requestSchema, reportData, fieldMappings); + if (dcpData.isEmpty() || hasPayloadDrivenValue(dcpData, requestSchema)) { + + return buildLegacyPushBody(platformDeviceId, fieldMappings, reportData); + } + + // 覆写 deviceId 为子设备的平台 ID + if (StringUtils.hasText(subMapping.getPlatformDeviceId())) { + dcpData.put("deviceId", subMapping.getPlatformDeviceId()); + } + + JSONArray arr = new JSONArray(); + arr.add(dcpData); + return arr; + } + // ==================== 构建辅助 ==================== private JSONArray buildArrayPushBody(PlatformDeviceMapping mapping, JSONObject bodyFields, @@ -505,7 +586,7 @@ public class DcpPlatformAdapter { private boolean hasPayloadDrivenValue(JSONObject body, JSONArray requestSchema) { if (body == null || body.isEmpty() || requestSchema == null || requestSchema.isEmpty()) { - return false; + return true; } for (int i = 0; i < requestSchema.size(); i++) { JSONObject schemaField = requestSchema.getJSONObject(i); @@ -514,10 +595,10 @@ public class DcpPlatformAdapter { } if (usesPayload(schemaField.getString("source")) && body.containsKey(schemaField.getString("name"))) { - return true; + return false; } } - return false; + return true; } private boolean usesPayload(String source) { @@ -681,8 +762,6 @@ public class DcpPlatformAdapter { // ==================== HTTP 执行 ==================== - // ==================== HTTP 执行 ==================== - private void doHttpPost(PlatformInstance platform, String url, String bodyStr, PlatformPushLog logEntry) { long startMs = System.currentTimeMillis(); logEntry.setRequestUrl(url); @@ -717,10 +796,9 @@ public class DcpPlatformAdapter { } } - private PlatformPushLog createLogEntry(PlatformInstance platform, PlatformDeviceMapping mapping, String ifaceCode) { - return createLogEntry(platform, mapping, ifaceCode, mapping.getSerialNumber()); - } - + /** + * 创建记录日志 + */ private PlatformPushLog createLogEntry(PlatformInstance platform, PlatformDeviceMapping mapping, String ifaceCode, String serialNumber) { PlatformPushLog logEntry = new PlatformPushLog(); diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/service/impl/PlatformDeviceSubMappingServiceImpl.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/service/impl/PlatformDeviceSubMappingServiceImpl.java index 55608d2f..8e331fbe 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/service/impl/PlatformDeviceSubMappingServiceImpl.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/platform/service/impl/PlatformDeviceSubMappingServiceImpl.java @@ -1,6 +1,5 @@ package com.fastbee.iot.platform.service.impl; -import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.fastbee.iot.platform.domain.PlatformDeviceSubMapping; import com.fastbee.iot.platform.mapper.PlatformDeviceSubMappingMapper; diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceServiceImpl.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceServiceImpl.java index fff7d438..8ce4fa7e 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceServiceImpl.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceServiceImpl.java @@ -129,7 +129,6 @@ public class DeviceServiceImpl implements IDeviceService { @Resource private IPAddressService ipAddressService; - @Resource private EventLogMapper eventLogMapper; @Resource @@ -464,6 +463,7 @@ public class DeviceServiceImpl implements IDeviceService { // 删除网关绑定子设备关系 if (DeviceType.GATEWAY.getCode() == device.getDeviceType()) { subGatewayMapper.deleteGatewayByGwDeviceId(deviceId); + // todo 需要删子设备 } // 删除modbus轮询指令 String keyModbus = RedisKeyBuilder.buildModbusRuntimeCacheKey(device.getSerialNumber()); diff --git a/yack-web/src/views/platform/device-mapping/index.vue b/yack-web/src/views/platform/device-mapping/index.vue index 65f65d50..9b382073 100644 --- a/yack-web/src/views/platform/device-mapping/index.vue +++ b/yack-web/src/views/platform/device-mapping/index.vue @@ -155,7 +155,7 @@