fix: 修改设备故障码记录相关逻辑。
This commit is contained in:
@@ -16,11 +16,18 @@ import com.intc.fishery.domain.AquUser;
|
||||
import com.intc.fishery.domain.Device;
|
||||
import com.intc.fishery.domain.MessageWarn;
|
||||
import com.intc.fishery.domain.Pond;
|
||||
import com.intc.fishery.constant.DefineDeviceWarnCode;
|
||||
import com.intc.fishery.mapper.AquUserMapper;
|
||||
import com.intc.fishery.domain.DeviceErrorCode;
|
||||
import com.intc.fishery.domain.DeviceSwitch;
|
||||
import com.intc.fishery.constant.DefineDeviceErrorCode;
|
||||
import com.intc.fishery.mapper.DeviceMapper;
|
||||
import com.intc.fishery.mapper.DeviceSwitchMapper;
|
||||
import com.intc.fishery.mapper.DeviceErrorCodeMapper;
|
||||
import com.intc.fishery.mapper.LinkedCtrlMapper;
|
||||
import com.intc.fishery.mapper.MessageWarnMapper;
|
||||
import com.intc.fishery.mapper.PondMapper;
|
||||
import com.intc.iot.service.IotCloudService;
|
||||
import com.intc.tdengine.domain.DeviceSensorData;
|
||||
import com.intc.tdengine.service.IDeviceSensorDataService;
|
||||
import com.intc.iot.config.AliyunIotProperties;
|
||||
@@ -89,6 +96,21 @@ public class DeviceDataHandler {
|
||||
*/
|
||||
private final PondMapper pondMapper;
|
||||
|
||||
/**
|
||||
* 设备故障码 Mapper
|
||||
*/
|
||||
private final DeviceErrorCodeMapper deviceErrorCodeMapper;
|
||||
|
||||
/**
|
||||
* 联动控制 Mapper
|
||||
*/
|
||||
private final LinkedCtrlMapper linkedCtrlMapper;
|
||||
|
||||
/**
|
||||
* IoT 云端服务(下发属性指令)
|
||||
*/
|
||||
private final IotCloudService iotCloudService;
|
||||
|
||||
/**
|
||||
* VMS 语音通知服务
|
||||
*/
|
||||
@@ -191,6 +213,14 @@ public class DeviceDataHandler {
|
||||
// 判断是否为控制器设备(使用配置文件中的ProductKey)
|
||||
boolean isController = controllerProductKey.equals(productKey);
|
||||
|
||||
// 收到设备属性上报,说明设备在线,清除离线标志
|
||||
clearDeviceOfflineFlag(deviceName);
|
||||
|
||||
// 处理探头错误码(sensorErrorCode 位掩码:更新 Device.warnCode 中探头离线位)
|
||||
if (params != null && params.containsKey("sensorErrorCode")) {
|
||||
handleSensorErrorCode(deviceName, params);
|
||||
}
|
||||
|
||||
if (params != null && params.size() > 0) {
|
||||
try {
|
||||
// 如果是控制器,需要区分处理
|
||||
@@ -199,6 +229,10 @@ public class DeviceDataHandler {
|
||||
if (hasSwitchData(params)) {
|
||||
// 开关数据:更新开关状态和电压电流
|
||||
handleSwitchData(deviceName, params);
|
||||
// 控制器故障码处理(C# itemController.errorCode)
|
||||
if (params.containsKey("errorCode")) {
|
||||
handleControllerErrorCode(deviceName, params);
|
||||
}
|
||||
} else if (hasSensorData(params)) {
|
||||
// 传感器数据:写入TDengine
|
||||
sensorData = convertToSensorData(productKey, deviceName, params);
|
||||
@@ -209,6 +243,11 @@ public class DeviceDataHandler {
|
||||
|
||||
// 更新设备表的实时数据
|
||||
updateDeviceRealTimeData(deviceName, sensorData);
|
||||
|
||||
// 联动控制:根据溶解氧数据控制开关
|
||||
if (sensorData.getDissolvedOxygen() != null) {
|
||||
handleLinkedCtrl(deviceName, sensorData.getDissolvedOxygen());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 既不是开关数据也不是传感器数据
|
||||
@@ -225,6 +264,11 @@ public class DeviceDataHandler {
|
||||
|
||||
// 更新设备表的实时数据
|
||||
updateDeviceRealTimeData(deviceName, sensorData);
|
||||
|
||||
// 联动控制:根据溶解氧数据控制开关
|
||||
if (sensorData.getDissolvedOxygen() != null) {
|
||||
handleLinkedCtrl(deviceName, sensorData.getDissolvedOxygen());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.debug("数据字段不足3个,已过滤: {} (字段数: {})", deviceName, params.size());
|
||||
@@ -830,6 +874,9 @@ public class DeviceDataHandler {
|
||||
int ttlMinutes = offlineDelayMinutes * OFFLINE_WAIT_TTL_MULTIPLIER;
|
||||
RedisUtils.setCacheObject(redisKey, offlineTime, Duration.ofMinutes(ttlMinutes));
|
||||
log.info("[离线告警] 设备离线,已记录等待标记,将在 {} 分钟后触发告警: {}", offlineDelayMinutes, deviceName);
|
||||
|
||||
// 同步更新 Device.warnCode,写入离线标志位
|
||||
setDeviceWarnCodeBit(deviceName, DefineDeviceWarnCode.DeviceOffline, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -844,6 +891,9 @@ public class DeviceDataHandler {
|
||||
if (removed) {
|
||||
log.info("[离线告警] 设备已上线,取消离线告警: {}", deviceName);
|
||||
}
|
||||
|
||||
// 同步更新 Device.warnCode,清除离线标志位
|
||||
setDeviceWarnCodeBit(deviceName, DefineDeviceWarnCode.DeviceOffline, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1094,25 +1144,703 @@ public class DeviceDataHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发设备故障告警
|
||||
* 针对设备事件(error)的紧急告警
|
||||
* 触发设备故障告警(对应 IoT 事件路径 error 事件)
|
||||
* 参考 C# CreateNoticeCall 逻辑:查设备→查塘口→写告警消息→创建电话通知记录→立即发送
|
||||
*
|
||||
* @param deviceName 设备名称
|
||||
* @param params 事件参数
|
||||
*/
|
||||
private void triggerDeviceErrorAlarm(String deviceName, JSONObject params) {
|
||||
try {
|
||||
String errorMsg = params != null ? params.toString() : "设备故障";
|
||||
log.error("[设备故障] 设备: {}, 故障信息: {}", deviceName, errorMsg);
|
||||
Device device = deviceMapper.selectOne(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Device>()
|
||||
.eq(Device::getSerialNum, deviceName)
|
||||
.last("LIMIT 1")
|
||||
);
|
||||
if (device == null) {
|
||||
log.debug("[\u8bbe\u5907\u6545\u969c] \u8bbe\u5907\u4e0d\u5b58\u5728\uff0c\u8df3\u8fc7: {}", deviceName);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: 实现紧急告警逻辑
|
||||
// 1. 查询设备信息和用户
|
||||
// 2. 创建紧急告警记录
|
||||
// 3. 立即发送语音通知
|
||||
// 4. 可选:发送短信、推送消息等多种通知方式
|
||||
Long deviceId = device.getId();
|
||||
Long userId = device.getUserId();
|
||||
if (userId == null) {
|
||||
log.debug("[\u8bbe\u5907\u6545\u969c] \u8bbe\u5907\u672a\u7ed1\u5b9a\u7528\u6237\uff0c\u8df3\u8fc7: {}", deviceName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (device.getPondId() == null) {
|
||||
log.debug("[\u8bbe\u5907\u6545\u969c] \u8bbe\u5907\u672a\u7ed1\u5b9a\u5858\u53e3\uff0c\u8df3\u8fc7: {}", deviceName);
|
||||
return;
|
||||
}
|
||||
|
||||
Pond pond = pondMapper.selectById(device.getPondId());
|
||||
if (pond == null) {
|
||||
log.debug("[\u8bbe\u5907\u6545\u969c] \u5858\u53e3\u4e0d\u5b58\u5728\uff0c\u8df3\u8fc7: {}", deviceName);
|
||||
return;
|
||||
}
|
||||
|
||||
String pondName = pond.getPondName();
|
||||
String displayName = device.getDeviceName() != null ? device.getDeviceName() : deviceName;
|
||||
|
||||
// \u89e3\u6790\u4e8b\u4ef6\u53c2\u6570\u83b7\u53d6\u6545\u969c\u7801
|
||||
int errorCode = params != null && params.containsKey("errorCode")
|
||||
? params.getInt("errorCode", 0) : 0;
|
||||
String errorDesc = DefineDeviceErrorCode.getErrorMessage(errorCode);
|
||||
if (StrUtil.isBlank(errorDesc)) {
|
||||
errorDesc = "\u8bbe\u5907\u6545\u969c(code=" + errorCode + ")";
|
||||
}
|
||||
|
||||
String warnMsg = String.format("%s\u5858\u53e3\u4e2d%s(%s)\u53d1\u751f\u6545\u969c\uff1a%s",
|
||||
pondName, displayName, deviceName, errorDesc);
|
||||
log.error("[\u8bbe\u5907\u6545\u969c] \u8bbe\u5907: {}, \u6545\u969c\u4fe1\u606f: {}", deviceName, warnMsg);
|
||||
|
||||
// \u83b7\u53d6\u544a\u8b66\u624b\u673a\u53f7
|
||||
AquUser aquUser = aquUserMapper.selectById(userId);
|
||||
if (aquUser == null) {
|
||||
log.warn("[\u8bbe\u5907\u6545\u969c] \u7528\u6237\u4e0d\u5b58\u5728\uff0c\u8df3\u8fc7: {}", deviceName);
|
||||
return;
|
||||
}
|
||||
java.util.List<String> phoneList = parseWarnPhoneList(aquUser.getWarnPhoneJson());
|
||||
if (phoneList == null || phoneList.isEmpty()) {
|
||||
log.warn("[\u8bbe\u5907\u6545\u969c] \u7528\u6237\u672a\u914d\u7f6e\u544a\u8b66\u624b\u673a\u53f7\uff0c\u8df3\u8fc7: {}", deviceName);
|
||||
return;
|
||||
}
|
||||
|
||||
// \u53cc\u91cd\u6291\u5236
|
||||
String errorTitle = "\u63a7\u5236\u5668\u6545\u969c";
|
||||
int maxBatchMinutesErr = CALL_NOTICE_RETRY_COUNT * CALL_NOTICE_MINUTE_INTERVAL * 2;
|
||||
LocalDateTime activeBatchStartErr = LocalDateTime.now().minusMinutes(maxBatchMinutesErr);
|
||||
long activeCountErr = warnCallNoticeMapper.countActiveByDeviceAndTitleAfter(deviceId, errorTitle, activeBatchStartErr);
|
||||
if (activeCountErr > 0) {
|
||||
log.debug("[\u8bbe\u5907\u6545\u969c] \u5f53\u524d\u6709\u6d3b\u8dc3\u901a\u77e5\u6279\u6b21\u5728\u8fdb\u884c\uff0c\u8df3\u8fc7: {}", deviceName);
|
||||
return;
|
||||
}
|
||||
LocalDateTime suppressTimeErr = LocalDateTime.now().minusHours(aliyunIotProperties.getCallSuccessSuppressHours());
|
||||
long recentCountErr = warnCallNoticeMapper.countSuccessByDeviceAndTitleAfter(deviceId, errorTitle, suppressTimeErr);
|
||||
if (recentCountErr > 0) {
|
||||
log.debug("[\u8bbe\u5907\u6545\u969c] {}h\u5185\u5df2\u6709\u56de\u6267\u6210\u529f\u7684\u901a\u77e5\uff0c\u8df3\u8fc7: {}", aliyunIotProperties.getCallSuccessSuppressHours(), deviceName);
|
||||
return;
|
||||
}
|
||||
|
||||
// \u5199\u5165\u544a\u8b66\u6d88\u606f
|
||||
MessageWarn warn = createMessageWarn(deviceId, userId, pondName, errorTitle, WARN_TYPE_DISSOLVED_OXYGEN, warnMsg);
|
||||
messageWarnMapper.insert(warn);
|
||||
|
||||
// \u521b\u5efa\u7535\u8bdd\u901a\u77e5\u8bb0\u5f55
|
||||
LocalDateTime baseTime = LocalDateTime.now();
|
||||
int index = 0;
|
||||
AquWarnCallNotice firstCallNotice = null;
|
||||
|
||||
for (int retry = 0; retry < CALL_NOTICE_RETRY_COUNT; retry++) {
|
||||
for (String phoneNum : phoneList) {
|
||||
if (StrUtil.isBlank(phoneNum) || phoneNum.length() < 11) {
|
||||
continue;
|
||||
}
|
||||
LocalDateTime callTime = baseTime.plusMinutes((long) CALL_NOTICE_MINUTE_INTERVAL * index);
|
||||
index++;
|
||||
|
||||
AquWarnCallNotice callNotice = new AquWarnCallNotice();
|
||||
callNotice.setUserId(userId);
|
||||
callNotice.setMobilePhone(phoneNum);
|
||||
callNotice.setDeviceId(deviceId);
|
||||
callNotice.setPondName(pondName);
|
||||
callNotice.setCallTime(callTime);
|
||||
callNotice.setCallStatus(CALL_STATUS_NO_CALL);
|
||||
warnCallNoticeMapper.insert(callNotice);
|
||||
|
||||
if (warn.getId() != null) {
|
||||
AquMapMessageWarnCallNotice mapRecord = new AquMapMessageWarnCallNotice();
|
||||
mapRecord.setMessageWarnId(warn.getId());
|
||||
mapRecord.setCallNoticeId(callNotice.getId());
|
||||
mapMessageWarnCallNoticeMapper.insert(mapRecord);
|
||||
}
|
||||
|
||||
if (firstCallNotice == null) {
|
||||
firstCallNotice = callNotice;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (firstCallNotice != null) {
|
||||
sendVoiceNotification(firstCallNotice, displayName, errorTitle);
|
||||
}
|
||||
|
||||
log.info("[\u8bbe\u5907\u6545\u969c] \u5df2\u89e6\u53d1\u901a\u77e5 - \u8bbe\u5907\uff1a{}, \u5858\u53e3\uff1a{}, \u624b\u673a\u53f7\u6570\uff1a{}",
|
||||
deviceName, pondName, phoneList.size());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[设备故障] 触发故障告警失败: {}", e.getMessage(), e);
|
||||
log.error("[\u8bbe\u5907\u6545\u969c] \u89e6\u53d1\u6545\u969c\u544a\u8b66\u5931\u8d25: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理控制器上报的 errorCode(属性上报路径,对应 C# itemController.errorCode)
|
||||
* <ul>
|
||||
* <li>errorCode == 0:清除所有故障码(ClearErrorCode)</li>
|
||||
* <li>errorCode/100%10 == 2:删除指定故障码(RemoveErrorCode,errorCode -= 200)</li>
|
||||
* <li>errorCode/100%10 == 0:新增故障码(CreateErrorCode)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param deviceName 设备名称(serialNum)
|
||||
* @param params 上报参数
|
||||
*/
|
||||
private void handleControllerErrorCode(String deviceName, JSONObject params) {
|
||||
try {
|
||||
Integer rawCode = params.getInt("errorCode");
|
||||
if (rawCode == null) {
|
||||
log.warn("[故障码] 解析 errorCode 失败: {}", deviceName);
|
||||
return;
|
||||
}
|
||||
int errorCode = rawCode;
|
||||
|
||||
// 查询设备基本信息
|
||||
Device device = deviceMapper.selectOne(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Device>()
|
||||
.eq(Device::getSerialNum, deviceName)
|
||||
.last("LIMIT 1")
|
||||
);
|
||||
if (device == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (errorCode == 0) {
|
||||
// 清除所有故障码
|
||||
clearErrorCodeRecords(device);
|
||||
log.info("[故障码] 设备={} 清除所有故障码", deviceName);
|
||||
} else {
|
||||
int tempIndex = errorCode / 100 % 10;
|
||||
if (tempIndex == 2) {
|
||||
// 故障恢复
|
||||
int realCode = errorCode - 200;
|
||||
removeErrorCodeRecord(device, realCode);
|
||||
log.info("[故障码] 设备={} 清除故障码={}", deviceName, realCode);
|
||||
} else if (tempIndex == 0) {
|
||||
// 新增故障
|
||||
createErrorCodeRecord(device, errorCode);
|
||||
log.info("[故障码] 设备={} 新增故障码={}", deviceName, errorCode);
|
||||
} else {
|
||||
log.warn("[故障码] 设备={} 无法解析故障码={}", deviceName, errorCode);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[故障码] 处理失败[{}]: {}", deviceName, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建设备故障码记录,并根据故障类型生成告警消息和电话通知
|
||||
* 对应 C# CreateErrorCode 逻辑
|
||||
*
|
||||
* @param device 设备信息
|
||||
* @param errorCode 故障码
|
||||
*/
|
||||
private void createErrorCodeRecord(Device device, int errorCode) {
|
||||
try {
|
||||
Long deviceId = device.getId();
|
||||
if (deviceId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否已存在相同故障码
|
||||
long existCount = deviceErrorCodeMapper.selectCount(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<DeviceErrorCode>()
|
||||
.eq(DeviceErrorCode::getDeviceId, deviceId)
|
||||
.eq(DeviceErrorCode::getErrorCode, errorCode)
|
||||
);
|
||||
if (existCount > 0) {
|
||||
log.debug("[故障码] 设备={} 故障码={} 已存在,跳过", device.getSerialNum(), errorCode);
|
||||
return;
|
||||
}
|
||||
|
||||
// 查询塘口(如果设备在塘口中)
|
||||
String pondName = null;
|
||||
if (device.getPondId() != null) {
|
||||
Pond pond = pondMapper.selectById(device.getPondId());
|
||||
if (pond != null) {
|
||||
pondName = pond.getPondName();
|
||||
}
|
||||
}
|
||||
String displayName = device.getDeviceName() != null ? device.getDeviceName() : device.getSerialNum();
|
||||
|
||||
// 设备在塘口中且非山机才触发告警
|
||||
boolean isOffline = device.getWarnCode() != null
|
||||
&& (device.getWarnCode() & DefineDeviceWarnCode.DeviceOffline) != 0;
|
||||
boolean warnTrigger = !isOffline && pondName != null;
|
||||
|
||||
// 根据故障码范围分类生成告警内容
|
||||
String title = null;
|
||||
String message = null;
|
||||
int switchIndex = 0;
|
||||
|
||||
if (errorCode >= DefineDeviceErrorCode.Three_Switch1LosePhaseA
|
||||
&& errorCode <= DefineDeviceErrorCode.Three_Switch4LosePhaseC) {
|
||||
// 三相开关输出缺相
|
||||
int tempIndex = errorCode - DefineDeviceErrorCode.Three_Switch1LosePhaseA;
|
||||
String phaseName = DefineDeviceErrorCode.getPhaseName(tempIndex % 3);
|
||||
switchIndex = tempIndex / 3 + 1;
|
||||
DeviceSwitch sw = getDeviceSwitchByIndex(deviceId, switchIndex);
|
||||
// 缺相故障强制关闭开关
|
||||
forceSwitchOff(device, switchIndex);
|
||||
if (warnTrigger) {
|
||||
String swPondName = (sw != null && sw.getPondId() != null)
|
||||
? getPondName(sw.getPondId()) : pondName;
|
||||
String swName = sw != null && sw.getSwitchName() != null ? sw.getSwitchName() : ("\u5f00\u5173" + switchIndex);
|
||||
title = "\u63a7\u5236\u5668\u5f00\u5173\u8f93\u51fa\u7f3a\u76f8";
|
||||
message = String.format("%s\u5858\u53e3\u4e2d%s(%s)\u7684\u5f00\u5173%d(%s)\u8f93\u51fa\u7f3a%s\u76f8",
|
||||
swPondName, displayName, device.getSerialNum(), switchIndex, swName, phaseName);
|
||||
}
|
||||
} else if (errorCode >= DefineDeviceErrorCode.Three_Switch1OverElectricA
|
||||
&& errorCode <= DefineDeviceErrorCode.Three_Switch4OverElectricC) {
|
||||
// 三相过流
|
||||
int tempIndex = errorCode - DefineDeviceErrorCode.Three_Switch1OverElectricA;
|
||||
String phaseName = DefineDeviceErrorCode.getPhaseName(tempIndex % 3);
|
||||
switchIndex = tempIndex / 3 + 1;
|
||||
DeviceSwitch sw = getDeviceSwitchByIndex(deviceId, switchIndex);
|
||||
boolean electricWarnOpen = sw != null && sw.getElectricWarnOpen() != null && sw.getElectricWarnOpen() == 1;
|
||||
warnTrigger = warnTrigger && electricWarnOpen;
|
||||
if (warnTrigger) {
|
||||
String swPondName = (sw.getPondId() != null) ? getPondName(sw.getPondId()) : pondName;
|
||||
String swName = sw.getSwitchName() != null ? sw.getSwitchName() : ("\u5f00\u5173" + switchIndex);
|
||||
title = "\u63a7\u5236\u5668\u5f00\u5173\u8f93\u51fa\u8fc7\u6d41";
|
||||
message = String.format("%s\u5858\u53e3\u4e2d%s(%s)\u7684\u5f00\u5173%d(%s)\u8f93\u51fa%s\u76f8\u8fc7\u6d41",
|
||||
swPondName, displayName, device.getSerialNum(), switchIndex, swName, phaseName);
|
||||
}
|
||||
} else if (errorCode >= DefineDeviceErrorCode.Three_Switch1UnderElectricA
|
||||
&& errorCode <= DefineDeviceErrorCode.Three_Switch4UnderElectricC) {
|
||||
// 三相欠流
|
||||
int tempIndex = errorCode - DefineDeviceErrorCode.Three_Switch1UnderElectricA;
|
||||
String phaseName = DefineDeviceErrorCode.getPhaseName(tempIndex % 3);
|
||||
switchIndex = tempIndex / 3 + 1;
|
||||
DeviceSwitch sw = getDeviceSwitchByIndex(deviceId, switchIndex);
|
||||
boolean electricWarnOpen = sw != null && sw.getElectricWarnOpen() != null && sw.getElectricWarnOpen() == 1;
|
||||
warnTrigger = warnTrigger && electricWarnOpen;
|
||||
if (warnTrigger) {
|
||||
String swPondName = (sw.getPondId() != null) ? getPondName(sw.getPondId()) : pondName;
|
||||
String swName = sw.getSwitchName() != null ? sw.getSwitchName() : ("\u5f00\u5173" + switchIndex);
|
||||
title = "\u63a7\u5236\u5668\u5f00\u5173\u8f93\u51fa\u6b20\u6d41";
|
||||
message = String.format("%s\u5858\u53e3\u4e2d%s(%s)\u7684\u5f00\u5173%d(%s)\u8f93\u51fa%s\u76f8\u6b20\u6d41",
|
||||
swPondName, displayName, device.getSerialNum(), switchIndex, swName, phaseName);
|
||||
}
|
||||
} else if (errorCode >= DefineDeviceErrorCode.Three_Switch1ElectricEmpty
|
||||
&& errorCode <= DefineDeviceErrorCode.Three_Switch4ElectricEmpty) {
|
||||
// 三相空载
|
||||
switchIndex = errorCode - DefineDeviceErrorCode.Three_Switch1ElectricEmpty + 1;
|
||||
DeviceSwitch sw = getDeviceSwitchByIndex(deviceId, switchIndex);
|
||||
boolean electricWarnOpen = sw != null && sw.getElectricWarnOpen() != null && sw.getElectricWarnOpen() == 1;
|
||||
warnTrigger = warnTrigger && electricWarnOpen;
|
||||
if (warnTrigger) {
|
||||
String swPondName = (sw.getPondId() != null) ? getPondName(sw.getPondId()) : pondName;
|
||||
String swName = sw.getSwitchName() != null ? sw.getSwitchName() : ("\u5f00\u5173" + switchIndex);
|
||||
title = "\u63a7\u5236\u5668\u5f00\u5173\u8f93\u51fa\u7a7a\u8f7d";
|
||||
message = String.format("%s\u5858\u53e3\u4e2d%s(%s)\u7684\u5f00\u5173%d(%s)\u8f93\u51fa\u7a7a\u8f7d",
|
||||
swPondName, displayName, device.getSerialNum(), switchIndex, swName);
|
||||
}
|
||||
} else if (errorCode >= DefineDeviceErrorCode.One_Switch1OverElectric
|
||||
&& errorCode <= DefineDeviceErrorCode.One_Switch4OverElectric) {
|
||||
// 单相过流
|
||||
switchIndex = errorCode - DefineDeviceErrorCode.One_Switch1OverElectric + 1;
|
||||
DeviceSwitch sw = getDeviceSwitchByIndex(deviceId, switchIndex);
|
||||
boolean electricWarnOpen = sw != null && sw.getElectricWarnOpen() != null && sw.getElectricWarnOpen() == 1;
|
||||
warnTrigger = warnTrigger && electricWarnOpen;
|
||||
if (warnTrigger) {
|
||||
String swPondName = (sw.getPondId() != null) ? getPondName(sw.getPondId()) : pondName;
|
||||
String swName = sw.getSwitchName() != null ? sw.getSwitchName() : ("\u5f00\u5173" + switchIndex);
|
||||
title = "\u63a7\u5236\u5668\u5f00\u5173\u8f93\u51fa\u8fc7\u6d41";
|
||||
message = String.format("%s\u5858\u53e3\u4e2d%s(%s)\u7684\u5f00\u5173%d(%s)\u8f93\u51fa\u8fc7\u6d41",
|
||||
swPondName, displayName, device.getSerialNum(), switchIndex, swName);
|
||||
}
|
||||
} else if (errorCode >= DefineDeviceErrorCode.One_Switch1UnderElectric
|
||||
&& errorCode <= DefineDeviceErrorCode.One_Switch4UnderElectric) {
|
||||
// 单相欠流
|
||||
switchIndex = errorCode - DefineDeviceErrorCode.One_Switch1UnderElectric + 1;
|
||||
DeviceSwitch sw = getDeviceSwitchByIndex(deviceId, switchIndex);
|
||||
boolean electricWarnOpen = sw != null && sw.getElectricWarnOpen() != null && sw.getElectricWarnOpen() == 1;
|
||||
warnTrigger = warnTrigger && electricWarnOpen;
|
||||
if (warnTrigger) {
|
||||
String swPondName = (sw.getPondId() != null) ? getPondName(sw.getPondId()) : pondName;
|
||||
String swName = sw.getSwitchName() != null ? sw.getSwitchName() : ("\u5f00\u5173" + switchIndex);
|
||||
title = "\u63a7\u5236\u5668\u5f00\u5173\u8f93\u51fa\u6b20\u6d41";
|
||||
message = String.format("%s\u5858\u53e3\u4e2d%s(%s)\u7684\u5f00\u5173%d(%s)\u8f93\u51fa\u6b20\u6d41",
|
||||
swPondName, displayName, device.getSerialNum(), switchIndex, swName);
|
||||
}
|
||||
} else if (errorCode >= DefineDeviceErrorCode.One_Switch1ElectricEmpty
|
||||
&& errorCode <= DefineDeviceErrorCode.One_Switch4ElectricEmpty) {
|
||||
// 单相空载
|
||||
switchIndex = errorCode - DefineDeviceErrorCode.One_Switch1ElectricEmpty + 1;
|
||||
DeviceSwitch sw = getDeviceSwitchByIndex(deviceId, switchIndex);
|
||||
boolean electricWarnOpen = sw != null && sw.getElectricWarnOpen() != null && sw.getElectricWarnOpen() == 1;
|
||||
warnTrigger = warnTrigger && electricWarnOpen;
|
||||
if (warnTrigger) {
|
||||
String swPondName = (sw.getPondId() != null) ? getPondName(sw.getPondId()) : pondName;
|
||||
String swName = sw.getSwitchName() != null ? sw.getSwitchName() : ("\u5f00\u5173" + switchIndex);
|
||||
title = "\u63a7\u5236\u5668\u5f00\u5173\u8f93\u51fa\u7a7a\u8f7d";
|
||||
message = String.format("%s\u5858\u53e3\u4e2d%s(%s)\u7684\u5f00\u5173%d(%s)\u8f93\u51fa\u7a7a\u8f7d",
|
||||
swPondName, displayName, device.getSerialNum(), switchIndex, swName);
|
||||
}
|
||||
} else if (errorCode == DefineDeviceErrorCode.PowerOff) {
|
||||
// 断电:已存在离线状态则不触发断电告警
|
||||
boolean alreadyOffline = device.getWarnCode() != null
|
||||
&& (device.getWarnCode() & DefineDeviceWarnCode.DeviceOffline) != 0;
|
||||
warnTrigger = warnTrigger && !alreadyOffline;
|
||||
// 置位 ErrorPowerOff warnCode
|
||||
setDeviceWarnCodeBit(device.getSerialNum(), DefineDeviceWarnCode.ErrorPowerOff, true);
|
||||
if (warnTrigger) {
|
||||
title = "\u63a7\u5236\u5668\u65ad\u7535";
|
||||
message = String.format("%s\u5858\u53e3\u4e2d%s(%s)\u65ad\u7535",
|
||||
pondName, displayName, device.getSerialNum());
|
||||
}
|
||||
} else {
|
||||
warnTrigger = false;
|
||||
}
|
||||
|
||||
// 创建告警消息和电话通知
|
||||
if (warnTrigger && title != null && message != null) {
|
||||
Long userId = device.getUserId();
|
||||
if (userId != null) {
|
||||
MessageWarn warn = createMessageWarn(deviceId, userId, pondName, title, WARN_TYPE_DISSOLVED_OXYGEN, message);
|
||||
messageWarnMapper.insert(warn);
|
||||
// 创建电话通知记录
|
||||
createCallNotices(device, pondName, warn);
|
||||
}
|
||||
}
|
||||
|
||||
// 写入 DeviceErrorCode 数据库记录
|
||||
DeviceErrorCode data = new DeviceErrorCode();
|
||||
data.setDeviceId(deviceId);
|
||||
data.setSwitchIndex(switchIndex);
|
||||
data.setErrorCode(errorCode);
|
||||
deviceErrorCodeMapper.insert(data);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("[故障码] 创建故障码记录失败[{}]: {}", device.getSerialNum(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除设备指定故障码记录(对应 C# RemoveErrorCode)
|
||||
*
|
||||
* @param device 设备信息
|
||||
* @param errorCode 故障码
|
||||
*/
|
||||
private void removeErrorCodeRecord(Device device, int errorCode) {
|
||||
try {
|
||||
if (device.getId() == null) {
|
||||
return;
|
||||
}
|
||||
// PowerOff 恢复时清除 ErrorPowerOff warnCode 位
|
||||
if (errorCode == DefineDeviceErrorCode.PowerOff) {
|
||||
setDeviceWarnCodeBit(device.getSerialNum(), DefineDeviceWarnCode.ErrorPowerOff, false);
|
||||
}
|
||||
int rows = deviceErrorCodeMapper.delete(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<DeviceErrorCode>()
|
||||
.eq(DeviceErrorCode::getDeviceId, device.getId())
|
||||
.eq(DeviceErrorCode::getErrorCode, errorCode)
|
||||
);
|
||||
log.debug("[故障码] 设备={} 删除故障码={}, 影响行数={}", device.getSerialNum(), errorCode, rows);
|
||||
} catch (Exception e) {
|
||||
log.error("[故障码] 删除故障码失败[{}]: {}", device.getSerialNum(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除设备所有故障码记录(对应 C# ClearErrorCode)
|
||||
* 同时清除 ErrorPowerOff warnCode 位
|
||||
*
|
||||
* @param device 设备信息
|
||||
*/
|
||||
private void clearErrorCodeRecords(Device device) {
|
||||
try {
|
||||
if (device.getId() == null) {
|
||||
return;
|
||||
}
|
||||
deviceErrorCodeMapper.delete(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<DeviceErrorCode>()
|
||||
.eq(DeviceErrorCode::getDeviceId, device.getId())
|
||||
);
|
||||
// 清除 ErrorPowerOff 位
|
||||
setDeviceWarnCodeBit(device.getSerialNum(), DefineDeviceWarnCode.ErrorPowerOff, false);
|
||||
log.debug("[故障码] 设备={} 清除所有故障码完成", device.getSerialNum());
|
||||
} catch (Exception e) {
|
||||
log.error("[故障码] 清除故障码失败[{}]: {}", device.getSerialNum(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据开关序号查询 DeviceSwitch
|
||||
*/
|
||||
private DeviceSwitch getDeviceSwitchByIndex(Long deviceId, int switchIndex) {
|
||||
try {
|
||||
return deviceSwitchMapper.selectOne(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<DeviceSwitch>()
|
||||
.eq(DeviceSwitch::getDeviceId, deviceId)
|
||||
.eq(DeviceSwitch::getIndex, switchIndex)
|
||||
.last("LIMIT 1")
|
||||
);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据塘口ID获取塘口名称
|
||||
*/
|
||||
private String getPondName(Long pondId) {
|
||||
if (pondId == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Pond pond = pondMapper.selectById(pondId);
|
||||
return pond != null ? pond.getPondName() : null;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制关闭开关(故障导致的强制关闭)
|
||||
*/
|
||||
private void forceSwitchOff(Device device, int switchIndex) {
|
||||
try {
|
||||
if (device.getIotId() == null) {
|
||||
return;
|
||||
}
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put("Switch" + switchIndex, 0);
|
||||
boolean success = iotCloudService.setProperty(device.getIotId(), properties, false, 1);
|
||||
if (success) {
|
||||
// 更新DB中的开关状态
|
||||
DeviceSwitch sw = getDeviceSwitchByIndex(device.getId(), switchIndex);
|
||||
if (sw != null) {
|
||||
DeviceSwitch updateSw = new DeviceSwitch();
|
||||
updateSw.setId(sw.getId());
|
||||
updateSw.setIsOpen(0);
|
||||
updateSw.setLastTurnTime(new java.util.Date());
|
||||
deviceSwitchMapper.updateById(updateSw);
|
||||
}
|
||||
log.info("[故障码] 设备={} 开关{} 已强制关闭", device.getSerialNum(), switchIndex);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[故障码] 强制关闭开关失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建电话通知记录(内部辅助方法)
|
||||
*
|
||||
* @param device 设备信息
|
||||
* @param pondName 塘口名称
|
||||
* @param warn 告警消息记录
|
||||
*/
|
||||
private void createCallNotices(Device device, String pondName, MessageWarn warn) {
|
||||
try {
|
||||
Long userId = device.getUserId();
|
||||
if (userId == null) {
|
||||
return;
|
||||
}
|
||||
AquUser aquUser = aquUserMapper.selectById(userId);
|
||||
if (aquUser == null) {
|
||||
return;
|
||||
}
|
||||
java.util.List<String> phoneList = parseWarnPhoneList(aquUser.getWarnPhoneJson());
|
||||
if (phoneList == null || phoneList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LocalDateTime baseTime = LocalDateTime.now();
|
||||
int index = 0;
|
||||
AquWarnCallNotice firstCallNotice = null;
|
||||
|
||||
for (int retry = 0; retry < CALL_NOTICE_RETRY_COUNT; retry++) {
|
||||
for (String phoneNum : phoneList) {
|
||||
if (StrUtil.isBlank(phoneNum) || phoneNum.length() < 11) {
|
||||
continue;
|
||||
}
|
||||
LocalDateTime callTime = baseTime.plusMinutes((long) CALL_NOTICE_MINUTE_INTERVAL * index);
|
||||
index++;
|
||||
|
||||
AquWarnCallNotice callNotice = new AquWarnCallNotice();
|
||||
callNotice.setUserId(userId);
|
||||
callNotice.setMobilePhone(phoneNum);
|
||||
callNotice.setDeviceId(device.getId());
|
||||
callNotice.setPondName(pondName);
|
||||
callNotice.setCallTime(callTime);
|
||||
callNotice.setCallStatus(CALL_STATUS_NO_CALL);
|
||||
warnCallNoticeMapper.insert(callNotice);
|
||||
|
||||
if (warn.getId() != null) {
|
||||
AquMapMessageWarnCallNotice mapRecord = new AquMapMessageWarnCallNotice();
|
||||
mapRecord.setMessageWarnId(warn.getId());
|
||||
mapRecord.setCallNoticeId(callNotice.getId());
|
||||
mapMessageWarnCallNoticeMapper.insert(mapRecord);
|
||||
}
|
||||
|
||||
if (firstCallNotice == null) {
|
||||
firstCallNotice = callNotice;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String displayName = device.getDeviceName() != null ? device.getDeviceName() : device.getSerialNum();
|
||||
if (firstCallNotice != null) {
|
||||
sendVoiceNotification(firstCallNotice, displayName, warn.getTitle());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[故障码] 创建电话通知失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理探头错误码(参考 C# sensorErrorCode 逻辑)
|
||||
* sensorErrorCode 是设备上报的一个整数位掩码,每一位对应一种探头的离线状态。
|
||||
* 对 DetectorOxyOffline 特殊处理:新增离线时写入 Redis 待告警标记,恢复时删除。
|
||||
* 对 DetectorPHOffline / DetectorSalinityOffline:直接更新 warnCode 位。
|
||||
*
|
||||
* @param deviceName 设备名称(serialNum)
|
||||
* @param params 上报参数
|
||||
*/
|
||||
private void handleSensorErrorCode(String deviceName, JSONObject params) {
|
||||
try {
|
||||
Integer rawCode = params.getInt("sensorErrorCode");
|
||||
if (rawCode == null) {
|
||||
log.warn("[探头错误码] 解析 sensorErrorCode 失败: {}", deviceName);
|
||||
return;
|
||||
}
|
||||
int sensorErrorCode = rawCode;
|
||||
|
||||
// 查询设备,需要 id / warnCode / isOxygenUsed / pondId
|
||||
Device device = deviceMapper.selectOne(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Device>()
|
||||
.eq(Device::getSerialNum, deviceName)
|
||||
.select(Device::getId, Device::getWarnCode, Device::getIsOxygenUsed, Device::getPondId)
|
||||
.last("LIMIT 1")
|
||||
);
|
||||
if (device == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int currentCode = device.getWarnCode() != null ? device.getWarnCode() : 0;
|
||||
int newCode = currentCode;
|
||||
|
||||
// 1. DetectorOxyOffline:溶解氧探头离线——带延迟告警
|
||||
boolean oxyOfflineNow = (sensorErrorCode & DefineDeviceWarnCode.DetectorOxyOffline) != 0;
|
||||
boolean oxyOfflinePrev = (currentCode & DefineDeviceWarnCode.DetectorOxyOffline) != 0;
|
||||
if (oxyOfflineNow) {
|
||||
if (!oxyOfflinePrev) {
|
||||
// 新增离线:置位 + 写入 Redis 待告警标记
|
||||
newCode |= DefineDeviceWarnCode.DetectorOxyOffline;
|
||||
// 只有开启了溶氧检测且处于塘口中才创建待告警
|
||||
if (device.getIsOxygenUsed() != null && device.getIsOxygenUsed() == 1
|
||||
&& device.getPondId() != null) {
|
||||
String redisKey = "device:detector:oxy:offline:wait:" + deviceName;
|
||||
if (!Boolean.TRUE.equals(com.intc.common.redis.utils.RedisUtils.hasKey(redisKey))) {
|
||||
String offlineTime = LocalDateTime.now().format(DATETIME_FORMATTER);
|
||||
int offlineDelayMinutes = aliyunIotProperties.getOfflineWarnDelayMinutes();
|
||||
com.intc.common.redis.utils.RedisUtils.setCacheObject(
|
||||
redisKey, offlineTime,
|
||||
java.time.Duration.ofMinutes((long) offlineDelayMinutes * OFFLINE_WAIT_TTL_MULTIPLIER)
|
||||
);
|
||||
log.info("[探头错误码] 溶解氧探头离线,已写入待告警标记: {}", deviceName);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (oxyOfflinePrev) {
|
||||
// 恢复:清位 + 删除 Redis 待告警标记
|
||||
newCode &= ~DefineDeviceWarnCode.DetectorOxyOffline;
|
||||
String redisKey = "device:detector:oxy:offline:wait:" + deviceName;
|
||||
com.intc.common.redis.utils.RedisUtils.deleteObject(redisKey);
|
||||
log.info("[探头错误码] 溶解氧探头恢复,已删除待告警标记: {}", deviceName);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. DetectorPHOffline:直接更新位
|
||||
if ((sensorErrorCode & DefineDeviceWarnCode.DetectorPHOffline) != 0) {
|
||||
newCode |= DefineDeviceWarnCode.DetectorPHOffline;
|
||||
} else {
|
||||
newCode &= ~DefineDeviceWarnCode.DetectorPHOffline;
|
||||
}
|
||||
|
||||
// 3. DetectorSalinityOffline:直接更新位
|
||||
if ((sensorErrorCode & DefineDeviceWarnCode.DetectorSalinityOffline) != 0) {
|
||||
newCode |= DefineDeviceWarnCode.DetectorSalinityOffline;
|
||||
} else {
|
||||
newCode &= ~DefineDeviceWarnCode.DetectorSalinityOffline;
|
||||
}
|
||||
|
||||
// 有变化才更新数据库
|
||||
if (newCode != currentCode) {
|
||||
Device update = new Device();
|
||||
update.setId(device.getId());
|
||||
update.setWarnCode(newCode);
|
||||
deviceMapper.updateById(update);
|
||||
log.debug("[探头错误码] 设备={} warnCode: {} -> {}", deviceName, currentCode, newCode);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[探头错误码] 处理失败[{}]: {}", deviceName, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除设备离线标志(收到属性上报时调用)
|
||||
*
|
||||
* @param deviceName 设备名称(serialNum)
|
||||
*/
|
||||
private void clearDeviceOfflineFlag(String deviceName) {
|
||||
try {
|
||||
Device device = deviceMapper.selectOne(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Device>()
|
||||
.eq(Device::getSerialNum, deviceName)
|
||||
.select(Device::getId, Device::getWarnCode)
|
||||
.last("LIMIT 1")
|
||||
);
|
||||
if (device == null || device.getWarnCode() == null) {
|
||||
return;
|
||||
}
|
||||
// 只有当前有离线标志时才更新,避免无意义的 SQL
|
||||
if ((device.getWarnCode() & DefineDeviceWarnCode.DeviceOffline) != 0) {
|
||||
Device update = new Device();
|
||||
update.setId(device.getId());
|
||||
update.setWarnCode(device.getWarnCode() & ~DefineDeviceWarnCode.DeviceOffline);
|
||||
deviceMapper.updateById(update);
|
||||
log.debug("[在线] 收到属性上报,已清除设备离线标志: {}", deviceName);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[在线] 清除设备离线标志失败[{}]: {}", deviceName, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 Device.warnCode 指定位
|
||||
*
|
||||
* @param deviceName 设备名称(serialNum)
|
||||
* @param bit 要操作的位(DefineDeviceWarnCode 中的常量)
|
||||
* @param set true=置位, false=清位
|
||||
*/
|
||||
private void setDeviceWarnCodeBit(String deviceName, int bit, boolean set) {
|
||||
try {
|
||||
Device device = deviceMapper.selectOne(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Device>()
|
||||
.eq(Device::getSerialNum, deviceName)
|
||||
.select(Device::getId, Device::getWarnCode)
|
||||
.last("LIMIT 1")
|
||||
);
|
||||
if (device == null) {
|
||||
return;
|
||||
}
|
||||
int currentCode = device.getWarnCode() != null ? device.getWarnCode() : 0;
|
||||
int newCode = set ? (currentCode | bit) : (currentCode & ~bit);
|
||||
if (newCode == currentCode) {
|
||||
return; // 无变化,不更新
|
||||
}
|
||||
Device update = new Device();
|
||||
update.setId(device.getId());
|
||||
update.setWarnCode(newCode);
|
||||
deviceMapper.updateById(update);
|
||||
log.debug("[warnCode] 设备={}, {}位={}, {}→{}",
|
||||
deviceName, bit, set ? "置位" : "清位", currentCode, newCode);
|
||||
} catch (Exception e) {
|
||||
log.error("[warnCode] 更新设备告警码失败[{}]: {}", deviceName, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1398,22 +2126,17 @@ public class DeviceDataHandler {
|
||||
*/
|
||||
private void updateDeviceRealTimeData(String deviceName, DeviceSensorData sensorData) {
|
||||
try {
|
||||
// 查询设备
|
||||
Device device = deviceMapper.selectOne(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Device>()
|
||||
.eq(Device::getSerialNum, deviceName)
|
||||
.last("LIMIT 1")
|
||||
);
|
||||
|
||||
if (device == null) {
|
||||
log.debug("设备不存在,无法更新实时数据: {}", deviceName);
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新实时数据
|
||||
Device updateDevice = new Device();
|
||||
updateDevice.setId(device.getId());
|
||||
|
||||
if (sensorData.getDissolvedOxygen() != null) {
|
||||
updateDevice.setValueDissolvedOxygen(sensorData.getDissolvedOxygen());
|
||||
}
|
||||
@@ -1429,8 +2152,6 @@ public class DeviceDataHandler {
|
||||
if (sensorData.getSalinity() != null) {
|
||||
updateDevice.setValueSalinity(sensorData.getSalinity());
|
||||
}
|
||||
|
||||
// 执行更新
|
||||
int updated = deviceMapper.updateById(updateDevice);
|
||||
if (updated > 0) {
|
||||
log.debug("设备实时数据已更新: {}", deviceName);
|
||||
@@ -1440,4 +2161,149 @@ public class DeviceDataHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 溶解氧联动控制(参考 C# SetDeviceSwitchLinkedOpen 逻辑)
|
||||
* <ul>
|
||||
* <li>溶解氧 ≤ 下限且下限开关已开:开启联动开关(曝气机等)</li>
|
||||
* <li>溶解氧 ≥ 上限且上限开关已开且未触发过:关闭联动开关,设置 isOxyUpperTrigger=1</li>
|
||||
* <li>溶解氧 ≤ 上限-0.3且已触发过:重置 isOxyUpperTrigger=0</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param deviceName 设备名称(serialNum)
|
||||
* @param dissolvedOxygen 当前溶解氧値
|
||||
*/
|
||||
private void handleLinkedCtrl(String deviceName, double dissolvedOxygen) {
|
||||
try {
|
||||
// 查询设备(需要 id 和 iotId)
|
||||
Device device = deviceMapper.selectOne(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Device>()
|
||||
.eq(Device::getSerialNum, deviceName)
|
||||
.select(Device::getId, Device::getIotId, Device::getSerialNum, Device::getDeviceName)
|
||||
.last("LIMIT 1")
|
||||
);
|
||||
if (device == null || device.getId() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 查询该设备的所有联动控制配置
|
||||
List<com.intc.fishery.domain.LinkedCtrl> linkedCtrls = linkedCtrlMapper.selectList(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<com.intc.fishery.domain.LinkedCtrl>()
|
||||
.eq(com.intc.fishery.domain.LinkedCtrl::getDeviceId, device.getId())
|
||||
);
|
||||
if (linkedCtrls == null || linkedCtrls.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (com.intc.fishery.domain.LinkedCtrl linkedCtrl : linkedCtrls) {
|
||||
// 查询该联动控制关联的所有开关
|
||||
List<com.intc.fishery.domain.DeviceSwitch> switches = deviceSwitchMapper.selectList(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<com.intc.fishery.domain.DeviceSwitch>()
|
||||
.eq(com.intc.fishery.domain.DeviceSwitch::getLinkedCtrlId, linkedCtrl.getId())
|
||||
);
|
||||
if (switches == null || switches.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 溶解氧下限联动:溶解氧 ≤ 下限就开启开关
|
||||
if (linkedCtrl.getOxyLowerOpen() != null && linkedCtrl.getOxyLowerOpen() == 1
|
||||
&& linkedCtrl.getOxyLowerValue() != null
|
||||
&& dissolvedOxygen <= linkedCtrl.getOxyLowerValue()) {
|
||||
log.info("[联动控制] 设备={} 溶解氧={} ≤ 下限={},开启开关",
|
||||
deviceName, dissolvedOxygen, linkedCtrl.getOxyLowerValue());
|
||||
setDeviceSwitchLinkedOpen(device, switches, true);
|
||||
}
|
||||
|
||||
// 溶解氧上限联动(带滞回防止重复触发)
|
||||
boolean isOxyUpperTrigger = linkedCtrl.getIsOxyUpperTrigger() != null
|
||||
&& linkedCtrl.getIsOxyUpperTrigger() == 1;
|
||||
if (isOxyUpperTrigger) {
|
||||
// 已触发过:溶解氧降回 上限-0.3 则重置触发标志
|
||||
if (linkedCtrl.getOxyUpperValue() != null
|
||||
&& dissolvedOxygen <= linkedCtrl.getOxyUpperValue() - 0.3) {
|
||||
com.intc.fishery.domain.LinkedCtrl updateCtrl = new com.intc.fishery.domain.LinkedCtrl();
|
||||
updateCtrl.setId(linkedCtrl.getId());
|
||||
updateCtrl.setIsOxyUpperTrigger(0);
|
||||
linkedCtrlMapper.updateById(updateCtrl);
|
||||
log.info("[联动控制] 设备={} 溶解氧={} 降回至 上限-0.3={},重置触发标志",
|
||||
deviceName, dissolvedOxygen, linkedCtrl.getOxyUpperValue() - 0.3);
|
||||
}
|
||||
} else {
|
||||
// 未触发:溶解氧 ≥ 上限则关闭开关
|
||||
if (linkedCtrl.getOxyUpperOpen() != null && linkedCtrl.getOxyUpperOpen() == 1
|
||||
&& linkedCtrl.getOxyUpperValue() != null
|
||||
&& dissolvedOxygen >= linkedCtrl.getOxyUpperValue()) {
|
||||
// 设置触发标志并关闭开关
|
||||
com.intc.fishery.domain.LinkedCtrl updateCtrl = new com.intc.fishery.domain.LinkedCtrl();
|
||||
updateCtrl.setId(linkedCtrl.getId());
|
||||
updateCtrl.setIsOxyUpperTrigger(1);
|
||||
linkedCtrlMapper.updateById(updateCtrl);
|
||||
log.info("[联动控制] 设备={} 溶解氧={} ≥ 上限={},关闭开关",
|
||||
deviceName, dissolvedOxygen, linkedCtrl.getOxyUpperValue());
|
||||
setDeviceSwitchLinkedOpen(device, switches, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[联动控制] 处理失败[{}]: {}", deviceName, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按联动控制开启/关闭一组开关(参考 C# SetDeviceSwitchLinkedOpen)
|
||||
* 下发成功后更新 DeviceSwitch.isOpen 和 lastTurnTime。
|
||||
*
|
||||
* @param device 设备信息(含 iotId)
|
||||
* @param switches 待操作的开关列表
|
||||
* @param open true=开启, false=关闭
|
||||
*/
|
||||
private void setDeviceSwitchLinkedOpen(Device device, List<com.intc.fishery.domain.DeviceSwitch> switches, boolean open) {
|
||||
if (device == null || device.getIotId() == null || switches == null || switches.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
int targetState = open ? 1 : 0;
|
||||
// 过滤出当前就已是目标状态的开关(不需要操作)
|
||||
List<com.intc.fishery.domain.DeviceSwitch> toChange = new java.util.ArrayList<>();
|
||||
for (com.intc.fishery.domain.DeviceSwitch sw : switches) {
|
||||
if (sw.getIsOpen() == null || sw.getIsOpen() != targetState) {
|
||||
toChange.add(sw);
|
||||
}
|
||||
}
|
||||
if (toChange.isEmpty()) {
|
||||
log.debug("[联动控制] 开关已全部处于目标状态({}),无需操作", open ? "开启" : "关闭");
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建属性 Map:{"Switch1": 1, "Switch2": 0, ...}
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
for (com.intc.fishery.domain.DeviceSwitch sw : toChange) {
|
||||
if (sw.getIndex() != null) {
|
||||
properties.put("Switch" + sw.getIndex(), targetState);
|
||||
}
|
||||
}
|
||||
|
||||
// 调用 IoT 云端服务下发开关指令
|
||||
boolean success = iotCloudService.setProperty(device.getIotId(), properties, false, 1);
|
||||
if (success) {
|
||||
java.util.Date now = new java.util.Date();
|
||||
for (com.intc.fishery.domain.DeviceSwitch sw : toChange) {
|
||||
com.intc.fishery.domain.DeviceSwitch updateSw = new com.intc.fishery.domain.DeviceSwitch();
|
||||
updateSw.setId(sw.getId());
|
||||
updateSw.setIsOpen(targetState);
|
||||
updateSw.setLastTurnTime(now);
|
||||
deviceSwitchMapper.updateById(updateSw);
|
||||
}
|
||||
log.info("[联动控制] 设备={} 开关下发成功:{} -> {}",
|
||||
device.getSerialNum() != null ? device.getSerialNum() : device.getIotId(),
|
||||
properties.keySet(), open ? "开启" : "关闭");
|
||||
} else {
|
||||
log.warn("[联动控制] 设备={} 开关下发失败:{}",
|
||||
device.getSerialNum() != null ? device.getSerialNum() : device.getIotId(),
|
||||
properties.keySet());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[联动控制] 下发开关指令失败[{}]: {}", device.getIotId(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user