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.
- package/CHANGELOG.md +338 -0
- package/LICENSE +18 -0
- package/README.md +129 -0
- package/RELEASE_NOTES.md +257 -0
- package/config.schema.json +131 -0
- package/dist/errors.d.ts +35 -0
- package/dist/errors.js +57 -0
- package/dist/errors.js.map +1 -0
- package/dist/hubCapabilities.d.ts +21 -0
- package/dist/hubCapabilities.js +78 -0
- package/dist/hubCapabilities.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/platform.d.ts +113 -0
- package/dist/platform.js +797 -0
- package/dist/platform.js.map +1 -0
- package/dist/platformAccessory.d.ts +52 -0
- package/dist/platformAccessory.js +410 -0
- package/dist/platformAccessory.js.map +1 -0
- package/dist/powerviewHub.d.ts +133 -0
- package/dist/powerviewHub.js +459 -0
- package/dist/powerviewHub.js.map +1 -0
- package/dist/settings.d.ts +102 -0
- package/dist/settings.js +75 -0
- package/dist/settings.js.map +1 -0
- package/dist/shadeUtils.d.ts +138 -0
- package/dist/shadeUtils.js +292 -0
- package/dist/shadeUtils.js.map +1 -0
- package/package.json +58 -0
package/RELEASE_NOTES.md
ADDED
|
@@ -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
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -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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"}
|
package/dist/index.d.ts
ADDED
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"}
|