diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/AppDeviceXinghanController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/AppDeviceXinghanController.java index b4f53c51..85fb39a0 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/AppDeviceXinghanController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/device/AppDeviceXinghanController.java @@ -6,7 +6,9 @@ import com.fuyuanshen.app.domain.dto.DeviceInstructDto; import com.fuyuanshen.common.core.domain.R; import com.fuyuanshen.common.core.validate.AddGroup; import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessAnnotation; +import com.fuyuanshen.common.ratelimiter.annotation.FunctionAccessBatcAnnotation; import com.fuyuanshen.common.web.core.BaseController; +import com.fuyuanshen.equipment.domain.dto.AppDeviceSendMsgBo; import com.fuyuanshen.web.service.device.DeviceBJQBizService; import com.fuyuanshen.web.service.device.DeviceXinghanBizService; import lombok.RequiredArgsConstructor; @@ -33,6 +35,15 @@ public class AppDeviceXinghanController extends BaseController { return toAjax(appDeviceService.registerPersonInfo(bo)); } + /** + * 发送紧急通知 + */ + @PostMapping(value = "/sendAlarmMessage") + @FunctionAccessBatcAnnotation(value = "sendAlarmMessage", timeOut = 5, batchMaxTimeOut = 10) + public R sendAlarmMessage(@RequestBody AppDeviceSendMsgBo bo) { + return toAjax(appDeviceService.sendAlarmMessage(bo)); + } + /** * 上传设备logo图片 */ diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/constants/DeviceRedisKeyConstants.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/constants/DeviceRedisKeyConstants.java index 080debc7..99f9b5d6 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/constants/DeviceRedisKeyConstants.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/constants/DeviceRedisKeyConstants.java @@ -47,4 +47,9 @@ public class DeviceRedisKeyConstants { * 告警 */ public static final String DEVICE_ALARM_KEY_PREFIX = ":alarm"; + + /** + * 告警信息 + */ + public static final String DEVICE_ALARM_MESSAGE_KEY_PREFIX = ":alarmMessage"; } diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanDeviceDataRule.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanDeviceDataRule.java index e12c0665..23f92be0 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanDeviceDataRule.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanDeviceDataRule.java @@ -14,11 +14,13 @@ import com.fuyuanshen.global.mqtt.base.MqttXinghanJson; import com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants; import com.fuyuanshen.global.mqtt.constants.LightingCommandTypeConstants; import com.fuyuanshen.global.mqtt.constants.XingHanCommandTypeConstants; +import com.fuyuanshen.global.mqtt.listener.domain.FunctionAccessStatus; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.beans.factory.annotation.Autowired; +import java.time.Duration; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; @@ -58,6 +60,7 @@ public class XinghanDeviceDataRule implements MqttMessageRule { @Override public void execute(MqttRuleContext context) { + String functionAccess = FUNCTION_ACCESS_KEY + context.getDeviceImei(); try { // Latitude, longitude //主灯档位,激光灯档位,电量百分比,充电状态,电池剩余续航时间 @@ -65,8 +68,10 @@ public class XinghanDeviceDataRule implements MqttMessageRule { // 发送设备状态和位置信息到Redis asyncSendDeviceDataToRedisWithFuture(context.getDeviceImei(),deviceStatus); + RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(20)); } catch (Exception e) { log.error("处理上报数据命令时出错", e); + RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.FAILED.getCode(), Duration.ofSeconds(20)); } } diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanSendAlarmMessageRule.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanSendAlarmMessageRule.java new file mode 100644 index 00000000..4134e783 --- /dev/null +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanSendAlarmMessageRule.java @@ -0,0 +1,118 @@ +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_ALARM_MESSAGE_KEY_PREFIX; +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.DEVICE_KEY_PREFIX; + +/** + * 星汉设备发送紧急通知 下发规则: + *

+ * 1. 设备上行 sta_BreakNews=cover! => 仅标记成功
+ * 2. 设备上行 sta_BreakNews=数字 => GBK编码,每行文字为一包,一共4包,第一字节为包序号 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class XinghanSendAlarmMessageRule implements MqttMessageRule { + + private final MqttGateway mqttGateway; + private final ObjectMapper objectMapper; + + @Override + public String getCommandType() { + return XingHanCommandTypeConstants.XingHan_BREAK_NEWS; + } + + @Override + public void execute(MqttRuleContext ctx) { + String functionAccess = FUNCTION_ACCESS_KEY + ctx.getDeviceImei(); + try { + XinghanSendAlarmMessageRule.MqttXinghanAlarmMsgJson payload = objectMapper.convertValue( + ctx.getPayloadDict(), XinghanSendAlarmMessageRule.MqttXinghanAlarmMsgJson.class); + + String respText = payload.getStaBreakNews(); + log.info("设备上报紧急通知握手: {} ", respText); + + // 1. cover! —— 成功标记 + if ("cover!".equalsIgnoreCase(respText)) { + RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.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形式存储在Redis中 + String data = RedisUtils.getCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + ctx.getDeviceImei() + DEVICE_ALARM_MESSAGE_KEY_PREFIX); + if (data.isEmpty()) { + return; + } + // + ArrayList intData = new ArrayList<>(); + intData.add(blockIndex); + // 获取块原内容 转成GBK 再转成无符号十进制整数 + String blockTxt = data.split(",")[blockIndex-1]; + // 再按 GBK 编码把字符串转成字节数组,并逐个转为无符号十进制整数 + for (byte b : blockTxt.getBytes(GBK)) { + intData.add(b & 0xFF); // b & 0xFF 得到 0~255 的整数 + } + + Map map = new HashMap<>(); + map.put("ins_BreakNews", 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 MqttXinghanAlarmMsgJson { + /** + * 设备上行: + * 数字 -> 请求对应块号 + * cover! -> 写入成功 + */ + @JsonProperty("sta_BreakNews") + private String staBreakNews; + } +} diff --git a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanSendMsgRule.java b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanSendMsgRule.java index 68acb099..d45d5b3c 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanSendMsgRule.java +++ b/fys-admin/src/main/java/com/fuyuanshen/global/mqtt/rule/xinghan/XinghanSendMsgRule.java @@ -57,7 +57,7 @@ public class XinghanSendMsgRule implements MqttMessageRule { // 1. genius! —— 成功标记 if ("genius!".equalsIgnoreCase(respText)) { - RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.FAILED.getCode(), Duration.ofSeconds(20)); + RedisUtils.setCacheObject(functionAccess, FunctionAccessStatus.OK.getCode(), Duration.ofSeconds(20)); log.info("设备 {} 发送消息完成", ctx.getDeviceImei()); return; } diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceXinghanBizService.java b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceXinghanBizService.java index 40c2d7af..fc69d57b 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceXinghanBizService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/service/device/DeviceXinghanBizService.java @@ -39,8 +39,7 @@ import static com.fuyuanshen.common.core.constant.GlobalConstants.GLOBAL_REDIS_K import static com.fuyuanshen.common.core.utils.Bitmap80x12Generator.buildArr; import static com.fuyuanshen.common.core.utils.Bitmap80x12Generator.generateFixedBitmapData; 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; +import static com.fuyuanshen.global.mqtt.constants.DeviceRedisKeyConstants.*; @Slf4j @Service @@ -62,7 +61,7 @@ public class DeviceXinghanBizService { "ins_DetectGrade", Map.of(1, "低档", 2, "中档", 3, "高档"), "ins_LightGrade", Map.of(1, "强光", 2, "弱光"), "ins_SOSGrade", Map.of(1, "爆闪模式", 2, "红蓝模式"), - "ins_ShakeBit", Map.of(0, "未静止报警", 1, "正在静止报警") + "ins_ShakeBit", Map.of(1, "开启报警") // 再加 4、5、6…… 档,直接往 Map 里塞即可 ); @@ -166,6 +165,7 @@ public class DeviceXinghanBizService { list.add(bo.getPosition()); list.add(bo.getCode()); RedisUtils.setCacheList(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + deviceObj.getDeviceImei() + ":app_send_message_data", list); + RedisUtils.expire(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + deviceObj.getDeviceImei() + ":app_send_message_data", Duration.ofSeconds(5 * 60L)); Map payload = Map.of("ins_TexTrans", Collections.singletonList(0)); @@ -195,6 +195,62 @@ public class DeviceXinghanBizService { } } + /** + * 发送报警信息 + * @param bo + * @return + */ + public int sendAlarmMessage(AppDeviceSendMsgBo bo) { + try { + List deviceIds = bo.getDeviceIds(); + if (deviceIds == null || deviceIds.isEmpty()) { + throw new ServiceException("请选择设备"); + } + + for (Long deviceId : deviceIds) { + Device device = deviceMapper.selectById(deviceId); + if (device == null) { + throw new ServiceException("设备不存在" + deviceId); + } + if(isDeviceOffline(device.getDeviceImei())){ + throw new ServiceException(device.getDeviceName()+",设备已断开连接"); + } + try { + + RedisUtils.setCacheObject(GLOBAL_REDIS_KEY+ DEVICE_KEY_PREFIX + device.getDeviceImei() + DEVICE_ALARM_MESSAGE_KEY_PREFIX, bo.getSendMsg(),Duration.ofSeconds(5 * 60L)); + + Map payload = Map.of("ins_BreakNews", + Collections.singletonList(0)); + String topic = MqttConstants.GLOBAL_PUB_KEY + device.getDeviceImei(); + String json = JsonUtils.toJsonString(payload); + + try { + mqttGateway.sendMsgToMqtt(topic, 1, json); + } catch (Exception e) { + log.error("发送紧急通知失败, topic={}, payload={}", topic, json, e); + throw new ServiceException("发送紧急通知失败:" + e.getMessage()); + } + log.info("发送紧急通知=>topic:{},payload:{}", MqttConstants.GLOBAL_PUB_KEY+device.getDeviceImei(),json); + + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("id", deviceId) + .eq("binding_user_id", AppLoginHelper.getUserId()) + .set("send_msg", bo.getSendMsg()); + deviceMapper.update(updateWrapper); + recordDeviceLog(device.getId(), device.getDeviceName(), "发送紧急通知", bo.getSendMsg(), AppLoginHelper.getUserId()); + } catch (Exception e) { + log.info("设备发送告警信息信息失败:{}" ,deviceId); + throw new ServiceException("设备发送告警信息信息失败"); + } + + } + } catch (Exception e){ + e.printStackTrace(); + throw new ServiceException("发送告警信息指令失败"); + } + return 1; + } + /* ---------------------------------- 私有通用方法 ---------------------------------- */ private void sendCommand(DeviceInstructDto dto,