20260322:手动下发指令保存Device表
This commit is contained in:
parent
df0c9890e8
commit
629d361ca8
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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<String, String> 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<String, String> parseATResponse(String response) {
|
||||
Map<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> result = parseATResponseFromHex(hexString);
|
||||
return result.get("LBS");
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析SIM卡信息响应
|
||||
*
|
||||
* @param hexString 十六进制字符串
|
||||
* @return SIM卡信息Map
|
||||
*/
|
||||
public static Map<String, String> parseSIMInfo(String hexString) {
|
||||
Map<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> allFields = parseATResponseFromHex(imeiHex);
|
||||
System.out.println("解析结果: " + allFields);
|
||||
}
|
||||
}
|
||||
@ -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<String, String> 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) {
|
||||
|
||||
@ -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<DeviceStatusBo> 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());
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user