homebridge-powerview-universal 4.3.2

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.
@@ -0,0 +1,257 @@
1
+ # Release notes
2
+
3
+ User-facing notes for **homebridge-powerview-universal** releases. Use this file when publishing to npm or creating a [GitHub Release](https://github.com/sunnierseattle/homebridge-powerview-universal/releases).
4
+
5
+ ## How to publish a version
6
+
7
+ 1. Bump `version` in [package.json](package.json).
8
+ 2. Add a new **Latest** section below (copy the template).
9
+ 3. Record the same changes in [CHANGELOG.md](CHANGELOG.md) (developer-oriented, can be more detailed).
10
+ 4. Run `npm test`, `npm run build`, then publish or tag.
11
+ 5. Copy the **Latest** section into the GitHub Release description (title: `vX.Y.Z`).
12
+
13
+ ### Section template
14
+
15
+ ```markdown
16
+ ## Latest: X.Y.Z (YYYY-MM-DD)
17
+
18
+ ### Highlights
19
+
20
+ - One-line summary for installers (1–3 bullets).
21
+
22
+ ### Added
23
+
24
+ - …
25
+
26
+ ### Changed
27
+
28
+ - …
29
+
30
+ ### Fixed
31
+
32
+ - …
33
+
34
+ ### Notes
35
+
36
+ - Upgrade / config / HomeKit caveats if any.
37
+ ```
38
+
39
+ ---
40
+
41
+ ## Latest: 4.3.1 (2026-09-04)
42
+
43
+ First release published to npm.
44
+
45
+ ### Fixed
46
+
47
+ - Activating a scene logged `(0 shade(s))` even when it moved several. The count is now shown only
48
+ when the hub reports one.
49
+
50
+ ### Notes
51
+
52
+ - Scene activation has now been confirmed end to end against a live scene on a gen1 hub, tapped
53
+ from the Home app — the 4.3.0 caveat about it being untested no longer applies.
54
+
55
+ ---
56
+
57
+ ## 4.3.0 (2026-09-04)
58
+
59
+ ### Highlights
60
+
61
+ - **Scenes, for shades that move together.** Any scene you define in the PowerView app now appears
62
+ as a HomeKit switch. Activating it is one command to the hub, which moves the whole group at
63
+ once — the same way a Pebble remote does, and something the plugin could not do by commanding
64
+ shades one at a time.
65
+ - **The Home app stops claiming a shade has arrived before it has.** Position tiles now animate
66
+ while the shade travels, showing opening or closing, instead of jumping to the destination the
67
+ moment you tap.
68
+ - **Homebridge starts much faster.** The startup position sync no longer holds up launch; one
69
+ observed startup took 82 seconds to become ready.
70
+
71
+ ### Added
72
+
73
+ - `exposeScenes` (default on). Nothing appears unless you have scenes defined.
74
+
75
+ ### Notes
76
+
77
+ - Scene activation follows the documented PowerView API but has not been tested against a live
78
+ scene. If you define one and it misbehaves, that is the reason.
79
+
80
+ ---
81
+
82
+ ## 4.2.0 (2026-09-04)
83
+
84
+ ### Highlights
85
+
86
+ - **The Home app feels faster.** Position reads are answered instantly from the last known
87
+ position and refreshed in the background, instead of waiting on the hub. This removes the
88
+ "plugin slows down Homebridge / read handler was slow to respond" warnings, which appeared 15
89
+ times in a single startup on a five-shade system.
90
+ - **Reads never wake a shade motor.** The background refresh behind a read was using the RF
91
+ round-trip that physically spins the shade; it now uses the cheap cached read, and the RF path
92
+ is reserved for when the hub genuinely has no position.
93
+
94
+ ---
95
+
96
+ ## 4.1.2 (2026-09-04)
97
+
98
+ ### Highlights
99
+
100
+ - **Shades can no longer vanish from HomeKit.** If the hub answered the shade-list request with a
101
+ short list — which it does under load — the plugin removed every missing shade from HomeKit
102
+ outright, losing its room and any automations using it. A shade now has to be missing from
103
+ three consecutive responses before it is removed, and an empty response never removes anything.
104
+
105
+ ### Notes
106
+
107
+ - If shades disappeared on an earlier version, restart Homebridge and they will be re-added. You
108
+ may need to put them back in the right room in the Home app.
109
+
110
+ ---
111
+
112
+ ## 4.1.1 (2026-09-04)
113
+
114
+ Fixes a regression in 4.1.0.
115
+
116
+ ### Highlights
117
+
118
+ - **Shades no longer drop out of a group move.** 4.1.0 tightened the gap between hub requests to
119
+ 25ms, which a gen1 hub cannot take while its radio is transmitting — it drops the connection,
120
+ and the command was then lost rather than retried. Setting five shades at once could move only
121
+ three. The spacing is back to 100ms, and a dropped connection is now retried.
122
+
123
+ ### Notes
124
+
125
+ - `requestIntervalMs` remains configurable. If you retune it, test it with shades actually
126
+ moving; cached reads will not reveal the problem.
127
+
128
+ ---
129
+
130
+ ## 4.1.0 (2026-09-04)
131
+
132
+ Responsiveness work on top of 4.0.0.
133
+
134
+ ### Highlights
135
+
136
+ - **Shades react faster to a tap.** Commands from the Home app now jump ahead of background
137
+ refreshes instead of queuing behind them, and the delay between hub requests drops from 100ms
138
+ to 25ms — about 375ms off closing five shades.
139
+
140
+ ### Added
141
+
142
+ - `requestIntervalMs` (default `25`) — raise it if your hub misbehaves under load.
143
+
144
+ ### Notes
145
+
146
+ - This does **not** make shades move in unison. The hub takes one command per shade, so five
147
+ shades means five round-trips and roughly 350ms of unavoidable spread. A remote looks
148
+ synchronised because it broadcasts to all the motors at once over RF. Matching that through the
149
+ hub needs scene support, which is not implemented yet.
150
+
151
+ ---
152
+
153
+ ## 4.0.0 (2026-09-04)
154
+
155
+ First release under the name **homebridge-powerview-universal**. A major version
156
+ because the package is renamed — this does not upgrade in place.
157
+
158
+ ### Highlights
159
+
160
+ - **Your shades stop waking up at night.** Battery polling was an RF round-trip that spins the
161
+ motor, ran every 6 hours from plugin start, and had no off switch — so a poll always landed
162
+ overnight. It is now off by default, and opt-in polling runs once a day at a time you choose.
163
+ - **Positions are correct on a cold start.** Last known positions survive a restart, so shades no
164
+ longer show as "fully closed" in the Home app while the first refresh is still in flight.
165
+ - **The Home app stops timing out.** Position reads answer instantly from cache and refresh in the
166
+ background, instead of blocking on a hub round-trip that HomeKit gives up on.
167
+ - **Battery percentages above 90% are real numbers now**, and 20 more shade types are driven from
168
+ the capabilities the hub reports rather than assumed to be rollers.
169
+
170
+ ### Added
171
+
172
+ - `batteryPolling` (default `false`) and `batteryPollAt` (default `14:00`) — opt in to a daily
173
+ battery measurement at a time that suits you.
174
+ - `syncPositionsOnStart` (default `true`) and `quietHours` (default 21:00–08:00) — re-sync unknown
175
+ positions at startup, but never inside the quiet window.
176
+ - An ISC `LICENSE` file.
177
+
178
+ ### Changed
179
+
180
+ - Shade behaviour comes from the hub's `capabilities` field, falling back to the documented
181
+ 26-type table. Types the plugin cannot yet drive correctly now warn once per shade instead of
182
+ silently reporting a wrong position or tilt.
183
+ - Logs are much quieter: positions are logged only when they change, and the "invalid position"
184
+ and low-battery warnings no longer fire on the happy path.
185
+
186
+ ### Fixed
187
+
188
+ - **Tilting a blind no longer makes it report as fully closed.** Whenever the hub sent a tilt
189
+ value, the shade's position was overwritten with 0 in the same update.
190
+ - **Restarting Homebridge no longer shows every shade as fully closed** until the first refresh
191
+ lands.
192
+ - **Hold** now stops a moving shade instead of jogging it.
193
+ - Battery percentage is read as pack voltage, not as a percentage — previously anything above 100
194
+ collapsed to one of four values, so 67% and 98% both showed as 90%.
195
+ - Hub requests time out after 15s instead of hanging forever, and every request is fully
196
+ serialised — including reading the response body, which previously still overlapped the next
197
+ request and left the hub returning truncated JSON.
198
+ - A poll now costs half the hub requests it used to.
199
+ - Shades that the hub has never polled no longer raise a false low-battery warning.
200
+
201
+ ### Notes
202
+
203
+ - Requires Homebridge **1.8+** or **2.x** and Node **22** or **24**.
204
+ - **Uninstall `homebridge-powerview-3` before installing this package.** Homebridge migrates your
205
+ cached shades to the new name automatically, but only if one plugin claims the `PowerView`
206
+ platform name — with both installed, the cached accessories are dropped and your shades come
207
+ back as new tiles.
208
+ - Your `config.json` needs no changes; all new options have defaults.
209
+ - PowerView **scenes** are probed at startup but still not exposed as HomeKit accessories.
210
+
211
+ ---
212
+
213
+ ## 3.1.2 (2026-05-18)
214
+
215
+ ### Highlights
216
+
217
+ - Battery percentage now appears in the Home app via a linked HomeKit Battery service on each shade (when the hub reports battery data).
218
+ - More resilient hub communication: retries when the hub is busy, cached positions on refresh timeouts, and optional `strictErrors` for debugging.
219
+
220
+ ### Added
221
+
222
+ - Linked **Battery** service per shade (`BatteryLevel` 0–100%) from hub `batteryStrength` / `batteryStatus`.
223
+ - Hub HTTP **423** retry, startup capability probes, position **cache** for failed reads/refreshes.
224
+ - Platform option **`strictErrors`** (default `false`).
225
+ - Per-shade **firmware** in Accessory Information; **low battery** warning; TDBU top-rail name from `secondaryName`.
226
+ - **Hold** and **Identify** trigger shade **jog** where the hub supports it.
227
+ - Periodic battery refresh (`updateBatteryLevel`, about every 6 hours).
228
+
229
+ ### Changed
230
+
231
+ - Shade list failures during poll no longer remove existing accessories.
232
+ - `putShade` omits invalid hub position kind **4** (error) from merged updates.
233
+
234
+ ### Notes
235
+
236
+ - Requires Homebridge **1.8+** or **2.x** and Node **22** or **24**.
237
+ - PowerView **scenes** are probed at startup but not exposed as HomeKit accessories yet.
238
+ - After upgrading, restart Homebridge; remove duplicate shade tiles in Home if you migrated from an older plugin id.
239
+
240
+ ---
241
+
242
+ ## 3.1.1 (2026-05-18)
243
+
244
+ - Linked HomeKit Battery service per shade (battery % in Home app).
245
+
246
+ ## 3.1.0 (2026-05-18)
247
+
248
+ - Hub resilience, `strictErrors`, position cache, battery/firmware metadata, jog via Hold/Identify, unit tests.
249
+
250
+ ## 3.0.0 (2026-05-17)
251
+
252
+ - **Breaking:** Package renamed to `homebridge-powerview-3`; Homebridge 2.x / Node 22–24; TypeScript rewrite.
253
+ - See [CHANGELOG.md](CHANGELOG.md) for migration from homebridge-powerview-2.
254
+
255
+ ## Earlier versions
256
+
257
+ See [CHANGELOG.md](CHANGELOG.md) and [homebridge-powerview-2](https://github.com/owenselles/homebridge-powerview-2).
@@ -0,0 +1,131 @@
1
+ {
2
+ "pluginAlias": "PowerView",
3
+ "pluginType": "platform",
4
+ "singular": true,
5
+ "strictValidation": false,
6
+ "schema": {
7
+ "type": "object",
8
+ "required": [
9
+ "name"
10
+ ],
11
+ "additionalProperties": false,
12
+ "properties": {
13
+ "name": {
14
+ "title": "Name",
15
+ "type": "string",
16
+ "default": "PowerView"
17
+ },
18
+ "host": {
19
+ "title": "Hub host",
20
+ "type": "string",
21
+ "description": "Hostname or IP of the PowerView hub.",
22
+ "placeholder": "powerview-hub.local"
23
+ },
24
+ "refreshShades": {
25
+ "title": "Refresh on read",
26
+ "type": "boolean",
27
+ "default": false,
28
+ "description": "Request fresh shade positions from the hub on every HomeKit read."
29
+ },
30
+ "pollShadesForUpdate": {
31
+ "title": "Poll every 30 seconds",
32
+ "type": "boolean",
33
+ "default": false,
34
+ "description": "Periodically poll the hub for shade position updates."
35
+ },
36
+ "strictErrors": {
37
+ "title": "Strict errors",
38
+ "type": "boolean",
39
+ "default": false,
40
+ "description": "When enabled, HomeKit reads fail on hub errors instead of returning the last known position."
41
+ },
42
+ "batteryPolling": {
43
+ "title": "Daily battery poll",
44
+ "type": "boolean",
45
+ "default": false,
46
+ "description": "Off by default. When enabled, refreshes shade battery levels once a day. Each poll wakes every shade motor over RF: it is audible and nudges the shade, so do not schedule it near sleeping hours. Battery level still reaches HomeKit from ordinary hub reads without this."
47
+ },
48
+ "batteryPollAt": {
49
+ "title": "Battery poll time",
50
+ "type": "string",
51
+ "default": "14:00",
52
+ "pattern": "^([01]?[0-9]|2[0-3]):[0-5][0-9]$",
53
+ "placeholder": "14:00",
54
+ "description": "Local time of day (HH:MM) for the daily battery poll.",
55
+ "condition": {
56
+ "functionBody": "return model.batteryPolling === true;"
57
+ }
58
+ },
59
+ "syncPositionsOnStart": {
60
+ "title": "Sync positions at startup",
61
+ "type": "boolean",
62
+ "default": true,
63
+ "description": "Re-read shade positions over RF once when the plugin starts, for shades the hub has no position for. Wakes those motors briefly. Without it, a shade moved by remote can show a stale position indefinitely."
64
+ },
65
+ "quietHours": {
66
+ "title": "Quiet hours",
67
+ "type": "object",
68
+ "description": "Local-time window in which the plugin will not actuate hardware. Applies to the startup position sync, so an unattended overnight restart stays silent. Set both to the same hour to disable.",
69
+ "properties": {
70
+ "start": {
71
+ "title": "Quiet from (hour)",
72
+ "type": "integer",
73
+ "default": 21,
74
+ "minimum": 0,
75
+ "maximum": 23
76
+ },
77
+ "end": {
78
+ "title": "Quiet until (hour)",
79
+ "type": "integer",
80
+ "default": 8,
81
+ "minimum": 0,
82
+ "maximum": 23
83
+ }
84
+ }
85
+ },
86
+ "forceRollerShades": {
87
+ "title": "Force roller shade IDs",
88
+ "type": "array",
89
+ "items": {
90
+ "type": "integer"
91
+ },
92
+ "description": "Treat these shade IDs as roller shades regardless of hub type."
93
+ },
94
+ "forceTopBottomShades": {
95
+ "title": "Force top/bottom shade IDs",
96
+ "type": "array",
97
+ "items": {
98
+ "type": "integer"
99
+ }
100
+ },
101
+ "forceHorizontalShades": {
102
+ "title": "Force horizontal vane shade IDs",
103
+ "type": "array",
104
+ "items": {
105
+ "type": "integer"
106
+ }
107
+ },
108
+ "forceVerticalShades": {
109
+ "title": "Force vertical vane shade IDs",
110
+ "type": "array",
111
+ "items": {
112
+ "type": "integer"
113
+ }
114
+ },
115
+ "requestIntervalMs": {
116
+ "title": "Hub request spacing (ms)",
117
+ "type": "integer",
118
+ "default": 25,
119
+ "minimum": 0,
120
+ "maximum": 1000,
121
+ "description": "Delay between requests to the hub. The hub answers one at a time, and this spacing sits on top of that. Lower feels faster; raise it if your hub returns errors or truncated responses under load."
122
+ },
123
+ "exposeScenes": {
124
+ "title": "Expose hub scenes as switches",
125
+ "type": "boolean",
126
+ "default": true,
127
+ "description": "Publish each PowerView scene as a HomeKit switch. Activating one is a single hub call that moves the whole group together, which individual shade commands cannot match. Nothing appears if you have no scenes defined in the PowerView app."
128
+ }
129
+ }
130
+ }
131
+ }
@@ -0,0 +1,35 @@
1
+ import type { Logging } from 'homebridge';
2
+ import type { HubUserData } from './powerviewHub.js';
3
+ export declare enum HubErrorCode {
4
+ Unreachable = "Unreachable",
5
+ Timeout = "Timeout",
6
+ HttpError = "HttpError",
7
+ Maintenance = "Maintenance",
8
+ NotFound = "NotFound",
9
+ BadRequest = "BadRequest",
10
+ EmptyBody = "EmptyBody"
11
+ }
12
+ export declare class HubError extends Error {
13
+ readonly code: HubErrorCode;
14
+ readonly status?: number | undefined;
15
+ readonly userData?: HubUserData | undefined;
16
+ readonly name = "HubError";
17
+ constructor(message: string, code: HubErrorCode, status?: number | undefined, userData?: HubUserData | undefined, options?: {
18
+ cause?: unknown;
19
+ });
20
+ }
21
+ export declare function isHubError(error: unknown): error is HubError;
22
+ export declare function isRetryableHubError(error: unknown): boolean;
23
+ /**
24
+ * Formats an unknown thrown value for log output (message and stack when available).
25
+ */
26
+ export declare function formatError(error: unknown): string;
27
+ /**
28
+ * Logs an error with a message prefix using Homebridge's logging API.
29
+ */
30
+ export declare function logError(log: Logging, message: string, error: unknown): void;
31
+ /**
32
+ * Registers process-level handlers so async/sync failures are logged instead of
33
+ * crashing the child bridge with no context.
34
+ */
35
+ export declare function registerProcessErrorHandlers(log: Logging, pluginName: string): void;
package/dist/errors.js ADDED
@@ -0,0 +1,57 @@
1
+ export var HubErrorCode;
2
+ (function (HubErrorCode) {
3
+ HubErrorCode["Unreachable"] = "Unreachable";
4
+ HubErrorCode["Timeout"] = "Timeout";
5
+ HubErrorCode["HttpError"] = "HttpError";
6
+ HubErrorCode["Maintenance"] = "Maintenance";
7
+ HubErrorCode["NotFound"] = "NotFound";
8
+ HubErrorCode["BadRequest"] = "BadRequest";
9
+ HubErrorCode["EmptyBody"] = "EmptyBody";
10
+ })(HubErrorCode || (HubErrorCode = {}));
11
+ export class HubError extends Error {
12
+ code;
13
+ status;
14
+ userData;
15
+ name = 'HubError';
16
+ constructor(message, code, status, userData, options) {
17
+ super(message, options);
18
+ this.code = code;
19
+ this.status = status;
20
+ this.userData = userData;
21
+ }
22
+ }
23
+ export function isHubError(error) {
24
+ return error instanceof HubError;
25
+ }
26
+ export function isRetryableHubError(error) {
27
+ return isHubError(error) && error.code === HubErrorCode.Maintenance;
28
+ }
29
+ /**
30
+ * Formats an unknown thrown value for log output (message and stack when available).
31
+ */
32
+ export function formatError(error) {
33
+ if (error instanceof Error) {
34
+ return error.stack ?? error.message;
35
+ }
36
+ return String(error);
37
+ }
38
+ /**
39
+ * Logs an error with a message prefix using Homebridge's logging API.
40
+ */
41
+ export function logError(log, message, error) {
42
+ log.error(message, formatError(error));
43
+ }
44
+ /**
45
+ * Registers process-level handlers so async/sync failures are logged instead of
46
+ * crashing the child bridge with no context.
47
+ */
48
+ export function registerProcessErrorHandlers(log, pluginName) {
49
+ const label = pluginName;
50
+ process.on('unhandledRejection', (reason) => {
51
+ log.error(`[${label}] Unhandled promise rejection:`, formatError(reason));
52
+ });
53
+ process.on('uncaughtException', (error) => {
54
+ log.error(`[${label}] Uncaught exception:`, formatError(error));
55
+ });
56
+ }
57
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAIA,MAAM,CAAN,IAAY,YAQX;AARD,WAAY,YAAY;IACtB,2CAA2B,CAAA;IAC3B,mCAAmB,CAAA;IACnB,uCAAuB,CAAA;IACvB,2CAA2B,CAAA;IAC3B,qCAAqB,CAAA;IACrB,yCAAyB,CAAA;IACzB,uCAAuB,CAAA;AACzB,CAAC,EARW,YAAY,KAAZ,YAAY,QAQvB;AAED,MAAM,OAAO,QAAS,SAAQ,KAAK;IAKf;IACA;IACA;IANT,IAAI,GAAG,UAAU,CAAC;IAE3B,YACE,OAAe,EACC,IAAkB,EAClB,MAAe,EACf,QAAsB,EACtC,OAA6B;QAE7B,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QALR,SAAI,GAAJ,IAAI,CAAc;QAClB,WAAM,GAAN,MAAM,CAAS;QACf,aAAQ,GAAR,QAAQ,CAAc;IAIxC,CAAC;CACF;AAED,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,OAAO,KAAK,YAAY,QAAQ,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,KAAc;IAChD,OAAO,UAAU,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,CAAC,WAAW,CAAC;AACtE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC;IACtC,CAAC;IACD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,QAAQ,CAAC,GAAY,EAAE,OAAe,EAAE,KAAc;IACpE,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;AACzC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,4BAA4B,CAAC,GAAY,EAAE,UAAkB;IAC3E,MAAM,KAAK,GAAG,UAAU,CAAC;IAEzB,OAAO,CAAC,EAAE,CAAC,oBAAoB,EAAE,CAAC,MAAM,EAAE,EAAE;QAC1C,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,gCAAgC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;IAC5E,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,EAAE,CAAC,mBAAmB,EAAE,CAAC,KAAK,EAAE,EAAE;QACxC,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,uBAAuB,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type { Logging } from 'homebridge';
2
+ import type { PowerViewHub } from './powerviewHub.js';
3
+ export interface HubCapabilities {
4
+ fwVersionSupported: boolean;
5
+ scenesSupported: boolean;
6
+ sceneCollectionsSupported: boolean;
7
+ generationHint: 'gen1' | 'gen2' | 'unknown';
8
+ }
9
+ export declare function generationHintFromHubName(name: string | undefined): HubCapabilities['generationHint'];
10
+ /**
11
+ * Gen 1 and Gen 2 hubs both report mainProcessor.name = "PowerView Hub", so the
12
+ * name alone can never identify the generation. The firmware major revision does:
13
+ * Gen 1 reports revision 1, Gen 2 reports revision 2. Gen 1 hubs additionally
14
+ * expose no `radio` firmware block at all.
15
+ */
16
+ export declare function generationHintFromFirmware(firmware: {
17
+ mainProcessor?: {
18
+ revision?: number;
19
+ };
20
+ } | undefined): HubCapabilities['generationHint'];
21
+ export declare function probeHubCapabilities(hub: PowerViewHub, log: Logging, hubFirmwareName?: string): Promise<HubCapabilities>;
@@ -0,0 +1,78 @@
1
+ import { HubErrorCode, isHubError } from './errors.js';
2
+ export function generationHintFromHubName(name) {
3
+ if (!name) {
4
+ return 'unknown';
5
+ }
6
+ if (/hub\s*2|2\.0|gen\s*2/i.test(name)) {
7
+ return 'gen2';
8
+ }
9
+ if (/hub\s*1|1\.0|gen\s*1/i.test(name)) {
10
+ return 'gen1';
11
+ }
12
+ return 'unknown';
13
+ }
14
+ /**
15
+ * Gen 1 and Gen 2 hubs both report mainProcessor.name = "PowerView Hub", so the
16
+ * name alone can never identify the generation. The firmware major revision does:
17
+ * Gen 1 reports revision 1, Gen 2 reports revision 2. Gen 1 hubs additionally
18
+ * expose no `radio` firmware block at all.
19
+ */
20
+ export function generationHintFromFirmware(firmware) {
21
+ const revision = firmware?.mainProcessor?.revision;
22
+ if (revision === 1) {
23
+ return 'gen1';
24
+ }
25
+ if (revision === 2) {
26
+ return 'gen2';
27
+ }
28
+ return 'unknown';
29
+ }
30
+ export async function probeHubCapabilities(hub, log, hubFirmwareName) {
31
+ const capabilities = {
32
+ fwVersionSupported: false,
33
+ scenesSupported: false,
34
+ sceneCollectionsSupported: false,
35
+ generationHint: generationHintFromHubName(hubFirmwareName),
36
+ };
37
+ try {
38
+ const firmware = await hub.getFirmwareVersion();
39
+ capabilities.fwVersionSupported = true;
40
+ if (capabilities.generationHint === 'unknown') {
41
+ capabilities.generationHint = generationHintFromFirmware(firmware);
42
+ }
43
+ }
44
+ catch (err) {
45
+ if (isHubError(err) && err.code === HubErrorCode.NotFound) {
46
+ log.debug('Hub does not expose GET /api/fwversion');
47
+ }
48
+ else {
49
+ log.warn('Could not probe /api/fwversion:', err instanceof Error ? err.message : String(err));
50
+ }
51
+ }
52
+ try {
53
+ await hub.getScenes();
54
+ capabilities.scenesSupported = true;
55
+ }
56
+ catch (err) {
57
+ if (isHubError(err) && err.code === HubErrorCode.NotFound) {
58
+ log.debug('Hub does not expose GET /api/scenes');
59
+ }
60
+ else {
61
+ log.warn('Could not probe /api/scenes:', err instanceof Error ? err.message : String(err));
62
+ }
63
+ }
64
+ try {
65
+ await hub.getSceneCollections();
66
+ capabilities.sceneCollectionsSupported = true;
67
+ }
68
+ catch (err) {
69
+ if (isHubError(err) && err.code === HubErrorCode.NotFound) {
70
+ log.debug('Hub does not expose GET /api/scenecollections');
71
+ }
72
+ else {
73
+ log.warn('Could not probe /api/scenecollections:', err instanceof Error ? err.message : String(err));
74
+ }
75
+ }
76
+ return capabilities;
77
+ }
78
+ //# sourceMappingURL=hubCapabilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hubCapabilities.js","sourceRoot":"","sources":["../src/hubCapabilities.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { API } from 'homebridge';
2
+ /**
3
+ * Registers the PowerView platform with Homebridge.
4
+ */
5
+ declare const _default: (api: API) => void;
6
+ export default _default;
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ import { PowerViewPlatform } from './platform.js';
2
+ import { PLATFORM_NAME } from './settings.js';
3
+ /**
4
+ * Registers the PowerView platform with Homebridge.
5
+ */
6
+ export default (api) => {
7
+ api.registerPlatform(PLATFORM_NAME, PowerViewPlatform);
8
+ };
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C;;GAEG;AACH,eAAe,CAAC,GAAQ,EAAE,EAAE;IAC1B,GAAG,CAAC,gBAAgB,CAAC,aAAa,EAAE,iBAAiB,CAAC,CAAC;AACzD,CAAC,CAAC"}