From ff3c3079aeeed0c86ff539c90cf097777f611a08 Mon Sep 17 00:00:00 2001 From: tianyongbao Date: Sun, 22 Mar 2026 22:21:38 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E6=95=85=E9=9A=9C=E7=A0=81=E8=AE=B0=E5=BD=95=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../constant/DefineDeviceErrorCode.java | 267 ++++-- .../constant/DefineDeviceWarnCode.java | 116 ++- .../fishery/controller/PondController.java | 4 +- .../intc/iot/handler/DeviceDataHandler.java | 900 +++++++++++++++++- 4 files changed, 1124 insertions(+), 163 deletions(-) diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/constant/DefineDeviceErrorCode.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/constant/DefineDeviceErrorCode.java index ea9a5aa..dcd2dd1 100644 --- a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/constant/DefineDeviceErrorCode.java +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/constant/DefineDeviceErrorCode.java @@ -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 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 "无"; + } + } } diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/constant/DefineDeviceWarnCode.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/constant/DefineDeviceWarnCode.java index 81cb358..99e8b35 100644 --- a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/constant/DefineDeviceWarnCode.java +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/constant/DefineDeviceWarnCode.java @@ -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; } } diff --git a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/controller/PondController.java b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/controller/PondController.java index c614584..ca29d78 100644 --- a/intc-modules/intc-fishery/src/main/java/com/intc/fishery/controller/PondController.java +++ b/intc-modules/intc-fishery/src/main/java/com/intc/fishery/controller/PondController.java @@ -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); } diff --git a/intc-modules/intc-iot/src/main/java/com/intc/iot/handler/DeviceDataHandler.java b/intc-modules/intc-iot/src/main/java/com/intc/iot/handler/DeviceDataHandler.java index 5d60727..3934493 100644 --- a/intc-modules/intc-iot/src/main/java/com/intc/iot/handler/DeviceDataHandler.java +++ b/intc-modules/intc-iot/src/main/java/com/intc/iot/handler/DeviceDataHandler.java @@ -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() + .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 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) + *
    + *
  • errorCode == 0:清除所有故障码(ClearErrorCode)
  • + *
  • errorCode/100%10 == 2:删除指定故障码(RemoveErrorCode,errorCode -= 200)
  • + *
  • errorCode/100%10 == 0:新增故障码(CreateErrorCode)
  • + *
+ * + * @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() + .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() + .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() + .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() + .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() + .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 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 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() + .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() + .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() + .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() .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 逻辑) + *
    + *
  • 溶解氧 ≤ 下限且下限开关已开:开启联动开关(曝气机等)
  • + *
  • 溶解氧 ≥ 上限且上限开关已开且未触发过:关闭联动开关,设置 isOxyUpperTrigger=1
  • + *
  • 溶解氧 ≤ 上限-0.3且已触发过:重置 isOxyUpperTrigger=0
  • + *
+ * + * @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() + .eq(Device::getSerialNum, deviceName) + .select(Device::getId, Device::getIotId, Device::getSerialNum, Device::getDeviceName) + .last("LIMIT 1") + ); + if (device == null || device.getId() == null) { + return; + } + + // 查询该设备的所有联动控制配置 + List linkedCtrls = linkedCtrlMapper.selectList( + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .eq(com.intc.fishery.domain.LinkedCtrl::getDeviceId, device.getId()) + ); + if (linkedCtrls == null || linkedCtrls.isEmpty()) { + return; + } + + for (com.intc.fishery.domain.LinkedCtrl linkedCtrl : linkedCtrls) { + // 查询该联动控制关联的所有开关 + List switches = deviceSwitchMapper.selectList( + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .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 switches, boolean open) { + if (device == null || device.getIotId() == null || switches == null || switches.isEmpty()) { + return; + } + try { + int targetState = open ? 1 : 0; + // 过滤出当前就已是目标状态的开关(不需要操作) + List 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 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); + } + } + }