0
0

feat(device): 新增星汉设备控制功能

- 添加星汉设备控制器 AppDeviceXinghanController- 实现星汉设备业务逻辑 DeviceXinghanBizService
- 增加开机 LOGO 下发规则 XinghanBootLogoRule
- 添加设备发送消息规则 XinghanSendMsgRule
- 更新 MQTT 命令类型常量 XingHanCommandTypeConstants
- 修改设备状态 JSON 结构 MqttXinghanJson
This commit is contained in:
2025-08-22 18:09:08 +08:00
parent 4077fd303f
commit 95aa01e1c2
7 changed files with 645 additions and 7 deletions

View File

@ -0,0 +1,143 @@
package com.fuyuanshen.global.mqtt.rule.xinghan;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fuyuanshen.common.core.utils.ImageToCArrayConverter;
import com.fuyuanshen.common.core.utils.StringUtils;
import com.fuyuanshen.common.json.utils.JsonUtils;
import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.global.mqtt.base.MqttMessageRule;
import com.fuyuanshen.global.mqtt.base.MqttRuleContext;
import com.fuyuanshen.global.mqtt.config.MqttGateway;
import com.fuyuanshen.global.mqtt.constants.LightingCommandTypeConstants;
import com.fuyuanshen.global.mqtt.constants.MqttConstants;
import com.fuyuanshen.global.mqtt.constants.XingHanCommandTypeConstants;
import com.fuyuanshen.global.mqtt.listener.domain.FunctionAccessStatus;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.CRC32;
import static com.fuyuanshen.common.core.constant.GlobalConstants.FUNCTION_ACCESS_KEY;
import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY;
import static com.fuyuanshen.common.core.utils.ImageToCArrayConverter.convertHexToDecimal;
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_BOOT_LOGO_KEY_PREFIX;
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX;
/**
* 星汉设备开机 LOGO 下发规则:
* <p>
* 1. 设备上行 sta_PicTarns=great! => 仅标记成功<br>
* 2. 设备上行 sta_PicTarns=数字 => 下发第 N 块数据256B/块,带 CRC32
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class XinghanBootLogoRule implements MqttMessageRule {
private final MqttGateway mqttGateway;
private final ObjectMapper objectMapper;
@Override
public String getCommandType() {
return XingHanCommandTypeConstants.XingHan_BOOT_LOGO;
}
@Override
public void execute(MqttRuleContext ctx) {
final String functionAccessKey = FUNCTION_ACCESS_KEY + ctx.getDeviceImei();
try {
MqttXinghanLogoJson payload = objectMapper.convertValue(
ctx.getPayloadDict(), MqttXinghanLogoJson.class);
String respText = payload.getStaPicTrans();
log.warn("设备上报LOGO{}", respText);
// 1. great! —— 成功标记
if ("great!".equalsIgnoreCase(respText)) {
RedisUtils.setCacheObject(functionAccessKey,
FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(20));
log.info("设备 {} 开机 LOGO 写入成功", ctx.getDeviceImei());
return;
}
// 2. 数字 —— 下发数据块
int blockIndex;
try {
blockIndex = Integer.parseInt(respText);
} catch (NumberFormatException ex) {
log.warn("设备 {} LOGO 上报非法块号:{}", ctx.getDeviceImei(), respText);
return;
}
String hexImage = RedisUtils.getCacheObject(
GLOBAL_REDIS_KEY + DEVICE_KEY_PREFIX + ctx.getDeviceImei() + DEVICE_BOOT_LOGO_KEY_PREFIX);
if (StringUtils.isEmpty(hexImage)) {
return;
}
byte[] fullBin = ImageToCArrayConverter.convertStringToByteArray(hexImage);
byte[] chunk = ImageToCArrayConverter.getChunk(fullBin, blockIndex - 1, CHUNK_SIZE);
log.info("设备 {} 第 {} 块数据长度: {} bytes", ctx.getDeviceImei(), blockIndex, chunk.length);
// 组装下发数据
ArrayList<Integer> dataFrame = new ArrayList<>();
dataFrame.add(blockIndex); // 块号
ImageToCArrayConverter.buildArr(convertHexToDecimal(chunk), dataFrame);
dataFrame.addAll(crc32AsList(chunk)); // CRC32
Map<String, Object> pub = new HashMap<>();
pub.put("ins_PicTrans", dataFrame);
String topic = MqttConstants.GLOBAL_PUB_KEY + ctx.getDeviceImei();
String json = JsonUtils.toJsonString(pub);
mqttGateway.sendMsgToMqtt(topic, 1, json);
log.info("下发开机 LOGO 数据 => topic:{}, payload:{}", topic, json);
} catch (Exception e) {
log.error("处理设备 {} 开机 LOGO 失败", ctx.getDeviceImei(), e);
RedisUtils.setCacheObject(functionAccessKey,
FunctionAccessStatus.FAILED.getCode(), Duration.ofSeconds(20));
}
}
/* ---------- 内部工具 ---------- */
private static final int CHUNK_SIZE = 256;
private static ArrayList<Integer> crc32AsList(byte[] data) {
CRC32 crc = new CRC32();
crc.update(data);
byte[] crcBytes = ByteBuffer.allocate(4)
.order(ByteOrder.BIG_ENDIAN)
.putInt((int) crc.getValue())
.array();
ArrayList<Integer> list = new ArrayList<>(4);
for (byte b : crcBytes) {
list.add(Byte.toUnsignedInt(b));
}
return list;
}
/* ---------- DTO ---------- */
@Data
private static class MqttXinghanLogoJson {
/**
* 设备上行:
* 数字 -> 请求对应块号
* great! -> 写入成功
*/
@JsonProperty("sta_PicTrans")
private String staPicTrans;
}
}

