From 629d361ca86c978728a612f740427c21fd0c089c Mon Sep 17 00:00:00 2001 From: George Date: Sun, 22 Mar 2026 11:12:16 +0800 Subject: [PATCH] =?UTF-8?q?20260322:=E6=89=8B=E5=8A=A8=E4=B8=8B=E5=8F=91?= =?UTF-8?q?=E6=8C=87=E4=BB=A4=E4=BF=9D=E5=AD=98Device=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application-dev.yml | 12 +- .../src/main/resources/application-test.yml | 4 +- .../utils/dtu/ATCommandParserUtils.java | 306 ++++++++++++++++++ .../iot/data/aspect/DeviceDebugLogAspect.java | 39 ++- .../data/consumer/DeviceStatusConsumer.java | 65 ---- .../src/views/iot/device/running-status.vue | 29 +- 6 files changed, 347 insertions(+), 108 deletions(-) create mode 100644 fastbee-common/src/main/java/com/fastbee/common/utils/dtu/ATCommandParserUtils.java diff --git a/fastbee-admin/src/main/resources/application-dev.yml b/fastbee-admin/src/main/resources/application-dev.yml index e594494b..1edf2dc5 100644 --- a/fastbee-admin/src/main/resources/application-dev.yml +++ b/fastbee-admin/src/main/resources/application-dev.yml @@ -18,9 +18,9 @@ spring: master: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://47.121.180.229:3306/fastbee?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + url: jdbc:mysql://localhost:3306/fastbee?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root - password: Yan@ancekong-mysql + password: fastbee druid: filters: stat,wall stat: @@ -56,7 +56,7 @@ spring: # ds0: # 配置 mysql 数据源 # type: com.alibaba.druid.pool.DruidDataSource # driver-class-name: com.mysql.cj.jdbc.Driver - # url: jdbc:mysql://47.121.180.229:3306/fastbee?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + # url: jdbc:mysql://localhost:3306/fastbee?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 # username: root # password: fastbee # filters: stat,wall @@ -100,7 +100,7 @@ spring: # redis 配置 redis: - host: 47.121.180.229 # 地址 + host: localhost # 地址 port: 6379 # 端口,默认为6379 database: 0 # 数据库索引 password: fastbee # 密码 @@ -115,7 +115,7 @@ spring: mqtt: username: fastbee # 账号 password: fastbee # 密码 - host-url: tcp://47.121.180.229:1883 # mqtt连接tcp地址 + host-url: tcp://localhost:1883 # mqtt连接tcp地址 client-id: ${random.int} # 客户端Id,不能相同,采用随机数 ${random.value} default-topic: test # 默认主题 timeout: 30 # 超时时间 @@ -123,7 +123,7 @@ spring: clearSession: true # 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息) # EMQX API配置需要在运行emqx之后去EMQX管理后台手动创建API Key和Secret,并配置到application.yml中 emqx: - host: 47.121.180.229 # EMQX服务器地址 + host: localhost # EMQX服务器地址 port: 18083 ApiKey: emqx.key.123 # EMQX API Key ApiSecret: emqx.api.123 diff --git a/fastbee-admin/src/main/resources/application-test.yml b/fastbee-admin/src/main/resources/application-test.yml index 69ea337f..711b866f 100644 --- a/fastbee-admin/src/main/resources/application-test.yml +++ b/fastbee-admin/src/main/resources/application-test.yml @@ -31,7 +31,7 @@ spring: wall: none-base-statement-allow: true taos: # 配置 taos 数据源 - enabled: true + enabled: false type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.taosdata.jdbc.TSDBDriver url: jdbc:TAOS://fastbee:6030/fastbee_log?timezone=UTC-8&charset=utf-8 @@ -50,7 +50,7 @@ spring: host: 47.121.180.229 # 地址 port: 6379 # 端口,默认为6379 database: 0 # 数据库索引 - password: Yan@ancekong-redis # 密码 + password: fastbee # 密码 timeout: 10s # 连接超时时间 lettuce: pool: diff --git a/fastbee-common/src/main/java/com/fastbee/common/utils/dtu/ATCommandParserUtils.java b/fastbee-common/src/main/java/com/fastbee/common/utils/dtu/ATCommandParserUtils.java new file mode 100644 index 00000000..6aff19b1 --- /dev/null +++ b/fastbee-common/src/main/java/com/fastbee/common/utils/dtu/ATCommandParserUtils.java @@ -0,0 +1,306 @@ +package com.fastbee.common.utils.dtu; + +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * AT指令响应解析工具类 + * 用于解析AT指令返回的数据,提取key-value信息 + */ +/** + * @description: AT指令解析工具类 + * 用于解析AT指令返回的数据,提取key-value信息 + * @author Sumuxi + * @date 2026/3/22 + */ +public class ATCommandParserUtils { + + /** + * 从十六进制字符串解析AT响应并提取键值对 + * + * @param hexString 十六进制字符串,如 "0a 2b 49 4d 45 49 3a 22 38 36..." + * @return 解析结果Map,key为字段名,value为字段值 + */ + public static Map parseATResponseFromHex(String hexString) { + String cleanHex = hexString.replaceAll("\\s+", ""); + byte[] data = hexStringToBytes(cleanHex); + String response = new String(data, StandardCharsets.UTF_8); + return parseATResponse(response); + } + + /** + * 从AT响应字符串解析键值对 + * 支持格式: + * - +KEY: "value" + * - +KEY: value + * - +KEY="value" + * - KEY: "value" + * + * @param response AT响应字符串 + * @return 解析结果Map + */ + public static Map parseATResponse(String response) { + Map result = new HashMap<>(); + + response = response.trim(); + String[] lines = response.split("\\r?\\n"); + + for (String line : lines) { + line = line.trim(); + if (line.isEmpty() || line.equals("OK") || line.equals("ERROR")) { + continue; + } + + // 支持冒号或等号分隔,支持带引号或不带引号的值 + Pattern pattern = Pattern.compile("^\\+?([A-Z_]+)[:=]\\s*(?:\"([^\"]*)\"|([^\\r\\n]*))$"); + Matcher matcher = pattern.matcher(line); + + if (matcher.find()) { + String key = matcher.group(1); + String value = matcher.group(2); + if (value == null) { + value = matcher.group(3); + } + if (value != null) { + value = value.trim(); + } + result.put(key, value); + } + } + + return result; + } + + /** + * 解析IMEI响应 + * 示例输入: 0a 2b 49 4d 45 49 3a 22 38 36 31 38 37 37 30 37 38 32 35 32 35 33 32 22 0a 0a 4f 4b 0a + * 对应文本: +IMEI:"861877078252532"\n\nOK\n + * + * @param hexString 十六进制字符串 + * @return IMEI号,解析失败返回null + */ + public static String parseIMEI(String hexString) { + Map result = parseATResponseFromHex(hexString); + return result.get("IMEI"); + } + + /** + * 解析ICCID响应 + * 示例输入: 0d0a2b49434349443a223839383630383538313132354432373832343033220d0a4f4b0d0a + * 对应文本: \r\n+ICCID:"898608581125D2782403"\r\nOK\r\n + * + * @param hexString 十六进制字符串 + * @return ICCID号,解析失败返回null + */ + public static String parseICCID(String hexString) { + Map result = parseATResponseFromHex(hexString); + return result.get("ICCID"); + } + + /** + * 解析CSQ信号强度响应 + * 示例输入: 2b4353513a32332c39390d0a4f4b0d0a + * 对应文本: +CSQ:23,99\r\nOK\r\n + * + * @param hexString 十六进制字符串 + * @return 信号强度值,如23,解析失败返回null + */ + public static Integer parseCSQ(String hexString) { + Map result = parseATResponseFromHex(hexString); + String csqValue = result.get("CSQ"); + if (csqValue != null && csqValue.contains(",")) { + String[] parts = csqValue.split(","); + try { + return Integer.parseInt(parts[0]); + } catch (NumberFormatException e) { + return null; + } + } + return null; + } + + /** + * 解析LBS基站定位响应 + * 示例输入: 0d0a2b4c42533d2232382e3132353833382c3131322e383632363339220d0a4f4b0d0a + * 对应文本: \r\n+LBS="28.125838,112.862639"\r\nOK\r\n + * + * @param hexString 十六进制字符串 + * @return 经纬度数组 [经度, 纬度],解析失败返回null + */ + public static double[] parseLBS(String hexString) { + Map result = parseATResponseFromHex(hexString); + String lbsValue = result.get("LBS"); + if (lbsValue != null && lbsValue.contains(",")) { + String[] parts = lbsValue.split(","); + try { + // 注意:实际顺序可能是 [纬度,经度] 或 [经度,纬度],根据文档通常为经度,纬度 + double latitude = Double.parseDouble(parts[0]); + double longitude = Double.parseDouble(parts[1]); + return new double[]{latitude, longitude}; + } catch (NumberFormatException e) { + return null; + } + } + return null; + } + + /** + * 解析LBS响应,返回经纬度字符串 + * + * @param hexString 十六进制字符串 + * @return 经纬度字符串,如 "28.125838,112.862639" + */ + public static String parseLBSString(String hexString) { + Map result = parseATResponseFromHex(hexString); + return result.get("LBS"); + } + + /** + * 解析SIM卡信息响应 + * + * @param hexString 十六进制字符串 + * @return SIM卡信息Map + */ + public static Map parseSIMInfo(String hexString) { + Map result = new HashMap<>(); + String cleanHex = hexString.replaceAll("\\s+", ""); + byte[] data = hexStringToBytes(cleanHex); + String response = new String(data, StandardCharsets.UTF_8); + + String[] lines = response.split("\\r?\\n"); + for (String line : lines) { + line = line.trim(); + if (line.isEmpty() || line.equals("OK")) { + continue; + } + // 格式: field: value + Pattern pattern = Pattern.compile("^([a-z_]+):\\s*(.+)$"); + Matcher matcher = pattern.matcher(line); + if (matcher.find()) { + result.put(matcher.group(1), matcher.group(2)); + } + } + return result; + } + + /** + * 解析UTC时间响应 + * 示例输入: 2b5554433a2231373534343633353332220d0a4f4b0d0a + * 对应文本: +UTC:"1754463532"\r\nOK\r\n + * + * @param hexString 十六进制字符串 + * @return UTC时间戳(秒),解析失败返回null + */ + public static Long parseUTC(String hexString) { + Map result = parseATResponseFromHex(hexString); + String utcValue = result.get("UTC"); + if (utcValue != null) { + try { + return Long.parseLong(utcValue); + } catch (NumberFormatException e) { + return null; + } + } + return null; + } + + /** + * 解析时间响应 + * 示例输入: 2b54494d453a22323032352d30382d30362031343a35393a3436220d0a4f4b0d0a + * 对应文本: +TIME:"2025-08-06 14:59:46"\r\nOK\r\n + * + * @param hexString 十六进制字符串 + * @return 时间字符串,如 "2025-08-06 14:59:46" + */ + public static String parseTime(String hexString) { + Map result = parseATResponseFromHex(hexString); + return result.get("TIME"); + } + + /** + * 解析网络注册状态响应 + * 示例输入: 2b43455245473a310d0a4f4b0d0a + * 对应文本: +CEREG:1\r\nOK\r\n + * + * @param hexString 十六进制字符串 + * @return 注册状态码 (0-5),解析失败返回null + */ + public static Integer parseCEREG(String hexString) { + Map result = parseATResponseFromHex(hexString); + String ceregValue = result.get("CEREG"); + if (ceregValue != null) { + try { + return Integer.parseInt(ceregValue); + } catch (NumberFormatException e) { + return null; + } + } + return null; + } + + /** + * 十六进制字符串转字节数组 + * + * @param hex 十六进制字符串(不含空格) + * @return 字节数组 + */ + private static byte[] hexStringToBytes(String hex) { + if (hex == null || hex.isEmpty()) { + return new byte[0]; + } + + int len = hex.length(); + if (len % 2 != 0) { + throw new IllegalArgumentException("十六进制字符串长度必须为偶数"); + } + + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4) + + Character.digit(hex.charAt(i + 1), 16)); + } + return data; + } + + /** + * 通用解析方法,返回完整的响应文本 + * + * @param hexString 十六进制字符串 + * @return 解码后的文本响应 + */ + public static String decodeHexResponse(String hexString) { + String cleanHex = hexString.replaceAll("\\s+", ""); + byte[] data = hexStringToBytes(cleanHex); + return new String(data, StandardCharsets.UTF_8); + } + + // 测试方法 + public static void main(String[] args) { + // 测试IMEI解析 + String imeiHex = "0d0a2b494d45493a22383631383737303738323532353332220d0a0d0a4f4b0d0a"; + System.out.println("IMEI解析结果: " + parseIMEI(imeiHex)); + + // 测试ICCID解析 + String iccidHex = "0d0a2b49434349443a223839383630383538313132354432373832343033220d0a4f4b0d0a"; + System.out.println("ICCID解析结果: " + parseICCID(iccidHex)); + + // 测试LBS解析(修正后应能正常解析) + String lbsHex = "0d0a2b4c42533d2232382e3132353833382c3131322e383632363339220d0a4f4b0d0a"; + System.out.println("LBS解析结果: " + parseLBSString(lbsHex)); + double[] lbs = parseLBS(lbsHex); + if (lbs != null) { + System.out.println("纬度: " + lbs[0] + ", 经度: " + lbs[1]); + } + + // 测试通用解析 + String response = decodeHexResponse(imeiHex); + System.out.println("\n解码后的响应: " + response); + + // 测试解析所有字段 + Map allFields = parseATResponseFromHex(imeiHex); + System.out.println("解析结果: " + allFields); + } +} diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/aspect/DeviceDebugLogAspect.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/aspect/DeviceDebugLogAspect.java index 3c1eb7b0..96916151 100644 --- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/aspect/DeviceDebugLogAspect.java +++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/aspect/DeviceDebugLogAspect.java @@ -6,18 +6,20 @@ import com.fastbee.common.core.device.DeviceAndProtocol; import com.fastbee.common.core.mq.DeviceReportBo; import com.fastbee.common.core.mq.message.DeviceData; import com.fastbee.common.core.mq.message.DeviceMessage; -import com.fastbee.common.core.redis.RedisCache; import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem; import com.fastbee.common.enums.DeviceLogTypeEnum; import com.fastbee.common.enums.TopicType; +import com.fastbee.common.utils.DateUtils; import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.dtu.ATCommandParserUtils; import com.fastbee.common.utils.gateway.mq.TopicsUtils; -import com.fastbee.iot.domain.*; +import com.fastbee.iot.domain.Device; +import com.fastbee.iot.domain.Product; +import com.fastbee.iot.domain.ProductAuthorize; +import com.fastbee.iot.mapper.DeviceMapper; import com.fastbee.iot.service.IDeviceService; -import com.fastbee.iot.service.IDeviceUserService; import com.fastbee.iot.service.IProductAuthorizeService; import com.fastbee.iot.service.IProductService; -import com.fastbee.iot.tdengine.service.ILogService; import com.fastbee.mqtt.manager.MqttRemoteManager; import com.fastbee.mqtt.model.PushMessageBo; import io.netty.buffer.ByteBuf; @@ -31,10 +33,7 @@ import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import javax.annotation.Resource; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Objects; +import java.util.*; import java.util.function.Function; /** @@ -57,9 +56,6 @@ import java.util.function.Function; @Slf4j public class DeviceDebugLogAspect { - @Resource - private ILogService logService; - @Resource private IDeviceService deviceService; @@ -76,7 +72,7 @@ public class DeviceDebugLogAspect { private TopicsUtils topicsUtils; @Resource - private RedisCache redisCache; + private DeviceMapper deviceMapper; /** * 拦截数据解析方法 @@ -273,9 +269,6 @@ public class DeviceDebugLogAspect { String errorMsg = e.getMessage(); return StringUtils.isEmpty(errorMsg) ? e.getClass().getName() : errorMsg; } - @Resource - private IDeviceUserService deviceUserService; - /** * 检查设备是否已授权 @@ -319,6 +312,22 @@ public class DeviceDebugLogAspect { return; } + // 跟新IMEI、ICCID + if (StringUtils.isEmpty(device.getImei()) || StringUtils.isEmpty(device.getIccid())) { + Map atCommandMap = ATCommandParserUtils.parseATResponseFromHex(context.sourceData); + Device updateDevice = new Device(); + if (StringUtils.isEmpty(device.getImei())) { + updateDevice.setImei(atCommandMap.get("IMEI")); + } + if (StringUtils.isEmpty(device.getIccid())) { + updateDevice.setIccid(atCommandMap.get("ICCID")); + } + updateDevice.setDeviceId(device.getDeviceId()); + updateDevice.setUpdateBy("DTU"); + updateDevice.setUpdateTime(DateUtils.getNowDate()); + deviceMapper.updateDevice(updateDevice); + } + // 获取产品ID Long productId = context.getProductId(); if (productId == null) { 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 37680367..b465291d 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 @@ -7,19 +7,14 @@ import com.fastbee.common.core.mq.DeviceStatusBo; import com.fastbee.common.core.mq.MQSendMessageBo; import com.fastbee.common.core.mq.SubDeviceBo; import com.fastbee.common.core.mq.message.ReportDataBo; -import com.fastbee.common.core.mq.message.DeviceMessage; import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem; import com.fastbee.common.enums.DeviceStatus; -import com.fastbee.common.core.redis.RedisCache; -import com.fastbee.common.utils.StringUtils; -import com.fastbee.common.enums.TopicType; import com.fastbee.iot.cache.IDeviceCache; import com.fastbee.iot.data.service.IMqttMessagePublish; import com.fastbee.iot.data.service.IRuleEngine; import com.fastbee.iot.domain.Device; import com.fastbee.iot.domain.Product; import com.fastbee.iot.model.ThingsModels.ThingsModelShadow; -import com.fastbee.iot.data.service.IDeviceMessageService; import com.fastbee.iot.service.IDeviceService; import com.fastbee.iot.service.IProductService; import com.fastbee.iot.service.ISubGatewayService; @@ -35,7 +30,6 @@ import org.springframework.util.CollectionUtils; import javax.annotation.Resource; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.TimeUnit; /** * 设备状态消息处理类 @@ -63,20 +57,8 @@ public class DeviceStatusConsumer { private Boolean enabled; @Resource private ISubGatewayService subGatewayService; - - @Resource - private IDeviceMessageService deviceMessageService; - - @Resource - private RedisCache redisCache; - private SnowflakeIdWorker snowflakeIdWorker = new SnowflakeIdWorker(2); - 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:"; - public synchronized void consume(DeviceStatusBo bo) { try { List list = new ArrayList<>(); @@ -108,10 +90,6 @@ public class DeviceStatusConsumer { } /*更新设备状态*/ deviceCache.updateDeviceStatusCache(statusBo, device); - // 设备首次上线,自动下发 AT 指令获取 IMEI/ICCID(字段为空才下发,并做简单限流) - if (status.equals(DeviceStatus.ONLINE)) { - autoFetchImeiIccidIfNeeded(device); - } //处理影子模式值 this.handlerShadow(device, status); //设备上下线执行规则引擎 @@ -129,49 +107,6 @@ public class DeviceStatusConsumer { } } - private void autoFetchImeiIccidIfNeeded(Device device) { - if (device == null || StringUtils.isEmpty(device.getSerialNumber())) { - log.info("=>设备首次上线,设备为空{}", device); - return; - } - String serialNumber = device.getSerialNumber(); - - boolean needImei = StringUtils.isEmpty(device.getImei()); - boolean needIccid = StringUtils.isEmpty(device.getIccid()); - if (!needImei && !needIccid) { - log.info("=>设备首次上线,设备已存在IMEI/ICCID,无需获取{}", device); - return; - } - - if (needImei) { - 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); - } - } - - if (needIccid) { - 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); - } - } - } - - private void sendAtCommand(String serialNumber, String command) { - DeviceMessage msg = new DeviceMessage(); - msg.setSerialNumber(serialNumber); - msg.setTopicName(TopicType.FUNCTION_GET.getTopicSuffix()); - msg.setMessage(command); - msg.setDataType("ascii"); - deviceMessageService.messagePost(msg); - log.info("自动下发 AT 指令获取信息, serialNumber={}, cmd={}", serialNumber, command); - } - private void handlerShadow(Device device, DeviceStatus status) { //获取设备协议编码 DeviceAndProtocol dp = deviceService.selectProtocolBySerialNumber(device.getSerialNumber()); diff --git a/yack-web/src/views/iot/device/running-status.vue b/yack-web/src/views/iot/device/running-status.vue index 8bea8d0d..df4e5955 100644 --- a/yack-web/src/views/iot/device/running-status.vue +++ b/yack-web/src/views/iot/device/running-status.vue @@ -645,9 +645,9 @@ export default { if (!this.deviceInfo || !this.deviceInfo.deviceId) return; const res = await getDevice(this.deviceInfo.deviceId); if (res && res.code === 200 && res.data) { - // 只刷新这两个字段,避免影响运行态数据 - this.deviceInfo.imei = res.data.imei; - this.deviceInfo.iccid = res.data.iccid; + // 只刷新这两个字段,避免影响运行态数据/使用 $set 确保响应式更新 + this.$set(this.deviceInfo, 'imei', res.data.imei); + this.$set(this.deviceInfo, 'iccid', res.data.iccid); } }, @@ -667,9 +667,9 @@ export default { // 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)); + const hexMessage = this.isHexLike(trimmed) + ? this.normalizeHexString(trimmed) + : this.asciiCommandToHex(trimmed); await messagePost({ message: hexMessage, @@ -679,9 +679,9 @@ export default { }); this.$message({ type: 'success', message: '已下发指令,等待设备回复解析…' }); - // 后端收到回复后会自动解析并写入数据库,这里做一次轻量轮询刷新 - setTimeout(() => this.refreshDeviceBaseInfo(), 1200); - setTimeout(() => this.refreshDeviceBaseInfo(), 2500); + // 后端收到回复后会自动解析并写入数据库,这里做一次轻量轮询刷新3秒、5秒各一次 + setTimeout(() => this.refreshDeviceBaseInfo(), 3000); + setTimeout(() => this.refreshDeviceBaseInfo(), 8000); } catch (e) { this.$message({ type: 'error', message: e && e.message ? e.message : '指令下发失败' }); } finally { @@ -690,17 +690,6 @@ 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);