Files
water/docs/superpowers/plans/2026-07-13-device-status-lwt-mac.md
2026-07-13 14:22:11 +08:00

5.3 KiB

Device Status LWT MAC Resolution Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Verify and document that /MAC/publish/status last-will messages resolve the MAC address to deviceNo before updating device status.

Architecture: Keep DeviceStatusHandler focused on status parsing and delegate Topic identity resolution to the existing DeviceIdentityResolver. Add a focused integration-style unit test using the real resolver and a mocked mapper so the complete MAC-to-device-number path is covered without duplicating database access in the handler.

Tech Stack: Java 17, Spring Boot, JUnit 5, Mockito, AssertJ, MyBatis-Plus

Global Constraints

  • Only /MAC/publish/status may use a MAC address for this change.
  • Other MQTT communication continues to use deviceNo.
  • A missing MAC mapping must not update device status.
  • Preserve all existing uncommitted workspace changes.

Task 1: Cover MAC Last-Will Resolution

Files:

  • Modify: water-modules/water-app/src/test/java/org/dromara/app/handler/DeviceStatusHandlerTest.java

Interfaces:

  • Consumes: DeviceIdentityResolver(AppDeviceMapper) and DeviceStatusHandler.handle(String, String, boolean)

  • Produces: Regression coverage proving a Topic MAC is converted to AppDevice.deviceNo

  • Step 1: Add the mapper mock and MAC last-will test

Add imports and a mapper mock:

import org.dromara.app.domain.AppDevice;
import org.dromara.app.mapper.AppDeviceMapper;

@Mock
private AppDeviceMapper appDeviceMapper;

Add this test:

@Test
void handleResolvesLastWillTopicMacToDeviceNo() {
    String macAddress = "DC:DA:0C:FA:29:5E";
    AppDevice device = new AppDevice();
    device.setDeviceNo("D01");
    when(appDeviceMapper.selectById(macAddress)).thenReturn(null);
    when(appDeviceMapper.selectByMac("dc:da:0c:fa:29:5e")).thenReturn(device);
    DeviceIdentityResolver resolver = new DeviceIdentityResolver(appDeviceMapper);
    DeviceStatusHandler handler = new DeviceStatusHandler(resolver, deviceStatusService, new ObjectMapper());

    handler.handle(macAddress, "{\"status\":\"offline\"}", false);

    verify(appDeviceMapper).selectByMac("dc:da:0c:fa:29:5e");
    verify(deviceStatusService).markOffline("D01", "设备 MQTT 状态离线");
    verify(deviceStatusService, never()).markOffline(macAddress, "设备 MQTT 状态离线");
}

Add the missing-device guard test:

@Test
void handleDoesNotUpdateStatusWhenLastWillMacIsUnknown() {
    String macAddress = "DC:DA:0C:FA:29:5E";
    when(appDeviceMapper.selectById(macAddress)).thenReturn(null);
    when(appDeviceMapper.selectByMac("dc:da:0c:fa:29:5e")).thenReturn(null);
    DeviceIdentityResolver resolver = new DeviceIdentityResolver(appDeviceMapper);
    DeviceStatusHandler handler = new DeviceStatusHandler(resolver, deviceStatusService, new ObjectMapper());

    handler.handle(macAddress, "{\"status\":\"offline\"}", false);

    verify(deviceStatusService, never()).markOffline(anyString(), anyString());
    verify(deviceStatusService, never()).markOnline(anyString());
}
  • Step 2: Run the focused test

Run:

mvn -pl water-modules/water-app -am "-DskipTests=false" "-Dmaven.test.skip=false" "-Dprofiles.active=dev" "-Dtest=DeviceStatusHandlerTest#handleResolvesLastWillTopicMacToDeviceNo+handleDoesNotUpdateStatusWhenLastWillMacIsUnknown" "-Dsurefire.failIfNoSpecifiedTests=false" test

Expected: both tests PASS. The requested production path already exists in DeviceIdentityResolver; these characterization tests make that behavior explicit and prevent regression.

Task 2: Clarify Device Status Topic Identity

Files:

  • Modify: water-modules/water-app/src/main/java/org/dromara/app/handler/DeviceStatusHandler.java:15-19

Interfaces:

  • Consumes: Existing DeviceIdentityResolver.resolveDeviceNo(String) behavior

  • Produces: Javadoc that accurately documents device-number and MAC Topic identities

  • Step 1: Update handler Javadoc

Replace the class description with:

/**
 * 设备在线/离线状态处理器,匹配 /{deviceIdentity}/publish/status。
 * <p>
 * deviceIdentity 支持设备编号;设备遗嘱消息允许使用 MAC 地址,处理前统一解析为设备编号。
 * 设备通过 status=online 标记上线,通过 LWT status=offline 标记异常离线。
 */
  • Step 2: Run all handler tests

Run:

mvn -pl water-modules/water-app -am "-DskipTests=false" "-Dmaven.test.skip=false" "-Dprofiles.active=dev" "-Dtest=DeviceStatusHandlerTest" "-Dsurefire.failIfNoSpecifiedTests=false" test

Expected: all DeviceStatusHandlerTest tests PASS with zero failures and errors.

  • Step 3: Check the final diff

Run:

git diff --check -- water-modules/water-app/src/main/java/org/dromara/app/handler/DeviceStatusHandler.java water-modules/water-app/src/test/java/org/dromara/app/handler/DeviceStatusHandlerTest.java

Expected: exit code 0 and no whitespace errors.

  • Step 4: Leave implementation changes uncommitted for review

Both implementation files already contain user changes. Do not create a commit that would mix those changes with this task.