fix: 修改设备故障码记录相关逻辑。

This commit is contained in:
tianyongbao
2026-03-22 22:21:38 +08:00
parent a5d6394dc2
commit 0a6fed1b01
4 changed files with 1124 additions and 163 deletions

View File

@@ -4,125 +4,198 @@ import java.util.HashMap;
import java.util.Map;
/**
* 设备故障码常量定义
* 设备故障码常量定义(与 C# DefineDeviceErrorCode 对齐)
* 所有的恢复码都 +200
*
* @author intc
*/
public class DefineDeviceErrorCode {
/**
* 电源关闭
*/
public static final int PowerOff = 1;
// 三相电压告警 (2-7)
/**
* 三相A相过压
*/
public static final int Three_PhaseA_OverVoltage = 2;
/**
* 三相B相过压
*/
public static final int Three_PhaseB_OverVoltage = 3;
/**
* 三相C相过压
*/
public static final int Three_PhaseC_OverVoltage = 4;
/**
* 三相A相欠压
*/
public static final int Three_PhaseA_UnderVoltage = 5;
/**
* 三相B相欠压
*/
public static final int Three_PhaseB_UnderVoltage = 6;
/**
* 三相C相欠压
*/
public static final int Three_PhaseC_UnderVoltage = 7;
// 三相输入缺相 (1001-1003)。一旦缺相会强制4路开关断开
public static final int Three_InputLosePhaseA = 1001;
public static final int Three_InputLosePhaseB = 1002;
public static final int Three_InputLosePhaseC = 1003;
// 三相电流告警 (8-23)
/**
* 三相开关1路A相过流
*/
public static final int Three_Switch1OverElectricA = 8;
public static final int Three_Switch1OverElectricB = 9;
public static final int Three_Switch1OverElectricC = 10;
public static final int Three_Switch2OverElectricA = 11;
public static final int Three_Switch2OverElectricB = 12;
public static final int Three_Switch2OverElectricC = 13;
public static final int Three_Switch3OverElectricA = 14;
public static final int Three_Switch3OverElectricB = 15;
public static final int Three_Switch3OverElectricC = 16;
public static final int Three_Switch4OverElectricA = 17;
public static final int Three_Switch4OverElectricB = 18;
public static final int Three_Switch4OverElectricC = 19;
/**
* 三相开关1路缺相
*/
public static final int Three_Switch1ElectricEmpty = 20;
public static final int Three_Switch2ElectricEmpty = 21;
public static final int Three_Switch3ElectricEmpty = 22;
public static final int Three_Switch4ElectricEmpty = 23;
// 三相过压欠压 (1004-1009)。过压欠压不会强制开关关闭触发机制高于120%/低于80%
public static final int Three_PhaseA_OverVoltage = 1004;
public static final int Three_PhaseA_UnderVoltage = 1005;
public static final int Three_PhaseB_OverVoltage = 1006;
public static final int Three_PhaseB_UnderVoltage = 1007;
public static final int Three_PhaseC_OverVoltage = 1008;
public static final int Three_PhaseC_UnderVoltage = 1009;
// 单相电流告警 (24-31)
/**
* 单相开关1路过流
*/
public static final int One_Switch1OverElectric = 24;
public static final int One_Switch2OverElectric = 25;
public static final int One_Switch3OverElectric = 26;
public static final int One_Switch4OverElectric = 27;
/**
* 单相开关1路缺相
*/
public static final int One_Switch1ElectricEmpty = 28;
public static final int One_Switch2ElectricEmpty = 29;
public static final int One_Switch3ElectricEmpty = 30;
public static final int One_Switch4ElectricEmpty = 31;
// 三相端口缺相 (1010-1021)。一旦缺相对应开关强制断开
public static final int Three_Switch1LosePhaseA = 1010;
public static final int Three_Switch1LosePhaseB = 1011;
public static final int Three_Switch1LosePhaseC = 1012;
public static final int Three_Switch2LosePhaseA = 1013;
public static final int Three_Switch2LosePhaseB = 1014;
public static final int Three_Switch2LosePhaseC = 1015;
public static final int Three_Switch3LosePhaseA = 1016;
public static final int Three_Switch3LosePhaseB = 1017;
public static final int Three_Switch3LosePhaseC = 1018;
public static final int Three_Switch4LosePhaseA = 1019;
public static final int Three_Switch4LosePhaseB = 1020;
public static final int Three_Switch4LosePhaseC = 1021;
// 三相过流 (1022-1033)
public static final int Three_Switch1OverElectricA = 1022;
public static final int Three_Switch1OverElectricB = 1023;
public static final int Three_Switch1OverElectricC = 1024;
public static final int Three_Switch2OverElectricA = 1025;
public static final int Three_Switch2OverElectricB = 1026;
public static final int Three_Switch2OverElectricC = 1027;
public static final int Three_Switch3OverElectricA = 1028;
public static final int Three_Switch3OverElectricB = 1029;
public static final int Three_Switch3OverElectricC = 1030;
public static final int Three_Switch4OverElectricA = 1031;
public static final int Three_Switch4OverElectricB = 1032;
public static final int Three_Switch4OverElectricC = 1033;
// 三相欠流 (1034-1045)
public static final int Three_Switch1UnderElectricA = 1034;
public static final int Three_Switch1UnderElectricB = 1035;
public static final int Three_Switch1UnderElectricC = 1036;
public static final int Three_Switch2UnderElectricA = 1037;
public static final int Three_Switch2UnderElectricB = 1038;
public static final int Three_Switch2UnderElectricC = 1039;
public static final int Three_Switch3UnderElectricA = 1040;
public static final int Three_Switch3UnderElectricB = 1041;
public static final int Three_Switch3UnderElectricC = 1042;
public static final int Three_Switch4UnderElectricA = 1043;
public static final int Three_Switch4UnderElectricB = 1044;
public static final int Three_Switch4UnderElectricC = 1045;
// 三相空载 (1046-1049)
public static final int Three_Switch1ElectricEmpty = 1046;
public static final int Three_Switch2ElectricEmpty = 1047;
public static final int Three_Switch3ElectricEmpty = 1048;
public static final int Three_Switch4ElectricEmpty = 1049;
// 单相过流 (2009-2012)
public static final int One_Switch1OverElectric = 2009;
public static final int One_Switch2OverElectric = 2010;
public static final int One_Switch3OverElectric = 2011;
public static final int One_Switch4OverElectric = 2012;
// 单相欠流 (2013-2016)
public static final int One_Switch1UnderElectric = 2013;
public static final int One_Switch2UnderElectric = 2014;
public static final int One_Switch3UnderElectric = 2015;
public static final int One_Switch4UnderElectric = 2016;
// 单相空载 (2017-2020)
public static final int One_Switch1ElectricEmpty = 2017;
public static final int One_Switch2ElectricEmpty = 2018;
public static final int One_Switch3ElectricEmpty = 2019;
public static final int One_Switch4ElectricEmpty = 2020;
// 其他故障
/** 断电告警。设备断电后蓄电池续航约1分钟确保断电故障上报 */
public static final int PowerOff = 8001;
/** 控制板通信故障(后台监控使用) */
public static final int ControllerWarn = 8002;
/** 计量芯片故障(后台监控使用) */
public static final int MeterShipWarn = 8003;
private static final Map<Integer, String> ERROR_CODE_MESSAGES = new HashMap<>();
static {
ERROR_CODE_MESSAGES.put(PowerOff, "电源关闭");
ERROR_CODE_MESSAGES.put(Three_PhaseA_OverVoltage, "A相过压");
ERROR_CODE_MESSAGES.put(Three_PhaseB_OverVoltage, "B相过压");
ERROR_CODE_MESSAGES.put(Three_PhaseC_OverVoltage, "C相过压");
// 三相输入缺相
ERROR_CODE_MESSAGES.put(Three_InputLosePhaseA, "输入缺A相");
ERROR_CODE_MESSAGES.put(Three_InputLosePhaseB, "输入缺B相");
ERROR_CODE_MESSAGES.put(Three_InputLosePhaseC, "输入缺C相");
// 三相过压欠压
ERROR_CODE_MESSAGES.put(Three_PhaseA_OverVoltage, "A相过压");
ERROR_CODE_MESSAGES.put(Three_PhaseA_UnderVoltage, "A相欠压");
ERROR_CODE_MESSAGES.put(Three_PhaseB_OverVoltage, "B相过压");
ERROR_CODE_MESSAGES.put(Three_PhaseB_UnderVoltage, "B相欠压");
ERROR_CODE_MESSAGES.put(Three_PhaseC_OverVoltage, "C相过压");
ERROR_CODE_MESSAGES.put(Three_PhaseC_UnderVoltage, "C相欠压");
ERROR_CODE_MESSAGES.put(Three_Switch1OverElectricA, "1路A相过流");
ERROR_CODE_MESSAGES.put(Three_Switch1OverElectricB, "1路B相过流");
ERROR_CODE_MESSAGES.put(Three_Switch1OverElectricC, "1路C相过流");
ERROR_CODE_MESSAGES.put(Three_Switch2OverElectricA, "2路A相过流");
ERROR_CODE_MESSAGES.put(Three_Switch2OverElectricB, "2路B相过流");
ERROR_CODE_MESSAGES.put(Three_Switch2OverElectricC, "2路C相过流");
ERROR_CODE_MESSAGES.put(Three_Switch3OverElectricA, "3路A相过流");
ERROR_CODE_MESSAGES.put(Three_Switch3OverElectricB, "3路B相过流");
ERROR_CODE_MESSAGES.put(Three_Switch3OverElectricC, "3路C相过流");
ERROR_CODE_MESSAGES.put(Three_Switch4OverElectricA, "4路A相过流");
ERROR_CODE_MESSAGES.put(Three_Switch4OverElectricB, "4路B相过流");
ERROR_CODE_MESSAGES.put(Three_Switch4OverElectricC, "4路C相过流");
ERROR_CODE_MESSAGES.put(Three_Switch1ElectricEmpty, "1路缺");
ERROR_CODE_MESSAGES.put(Three_Switch2ElectricEmpty, "2路缺相");
ERROR_CODE_MESSAGES.put(Three_Switch3ElectricEmpty, "3路缺相");
ERROR_CODE_MESSAGES.put(Three_Switch4ElectricEmpty, "4路缺相");
ERROR_CODE_MESSAGES.put(One_Switch1OverElectric, "1路过流");
ERROR_CODE_MESSAGES.put(One_Switch2OverElectric, "2路过流");
ERROR_CODE_MESSAGES.put(One_Switch3OverElectric, "3路过流");
ERROR_CODE_MESSAGES.put(One_Switch4OverElectric, "4路过流");
ERROR_CODE_MESSAGES.put(One_Switch1ElectricEmpty, "1路缺相");
ERROR_CODE_MESSAGES.put(One_Switch2ElectricEmpty, "2路缺相");
ERROR_CODE_MESSAGES.put(One_Switch3ElectricEmpty, "3路缺相");
ERROR_CODE_MESSAGES.put(One_Switch4ElectricEmpty, "4路缺相");
// 三相端口缺相
ERROR_CODE_MESSAGES.put(Three_Switch1LosePhaseA, "端口1输出缺A相");
ERROR_CODE_MESSAGES.put(Three_Switch1LosePhaseB, "端口1输出缺B相");
ERROR_CODE_MESSAGES.put(Three_Switch1LosePhaseC, "端口1输出缺C相");
ERROR_CODE_MESSAGES.put(Three_Switch2LosePhaseA, "端口2输出缺A相");
ERROR_CODE_MESSAGES.put(Three_Switch2LosePhaseB, "端口2输出缺B相");
ERROR_CODE_MESSAGES.put(Three_Switch2LosePhaseC, "端口2输出缺C相");
ERROR_CODE_MESSAGES.put(Three_Switch3LosePhaseA, "端口3输出缺A相");
ERROR_CODE_MESSAGES.put(Three_Switch3LosePhaseB, "端口3输出缺B相");
ERROR_CODE_MESSAGES.put(Three_Switch3LosePhaseC, "端口3输出缺C相");
ERROR_CODE_MESSAGES.put(Three_Switch4LosePhaseA, "端口4输出缺A相");
ERROR_CODE_MESSAGES.put(Three_Switch4LosePhaseB, "端口4输出缺B相");
ERROR_CODE_MESSAGES.put(Three_Switch4LosePhaseC, "端口4输出缺C");
// 三相过流
ERROR_CODE_MESSAGES.put(Three_Switch1OverElectricA, "端口1输出A相过流");
ERROR_CODE_MESSAGES.put(Three_Switch1OverElectricB, "端口1输出B相过流");
ERROR_CODE_MESSAGES.put(Three_Switch1OverElectricC, "端口1输出C相过流");
ERROR_CODE_MESSAGES.put(Three_Switch2OverElectricA, "端口2输出A相过流");
ERROR_CODE_MESSAGES.put(Three_Switch2OverElectricB, "端口2输出B相过流");
ERROR_CODE_MESSAGES.put(Three_Switch2OverElectricC, "端口2输出C相过流");
ERROR_CODE_MESSAGES.put(Three_Switch3OverElectricA, "端口3输出A相过流");
ERROR_CODE_MESSAGES.put(Three_Switch3OverElectricB, "端口3输出B相过流");
ERROR_CODE_MESSAGES.put(Three_Switch3OverElectricC, "端口3输出C相过流");
ERROR_CODE_MESSAGES.put(Three_Switch4OverElectricA, "端口4输出A相过流");
ERROR_CODE_MESSAGES.put(Three_Switch4OverElectricB, "端口4输出B相过流");
ERROR_CODE_MESSAGES.put(Three_Switch4OverElectricC, "端口4输出C相过流");
// 三相欠流
ERROR_CODE_MESSAGES.put(Three_Switch1UnderElectricA, "端口1输出A相欠流");
ERROR_CODE_MESSAGES.put(Three_Switch1UnderElectricB, "端口1输出B相欠流");
ERROR_CODE_MESSAGES.put(Three_Switch1UnderElectricC, "端口1输出C相欠流");
ERROR_CODE_MESSAGES.put(Three_Switch2UnderElectricA, "端口2输出A相欠流");
ERROR_CODE_MESSAGES.put(Three_Switch2UnderElectricB, "端口2输出B相欠流");
ERROR_CODE_MESSAGES.put(Three_Switch2UnderElectricC, "端口2输出C相欠流");
ERROR_CODE_MESSAGES.put(Three_Switch3UnderElectricA, "端口3输出A相欠流");
ERROR_CODE_MESSAGES.put(Three_Switch3UnderElectricB, "端口3输出B相欠流");
ERROR_CODE_MESSAGES.put(Three_Switch3UnderElectricC, "端口3输出C相欠流");
ERROR_CODE_MESSAGES.put(Three_Switch4UnderElectricA, "端口4输出A相欠流");
ERROR_CODE_MESSAGES.put(Three_Switch4UnderElectricB, "端口4输出B相欠流");
ERROR_CODE_MESSAGES.put(Three_Switch4UnderElectricC, "端口4输出C相欠流");
// 三相空载
ERROR_CODE_MESSAGES.put(Three_Switch1ElectricEmpty, "端口1输出空载");
ERROR_CODE_MESSAGES.put(Three_Switch2ElectricEmpty, "端口2输出空载");
ERROR_CODE_MESSAGES.put(Three_Switch3ElectricEmpty, "端口3输出空载");
ERROR_CODE_MESSAGES.put(Three_Switch4ElectricEmpty, "端口4输出空载");
// 单相过流
ERROR_CODE_MESSAGES.put(One_Switch1OverElectric, "端口1输出过流");
ERROR_CODE_MESSAGES.put(One_Switch2OverElectric, "端口2输出过流");
ERROR_CODE_MESSAGES.put(One_Switch3OverElectric, "端口3输出过流");
ERROR_CODE_MESSAGES.put(One_Switch4OverElectric, "端口4输出过流");
// 单相欠流
ERROR_CODE_MESSAGES.put(One_Switch1UnderElectric, "端口1输出欠流");
ERROR_CODE_MESSAGES.put(One_Switch2UnderElectric, "端口2输出欠流");
ERROR_CODE_MESSAGES.put(One_Switch3UnderElectric, "端口3输出欠流");
ERROR_CODE_MESSAGES.put(One_Switch4UnderElectric, "端口4输出欠流");
// 单相空载
ERROR_CODE_MESSAGES.put(One_Switch1ElectricEmpty, "端口1输出空载");
ERROR_CODE_MESSAGES.put(One_Switch2ElectricEmpty, "端口2输出空载");
ERROR_CODE_MESSAGES.put(One_Switch3ElectricEmpty, "端口3输出空载");
ERROR_CODE_MESSAGES.put(One_Switch4ElectricEmpty, "端口4输出空载");
// 其他
ERROR_CODE_MESSAGES.put(PowerOff, "断电告警");
ERROR_CODE_MESSAGES.put(ControllerWarn, "控制板通信故障");
ERROR_CODE_MESSAGES.put(MeterShipWarn, "计量芯片故障");
}
private DefineDeviceErrorCode() {
}
/**
* 获取故障码对应的错误信息
* 获取故障码对应的错误描述
*/
public static String getErrorMessage(int errorCode) {
return ERROR_CODE_MESSAGES.getOrDefault(errorCode, "");
}
/**
* 根据相序索引获取相名称0=A, 1=B, 2=C
*/
public static String getPhaseName(int phaseIndex) {
switch (phaseIndex) {
case 0: return "A";
case 1: return "B";
case 2: return "C";
default: return "";
}
}
}

