homebridge-rpi-procmon 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nima Beathoven
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # homebridge-rpi-procmon
2
+
3
+ `homebridge-rpi-procmon` is a Homebridge dynamic platform plugin that reads the `rpi-procmon` HTTP API and exposes monitor health into Apple Home.
4
+
5
+ ## Current Scope
6
+
7
+ - Polls `GET /status` from `rpi-procmon`
8
+ - Exposes one optional overall Contact Sensor for the procmon supervisor
9
+ - Exposes one Contact Sensor per selected procmon monitor, with labels that distinguish services, containers, and plugin-level monitors
10
+ - Updates cached accessories dynamically as monitor inventory changes
11
+
12
+ This initial version is intentionally read-only. Restart actions, host reboot, and procmon restart switches should only be added after `rpi-procmon` exposes authenticated control endpoints.
13
+
14
+ ## Accessory Mapping
15
+
16
+ - Overall procmon status: `ContactSensor`
17
+ - `Closed` = healthy or recovering
18
+ - `Open` = degraded, failed, unknown
19
+ - Per-monitor health: `ContactSensor`
20
+ - `Closed` = healthy
21
+ - `Open` = degraded, failed, recovering, unknown
22
+
23
+ Naming model:
24
+
25
+ - `Rpi Procmon Supervisor`
26
+ - `Service: Homebridge`
27
+ - `Container: Scrypted`
28
+ - `Plugin: Scrypted Arlo`
29
+
30
+ That keeps procmon itself visually distinct from the items it monitors, while still using a simple binary health model in Apple Home. Text-heavy issue history belongs in a Homebridge UI, not in HomeKit characteristics.
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ npm install -g homebridge-rpi-procmon
36
+ ```
37
+
38
+ ## Example Config
39
+
40
+ ```json
41
+ {
42
+ "platform": "RpiProcmon",
43
+ "name": "Rpi Procmon",
44
+ "endpoint": "http://192.168.5.11:9645",
45
+ "pollIntervalSeconds": 15,
46
+ "requestTimeoutMs": 4000,
47
+ "exposeOverallSensor": true,
48
+ "exposeDisabledMonitors": false,
49
+ "hideHealthyMonitors": false,
50
+ "includeMonitors": [],
51
+ "excludeMonitors": []
52
+ }
53
+ ```
54
+
55
+ ## Development
56
+
57
+ ```bash
58
+ npm install
59
+ npm run check
60
+ ```
61
+
62
+ ## Publishing Notes
63
+
64
+ - Package name: `homebridge-rpi-procmon`
65
+ - Plugin alias: `RpiProcmon`
66
+ - Requires `homebridge` `^1.11.2 || ^2.0.0-alpha.0`
67
+ - Requires Node `^18.15.0 || ^20.7.0 || ^22 || ^24`
68
+
69
+ ## Roadmap
70
+
71
+ - Add a Homebridge custom UI for recent issues and recovery history
72
+ - Add optional restart/reboot action switches after procmon gains authenticated write endpoints
73
+ - Add tests around config filtering and snapshot normalization
@@ -0,0 +1,73 @@
1
+ {
2
+ "pluginAlias": "RpiProcmon",
3
+ "pluginType": "platform",
4
+ "headerDisplay": "Expose rpi-procmon monitor health to Apple Home as Contact Sensors.",
5
+ "schema": {
6
+ "type": "object",
7
+ "properties": {
8
+ "name": {
9
+ "title": "Platform Name",
10
+ "type": "string",
11
+ "default": "Rpi Procmon",
12
+ "description": "Display name for this Homebridge platform instance."
13
+ },
14
+ "endpoint": {
15
+ "title": "Procmon API Base URL",
16
+ "type": "string",
17
+ "default": "http://127.0.0.1:9645",
18
+ "description": "Base URL for the procmon API. Example: http://192.168.5.11:9645"
19
+ },
20
+ "pollIntervalSeconds": {
21
+ "title": "Poll Interval (seconds)",
22
+ "type": "integer",
23
+ "default": 15,
24
+ "minimum": 5,
25
+ "maximum": 300,
26
+ "description": "How often the plugin fetches /status from procmon."
27
+ },
28
+ "requestTimeoutMs": {
29
+ "title": "Request Timeout (ms)",
30
+ "type": "integer",
31
+ "default": 4000,
32
+ "minimum": 1000,
33
+ "maximum": 30000,
34
+ "description": "HTTP timeout for procmon API requests."
35
+ },
36
+ "exposeOverallSensor": {
37
+ "title": "Expose Overall Fault Sensor",
38
+ "type": "boolean",
39
+ "default": true,
40
+ "description": "Create one top-level Contact Sensor for overall procmon health."
41
+ },
42
+ "exposeDisabledMonitors": {
43
+ "title": "Expose Disabled Monitors",
44
+ "type": "boolean",
45
+ "default": false,
46
+ "description": "Include monitors that procmon reports as disabled."
47
+ },
48
+ "hideHealthyMonitors": {
49
+ "title": "Hide Healthy Monitors",
50
+ "type": "boolean",
51
+ "default": false,
52
+ "description": "Only expose monitors that currently need attention."
53
+ },
54
+ "includeMonitors": {
55
+ "title": "Include Monitor IDs",
56
+ "type": "array",
57
+ "description": "Optional allowlist of procmon monitor ids to expose.",
58
+ "items": {
59
+ "type": "string"
60
+ }
61
+ },
62
+ "excludeMonitors": {
63
+ "title": "Exclude Monitor IDs",
64
+ "type": "array",
65
+ "description": "Optional denylist of procmon monitor ids to hide.",
66
+ "items": {
67
+ "type": "string"
68
+ }
69
+ }
70
+ },
71
+ "required": ["endpoint"]
72
+ }
73
+ }
@@ -0,0 +1,13 @@
1
+ import type { PlatformAccessory } from "homebridge";
2
+ import type { ProcmonPlatform } from "../platform";
3
+ import type { ProcmonMonitorState } from "../types";
4
+ export declare class MonitorSensorAccessory {
5
+ private readonly platform;
6
+ private readonly accessory;
7
+ private readonly monitor;
8
+ private readonly informationService;
9
+ private readonly service;
10
+ constructor(platform: ProcmonPlatform, accessory: PlatformAccessory, monitor: ProcmonMonitorState);
11
+ update(monitor: ProcmonMonitorState): void;
12
+ private configureInformation;
13
+ }
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MonitorSensorAccessory = void 0;
4
+ const config_1 = require("../config");
5
+ class MonitorSensorAccessory {
6
+ platform;
7
+ accessory;
8
+ monitor;
9
+ informationService;
10
+ service;
11
+ constructor(platform, accessory, monitor) {
12
+ this.platform = platform;
13
+ this.accessory = accessory;
14
+ this.monitor = monitor;
15
+ this.informationService =
16
+ this.accessory.getService(this.platform.api.hap.Service.AccessoryInformation) ??
17
+ this.accessory.addService(this.platform.api.hap.Service.AccessoryInformation);
18
+ this.service =
19
+ this.accessory.getService(this.platform.api.hap.Service.ContactSensor) ??
20
+ this.accessory.addService(this.platform.api.hap.Service.ContactSensor);
21
+ this.configureInformation();
22
+ this.update(monitor);
23
+ }
24
+ update(monitor) {
25
+ const needsAttention = (0, config_1.monitorNeedsAttention)(monitor.status);
26
+ const role = (0, config_1.monitorRole)(monitor);
27
+ const label = (0, config_1.monitorDisplayName)(monitor);
28
+ this.accessory.displayName = label;
29
+ this.accessory.context = {
30
+ kind: "monitor",
31
+ monitorId: monitor.id,
32
+ role,
33
+ label,
34
+ };
35
+ this.service.setCharacteristic(this.platform.api.hap.Characteristic.Name, label);
36
+ this.service.setCharacteristic(this.platform.api.hap.Characteristic.ContactSensorState, needsAttention
37
+ ? this.platform.api.hap.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED
38
+ : this.platform.api.hap.Characteristic.ContactSensorState.CONTACT_DETECTED);
39
+ this.service.setCharacteristic(this.platform.api.hap.Characteristic.StatusActive, monitor.enabled);
40
+ this.service.setCharacteristic(this.platform.api.hap.Characteristic.StatusFault, needsAttention
41
+ ? this.platform.api.hap.Characteristic.StatusFault.GENERAL_FAULT
42
+ : this.platform.api.hap.Characteristic.StatusFault.NO_FAULT);
43
+ this.service.setCharacteristic(this.platform.api.hap.Characteristic.StatusTampered, this.platform.api.hap.Characteristic.StatusTampered.NOT_TAMPERED);
44
+ }
45
+ configureInformation() {
46
+ const model = accessoryModel((0, config_1.monitorRole)(this.monitor));
47
+ this.informationService
48
+ .setCharacteristic(this.platform.api.hap.Characteristic.Manufacturer, "rpi-procmon")
49
+ .setCharacteristic(this.platform.api.hap.Characteristic.Model, model)
50
+ .setCharacteristic(this.platform.api.hap.Characteristic.SerialNumber, this.monitor.id);
51
+ }
52
+ }
53
+ exports.MonitorSensorAccessory = MonitorSensorAccessory;
54
+ function accessoryModel(role) {
55
+ switch (role) {
56
+ case "service":
57
+ return "Procmon Service Sensor";
58
+ case "container":
59
+ return "Procmon Container Sensor";
60
+ case "plugin":
61
+ return "Procmon Plugin Sensor";
62
+ default:
63
+ return "Procmon Monitor Sensor";
64
+ }
65
+ }
66
+ //# sourceMappingURL=monitor-sensor-accessory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"monitor-sensor-accessory.js","sourceRoot":"","sources":["../../src/accessories/monitor-sensor-accessory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { PlatformAccessory } from "homebridge";
2
+ import type { ProcmonPlatform } from "../platform";
3
+ import type { ProcmonStatus } from "../types";
4
+ export declare class OverallSensorAccessory {
5
+ private readonly platform;
6
+ private readonly accessory;
7
+ private readonly informationService;
8
+ private readonly service;
9
+ constructor(platform: ProcmonPlatform, accessory: PlatformAccessory, snapshot: ProcmonStatus);
10
+ update(snapshot: ProcmonStatus): void;
11
+ private configureInformation;
12
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OverallSensorAccessory = void 0;
4
+ const config_1 = require("../config");
5
+ class OverallSensorAccessory {
6
+ platform;
7
+ accessory;
8
+ informationService;
9
+ service;
10
+ constructor(platform, accessory, snapshot) {
11
+ this.platform = platform;
12
+ this.accessory = accessory;
13
+ this.informationService =
14
+ this.accessory.getService(this.platform.api.hap.Service.AccessoryInformation) ??
15
+ this.accessory.addService(this.platform.api.hap.Service.AccessoryInformation);
16
+ this.service =
17
+ this.accessory.getService(this.platform.api.hap.Service.ContactSensor) ??
18
+ this.accessory.addService(this.platform.api.hap.Service.ContactSensor);
19
+ this.configureInformation();
20
+ this.update(snapshot);
21
+ }
22
+ update(snapshot) {
23
+ const needsAttention = (0, config_1.overallNeedsAttention)(snapshot.overall_status);
24
+ const sensorName = (0, config_1.overallDisplayName)(this.platform.normalizedConfig.name);
25
+ this.accessory.displayName = sensorName;
26
+ this.accessory.context = {
27
+ kind: "overall",
28
+ label: sensorName,
29
+ };
30
+ this.service.setCharacteristic(this.platform.api.hap.Characteristic.Name, sensorName);
31
+ this.service.setCharacteristic(this.platform.api.hap.Characteristic.ContactSensorState, needsAttention
32
+ ? this.platform.api.hap.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED
33
+ : this.platform.api.hap.Characteristic.ContactSensorState.CONTACT_DETECTED);
34
+ this.service.setCharacteristic(this.platform.api.hap.Characteristic.StatusActive, true);
35
+ this.service.setCharacteristic(this.platform.api.hap.Characteristic.StatusFault, needsAttention
36
+ ? this.platform.api.hap.Characteristic.StatusFault.GENERAL_FAULT
37
+ : this.platform.api.hap.Characteristic.StatusFault.NO_FAULT);
38
+ }
39
+ configureInformation() {
40
+ this.informationService
41
+ .setCharacteristic(this.platform.api.hap.Characteristic.Manufacturer, "rpi-procmon")
42
+ .setCharacteristic(this.platform.api.hap.Characteristic.Model, "Procmon Supervisor Sensor")
43
+ .setCharacteristic(this.platform.api.hap.Characteristic.SerialNumber, "overall");
44
+ }
45
+ }
46
+ exports.OverallSensorAccessory = OverallSensorAccessory;
47
+ //# sourceMappingURL=overall-sensor-accessory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overall-sensor-accessory.js","sourceRoot":"","sources":["../../src/accessories/overall-sensor-accessory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { Logger } from "homebridge";
2
+ import type { MonitorRole, NormalizedConfig, ProcmonMonitorState, ProcmonPlatformConfig, ProcmonStatus } from "./types";
3
+ export declare function normalizeConfig(config: ProcmonPlatformConfig, log: Logger): NormalizedConfig;
4
+ export declare function selectVisibleMonitors(snapshot: ProcmonStatus, config: NormalizedConfig): ProcmonMonitorState[];
5
+ export declare function overallNeedsAttention(status: string): boolean;
6
+ export declare function monitorNeedsAttention(status: string): boolean;
7
+ export declare function overallDisplayName(name: string): string;
8
+ export declare function monitorDisplayName(monitor: ProcmonMonitorState): string;
9
+ export declare function monitorRole(monitor: ProcmonMonitorState): MonitorRole;
package/dist/config.js ADDED
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeConfig = normalizeConfig;
4
+ exports.selectVisibleMonitors = selectVisibleMonitors;
5
+ exports.overallNeedsAttention = overallNeedsAttention;
6
+ exports.monitorNeedsAttention = monitorNeedsAttention;
7
+ exports.overallDisplayName = overallDisplayName;
8
+ exports.monitorDisplayName = monitorDisplayName;
9
+ exports.monitorRole = monitorRole;
10
+ const DEFAULT_ENDPOINT = "http://127.0.0.1:9645";
11
+ const DEFAULT_POLL_INTERVAL_SECONDS = 15;
12
+ const DEFAULT_TIMEOUT_MS = 4000;
13
+ function normalizeConfig(config, log) {
14
+ const endpoint = normalizeEndpoint(config.endpoint ?? DEFAULT_ENDPOINT);
15
+ const pollIntervalSeconds = clampInteger(config.pollIntervalSeconds, DEFAULT_POLL_INTERVAL_SECONDS, 5, 300);
16
+ const requestTimeoutMs = clampInteger(config.requestTimeoutMs, DEFAULT_TIMEOUT_MS, 1000, 30000);
17
+ if (config.endpoint && endpoint !== config.endpoint.replace(/\/+$/, "")) {
18
+ log.debug(`normalized endpoint to ${endpoint}`);
19
+ }
20
+ return {
21
+ name: cleanString(config.name) || "Rpi Procmon",
22
+ endpoint,
23
+ pollIntervalSeconds,
24
+ requestTimeoutMs,
25
+ exposeOverallSensor: config.exposeOverallSensor !== false,
26
+ exposeDisabledMonitors: config.exposeDisabledMonitors === true,
27
+ hideHealthyMonitors: config.hideHealthyMonitors === true,
28
+ includeMonitors: new Set(cleanStringArray(config.includeMonitors)),
29
+ excludeMonitors: new Set(cleanStringArray(config.excludeMonitors)),
30
+ };
31
+ }
32
+ function selectVisibleMonitors(snapshot, config) {
33
+ return (snapshot.monitors || []).filter((monitor) => {
34
+ if (!config.exposeDisabledMonitors && (!monitor.enabled || monitor.status === "disabled")) {
35
+ return false;
36
+ }
37
+ if (config.includeMonitors.size > 0 && !config.includeMonitors.has(monitor.id)) {
38
+ return false;
39
+ }
40
+ if (config.excludeMonitors.has(monitor.id)) {
41
+ return false;
42
+ }
43
+ if (config.hideHealthyMonitors && isHealthyMonitor(monitor.status)) {
44
+ return false;
45
+ }
46
+ return true;
47
+ });
48
+ }
49
+ function overallNeedsAttention(status) {
50
+ return !["healthy", "recovering"].includes(status);
51
+ }
52
+ function monitorNeedsAttention(status) {
53
+ return !["healthy"].includes(status);
54
+ }
55
+ function overallDisplayName(name) {
56
+ return `${name} Supervisor`;
57
+ }
58
+ function monitorDisplayName(monitor) {
59
+ const role = monitorRole(monitor);
60
+ const rawName = cleanMonitorName(monitor.name || monitor.id);
61
+ switch (role) {
62
+ case "service":
63
+ return `Service: ${trimRoleSuffix(rawName, / service$/i)}`;
64
+ case "container":
65
+ return `Container: ${trimRoleSuffix(rawName, / container$/i)}`;
66
+ case "plugin":
67
+ return `Plugin: ${rawName}`;
68
+ default:
69
+ return `Monitor: ${rawName}`;
70
+ }
71
+ }
72
+ function monitorRole(monitor) {
73
+ switch (monitor.type) {
74
+ case "systemd-service":
75
+ return "service";
76
+ case "docker-container":
77
+ return "container";
78
+ case "docker-log":
79
+ return "plugin";
80
+ default:
81
+ return "monitor";
82
+ }
83
+ }
84
+ function isHealthyMonitor(status) {
85
+ return status === "healthy";
86
+ }
87
+ function normalizeEndpoint(value) {
88
+ const trimmed = cleanString(value);
89
+ if (!trimmed) {
90
+ return DEFAULT_ENDPOINT;
91
+ }
92
+ return trimmed.replace(/\/+$/, "");
93
+ }
94
+ function cleanString(value) {
95
+ return (value ?? "").trim();
96
+ }
97
+ function cleanMonitorName(value) {
98
+ const trimmed = cleanString(value);
99
+ if (!trimmed) {
100
+ return "Unnamed";
101
+ }
102
+ return trimmed.replace(/^service:\s*/i, "").replace(/^container:\s*/i, "").replace(/^plugin:\s*/i, "");
103
+ }
104
+ function trimRoleSuffix(value, suffix) {
105
+ const trimmed = value.replace(suffix, "").trim();
106
+ return trimmed || value;
107
+ }
108
+ function cleanStringArray(values) {
109
+ return (values ?? []).map((value) => value.trim()).filter(Boolean);
110
+ }
111
+ function clampInteger(value, fallback, min, max) {
112
+ if (!Number.isInteger(value)) {
113
+ return fallback;
114
+ }
115
+ const normalized = value;
116
+ return Math.min(Math.max(normalized, min), max);
117
+ }
118
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":";;AAcA,0CAoBC;AAED,sDAgBC;AAED,sDAEC;AAED,sDAEC;AAED,gDAEC;AAED,gDAcC;AAED,kCAWC;AAnFD,MAAM,gBAAgB,GAAG,uBAAuB,CAAC;AACjD,MAAM,6BAA6B,GAAG,EAAE,CAAC;AACzC,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEhC,SAAgB,eAAe,CAAC,MAA6B,EAAE,GAAW;IACxE,MAAM,QAAQ,GAAG,iBAAiB,CAAC,MAAM,CAAC,QAAQ,IAAI,gBAAgB,CAAC,CAAC;IACxE,MAAM,mBAAmB,GAAG,YAAY,CAAC,MAAM,CAAC,mBAAmB,EAAE,6BAA6B,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;IAC5G,MAAM,gBAAgB,GAAG,YAAY,CAAC,MAAM,CAAC,gBAAgB,EAAE,kBAAkB,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IAEhG,IAAI,MAAM,CAAC,QAAQ,IAAI,QAAQ,KAAK,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC;QACxE,GAAG,CAAC,KAAK,CAAC,0BAA0B,QAAQ,EAAE,CAAC,CAAC;IAClD,CAAC;IAED,OAAO;QACL,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,aAAa;QAC/C,QAAQ;QACR,mBAAmB;QACnB,gBAAgB;QAChB,mBAAmB,EAAE,MAAM,CAAC,mBAAmB,KAAK,KAAK;QACzD,sBAAsB,EAAE,MAAM,CAAC,sBAAsB,KAAK,IAAI;QAC9D,mBAAmB,EAAE,MAAM,CAAC,mBAAmB,KAAK,IAAI;QACxD,eAAe,EAAE,IAAI,GAAG,CAAC,gBAAgB,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;QAClE,eAAe,EAAE,IAAI,GAAG,CAAC,gBAAgB,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;KACnE,CAAC;AACJ,CAAC;AAED,SAAgB,qBAAqB,CAAC,QAAuB,EAAE,MAAwB;IACrF,OAAO,CAAC,QAAQ,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE;QAClD,IAAI,CAAC,MAAM,CAAC,sBAAsB,IAAI,CAAC,CAAC,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,UAAU,CAAC,EAAE,CAAC;YAC1F,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,MAAM,CAAC,eAAe,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;YAC/E,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;YAC3C,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,MAAM,CAAC,mBAAmB,IAAI,gBAAgB,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACnE,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAgB,qBAAqB,CAAC,MAAc;IAClD,OAAO,CAAC,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACrD,CAAC;AAED,SAAgB,qBAAqB,CAAC,MAAc;IAClD,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACvC,CAAC;AAED,SAAgB,kBAAkB,CAAC,IAAY;IAC7C,OAAO,GAAG,IAAI,aAAa,CAAC;AAC9B,CAAC;AAED,SAAgB,kBAAkB,CAAC,OAA4B;IAC7D,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IAClC,MAAM,OAAO,GAAG,gBAAgB,CAAC,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,EAAE,CAAC,CAAC;IAE7D,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,SAAS;YACZ,OAAO,YAAY,cAAc,CAAC,OAAO,EAAE,YAAY,CAAC,EAAE,CAAC;QAC7D,KAAK,WAAW;YACd,OAAO,cAAc,cAAc,CAAC,OAAO,EAAE,cAAc,CAAC,EAAE,CAAC;QACjE,KAAK,QAAQ;YACX,OAAO,WAAW,OAAO,EAAE,CAAC;QAC9B;YACE,OAAO,YAAY,OAAO,EAAE,CAAC;IACjC,CAAC;AACH,CAAC;AAED,SAAgB,WAAW,CAAC,OAA4B;IACtD,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACrB,KAAK,iBAAiB;YACpB,OAAO,SAAS,CAAC;QACnB,KAAK,kBAAkB;YACrB,OAAO,WAAW,CAAC;QACrB,KAAK,YAAY;YACf,OAAO,QAAQ,CAAC;QAClB;YACE,OAAO,SAAS,CAAC;IACrB,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,MAAc;IACtC,OAAO,MAAM,KAAK,SAAS,CAAC;AAC9B,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAa;IACtC,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IACnC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,gBAAgB,CAAC;IAC1B,CAAC;IACD,OAAO,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACrC,CAAC;AAED,SAAS,WAAW,CAAC,KAAyB;IAC5C,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AAC9B,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAa;IACrC,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IACnC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,OAAO,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;AACzG,CAAC;AAED,SAAS,cAAc,CAAC,KAAa,EAAE,MAAc;IACnD,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACjD,OAAO,OAAO,IAAI,KAAK,CAAC;AAC1B,CAAC;AAED,SAAS,gBAAgB,CAAC,MAA4B;IACpD,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AACrE,CAAC;AAED,SAAS,YAAY,CAAC,KAAyB,EAAE,QAAgB,EAAE,GAAW,EAAE,GAAW;IACzF,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,MAAM,UAAU,GAAG,KAAe,CAAC;IACnC,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;AAClD,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { API } from "homebridge";
2
+ declare const _default: (api: API) => void;
3
+ export = _default;
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ const platform_1 = require("./platform");
3
+ const settings_1 = require("./settings");
4
+ module.exports = (api) => {
5
+ api.registerPlatform(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, platform_1.ProcmonPlatform);
6
+ };
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,yCAA6C;AAC7C,yCAAwD;AAExD,iBAAS,CAAC,GAAQ,EAAQ,EAAE;IAC1B,GAAG,CAAC,gBAAgB,CAAC,sBAAW,EAAE,wBAAa,EAAE,0BAAe,CAAC,CAAC;AACpE,CAAC,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { API, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig } from "homebridge";
2
+ import type { AccessoryContext, NormalizedConfig } from "./types";
3
+ export declare class ProcmonPlatform implements DynamicPlatformPlugin {
4
+ readonly log: Logger;
5
+ readonly api: API;
6
+ readonly normalizedConfig: NormalizedConfig;
7
+ private readonly accessories;
8
+ private readonly client;
9
+ private refreshTimer?;
10
+ private refreshInFlight;
11
+ constructor(log: Logger, config: PlatformConfig, api: API);
12
+ configureAccessory(accessory: PlatformAccessory<AccessoryContext>): void;
13
+ private refresh;
14
+ private syncOverallAccessory;
15
+ private syncMonitorAccessories;
16
+ private removeAccessoryIfPresent;
17
+ }
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ProcmonPlatform = void 0;
4
+ const config_1 = require("./config");
5
+ const monitor_sensor_accessory_1 = require("./accessories/monitor-sensor-accessory");
6
+ const overall_sensor_accessory_1 = require("./accessories/overall-sensor-accessory");
7
+ const procmon_client_1 = require("./procmon-client");
8
+ const settings_1 = require("./settings");
9
+ const OVERALL_ACCESSORY_ID = "overall";
10
+ class ProcmonPlatform {
11
+ log;
12
+ api;
13
+ normalizedConfig;
14
+ accessories = new Map();
15
+ client;
16
+ refreshTimer;
17
+ refreshInFlight = false;
18
+ constructor(log, config, api) {
19
+ this.log = log;
20
+ this.api = api;
21
+ this.normalizedConfig = (0, config_1.normalizeConfig)(config, this.log);
22
+ this.client = new procmon_client_1.ProcmonClient(this.normalizedConfig.endpoint, this.normalizedConfig.requestTimeoutMs, this.log);
23
+ this.api.on("didFinishLaunching", () => {
24
+ this.log.info(`starting ${settings_1.PLUGIN_NAME} against ${this.normalizedConfig.endpoint}`);
25
+ void this.refresh();
26
+ this.refreshTimer = setInterval(() => {
27
+ void this.refresh();
28
+ }, this.normalizedConfig.pollIntervalSeconds * 1000);
29
+ });
30
+ this.api.on("shutdown", () => {
31
+ if (this.refreshTimer) {
32
+ clearInterval(this.refreshTimer);
33
+ }
34
+ });
35
+ }
36
+ configureAccessory(accessory) {
37
+ this.accessories.set(accessory.UUID, accessory);
38
+ }
39
+ async refresh() {
40
+ if (this.refreshInFlight) {
41
+ return;
42
+ }
43
+ this.refreshInFlight = true;
44
+ try {
45
+ const snapshot = await this.client.fetchStatus();
46
+ this.syncOverallAccessory(snapshot);
47
+ this.syncMonitorAccessories(snapshot, (0, config_1.selectVisibleMonitors)(snapshot, this.normalizedConfig));
48
+ }
49
+ catch (error) {
50
+ this.log.warn(`procmon refresh failed: ${String(error)}`);
51
+ }
52
+ finally {
53
+ this.refreshInFlight = false;
54
+ }
55
+ }
56
+ syncOverallAccessory(snapshot) {
57
+ const uuid = this.api.hap.uuid.generate(`${settings_1.PLUGIN_NAME}:${OVERALL_ACCESSORY_ID}`);
58
+ if (!this.normalizedConfig.exposeOverallSensor) {
59
+ this.removeAccessoryIfPresent(uuid);
60
+ return;
61
+ }
62
+ const existing = this.accessories.get(uuid);
63
+ if (existing) {
64
+ new overall_sensor_accessory_1.OverallSensorAccessory(this, existing, snapshot);
65
+ this.api.updatePlatformAccessories([existing]);
66
+ return;
67
+ }
68
+ const accessory = new this.api.platformAccessory((0, config_1.overallDisplayName)(this.normalizedConfig.name), uuid);
69
+ new overall_sensor_accessory_1.OverallSensorAccessory(this, accessory, snapshot);
70
+ this.accessories.set(uuid, accessory);
71
+ this.api.registerPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
72
+ }
73
+ syncMonitorAccessories(snapshot, monitors) {
74
+ const liveUUIDs = new Set();
75
+ if (this.normalizedConfig.exposeOverallSensor) {
76
+ liveUUIDs.add(this.api.hap.uuid.generate(`${settings_1.PLUGIN_NAME}:${OVERALL_ACCESSORY_ID}`));
77
+ }
78
+ for (const monitor of monitors) {
79
+ const uuid = this.api.hap.uuid.generate(`${settings_1.PLUGIN_NAME}:${monitor.id}`);
80
+ liveUUIDs.add(uuid);
81
+ const existing = this.accessories.get(uuid);
82
+ if (existing) {
83
+ new monitor_sensor_accessory_1.MonitorSensorAccessory(this, existing, monitor);
84
+ this.api.updatePlatformAccessories([existing]);
85
+ continue;
86
+ }
87
+ const accessory = new this.api.platformAccessory((0, config_1.monitorDisplayName)(monitor), uuid);
88
+ new monitor_sensor_accessory_1.MonitorSensorAccessory(this, accessory, monitor);
89
+ this.accessories.set(uuid, accessory);
90
+ this.api.registerPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
91
+ }
92
+ for (const [uuid, accessory] of this.accessories.entries()) {
93
+ if (liveUUIDs.has(uuid)) {
94
+ continue;
95
+ }
96
+ if (accessory.context.kind === "monitor" || accessory.context.kind === "overall") {
97
+ this.accessories.delete(uuid);
98
+ this.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
99
+ }
100
+ }
101
+ this.log.debug(`procmon sync complete: overall=${snapshot.overall_status} exposed_monitors=${monitors.length}`);
102
+ }
103
+ removeAccessoryIfPresent(uuid) {
104
+ const accessory = this.accessories.get(uuid);
105
+ if (!accessory) {
106
+ return;
107
+ }
108
+ this.accessories.delete(uuid);
109
+ this.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
110
+ }
111
+ }
112
+ exports.ProcmonPlatform = ProcmonPlatform;
113
+ //# sourceMappingURL=platform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":";;;AAQA,qCAA0G;AAC1G,qFAAgF;AAChF,qFAAgF;AAChF,qDAAiD;AACjD,yCAAwD;AAGxD,MAAM,oBAAoB,GAAG,SAAS,CAAC;AAEvC,MAAa,eAAe;IASR;IAEA;IAVF,gBAAgB,CAAmB;IAElC,WAAW,GAAG,IAAI,GAAG,EAA+C,CAAC;IACrE,MAAM,CAAgB;IAC/B,YAAY,CAAkB;IAC9B,eAAe,GAAG,KAAK,CAAC;IAEhC,YACkB,GAAW,EAC3B,MAAsB,EACN,GAAQ;QAFR,QAAG,GAAH,GAAG,CAAQ;QAEX,QAAG,GAAH,GAAG,CAAK;QAExB,IAAI,CAAC,gBAAgB,GAAG,IAAA,wBAAe,EAAC,MAA+B,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;QACnF,IAAI,CAAC,MAAM,GAAG,IAAI,8BAAa,CAC7B,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAC9B,IAAI,CAAC,gBAAgB,CAAC,gBAAgB,EACtC,IAAI,CAAC,GAAG,CACT,CAAC;QAEF,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,oBAAoB,EAAE,GAAG,EAAE;YACrC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,sBAAW,YAAY,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CAAC,CAAC;YACnF,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC;YACpB,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC,GAAG,EAAE;gBACnC,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC;YACtB,CAAC,EAAE,IAAI,CAAC,gBAAgB,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC;QACvD,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,UAAU,EAAE,GAAG,EAAE;YAC3B,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;gBACtB,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACnC,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,kBAAkB,CAAC,SAA8C;QAC/D,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IAClD,CAAC;IAEO,KAAK,CAAC,OAAO;QACnB,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACzB,OAAO;QACT,CAAC;QAED,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;QAC5B,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;YACjD,IAAI,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;YACpC,IAAI,CAAC,sBAAsB,CAAC,QAAQ,EAAE,IAAA,8BAAqB,EAAC,QAAQ,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC;QAChG,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,2BAA2B,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC5D,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC;QAC/B,CAAC;IACH,CAAC;IAEO,oBAAoB,CAAC,QAAuB;QAClD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,sBAAW,IAAI,oBAAoB,EAAE,CAAC,CAAC;QAElF,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,mBAAmB,EAAE,CAAC;YAC/C,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,CAAC;YACpC,OAAO;QACT,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC5C,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,iDAAsB,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;YACrD,IAAI,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;YAC/C,OAAO;QACT,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,iBAAiB,CAC9C,IAAA,2BAAkB,EAAC,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAC9C,IAAI,CACL,CAAC;QACF,IAAI,iDAAsB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;QACtD,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACtC,IAAI,CAAC,GAAG,CAAC,2BAA2B,CAAC,sBAAW,EAAE,wBAAa,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;IAChF,CAAC;IAEO,sBAAsB,CAAC,QAAuB,EAAE,QAA+B;QACrF,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;QACpC,IAAI,IAAI,CAAC,gBAAgB,CAAC,mBAAmB,EAAE,CAAC;YAC9C,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,sBAAW,IAAI,oBAAoB,EAAE,CAAC,CAAC,CAAC;QACtF,CAAC;QAED,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,sBAAW,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;YACxE,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACpB,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAE5C,IAAI,QAAQ,EAAE,CAAC;gBACb,IAAI,iDAAsB,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;gBACpD,IAAI,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAC/C,SAAS;YACX,CAAC;YAED,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,iBAAiB,CAC9C,IAAA,2BAAkB,EAAC,OAAO,CAAC,EAC3B,IAAI,CACL,CAAC;YACF,IAAI,iDAAsB,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;YACrD,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;YACtC,IAAI,CAAC,GAAG,CAAC,2BAA2B,CAAC,sBAAW,EAAE,wBAAa,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;QAChF,CAAC;QAED,KAAK,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,EAAE,CAAC;YAC3D,IAAI,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBACxB,SAAS;YACX,CAAC;YACD,IAAI,SAAS,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,SAAS,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBACjF,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBAC9B,IAAI,CAAC,GAAG,CAAC,6BAA6B,CAAC,sBAAW,EAAE,wBAAa,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;YAClF,CAAC;QACH,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,KAAK,CACZ,kCAAkC,QAAQ,CAAC,cAAc,qBAAqB,QAAQ,CAAC,MAAM,EAAE,CAChG,CAAC;IACJ,CAAC;IAEO,wBAAwB,CAAC,IAAY;QAC3C,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC7C,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO;QACT,CAAC;QACD,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC9B,IAAI,CAAC,GAAG,CAAC,6BAA6B,CAAC,sBAAW,EAAE,wBAAa,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;IAClF,CAAC;CACF;AAjID,0CAiIC"}
@@ -0,0 +1,9 @@
1
+ import type { Logger } from "homebridge";
2
+ import type { ProcmonStatus } from "./types";
3
+ export declare class ProcmonClient {
4
+ private readonly endpoint;
5
+ private readonly timeoutMs;
6
+ private readonly log;
7
+ constructor(endpoint: string, timeoutMs: number, log: Logger);
8
+ fetchStatus(): Promise<ProcmonStatus>;
9
+ }
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ProcmonClient = void 0;
4
+ class ProcmonClient {
5
+ endpoint;
6
+ timeoutMs;
7
+ log;
8
+ constructor(endpoint, timeoutMs, log) {
9
+ this.endpoint = endpoint;
10
+ this.timeoutMs = timeoutMs;
11
+ this.log = log;
12
+ }
13
+ async fetchStatus() {
14
+ const controller = new AbortController();
15
+ const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
16
+ try {
17
+ const response = await fetch(`${this.endpoint}/status`, {
18
+ headers: {
19
+ Accept: "application/json",
20
+ },
21
+ signal: controller.signal,
22
+ });
23
+ if (!response.ok) {
24
+ throw new Error(`GET /status failed with ${response.status} ${response.statusText}`);
25
+ }
26
+ const payload = await response.json();
27
+ payload.monitors ??= [];
28
+ return payload;
29
+ }
30
+ catch (error) {
31
+ this.log.debug(`procmon request failed: ${String(error)}`);
32
+ throw error;
33
+ }
34
+ finally {
35
+ clearTimeout(timeout);
36
+ }
37
+ }
38
+ }
39
+ exports.ProcmonClient = ProcmonClient;
40
+ //# sourceMappingURL=procmon-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"procmon-client.js","sourceRoot":"","sources":["../src/procmon-client.ts"],"names":[],"mappings":";;;AAIA,MAAa,aAAa;IAEL;IACA;IACA;IAHnB,YACmB,QAAgB,EAChB,SAAiB,EACjB,GAAW;QAFX,aAAQ,GAAR,QAAQ,CAAQ;QAChB,cAAS,GAAT,SAAS,CAAQ;QACjB,QAAG,GAAH,GAAG,CAAQ;IAC3B,CAAC;IAEJ,KAAK,CAAC,WAAW;QACf,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAErE,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,QAAQ,SAAS,EAAE;gBACtD,OAAO,EAAE;oBACP,MAAM,EAAE,kBAAkB;iBAC3B;gBACD,MAAM,EAAE,UAAU,CAAC,MAAM;aAC1B,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,IAAI,KAAK,CAAC,2BAA2B,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;YACvF,CAAC;YAED,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAmB,CAAC;YACvD,OAAO,CAAC,QAAQ,KAAK,EAAE,CAAC;YACxB,OAAO,OAAO,CAAC;QACjB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,2BAA2B,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC3D,MAAM,KAAK,CAAC;QACd,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;CACF;AAjCD,sCAiCC"}
@@ -0,0 +1,2 @@
1
+ export declare const PLUGIN_NAME = "homebridge-rpi-procmon";
2
+ export declare const PLATFORM_NAME = "RpiProcmon";
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PLATFORM_NAME = exports.PLUGIN_NAME = void 0;
4
+ exports.PLUGIN_NAME = "homebridge-rpi-procmon";
5
+ exports.PLATFORM_NAME = "RpiProcmon";
6
+ //# sourceMappingURL=settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.js","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":";;;AAAa,QAAA,WAAW,GAAG,wBAAwB,CAAC;AACvC,QAAA,aAAa,GAAG,YAAY,CAAC"}
@@ -0,0 +1,62 @@
1
+ import type { PlatformConfig } from "homebridge";
2
+ export interface ProcmonPlatformConfig extends PlatformConfig {
3
+ endpoint?: string;
4
+ pollIntervalSeconds?: number;
5
+ requestTimeoutMs?: number;
6
+ exposeOverallSensor?: boolean;
7
+ exposeDisabledMonitors?: boolean;
8
+ hideHealthyMonitors?: boolean;
9
+ includeMonitors?: string[];
10
+ excludeMonitors?: string[];
11
+ }
12
+ export interface NormalizedConfig {
13
+ name: string;
14
+ endpoint: string;
15
+ pollIntervalSeconds: number;
16
+ requestTimeoutMs: number;
17
+ exposeOverallSensor: boolean;
18
+ exposeDisabledMonitors: boolean;
19
+ hideHealthyMonitors: boolean;
20
+ includeMonitors: Set<string>;
21
+ excludeMonitors: Set<string>;
22
+ }
23
+ export interface ProcmonStatus {
24
+ app_version: string;
25
+ overall_status: string;
26
+ monitor_count: number;
27
+ healthy_count: number;
28
+ degraded_count: number;
29
+ recovering_count: number;
30
+ failed_count: number;
31
+ disabled_count: number;
32
+ last_updated_at: string;
33
+ monitors: ProcmonMonitorState[];
34
+ }
35
+ export interface ProcmonMonitorState {
36
+ id: string;
37
+ name: string;
38
+ type: string;
39
+ enabled: boolean;
40
+ status: string;
41
+ metadata?: Record<string, string>;
42
+ configured_checks?: ProcmonCheck[];
43
+ last_error?: string;
44
+ last_failure_reasons?: string[];
45
+ next_check_at?: string;
46
+ }
47
+ export interface ProcmonCheck {
48
+ id: string;
49
+ type: string;
50
+ container?: string;
51
+ command?: string;
52
+ }
53
+ export type MonitorRole = "service" | "container" | "plugin" | "monitor";
54
+ export type AccessoryContext = {
55
+ kind: "overall";
56
+ label?: string;
57
+ } | {
58
+ kind: "monitor";
59
+ monitorId: string;
60
+ role?: MonitorRole;
61
+ label?: string;
62
+ };
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "homebridge-rpi-procmon",
3
+ "displayName": "Homebridge Rpi Procmon",
4
+ "version": "0.1.1",
5
+ "description": "Homebridge dynamic platform plugin for exposing rpi-procmon monitor health in Apple Home.",
6
+ "license": "MIT",
7
+ "author": "nbeathoven",
8
+ "homepage": "https://github.com/nbeathoven/homebridge-rpi-procmon#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/nbeathoven/homebridge-rpi-procmon.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/nbeathoven/homebridge-rpi-procmon/issues"
15
+ },
16
+ "keywords": [
17
+ "homebridge-plugin",
18
+ "homebridge",
19
+ "homekit",
20
+ "raspberry-pi",
21
+ "procmon",
22
+ "monitoring"
23
+ ],
24
+ "main": "dist/index.js",
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "config.schema.json",
31
+ "README.md",
32
+ "LICENSE"
33
+ ],
34
+ "engines": {
35
+ "node": "^18.15.0 || ^20.7.0 || ^22 || ^24",
36
+ "homebridge": "^1.11.2 || ^2.0.0-alpha.0"
37
+ },
38
+ "peerDependencies": {
39
+ "homebridge": "^1.11.2 || ^2.0.0-alpha.0"
40
+ },
41
+ "devDependencies": {
42
+ "@types/node": "^22.13.14",
43
+ "@typescript-eslint/eslint-plugin": "^8.28.0",
44
+ "@typescript-eslint/parser": "^8.28.0",
45
+ "eslint": "^9.23.0",
46
+ "homebridge": "^1.11.2",
47
+ "typescript": "^5.8.2"
48
+ },
49
+ "scripts": {
50
+ "build": "tsc -p tsconfig.json",
51
+ "clean": "rm -rf dist",
52
+ "lint": "eslint \"src/**/*.ts\"",
53
+ "check": "npm run lint && npm run build",
54
+ "prepare": "npm run build",
55
+ "prepublishOnly": "npm run check"
56
+ },
57
+ "homebridge": {
58
+ "platforms": [
59
+ {
60
+ "platform": "RpiProcmon",
61
+ "name": "Rpi Procmon"
62
+ }
63
+ ]
64
+ }
65
+ }