From 8299a2dbe37824f98f1c84d9b2bcde575e2e1f4e Mon Sep 17 00:00:00 2001 From: Jon Gallant <2163001+jongio@users.noreply.github.com> Date: Sun, 19 Jul 2026 11:26:07 -0700 Subject: [PATCH] Flag weak Wi-Fi in device health Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- packages/cli/tests/devices-triage.test.ts | 28 ++++++++++- packages/sdk/src/device-health.ts | 39 +++++++++++++++ packages/sdk/src/types.ts | 7 ++- packages/sdk/tests/device-health.test.ts | 47 +++++++++++++++++++ packages/vscode/tests/tree-health.test.ts | 8 ++++ .../web/src/components/DeviceHealthBadge.tsx | 39 +++++++++++++++ .../web/tests/device-health-badge.test.tsx | 13 +++++ 7 files changed, 179 insertions(+), 2 deletions(-) diff --git a/packages/cli/tests/devices-triage.test.ts b/packages/cli/tests/devices-triage.test.ts index 17f60ef..2f651c9 100644 --- a/packages/cli/tests/devices-triage.test.ts +++ b/packages/cli/tests/devices-triage.test.ts @@ -43,7 +43,7 @@ function makeDevice(overrides: Partial & { serial: string }): Device { status: overrides.status ?? null, battery: overrides.battery ?? null, batteryState: null, - wifiStrength: null, + wifiStrength: overrides.wifiStrength ?? null, firmware: null, color: null, thumbnail: null, @@ -387,6 +387,32 @@ describe("devices --critical", () => { expect(parsed[0].health).toBeDefined(); expect(parsed[0].health.overall).toBe("warning"); }); + + it("keeps weak Wi-Fi devices in JSON mode", async () => { + const goodDevice = makeDevice({ serial: "GOOD", label: "Healthy", status: "online" }); + const weakWifiDevice = makeDevice({ + serial: "WIFI", + label: "WeakWifi", + status: "online", + wifiStrength: -78, + }); + + mockGetDevices.mockResolvedValue([goodDevice, weakWifiDevice]); + mockGetAllDeviceChannels.mockResolvedValue([ + makeChannel({ value: 200, units: "F", number: "1" }), + ]); + + const { devices } = await import("../src/commands/devices.js"); + await devices({ json: true, criticalOnly: true }); + + const raw = logSpy.mock.calls[0][0] as string; + const parsed = JSON.parse(raw); + expect(parsed).toHaveLength(1); + expect(parsed[0].serial).toBe("WIFI"); + expect(parsed[0].health.issues).toContainEqual( + expect.objectContaining({ code: "weak_wifi_signal", severity: "warning" }), + ); + }); }); // ============================================================================= diff --git a/packages/sdk/src/device-health.ts b/packages/sdk/src/device-health.ts index 33e5e17..7be094d 100644 --- a/packages/sdk/src/device-health.ts +++ b/packages/sdk/src/device-health.ts @@ -8,6 +8,40 @@ const STALE_CRITICAL_MS = 30 * 60 * 1000; const BATTERY_WARNING_THRESHOLD = 20; /** Battery percentage below which a critical issue is issued. */ const BATTERY_CRITICAL_THRESHOLD = 5; +/** RSSI at or below this dBm is warning-level Wi-Fi health. */ +const WIFI_WARNING_DBM = -75; +/** RSSI at or below this dBm is critical Wi-Fi health. */ +const WIFI_CRITICAL_DBM = -85; +/** Percentage at or below this value is warning-level Wi-Fi health. */ +const WIFI_WARNING_PERCENT = 30; +/** Percentage at or below this value is critical Wi-Fi health. */ +const WIFI_CRITICAL_PERCENT = 10; + +function assessWifiStrength(strength: number): DeviceHealthIssue | null { + const isRssi = strength < 0; + const detail = isRssi ? `RSSI ${strength} dBm` : `Signal ${strength}%`; + const critical = isRssi ? strength <= WIFI_CRITICAL_DBM : strength <= WIFI_CRITICAL_PERCENT; + if (critical) { + return { + code: "weak_wifi_signal", + severity: "critical", + message: "Wi-Fi signal critically weak", + detail, + }; + } + + const warning = isRssi ? strength <= WIFI_WARNING_DBM : strength <= WIFI_WARNING_PERCENT; + if (warning) { + return { + code: "weak_wifi_signal", + severity: "warning", + message: "Wi-Fi signal weak", + detail, + }; + } + + return null; +} /** * Assess the health of a device based on its state and channel data. @@ -80,6 +114,11 @@ export function assessDeviceHealth( } } + if (device.wifiStrength != null) { + const wifiIssue = assessWifiStrength(device.wifiStrength); + if (wifiIssue) issues.push(wifiIssue); + } + // Offline check if (device.status !== "online") { issues.push({ diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts index f838211..1ff3988 100644 --- a/packages/sdk/src/types.ts +++ b/packages/sdk/src/types.ts @@ -472,7 +472,12 @@ export interface DeviceHealth { /** A single health issue detected on a device. */ export interface DeviceHealthIssue { - readonly code: "stale_reading" | "low_battery" | "offline" | "firmware_outdated"; + readonly code: + | "stale_reading" + | "low_battery" + | "weak_wifi_signal" + | "offline" + | "firmware_outdated"; readonly severity: "warning" | "critical"; readonly message: string; readonly detail?: string; diff --git a/packages/sdk/tests/device-health.test.ts b/packages/sdk/tests/device-health.test.ts index c9ebedc..60b1f3d 100644 --- a/packages/sdk/tests/device-health.test.ts +++ b/packages/sdk/tests/device-health.test.ts @@ -168,6 +168,53 @@ describe("assessDeviceHealth", () => { ); }); + it("returns warning for weak RSSI", () => { + const now = new Date("2026-01-01T12:00:00Z"); + const device = makeDevice({ lastSeen: now, status: "online", wifiStrength: -78 }); + const channels = [makeChannel({ lastSeen: now })]; + + const result = assessDeviceHealth(device, channels, now); + + expect(result.overall).toBe("warning"); + expect(result.issues).toContainEqual( + expect.objectContaining({ + code: "weak_wifi_signal", + severity: "warning", + detail: "RSSI -78 dBm", + }), + ); + }); + + it("returns critical for very weak RSSI", () => { + const now = new Date("2026-01-01T12:00:00Z"); + const device = makeDevice({ lastSeen: now, status: "online", wifiStrength: -88 }); + const channels = [makeChannel({ lastSeen: now })]; + + const result = assessDeviceHealth(device, channels, now); + + expect(result.overall).toBe("critical"); + expect(result.issues).toContainEqual( + expect.objectContaining({ code: "weak_wifi_signal", severity: "critical" }), + ); + }); + + it("returns warning for low percentage Wi-Fi signal", () => { + const now = new Date("2026-01-01T12:00:00Z"); + const device = makeDevice({ lastSeen: now, status: "online", wifiStrength: 25 }); + const channels = [makeChannel({ lastSeen: now })]; + + const result = assessDeviceHealth(device, channels, now); + + expect(result.overall).toBe("warning"); + expect(result.issues).toContainEqual( + expect.objectContaining({ + code: "weak_wifi_signal", + severity: "warning", + detail: "Signal 25%", + }), + ); + }); + it("derives overall from worst severity", () => { const now = new Date("2026-01-01T12:00:00Z"); // Offline (warning) + critical battery diff --git a/packages/vscode/tests/tree-health.test.ts b/packages/vscode/tests/tree-health.test.ts index ad6a177..a602ec9 100644 --- a/packages/vscode/tests/tree-health.test.ts +++ b/packages/vscode/tests/tree-health.test.ts @@ -177,6 +177,14 @@ describe("DeviceNode health display", () => { expect(node.description).toContain("Warning"); }); + it("shows warning description for weak Wi-Fi", () => { + const device = makeDevice({ status: "online", battery: 80, wifiStrength: -78 }); + const channels = [makeChannel()]; + const node = new DeviceNode(device, false, false, channels); + + expect(node.description).toContain("Warning"); + }); + it("shows no health indicator when all is well", () => { const device = makeDevice({ status: "online", battery: 80 }); const channels = [makeChannel()]; diff --git a/packages/web/src/components/DeviceHealthBadge.tsx b/packages/web/src/components/DeviceHealthBadge.tsx index bee8d18..f3e7dad 100644 --- a/packages/web/src/components/DeviceHealthBadge.tsx +++ b/packages/web/src/components/DeviceHealthBadge.tsx @@ -10,6 +10,40 @@ const STALE_CRITICAL_MS = 30 * 60 * 1000; const BATTERY_WARNING_THRESHOLD = 20; /** Battery percentage below which a critical issue is issued. */ const BATTERY_CRITICAL_THRESHOLD = 5; +/** RSSI at or below this dBm is warning-level Wi-Fi health. */ +const WIFI_WARNING_DBM = -75; +/** RSSI at or below this dBm is critical Wi-Fi health. */ +const WIFI_CRITICAL_DBM = -85; +/** Percentage at or below this value is warning-level Wi-Fi health. */ +const WIFI_WARNING_PERCENT = 30; +/** Percentage at or below this value is critical Wi-Fi health. */ +const WIFI_CRITICAL_PERCENT = 10; + +function assessWifiStrength(strength: number): DeviceHealth["issues"][number] | null { + const isRssi = strength < 0; + const detail = isRssi ? `RSSI ${strength} dBm` : `Signal ${strength}%`; + const critical = isRssi ? strength <= WIFI_CRITICAL_DBM : strength <= WIFI_CRITICAL_PERCENT; + if (critical) { + return { + code: "weak_wifi_signal", + severity: "critical", + message: "Wi-Fi signal critically weak", + detail, + }; + } + + const warning = isRssi ? strength <= WIFI_WARNING_DBM : strength <= WIFI_WARNING_PERCENT; + if (warning) { + return { + code: "weak_wifi_signal", + severity: "warning", + message: "Wi-Fi signal weak", + detail, + }; + } + + return null; +} /** * Browser-local implementation of health assessment. @@ -67,6 +101,11 @@ function assessHealth(device: Device, channels: DeviceChannel[]): DeviceHealth { } } + if (device.wifiStrength != null) { + const wifiIssue = assessWifiStrength(device.wifiStrength); + if (wifiIssue) issues.push(wifiIssue); + } + if (device.status !== "online") { issues.push({ code: "offline", diff --git a/packages/web/tests/device-health-badge.test.tsx b/packages/web/tests/device-health-badge.test.tsx index ab80003..8b18e8c 100644 --- a/packages/web/tests/device-health-badge.test.tsx +++ b/packages/web/tests/device-health-badge.test.tsx @@ -167,4 +167,17 @@ describe("DeviceHealthBadge", () => { expect(screen.getByRole("tooltip")).toHaveTextContent("stale"); }); + + it("shows weak Wi-Fi signal in the tooltip", () => { + const device = makeDevice({ status: "online", battery: 80, wifiStrength: -78 }); + const channels = [makeChannel()]; + + render(); + + const badge = screen.getByLabelText("Device health: Warning"); + fireEvent.mouseEnter(badge); + + expect(screen.getByRole("tooltip")).toHaveTextContent("Wi-Fi signal weak"); + expect(screen.getByRole("tooltip")).toHaveTextContent("RSSI -78 dBm"); + }); });