From 1cfabdd2b4b61f3a6919a139d88409c1b427096d Mon Sep 17 00:00:00 2001 From: George Date: Fri, 20 Mar 2026 08:11:55 +0800 Subject: [PATCH] =?UTF-8?q?20260320:bug=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/consumer/DeviceStatusConsumer.java | 8 ++- .../src/views/iot/device/running-status.vue | 53 ++++++++++++++++++- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/consumer/DeviceStatusConsumer.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/consumer/DeviceStatusConsumer.java index 81b5e437..37680367 100644 --- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/consumer/DeviceStatusConsumer.java +++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/consumer/DeviceStatusConsumer.java @@ -72,8 +72,8 @@ public class DeviceStatusConsumer { private SnowflakeIdWorker snowflakeIdWorker = new SnowflakeIdWorker(2); - private static final String IMEI_CMD = "AT+IMEI\\r\\n"; - private static final String ICCID_CMD = "AT+ICCID\\r\\n"; + private static final String IMEI_CMD = "41542b494d45495c525c6e"; // AT+IMEI\r\n + private static final String ICCID_CMD = "41542b49434349445c525c6e"; // AT+ICCID\r\n private static final int AUTO_FETCH_COOLDOWN_SECONDS = 300; private static final String AUTO_FETCH_KEY_PREFIX = "iot:autoFetch:imeiIccid:"; @@ -131,6 +131,7 @@ public class DeviceStatusConsumer { private void autoFetchImeiIccidIfNeeded(Device device) { if (device == null || StringUtils.isEmpty(device.getSerialNumber())) { + log.info("=>设备首次上线,设备为空{}", device); return; } String serialNumber = device.getSerialNumber(); @@ -138,6 +139,7 @@ public class DeviceStatusConsumer { boolean needImei = StringUtils.isEmpty(device.getImei()); boolean needIccid = StringUtils.isEmpty(device.getIccid()); if (!needImei && !needIccid) { + log.info("=>设备首次上线,设备已存在IMEI/ICCID,无需获取{}", device); return; } @@ -145,6 +147,7 @@ public class DeviceStatusConsumer { String key = AUTO_FETCH_KEY_PREFIX + serialNumber + ":imei"; if (!redisCache.containsKey(key)) { redisCache.setCacheObject(key, "1", AUTO_FETCH_COOLDOWN_SECONDS, TimeUnit.SECONDS); + log.info("=>设备首次上线,下发AT指令获取IMEI,{}", device); sendAtCommand(serialNumber, IMEI_CMD); } } @@ -153,6 +156,7 @@ public class DeviceStatusConsumer { String key = AUTO_FETCH_KEY_PREFIX + serialNumber + ":iccid"; if (!redisCache.containsKey(key)) { redisCache.setCacheObject(key, "1", AUTO_FETCH_COOLDOWN_SECONDS, TimeUnit.SECONDS); + log.info("=>设备首次上线,下发AT指令获取ICCID,{}", device); sendAtCommand(serialNumber, ICCID_CMD); } } diff --git a/yack-web/src/views/iot/device/running-status.vue b/yack-web/src/views/iot/device/running-status.vue index 10802cfd..8bea8d0d 100644 --- a/yack-web/src/views/iot/device/running-status.vue +++ b/yack-web/src/views/iot/device/running-status.vue @@ -661,11 +661,21 @@ export default { if (type === 'imei') this.imeiLoading = true; if (type === 'iccid') this.iccidLoading = true; + // FUNCTION_GET 下发链路最终会走 ByteUtils.convertToHexByteArray(command)。 + // 因此这里必须传“十六进制字节串”,而不是原始 ASCII。 + // 同时兼容两种输入: + // 1) 用户输入 AT+IMEI\\r\\n 这类带转义的 ASCII:会先还原成真实 \r\n,再转 hex(不带空格) + // 2) 用户直接输入 41 54 ... 或 41542b...:会清理空格后直接使用(后端也会再次清理) + const trimmed = String(cmd).trim(); + const hexMessage = this.isHexLike(trimmed) + ? this.normalizeHexString(trimmed) + : this.asciiCommandToHex(this.normalizeAtEscapes(trimmed)); + await messagePost({ - message: cmd, + message: hexMessage, serialNumber: this.deviceInfo.serialNumber, topicName: '/function/get', - dataType: 'ascii', + dataType: 'hex', }); this.$message({ type: 'success', message: '已下发指令,等待设备回复解析…' }); @@ -680,6 +690,45 @@ export default { } }, + normalizeAtEscapes(str) { + // 输入框里用户通常会键入:AT+IMEI\\r\\n + // 但你当前的设备/链路期望的不是“真实 CR/LF(0d0a)”,而是把转义串当作占位符: + // 把 \\r\\n 期望转成 "/r/n"(即字符 '/','r','/','n',十六进制为 2f 72 2f 6e)。 + if (!str) return ''; + return String(str) + .replace(/\\r\\n/g, '/r/n') + .replace(/\\r/g, '/r') + .replace(/\\n/g, '/n'); + }, + + isHexLike(hexStr) { + if (!hexStr) return false; + const cleaned = this.normalizeHexString(hexStr); + if (!cleaned) return false; + return /^[0-9a-fA-F]+$/.test(cleaned); + }, + + normalizeHexString(hexStr) { + if (!hexStr) return ''; + return String(hexStr) + .trim() + .replace(/\s+/g, '') + .replace(/^0x/i, ''); + }, + + asciiCommandToHex(asciiStr) { + // 把字符串按字节(低8位)转成纯16进制(无空格) + if (!asciiStr) return ''; + let hex = ''; + for (let i = 0; i < asciiStr.length; i++) { + const c = asciiStr.charCodeAt(i) & 0xff; + let hexByte = c.toString(16).toUpperCase(); + if (hexByte.length === 1) hexByte = `0${hexByte}`; + hex += hexByte; + } + return hex; + }, + async saveImeiIccid(type) { try { if (!this.deviceInfo || !this.deviceInfo.deviceId) return;