From 990045f15713e81579e6336c8968973e40f438c1 Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Mon, 13 Apr 2026 03:33:52 -0300 Subject: [PATCH] metrics: suppress stale climate when controller offline --- src/metrics/TyphonMetrics.ts | 5 ++++ tests/TyphonMetrics.test.ts | 47 +++++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/metrics/TyphonMetrics.ts b/src/metrics/TyphonMetrics.ts index d2cb4ec..6d2fa0d 100644 --- a/src/metrics/TyphonMetrics.ts +++ b/src/metrics/TyphonMetrics.ts @@ -231,6 +231,11 @@ export class TyphonMetrics { new_framework_device: controller.newFrameworkDevice === null ? "unknown" : String(controller.newFrameworkDevice) }).set(1); + // AC Infinity returns stale snapshots while controllers are offline. + // Keep online/info visibility, but suppress climate/fan gauges until controller is online again. + if (!controller.online) { + continue; + } this.temperatureCelsius.labels(controllerLabels).set(controller.temperatureCelsius); this.relativeHumidityRatio.labels(controllerLabels).set(controller.relativeHumidityRatio); this.relativeHumidityPercent.labels(controllerLabels).set(controller.relativeHumidityRatio * 100); diff --git a/tests/TyphonMetrics.test.ts b/tests/TyphonMetrics.test.ts index 1244e95..3640585 100644 --- a/tests/TyphonMetrics.test.ts +++ b/tests/TyphonMetrics.test.ts @@ -76,7 +76,7 @@ describe("TyphonMetrics", () => { const metrics = new TyphonMetrics("0.1.0", false, registry); const snapshot = new ClimateSnapshot(1_700_000_050, [ - new ControllerClimate("c2", "Tent B", false, 22.2, 0.40, 0.8, null, null, [ + new ControllerClimate("c2", "Tent B", true, 22.2, 0.40, 0.8, null, null, [ new PortClimate(2, "Fan B", "outlet", false, false, 0, 3, 0, true, null, AcInfinityMode.Unknown) ]) ]); @@ -110,6 +110,51 @@ describe("TyphonMetrics", () => { })).toBe(1); }); + it("suppresses stale climate and fan gauges when controller is offline", async () => { + const registry = new Registry(); + const metrics = new TyphonMetrics("0.1.0", false, registry); + + const snapshot = new ClimateSnapshot(1_700_000_060, [ + new ControllerClimate("c3", "Tent C", false, 23.1, 0.42, 1.1, 11, false, [ + new PortClimate(3, "Fan C", "interior", true, true, 7, 8, 2, true, 1500, AcInfinityMode.Auto) + ]) + ]); + + metrics.updateFromSnapshot(snapshot); + const json = await registry.getMetricsAsJSON(); + + expect(valueForMetric(json, "typhon_controller_online", { + controller_id: "c3", + controller_name: "Tent C" + })).toBe(0); + expect(valueForMetric(json, "typhon_controller_info", { + controller_id: "c3", + controller_name: "Tent C", + device_type: "11", + new_framework_device: "false" + })).toBe(1); + + expect(valueForMetric(json, "typhon_temperature_celsius", { + controller_id: "c3", + controller_name: "Tent C" + })).toBeNull(); + expect(valueForMetric(json, "typhon_relative_humidity_percent", { + controller_id: "c3", + controller_name: "Tent C" + })).toBeNull(); + expect(valueForMetric(json, "typhon_vpd_kpa", { + controller_id: "c3", + controller_name: "Tent C" + })).toBeNull(); + expect(valueForMetric(json, "typhon_fan_speed_level", { + controller_id: "c3", + controller_name: "Tent C", + port: "3", + port_name: "Fan C", + fan_group: "interior" + })).toBeNull(); + }); + it("tracks polling failures", async () => { const registry = new Registry(); const metrics = new TyphonMetrics("0.1.0", false, registry);