Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion packages/cli/tests/devices-triage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function makeDevice(overrides: Partial<Device> & { 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,
Expand Down Expand Up @@ -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" }),
);
});
});

// =============================================================================
Expand Down
39 changes: 39 additions & 0 deletions packages/sdk/src/device-health.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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({
Expand Down
7 changes: 6 additions & 1 deletion packages/sdk/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
47 changes: 47 additions & 0 deletions packages/sdk/tests/device-health.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions packages/vscode/tests/tree-health.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()];
Expand Down
39 changes: 39 additions & 0 deletions packages/web/src/components/DeviceHealthBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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",
Expand Down
13 changes: 13 additions & 0 deletions packages/web/tests/device-health-badge.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(<DeviceHealthBadge device={device} channels={channels} />);

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");
});
});