View File

@@ -1,7 +1,7 @@
package com.intc.fishery.constant;
/**
* 设备告警码常量定义
* 设备告警码常量定义(与 C# DefineDeviceWarnCode 保持一致)
*
* @author intc
*/
@@ -12,90 +12,112 @@ public class DefineDeviceWarnCode {
public static final int None = 0;
/**
* 用户自定义告警最大码
* 溶解氧探头离线(检测仪 或 控制器) 1 << 0 = 1
*/
public static final int UserMaxCode = 65536;
public static final int DetectorOxyOffline = 1 << 0;
/**
* 低溶氧告警
* PH探头离线检测仪 1 << 1 = 2
*/
public static final int LowOxygen = 1;
public static final int DetectorPHOffline = 1 << 1;
/**
* 高温告警
* 盐度探头离线(检测仪) 1 << 2 = 4
*/
public static final int HighTemperature = 2;
public static final int DetectorSalinityOffline = 1 << 2;
/**
* 低温告警
* 浊度探头离线(检测仪) 1 << 3 = 8
*/
public static final int LowTemperature = 4;
public static final int DetectorTurbidityOffline = 1 << 3;
/**
* PH值告警
* 水位探头离线(检测仪) 1 << 4 = 16
*/
public static final int PHAlarm = 8;
public static final int DetectorWaterLevelOffline = 1 << 4;
/**
* 盐度告警
* 氨氮探头离线(检测仪) 1 << 5 = 32
*/
public static final int SalinityAlarm = 16;
public static final int DetectorNH3NOffline = 1 << 5;
/**
* 设备离线
* 设备离线(检测仪 或 控制器) 1 << 6 = 64
*/
public static final int DeviceOffline = 32;
public static final int DeviceOffline = 1 << 6;
/**
* 设备失联
* 溶解氧低于告警値(检测仪 或 控制器) 1 << 7 = 128
*/
public static final int DeviceDead = 64;
public static final int DissolvedOxyLow = 1 << 7;
/**
* 溶解氧探头未校准
* 温度低于告警値(检测仪 或 控制器) 1 << 8 = 256
*/
public static final int OxyDetectorNoCorrect = 1;
public static final int TempLow = 1 << 8;
/**
* 温度高于告警値(检测仪 或 控制器) 1 << 9 = 512
*/
public static final int TempHigh = 1 << 9;
/**
* 饱和度过高(检测仪 或 控制器) 1 << 10 = 1024
*/
public static final int SaturaHigh = 1 << 10;
/**
* 设备断电(控制器) 1 << 15 = 32768
*/
public static final int ErrorPowerOff = 1 << 15;
/**
* 溶解氧探头未校准 1 << 16 = 65536
*/
public static final int OxyDetectorNoCorrect = 1 << 16;
/**
* 设备过期(服务到期) 1 << 17 = 131072
*/
public static final int DeviceTimeDead = 1 << 17;
/**
* 用户自定义告警最大码 1 << 18 = 262144
*/
public static final int UserMaxCode = 1 << 18;
private DefineDeviceWarnCode() {
}
/**
* 转换告警码为告警描述
* 转换告警码为告警描述(与 C# ToWarnDescription 保持一致)
* 优先级:设备过期 > 设备离线 > 设备断电 > 溶解氧离线 > 溶解氧告警 > 温度告警 > 饱和度过高
*/
public static String toWarnDescription(int warnCode) {
if (warnCode == None) {
return "";
return null;
}
StringBuilder sb = new StringBuilder();
if ((warnCode & LowOxygen) != 0) {
sb.append("低溶氧告警,");
}
if ((warnCode & HighTemperature) != 0) {
sb.append("高温告警,");
}
if ((warnCode & LowTemperature) != 0) {
sb.append("低温告警,");
}
if ((warnCode & PHAlarm) != 0) {
sb.append("PH值告警");
}
if ((warnCode & SalinityAlarm) != 0) {
sb.append("盐度告警,");
if ((warnCode & DeviceTimeDead) != 0) {
return "设备过期";
}
if ((warnCode & DeviceOffline) != 0) {
sb.append("设备离线");
return "设备离线";
}
if ((warnCode & DeviceDead) != 0) {
sb.append("设备失联,");
if ((warnCode & ErrorPowerOff) != 0) {
return "设备断电";
}
// 移除最后的逗号
if (sb.length() > 0) {
sb.setLength(sb.length() - 1);
if ((warnCode & DetectorOxyOffline) != 0) {
return "溶解氧离线";
}
return sb.toString();
if ((warnCode & DissolvedOxyLow) != 0) {
return "溶解氧告警";
}
if ((warnCode & TempLow) != 0 || (warnCode & TempHigh) != 0) {
return "温度告警";
}
if ((warnCode & SaturaHigh) != 0) {
return "饱和度过高";
}
return null;
}
}

View File

@@ -805,8 +805,8 @@ public class PondController extends BaseController {
// 判断设备是否在线(简化判断,实际应根据业务逻辑)
if (device.getWarnCode() == null
|| (device.getWarnCode() != DefineDeviceWarnCode.DeviceDead
&& device.getWarnCode() != DefineDeviceWarnCode.DeviceOffline)) {
|| ((device.getWarnCode() & DefineDeviceWarnCode.DeviceTimeDead) == 0
&& (device.getWarnCode() & DefineDeviceWarnCode.DeviceOffline) == 0)) {
data.setIsAllDead(false);
}

View File

@@ -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删除指定故障码RemoveErrorCodeerrorCode -= 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);
}
}
}