Case 9: IoT device alarm grading
This case corresponds to Scenario 9: IoT device alarm grading in the JQuick-Java repository’s USE-CASES.md. It belongs to the IoT / device operations domain and solves real-time alarm decisions for sensors, gateways, cameras, industrial robots, and other devices: “six-dimension independent real-time metric scoring → alarm level/type → dispatch / notification / automated remediation / escalation deadline”.
Industry scenario
Section titled “Industry scenario”An IoT platform needs to grade alarms in real time for massive fleets of online devices. The device runtime snapshot includes: device ID, name, type (SENSOR/GATEWAY/CAMERA/ROBOT), criticality (HIGH/MEDIUM/LOW), current temperature, CPU usage, memory usage, error count within the window, whether disconnected, last heartbeat interval (seconds), battery level, and vibration intensity.
Scoring proceeds independently across six dimensions: temperature at the 90/80/70/60℃ tiers scores 35/25/15/8; error count at the 20/10/5/1 tiers scores 25/18/10/4; CPU and memory peak at the 95%/85%/75% tiers scores 20/12/6; battery at or below 5% scores 15, at or below 20% scores 8; disconnection scores 25 directly, heartbeat interval reaching 300s scores 15, reaching 120s scores 8; vibration at the 8/5/3 tiers scores 20/12/5. The six dimensions are summed and mapped to CRITICAL/MAJOR/MINOR/NORMAL via the 70/45/20 lines.
Alarm type is uniquely classified by business priority “network > temperature > performance > power > vibration”: disconnection or heartbeat over 300s is NETWORK, temperature reaching 70℃ is TEMPERATURE, CPU or memory reaching 85% is PERFORMANCE, battery at or below 20% is POWER, vibration reaching 5 is VIBRATION. On top of level and type, the chain also produces a dispatch decision with P0-P3 priority (scores above 45 always dispatch; 20-44 scores on HIGH-criticality devices also dispatch at P2), notification channel (CRITICAL phone call, MAJOR SMS, MINOR IM, otherwise email), automated remediation (scores above 70 or NETWORK type isolate ISOLATE, PERFORMANCE rate-limits RATE_LIMIT, TEMPERATURE/VIBRATION throttle THROTTLE), and escalation deadline (CRITICAL 5 minutes, MAJOR 15 minutes, MINOR 60 minutes, NORMAL 240 minutes).
The decision also carries hit indicator details and remediation action lists for alarm retrospectives and audit. Scenario 9 covers 6 profiles: steel furnace temperature sensor severe overheating at 80 points P0, campus gate gateway disconnection at 51 points P1, image recognition node performance alarm at 22 points MINOR without dispatch, critical device low battery at 25 points P2 dispatch, healthy device at 0 points NORMAL, and conveyor motor abnormal vibration at 38 points throttled.
Business pain points
Section titled “Business pain points”- Six-dimension thresholds (temperature 90/80/70/60℃, errors 20/10/5/1, heartbeat 300/120s, battery 5%/20%, etc.) are hard-coded in gateway scripts, streaming jobs, and alarm platforms; changing one threshold requires redeploying the whole chain.
- Priority-based alarm-type classification and six-dimension scoring are two separate logic sets scattered in different components; adding one alarm dimension requires changing multiple places.
- Dispatching depends not only on score but also on device criticality (20-44 scores on critical devices dispatch at P2); plain
if-elsestacking quickly rots and nobody dares to touch it. - Notification channels, automated remediation (isolate/rate-limit/throttle), and escalation deadlines all interlock with alarm level; changing one deadline can affect dispatch and notification.
- During alarm storms, operations need each alarm to carry which indicators were hit and which actions were executed; black-box alarms cannot be reviewed or attributed.
- Edge gateways and device platforms mostly run on JDK 8 and domestic environments with limited resources, unable to host heavy rule middleware.
Solution architecture
Section titled “Solution architecture”This case uses the standard “six-piece set”: the input model carries the device runtime snapshot, the decision result model carries level, type, dispatch, notification, remediation, and escalation deadline, the rule interface declares 13 rule methods, the XML script externalizes all scoring policies and thresholds, the Service handles six-dimension aggregation, clamping, and indicator/action assembly, and JUnit regresses with 6 alarm profiles.
| Layer | Responsibility | Files |
|---|---|---|
| Domain model | Device runtime snapshot, alarm decision result | DeviceAlarm.java, AlarmDecision.java |
| Rule set & XML | 13 methods covering six-dimension scoring + type classification + level/dispatch/notification/remediation/escalation | DeviceAlarmRules.java, device-alarm.xml |
| Orchestration service | Six-dimension aggregation clamped to 0-100, indicator details and remediation action list assembly | DeviceAlarmService.java |
| Tests | 6 alarm profiles (overheating / disconnection / performance / low battery / normal / vibration) | DeviceAlarmServiceTest.java |
Rule method breakdown
Section titled “Rule method breakdown”| Rule method | Policy | Output |
|---|---|---|
scoreTemperature |
≥90℃ 35 / ≥80 25 / ≥70 15 / ≥60 8 / otherwise 0 | 0-35 |
scoreError |
Errors ≥20 → 25 / ≥10 → 18 / ≥5 → 10 / ≥1 → 4 / otherwise 0 | 0-25 |
scoreResource |
Peak of CPU and memory: ≥95 → 20 / ≥85 → 12 / ≥75 → 6 / otherwise 0 | 0-20 |
scoreBattery |
Battery ≤5% → 15 / ≤20% → 8 / otherwise 0 | 0-15 |
scoreConnection |
Disconnected → 25 / heartbeat ≥300s → 15 / ≥120s → 8 / otherwise 0 | 0-25 |
scoreVibration |
Vibration ≥8 → 20 / ≥5 → 12 / ≥3 → 5 / otherwise 0 | 0-20 |
alarmLevel |
≥70 CRITICAL / ≥45 MAJOR / ≥20 MINOR / otherwise NORMAL | CRITICAL/MAJOR/MINOR/NORMAL |
alarmType |
Network (disconnected or heartbeat ≥300s) > temperature (≥70℃) > performance (CPU/memory ≥85) > power (≤20%) > vibration (≥5), otherwise NONE | NETWORK/TEMPERATURE/PERFORMANCE/POWER/VIBRATION/NONE |
needDispatch |
≥45 dispatch; 20-44 and HIGH-criticality dispatch; otherwise no | true / false |
dispatchPriority |
≥70 P0 / ≥45 P1 / 20-44 and HIGH → P2 / otherwise P3 | P0/P1/P2/P3 |
notifyChannel |
CRITICAL PHONE_CALL / MAJOR SMS / MINOR IM / otherwise EMAIL | PHONE_CALL/SMS/IM/EMAIL |
autoRemediation |
≥70 or NETWORK → ISOLATE; PERFORMANCE → RATE_LIMIT; TEMPERATURE/VIBRATION → THROTTLE; otherwise NONE | ISOLATE/RATE_LIMIT/THROTTLE/NONE |
escalateMinutes |
CRITICAL 5 / MAJOR 15 / MINOR 60 / NORMAL 240 | Escalation deadline (minutes) |
Code implementation (complete & runnable)
Section titled “Code implementation (complete & runnable)”Place the six files into a Maven project following the repository’s original package structure: the two models go into src/test/java/com/github/paohaijiao/demo/model/, the rule interface and test go into the com.github.paohaijiao.demo package, the orchestration service goes into com.github.paohaijiao.demo.service, and the XML script goes into src/test/resources/demo/device-alarm.xml; keep all package names exactly as in the source. Run mvn test -Dtest=DeviceAlarmServiceTest at the project root to execute.
DeviceAlarm.java
Section titled “DeviceAlarm.java”package com.github.paohaijiao.demo.model;
import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;
/** * IoT 设备运行快照(告警分级所需的实时指标) */@Data@NoArgsConstructor@AllArgsConstructorpublic class DeviceAlarm {
/** * 设备编号 */ private String deviceId;
/** * 设备名称 */ private String deviceName;
/** * 设备类型:SENSOR / GATEWAY / CAMERA / ROBOT */ private String deviceType;
/** * 设备重要性:HIGH / MEDIUM / LOW */ private String criticality;
/** * 温度(摄氏度) */ private double temperature;
/** * CPU 使用率(0-100) */ private int cpuUsage;
/** * 内存使用率(0-100) */ private int memoryUsage;
/** * 窗口期内错误次数 */ private int errorCount;
/** * 是否断连 */ private boolean connectionLost;
/** * 最近心跳间隔(秒) */ private int lastHeartbeatSeconds;
/** * 电池电量(0-100) */ private int batteryLevel;
/** * 振动强度 */ private double vibrationLevel;}AlarmDecision.java
Section titled “AlarmDecision.java”package com.github.paohaijiao.demo.model;
import lombok.Data;
import java.util.ArrayList;import java.util.List;
/** * IoT 设备告警决策结果 */@Datapublic class AlarmDecision {
/** * 设备编号 */ private String deviceId;
/** * 告警分(0-100) */ private int alarmScore;
/** * 告警等级:CRITICAL / MAJOR / MINOR / NORMAL */ private String alarmLevel;
/** * 告警类型:TEMPERATURE / PERFORMANCE / NETWORK / POWER / VIBRATION / NONE */ private String alarmType;
/** * 是否派发工单 */ private boolean needDispatch;
/** * 派单优先级:P0 / P1 / P2 / P3 */ private String dispatchPriority;
/** * 通知渠道:PHONE_CALL / SMS / IM / EMAIL */ private String notifyChannel;
/** * 自动处置动作:ISOLATE / RATE_LIMIT / THROTTLE / NONE */ private String autoRemediation;
/** * 升级时限(分钟) */ private int escalateMinutes;
/** * 命中指标明细 */ private List<String> indicators = new ArrayList<String>();
/** * 处置动作建议 */ private List<String> actions = new ArrayList<String>();
public void addIndicator(String indicator) { this.indicators.add(indicator); }
public void addAction(String action) { this.actions.add(action); }}DeviceAlarmRules.java
Section titled “DeviceAlarmRules.java”package com.github.paohaijiao.demo;
import com.github.paohaijiao.xml.param.Param;
/** * 场景 9:IoT 设备告警分级 * <p> * 各维度独立打分(温度/错误/资源/电量/连接/振动),再由综合分决定等级、派单、通知与自动处置。 */public interface DeviceAlarmRules {
int scoreTemperature(@Param("temperature") double temperature);
int scoreError(@Param("errorCount") int errorCount);
int scoreResource(@Param("cpuUsage") int cpuUsage, @Param("memoryUsage") int memoryUsage);
int scoreBattery(@Param("batteryLevel") int batteryLevel);
int scoreConnection(@Param("connectionLost") boolean connectionLost, @Param("lastHeartbeatSeconds") int lastHeartbeatSeconds);
int scoreVibration(@Param("vibrationLevel") double vibrationLevel);
String alarmLevel(@Param("alarmScore") int alarmScore);
String alarmType(@Param("connectionLost") boolean connectionLost, @Param("lastHeartbeatSeconds") int lastHeartbeatSeconds, @Param("temperature") double temperature, @Param("cpuUsage") int cpuUsage, @Param("memoryUsage") int memoryUsage, @Param("batteryLevel") int batteryLevel, @Param("vibrationLevel") double vibrationLevel);
boolean needDispatch(@Param("alarmScore") int alarmScore, @Param("criticality") String criticality);
String dispatchPriority(@Param("alarmScore") int alarmScore, @Param("criticality") String criticality);
String notifyChannel(@Param("alarmLevel") String alarmLevel, @Param("criticality") String criticality);
String autoRemediation(@Param("alarmType") String alarmType, @Param("alarmScore") int alarmScore);
int escalateMinutes(@Param("alarmLevel") String alarmLevel);}device-alarm.xml
Section titled “device-alarm.xml”<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE javas PUBLIC "-//PAOHAIJIAO//DTD API JAVA 1.0//EN" "classpath:paohaijiao/dtd/Jquick-java.dtd"><javas namespace="com.github.paohaijiao.demo.DeviceAlarmRules">
<!-- 温度评分:0~35 --> <java name="scoreTemperature" returnClass="int"> <![CDATA[ int def scoreTemperature(double:temperature) { if (temperature >= 90.0) { return 35; } else if (temperature >= 80.0) { return 25; } else if (temperature >= 70.0) { return 15; } else if (temperature >= 60.0) { return 8; } else { return 0; } } ]]> </java>
<!-- 错误次数评分:0~25 --> <java name="scoreError" returnClass="int"> <![CDATA[ int def scoreError(int:errorCount) { if (errorCount >= 20) { return 25; } else if (errorCount >= 10) { return 18; } else if (errorCount >= 5) { return 10; } else if (errorCount >= 1) { return 4; } else { return 0; } } ]]> </java>
<!-- 资源占用评分(取 CPU/内存峰值):0~20 --> <java name="scoreResource" returnClass="int"> <![CDATA[ int def scoreResource(int:cpuUsage, int:memoryUsage) { int peak = cpuUsage; if (memoryUsage > peak) { peak = memoryUsage; } if (peak >= 95) { return 20; } else if (peak >= 85) { return 12; } else if (peak >= 75) { return 6; } else { return 0; } } ]]> </java>
<!-- 电量评分:0~15 --> <java name="scoreBattery" returnClass="int"> <![CDATA[ int def scoreBattery(int:batteryLevel) { if (batteryLevel <= 5) { return 15; } else if (batteryLevel <= 20) { return 8; } else { return 0; } } ]]> </java>
<!-- 连接异常评分:0~25 --> <java name="scoreConnection" returnClass="int"> <![CDATA[ int def scoreConnection(boolean:connectionLost, int:lastHeartbeatSeconds) { if (connectionLost) { return 25; } else if (lastHeartbeatSeconds >= 300) { return 15; } else if (lastHeartbeatSeconds >= 120) { return 8; } else { return 0; } } ]]> </java>
<!-- 振动评分:0~20 --> <java name="scoreVibration" returnClass="int"> <![CDATA[ int def scoreVibration(double:vibrationLevel) { if (vibrationLevel >= 8.0) { return 20; } else if (vibrationLevel >= 5.0) { return 12; } else if (vibrationLevel >= 3.0) { return 5; } else { return 0; } } ]]> </java>
<!-- 告警等级 --> <java name="alarmLevel" returnClass="java.lang.String"> <![CDATA[ str def alarmLevel(int:alarmScore) { if (alarmScore >= 70) { return "CRITICAL"; } else if (alarmScore >= 45) { return "MAJOR"; } else if (alarmScore >= 20) { return "MINOR"; } else { return "NORMAL"; } } ]]> </java>
<!-- 告警类型(按业务优先级归类) --> <java name="alarmType" returnClass="java.lang.String"> <![CDATA[ str def alarmType(boolean:connectionLost, int:lastHeartbeatSeconds, double:temperature, int:cpuUsage, int:memoryUsage, int:batteryLevel, double:vibrationLevel) { if (connectionLost || lastHeartbeatSeconds >= 300) { return "NETWORK"; } else if (temperature >= 70.0) { return "TEMPERATURE"; } else if (cpuUsage >= 85 || memoryUsage >= 85) { return "PERFORMANCE"; } else if (batteryLevel <= 20) { return "POWER"; } else if (vibrationLevel >= 5.0) { return "VIBRATION"; } else { return "NONE"; } } ]]> </java>
<!-- 是否派单 --> <java name="needDispatch" returnClass="boolean"> <![CDATA[ boolean def needDispatch(int:alarmScore, str:criticality) { if (alarmScore >= 45) { return true; } else if (alarmScore >= 20 && criticality == "HIGH") { return true; } else { return false; } } ]]> </java>
<!-- 派单优先级 --> <java name="dispatchPriority" returnClass="java.lang.String"> <![CDATA[ str def dispatchPriority(int:alarmScore, str:criticality) { if (alarmScore >= 70) { return "P0"; } else if (alarmScore >= 45) { return "P1"; } else if (alarmScore >= 20 && criticality == "HIGH") { return "P2"; } else { return "P3"; } } ]]> </java>
<!-- 通知渠道 --> <java name="notifyChannel" returnClass="java.lang.String"> <![CDATA[ str def notifyChannel(str:alarmLevel, str:criticality) { if (alarmLevel == "CRITICAL") { return "PHONE_CALL"; } else if (alarmLevel == "MAJOR") { return "SMS"; } else if (alarmLevel == "MINOR") { return "IM"; } else { return "EMAIL"; } } ]]> </java>
<!-- 自动处置动作 --> <java name="autoRemediation" returnClass="java.lang.String"> <![CDATA[ str def autoRemediation(str:alarmType, int:alarmScore) { if (alarmScore >= 70) { return "ISOLATE"; } else if (alarmType == "NETWORK") { return "ISOLATE"; } else if (alarmType == "PERFORMANCE") { return "RATE_LIMIT"; } else if (alarmType == "TEMPERATURE") { return "THROTTLE"; } else if (alarmType == "VIBRATION") { return "THROTTLE"; } else { return "NONE"; } } ]]> </java>
<!-- 升级时限(分钟) --> <java name="escalateMinutes" returnClass="int"> <![CDATA[ int def escalateMinutes(str:alarmLevel) { if (alarmLevel == "CRITICAL") { return 5; } else if (alarmLevel == "MAJOR") { return 15; } else if (alarmLevel == "MINOR") { return 60; } else { return 240; } } ]]> </java>
</javas>DeviceAlarmService.java
Section titled “DeviceAlarmService.java”package com.github.paohaijiao.demo.service;
import com.github.paohaijiao.demo.DeviceAlarmRules;import com.github.paohaijiao.demo.model.AlarmDecision;import com.github.paohaijiao.demo.model.DeviceAlarm;
/** * 场景 9:IoT 设备告警分级服务 * <p> * 职责划分: * <ul> * <li><b>Java 侧</b>:六维评分汇总与封顶、告警类型与等级装配、指标与动作组装</li> * <li><b>规则侧</b>:各维度打分口径、类型归类优先级、等级/派单/通知阈值,全部外置在 XML 中</li> * </ul> */public class DeviceAlarmService {
private static final int SCORE_FLOOR = 0; private static final int SCORE_CEIL = 100;
private final DeviceAlarmRules rules;
public DeviceAlarmService(DeviceAlarmRules rules) { this.rules = rules; }
/** * 计算设备告警决策 * * @param device 设备运行快照 * @return 告警决策 */ public AlarmDecision evaluate(DeviceAlarm device) { AlarmDecision decision = new AlarmDecision(); decision.setDeviceId(device.getDeviceId());
int rawScore = rules.scoreTemperature(device.getTemperature()) + rules.scoreError(device.getErrorCount()) + rules.scoreResource(device.getCpuUsage(), device.getMemoryUsage()) + rules.scoreBattery(device.getBatteryLevel()) + rules.scoreConnection(device.isConnectionLost(), device.getLastHeartbeatSeconds()) + rules.scoreVibration(device.getVibrationLevel()); int alarmScore = clamp(rawScore);
String alarmLevel = rules.alarmLevel(alarmScore); String alarmType = rules.alarmType(device.isConnectionLost(), device.getLastHeartbeatSeconds(), device.getTemperature(), device.getCpuUsage(), device.getMemoryUsage(), device.getBatteryLevel(), device.getVibrationLevel());
decision.setAlarmScore(alarmScore); decision.setAlarmLevel(alarmLevel); decision.setAlarmType(alarmType); decision.setNeedDispatch(rules.needDispatch(alarmScore, device.getCriticality())); decision.setDispatchPriority(rules.dispatchPriority(alarmScore, device.getCriticality())); decision.setNotifyChannel(rules.notifyChannel(alarmLevel, device.getCriticality())); decision.setAutoRemediation(rules.autoRemediation(alarmType, alarmScore)); decision.setEscalateMinutes(rules.escalateMinutes(alarmLevel));
buildIndicators(device, decision); buildActions(device, decision); return decision; }
private int clamp(int score) { if (score < SCORE_FLOOR) { return SCORE_FLOOR; } if (score > SCORE_CEIL) { return SCORE_CEIL; } return score; }
private void buildIndicators(DeviceAlarm device, AlarmDecision decision) { if (device.getTemperature() >= 60.0) { decision.addIndicator("温度 " + device.getTemperature() + "℃"); } if (device.getCpuUsage() >= 75) { decision.addIndicator("CPU 使用率 " + device.getCpuUsage() + "%"); } if (device.getMemoryUsage() >= 75) { decision.addIndicator("内存使用率 " + device.getMemoryUsage() + "%"); } if (device.getErrorCount() >= 1) { decision.addIndicator("错误次数 " + device.getErrorCount()); } if (device.isConnectionLost()) { decision.addIndicator("设备断连"); } else if (device.getLastHeartbeatSeconds() >= 120) { decision.addIndicator("心跳间隔 " + device.getLastHeartbeatSeconds() + " 秒"); } if (device.getBatteryLevel() <= 20) { decision.addIndicator("电量 " + device.getBatteryLevel() + "%"); } if (device.getVibrationLevel() >= 3.0) { decision.addIndicator("振动强度 " + device.getVibrationLevel()); } }
private void buildActions(DeviceAlarm device, AlarmDecision decision) { if (decision.isNeedDispatch()) { decision.addAction("按 " + decision.getDispatchPriority() + " 优先级派发运维工单"); } else { decision.addAction("暂不派单,持续观察指标趋势"); } decision.addAction("通过 " + decision.getNotifyChannel() + " 通知责任人"); if (!"NONE".equals(decision.getAutoRemediation())) { decision.addAction("执行自动处置:" + decision.getAutoRemediation()); } if ("HIGH".equals(device.getCriticality()) && decision.getAlarmScore() >= 20) { decision.addAction("关键设备告警,同步通知设备负责人"); } decision.addAction(decision.getEscalateMinutes() + " 分钟内未恢复则升级"); }}DeviceAlarmServiceTest.java
Section titled “DeviceAlarmServiceTest.java”package com.github.paohaijiao.demo;
import com.github.paohaijiao.demo.model.AlarmDecision;import com.github.paohaijiao.demo.model.DeviceAlarm;import com.github.paohaijiao.demo.service.DeviceAlarmService;import com.github.paohaijiao.xml.JQuickJava;import org.junit.Assert;import org.junit.Before;import org.junit.Test;
/** * 场景 9 生产级样板:IoT 设备告警分级 * <p> * 覆盖 6 组告警形态:严重过热、网络断连、性能资源耗尽、低电量、 * 设备正常、振动异常。 */public class DeviceAlarmServiceTest {
private DeviceAlarmService service;
@Before public void setUp() { DeviceAlarmRules rules = JQuickJava.create() .createApi(DeviceAlarmRules.class, "demo/device-alarm.xml"); this.service = new DeviceAlarmService(rules); }
/** * 严重过热:温度 92℃ + 高频错误 22 次 + CPU 96% → 80 分 CRITICAL */ @Test public void shouldEscalateOverheatDeviceToCritical() { DeviceAlarm device = new DeviceAlarm(); device.setDeviceId("DEV-9001"); device.setDeviceName("炼钢炉温控传感器"); device.setDeviceType("SENSOR"); device.setCriticality("HIGH"); device.setTemperature(92.0); device.setCpuUsage(96); device.setMemoryUsage(70); device.setErrorCount(22); device.setConnectionLost(false); device.setLastHeartbeatSeconds(60); device.setBatteryLevel(80); device.setVibrationLevel(1.0);
AlarmDecision decision = service.evaluate(device);
Assert.assertEquals(80, decision.getAlarmScore()); Assert.assertEquals("CRITICAL", decision.getAlarmLevel()); Assert.assertEquals("TEMPERATURE", decision.getAlarmType()); Assert.assertTrue(decision.isNeedDispatch()); Assert.assertEquals("P0", decision.getDispatchPriority()); Assert.assertEquals("PHONE_CALL", decision.getNotifyChannel()); Assert.assertEquals("ISOLATE", decision.getAutoRemediation()); Assert.assertEquals(5, decision.getEscalateMinutes()); Assert.assertEquals(3, decision.getIndicators().size()); Assert.assertTrue(decision.getIndicators().contains("温度 92.0℃")); Assert.assertTrue(decision.getIndicators().contains("CPU 使用率 96%")); Assert.assertTrue(decision.getIndicators().contains("错误次数 22")); Assert.assertTrue(decision.getActions().contains("按 P0 优先级派发运维工单")); Assert.assertTrue(decision.getActions().contains("执行自动处置:ISOLATE")); }
/** * 网络断连:断连 + 错误 10 次 + 低电量 15% → 51 分 MAJOR */ @Test public void shouldClassifyNetworkOutage() { DeviceAlarm device = new DeviceAlarm(); device.setDeviceId("DEV-9002"); device.setDeviceName("园区闸机网关"); device.setDeviceType("GATEWAY"); device.setCriticality("HIGH"); device.setTemperature(40.0); device.setCpuUsage(50); device.setMemoryUsage(50); device.setErrorCount(10); device.setConnectionLost(true); device.setLastHeartbeatSeconds(600); device.setBatteryLevel(15); device.setVibrationLevel(0.5);
AlarmDecision decision = service.evaluate(device);
Assert.assertEquals(51, decision.getAlarmScore()); Assert.assertEquals("MAJOR", decision.getAlarmLevel()); Assert.assertEquals("NETWORK", decision.getAlarmType()); Assert.assertTrue(decision.isNeedDispatch()); Assert.assertEquals("P1", decision.getDispatchPriority()); Assert.assertEquals("SMS", decision.getNotifyChannel()); Assert.assertEquals("ISOLATE", decision.getAutoRemediation()); Assert.assertEquals(15, decision.getEscalateMinutes()); Assert.assertTrue(decision.getIndicators().contains("设备断连")); Assert.assertTrue(decision.getIndicators().contains("电量 15%")); }
/** * 性能资源耗尽:内存 92% + 错误 6 次,但设备重要性 MEDIUM → 22 分 MINOR 且不派单 */ @Test public void shouldFlagPerformanceIssueWithoutDispatch() { DeviceAlarm device = new DeviceAlarm(); device.setDeviceId("DEV-9003"); device.setDeviceName("图像识别节点"); device.setDeviceType("CAMERA"); device.setCriticality("MEDIUM"); device.setTemperature(45.0); device.setCpuUsage(88); device.setMemoryUsage(92); device.setErrorCount(6); device.setConnectionLost(false); device.setLastHeartbeatSeconds(30); device.setBatteryLevel(60); device.setVibrationLevel(1.0);
AlarmDecision decision = service.evaluate(device);
Assert.assertEquals(22, decision.getAlarmScore()); Assert.assertEquals("MINOR", decision.getAlarmLevel()); Assert.assertEquals("PERFORMANCE", decision.getAlarmType()); Assert.assertFalse(decision.isNeedDispatch()); Assert.assertEquals("P3", decision.getDispatchPriority()); Assert.assertEquals("IM", decision.getNotifyChannel()); Assert.assertEquals("RATE_LIMIT", decision.getAutoRemediation()); Assert.assertEquals(60, decision.getEscalateMinutes()); Assert.assertTrue(decision.getIndicators().contains("内存使用率 92%")); }
/** * 低电量:电量 3% + 错误 6 次,关键设备 → 25 分 MINOR 且按 P2 派单 */ @Test public void shouldAlertLowBatteryOnCriticalDevice() { DeviceAlarm device = new DeviceAlarm(); device.setDeviceId("DEV-9004"); device.setDeviceName("冷库温湿度记录仪"); device.setDeviceType("SENSOR"); device.setCriticality("HIGH"); device.setTemperature(50.0); device.setCpuUsage(40); device.setMemoryUsage(40); device.setErrorCount(6); device.setConnectionLost(false); device.setLastHeartbeatSeconds(50); device.setBatteryLevel(3); device.setVibrationLevel(0.2);
AlarmDecision decision = service.evaluate(device);
Assert.assertEquals(25, decision.getAlarmScore()); Assert.assertEquals("MINOR", decision.getAlarmLevel()); Assert.assertEquals("POWER", decision.getAlarmType()); Assert.assertTrue(decision.isNeedDispatch()); Assert.assertEquals("P2", decision.getDispatchPriority()); Assert.assertEquals("IM", decision.getNotifyChannel()); Assert.assertEquals("NONE", decision.getAutoRemediation()); Assert.assertTrue(decision.getIndicators().contains("电量 3%")); }
/** * 设备正常:各项指标健康 → 0 分 NORMAL */ @Test public void shouldKeepHealthyDeviceNormal() { DeviceAlarm device = new DeviceAlarm(); device.setDeviceId("DEV-9005"); device.setDeviceName("办公区环境传感器"); device.setDeviceType("SENSOR"); device.setCriticality("MEDIUM"); device.setTemperature(42.0); device.setCpuUsage(30); device.setMemoryUsage(40); device.setErrorCount(0); device.setConnectionLost(false); device.setLastHeartbeatSeconds(30); device.setBatteryLevel(85); device.setVibrationLevel(0.5);
AlarmDecision decision = service.evaluate(device);
Assert.assertEquals(0, decision.getAlarmScore()); Assert.assertEquals("NORMAL", decision.getAlarmLevel()); Assert.assertEquals("NONE", decision.getAlarmType()); Assert.assertFalse(decision.isNeedDispatch()); Assert.assertEquals("P3", decision.getDispatchPriority()); Assert.assertEquals("EMAIL", decision.getNotifyChannel()); Assert.assertEquals("NONE", decision.getAutoRemediation()); Assert.assertEquals(240, decision.getEscalateMinutes()); Assert.assertTrue(decision.getIndicators().isEmpty()); Assert.assertTrue(decision.getActions().contains("暂不派单,持续观察指标趋势")); }
/** * 振动异常:振动强度 9.5 + 错误 10 次 → 38 分 MINOR,自动限流 */ @Test public void shouldDetectAbnormalVibration() { DeviceAlarm device = new DeviceAlarm(); device.setDeviceId("DEV-9006"); device.setDeviceName("传送带电机"); device.setDeviceType("ROBOT"); device.setCriticality("MEDIUM"); device.setTemperature(55.0); device.setCpuUsage(70); device.setMemoryUsage(65); device.setErrorCount(10); device.setConnectionLost(false); device.setLastHeartbeatSeconds(40); device.setBatteryLevel(70); device.setVibrationLevel(9.5);
AlarmDecision decision = service.evaluate(device);
Assert.assertEquals(38, decision.getAlarmScore()); Assert.assertEquals("MINOR", decision.getAlarmLevel()); Assert.assertEquals("VIBRATION", decision.getAlarmType()); Assert.assertFalse(decision.isNeedDispatch()); Assert.assertEquals("P3", decision.getDispatchPriority()); Assert.assertEquals("IM", decision.getNotifyChannel()); Assert.assertEquals("THROTTLE", decision.getAutoRemediation()); Assert.assertTrue(decision.getIndicators().contains("振动强度 9.5")); Assert.assertTrue(decision.getActions().contains("执行自动处置:THROTTLE")); }}Run & verified results
Section titled “Run & verified results”Run at the project root:
mvn test -Dtest=DeviceAlarmServiceTestTest coverage
Section titled “Test coverage”| Test method | Business profile constructed | Key assertions / expected decision |
|---|---|---|
shouldEscalateOverheatDeviceToCritical |
Steel furnace temperature sensor: 92℃, 22 errors, CPU 96%, HIGH device | 80 points (35+25+20), CRITICAL, TEMPERATURE, dispatch P0, PHONE_CALL, ISOLATE, 5-minute escalation, 3 indicators |
shouldClassifyNetworkOutage |
Campus gate gateway: disconnected, heartbeat 600s, 10 errors, battery 15% | 51 points (25+18+8), MAJOR, NETWORK, dispatch P1, SMS, ISOLATE, 15 minutes |
shouldFlagPerformanceIssueWithoutDispatch |
Image recognition node: CPU 88%, memory 92%, 6 errors, MEDIUM device | 22 points (12+10), MINOR, PERFORMANCE, no dispatch, P3, IM, RATE_LIMIT, 60 minutes |
shouldAlertLowBatteryOnCriticalDevice |
Cold storage recorder: battery 3%, 6 errors, HIGH critical device | 25 points (15+10), MINOR, POWER, critical device P2 dispatch, IM, auto remediation NONE |
shouldKeepHealthyDeviceNormal |
Office environment sensor: all metrics healthy | 0 points, NORMAL, NONE, no dispatch, P3, EMAIL, NONE, 240 minutes, empty indicators |
shouldDetectAbnormalVibration |
Conveyor motor: vibration 9.5, 10 errors, MEDIUM device | 38 points (20+18), MINOR, VIBRATION, no dispatch, P3, IM, THROTTLE throttle |
Verified output
Section titled “Verified output”Two verified outputs recorded in USE-CASES.md Scenario 9:
Steel furnace temperature sensor severe overheating (92℃, 22 errors, CPU 96%):
告警分: 80 (CRITICAL) 类型: TEMPERATURE 派单: P0通知: PHONE_CALL 自动处置: ISOLATE 升级时限: 5 分钟Campus gate gateway disconnection (disconnected + heartbeat 600s + 10 errors + battery 15%):
告警分: 51 (MAJOR) 类型: NETWORK 派单: P1通知: SMS 自动处置: ISOLATE 升级时限: 15 分钟Technical highlights
Section titled “Technical highlights”- Each of the six dimensions is an independent scoring method; 13 rule methods cover scoring, classification, dispatch, notification, remediation, and escalation deadline, with all thresholds (70/45/20, 300/120s, 5%/20%, etc.) externalized in XML, so threshold changes do not require restarting business systems.
- The Service clamps the six-dimension raw score to 0-100 on the Java side and assembles the indicator list based on actual hits, so every alarm can be reviewed for “why this level and what actions were executed”.
scoreResourcefirst takes the peak of CPU and memory inside the script before tiering, avoiding double counting across two resource dimensions;alarmTypeexpresses the classification priority “network > temperature > performance > power > vibration” with a single positiveif-elsechain.- The dispatch rule uses device criticality as a second condition: only HIGH-criticality devices in the 20-44 range dispatch at P2, while ordinary devices are only observed, significantly suppressing alarm storms.
autoRemediationfront-loads “isolate whenever total ≥70”, then routes NETWORK/PERFORMANCE/TEMPERATURE/VIBRATION to isolate, rate-limit, and throttle respectively;@Paramname binding supportsalarmTypewith up to 7 inputs, and the proxy instance is reusable, backed by an ASM call chain.- Runs on Java 8 with no heavy rule platform dependency, suitable for deployment on resource-constrained edge gateways and IT-innovation environments.