View File

@ -0,0 +1,117 @@
package com.fuyuanshen.global.mqtt.rule.xinghan;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fuyuanshen.common.json.utils.JsonUtils;
import com.fuyuanshen.common.redis.utils.RedisUtils;
import com.fuyuanshen.global.mqtt.base.MqttMessageRule;
import com.fuyuanshen.global.mqtt.base.MqttRuleContext;
import com.fuyuanshen.global.mqtt.config.MqttGateway;
import com.fuyuanshen.global.mqtt.constants.MqttConstants;
import com.fuyuanshen.global.mqtt.constants.XingHanCommandTypeConstants;
import com.fuyuanshen.global.mqtt.listener.domain.FunctionAccessStatus;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.nio.charset.Charset;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.fuyuanshen.common.core.constant.GlobalConstants.FUNCTION_ACCESS_KEY;
import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY;
import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX;
/**
* 星汉设备发送消息 下发规则:
* <p>
* 1. 设备上行 sta_TexTarns=genius! => 仅标记成功<br>
* 2. 设备上行 sta_TexTarns=数字 => GBK编码每行文字为一包一共4包第一字节为包序号
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class XinghanSendMsgRule implements MqttMessageRule {
private final MqttGateway mqttGateway;
private final ObjectMapper objectMapper;
@Override
public String getCommandType() {
return XingHanCommandTypeConstants.XingHan_ESEND_MSG;
}
@Override
public void execute(MqttRuleContext ctx) {
String functionAccess = FUNCTION_ACCESS_KEY + ctx.getDeviceImei();
try {
XinghanSendMsgRule.MqttXinghanMsgJson payload = objectMapper.convertValue(
ctx.getPayloadDict(), XinghanSendMsgRule.MqttXinghanMsgJson.class);
String respText = payload.getStaTexTrans();
log.info("设备上报人员信息: {} ", respText);
// 1. genius! —— 成功标记
if ("genius!".equalsIgnoreCase(respText)) {
RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.FAILED.getCode(), Duration.ofSeconds(20));
log.info("设备 {} 发送消息完成", ctx.getDeviceImei());
return;
}
// 2. 数字 —— 下发数据块
int blockIndex;
try {
blockIndex = Integer.parseInt(respText);
} catch (NumberFormatException ex) {
log.warn("设备 {} 消息上报非法块号:{}", ctx.getDeviceImei(), respText);
return;
}
// 将发送的信息原文本以List<String>形式存储在Redis中
List<String> data = RedisUtils.getCacheList(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + ctx.getDeviceImei() + ":app_send_message_data");
if (data.isEmpty()) {
return;
}
//
ArrayList<Integer> intData = new ArrayList<>();
intData.add(blockIndex);
// 获取块原内容 转成GBK 再转成无符号十进制整数
String blockTxt = data.get(blockIndex-1);
// 再按 GBK 编码把字符串转成字节数组,并逐个转为无符号十进制整数
for (byte b : blockTxt.getBytes(GBK)) {
intData.add(b & 0xFF); // b & 0xFF 得到 0~255 的整数
}
Map<String, Object> map = new HashMap<>();
map.put("ins_TexTrans", intData);
String topic = MqttConstants.GLOBAL_PUB_KEY + ctx.getDeviceImei();
String json = JsonUtils.toJsonString(map);
mqttGateway.sendMsgToMqtt(topic, 1, json);
log.info("发送设备信息数据到设备消息=>topic:{},payload:{}",
MqttConstants.GLOBAL_PUB_KEY + ctx.getDeviceImei(),
JsonUtils.toJsonString(map));
} catch (Exception e) {
log.error("处理发送设备信息时出错", e);
RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.FAILED.getCode(), Duration.ofSeconds(20));
}
}
private static final Charset GBK = Charset.forName("GBK");
/* ---------- DTO ---------- */
@Data
private static class MqttXinghanMsgJson {
/**
* 设备上行:
* 数字 -> 请求对应块号
* genius! -> 写入成功
*/
@JsonProperty("sta_TexTrans")
private String staTexTrans;
}
}