homebridge-bluos 0.1.0
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 +7 -0
- package/DEVELOPMENT.md +65 -0
- package/LICENSE +202 -0
- package/README.md +251 -0
- package/SECURITY.md +43 -0
- package/config.schema.json +135 -0
- package/dist/api/client.d.ts +131 -0
- package/dist/api/client.js +226 -0
- package/dist/api/discovery.d.ts +136 -0
- package/dist/api/discovery.js +402 -0
- package/dist/api/http.d.ts +52 -0
- package/dist/api/http.js +136 -0
- package/dist/api/identity.d.ts +73 -0
- package/dist/api/identity.js +120 -0
- package/dist/api/index.d.ts +14 -0
- package/dist/api/index.js +30 -0
- package/dist/api/sync-status.d.ts +50 -0
- package/dist/api/sync-status.js +191 -0
- package/dist/api/xml.d.ts +76 -0
- package/dist/api/xml.js +365 -0
- package/dist/devices/base-accessory.d.ts +131 -0
- package/dist/devices/base-accessory.js +236 -0
- package/dist/devices/battery-accessory.d.ts +28 -0
- package/dist/devices/battery-accessory.js +85 -0
- package/dist/devices/host.d.ts +45 -0
- package/dist/devices/host.js +14 -0
- package/dist/devices/index.d.ts +14 -0
- package/dist/devices/index.js +30 -0
- package/dist/devices/mute-accessory.d.ts +35 -0
- package/dist/devices/mute-accessory.js +71 -0
- package/dist/devices/volume-accessory.d.ts +66 -0
- package/dist/devices/volume-accessory.js +218 -0
- package/dist/devices/volume-preset-accessory.d.ts +32 -0
- package/dist/devices/volume-preset-accessory.js +89 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +19 -0
- package/dist/platform.d.ts +124 -0
- package/dist/platform.js +489 -0
- package/dist/poller.d.ts +109 -0
- package/dist/poller.js +300 -0
- package/dist/settings.d.ts +184 -0
- package/dist/settings.js +210 -0
- package/dist/types/index.d.ts +218 -0
- package/dist/types/index.js +38 -0
- package/dist/ui-api.d.ts +20 -0
- package/dist/ui-api.js +32 -0
- package/dist/utils/context.d.ts +18 -0
- package/dist/utils/context.js +56 -0
- package/dist/utils/errors.d.ts +37 -0
- package/dist/utils/errors.js +92 -0
- package/dist/utils/index.d.ts +13 -0
- package/dist/utils/index.js +29 -0
- package/dist/utils/serial.d.ts +22 -0
- package/dist/utils/serial.js +37 -0
- package/dist/utils/timing.d.ts +52 -0
- package/dist/utils/timing.js +74 -0
- package/dist/utils/validators.d.ts +99 -0
- package/dist/utils/validators.js +461 -0
- package/docs/FEATURES.md +91 -0
- package/docs/PROTOCOL.md +194 -0
- package/homebridge-ui/public/index.html +87 -0
- package/homebridge-ui/public/index.js +475 -0
- package/homebridge-ui/server.js +189 -0
- package/package.json +91 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file. This file is maintained automatically by [release-please](https://github.com/googleapis/release-please) based on [Conventional Commits](https://www.conventionalcommits.org).
|
|
4
|
+
|
|
5
|
+
## Unreleased
|
|
6
|
+
|
|
7
|
+
* initial BluOS plugin
|
package/DEVELOPMENT.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Development
|
|
2
|
+
|
|
3
|
+
HomeKit talks to the platform. The platform owns accessory identity and starts one poller per zone. Each poller long-polls through the client; the client is the only place that talks to a player. Accessories render the last observation as HAP.
|
|
4
|
+
|
|
5
|
+
```mermaid
|
|
6
|
+
flowchart LR
|
|
7
|
+
HomeKit --> platform
|
|
8
|
+
platform --> poller
|
|
9
|
+
poller --> client
|
|
10
|
+
client --> player["BluOS player"]
|
|
11
|
+
ui["Settings page"] --> uiApi["ui-api"]
|
|
12
|
+
uiApi --> client
|
|
13
|
+
uiApi --> discovery
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`src/api/` is the wire. `src/devices/` is HomeKit. `src/platform.ts` is the join. Protocol facts live in [docs/PROTOCOL.md](docs/PROTOCOL.md), not here.
|
|
17
|
+
|
|
18
|
+
`dist/` is committed so a git install works. CI fails if it drifts from `src/`. Run `npm run build` and commit `dist/` with every source change. There is no `prepare` script: it would dirty the tree on every `npm install`. `prepublishOnly` still rebuilds.
|
|
19
|
+
|
|
20
|
+
## Invariants
|
|
21
|
+
|
|
22
|
+
Break these and you break someone's rooms, or you guess the protocol.
|
|
23
|
+
|
|
24
|
+
- **Identity is `MAC:port:kind` (plus preset level), never the address.** A DHCP change must not mint new accessories. Adopt a cached accessory that matches identity; do not replace it. Changing a preset's level *is* a new accessory.
|
|
25
|
+
- **Bad config disables the platform. It does not unregister anything.** Rooms and automations survive a typo. One bad `devices[]` entry is skipped; only a wholly unusable config is fatal.
|
|
26
|
+
- **Unknown is No Response**, not zero. Until a real `/SyncStatus` arrives, and again when the player stops answering, characteristics report `SERVICE_COMMUNICATION_FAILURE`.
|
|
27
|
+
- **Mute is inferred from `muteVolume` / `muteDb`.** `/SyncStatus` never sends `mute`. `volume="0" db="-100"` is the same for mute and for level zero. See PROTOCOL.md.
|
|
28
|
+
- **`tell_slaves` is decided in one place** (`BaseAccessory.writeScope`): a group leader carries the group, everyone else writes locally. Do not special-case it per accessory.
|
|
29
|
+
- **Rate rules live in `api/client.ts`.** One second between same-resource calls, 100 ms between control calls, writes serialised per chassis. Call through the client; do not open your own HTTP.
|
|
30
|
+
- **Hardware wins the spec.** Record the measurement in PROTOCOL.md and pin a fixture. Never commit a raw capture — [scripts/README.md](scripts/README.md) is the pseudonymise path, and `tests/unit/fixtures.test.ts` allowlists every address and MAC in the tree.
|
|
31
|
+
|
|
32
|
+
A capability ships if HomeKit can express it as a tile, a scene, an automation or a spoken command. A library, a queue and artwork cannot, so they stay in the BluOS app.
|
|
33
|
+
|
|
34
|
+
## Commands
|
|
35
|
+
|
|
36
|
+
Node 20+, matching `engines`. CI runs 20 / 22 / 24, a Homebridge 1.6 floor job, and a runtime `npm audit`.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install
|
|
40
|
+
npm run build # commit dist/ with the source
|
|
41
|
+
npm run lint # warnings are failures
|
|
42
|
+
npx tsc --noEmit -p tsconfig.test.json # src + tests
|
|
43
|
+
npm test # jest with coverage (NODE_ENV=test)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The suite never touches the network. Before a release, against real players (read-only):
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm run build
|
|
50
|
+
node scripts/smoke.js
|
|
51
|
+
node scripts/grouping.js
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Tests inject fakes. `tests/helpers/hap.ts` is the HAP stand-in. `api/http.ts` is the one module tested against a loopback socket. Coverage includes `homebridge-ui/` and is gated at 80%. Fake timers, not real sleeps. Module mocks are plain classes behind a getter — `resetMocks` strips `mockImplementation` on a factory.
|
|
55
|
+
|
|
56
|
+
## Adding a capability
|
|
57
|
+
|
|
58
|
+
1. Verify the endpoint on a real player. If it disagrees with the spec, write it down in PROTOCOL.md first.
|
|
59
|
+
2. Parse in `src/api/sync-status.ts`, call through `BluOSClient`.
|
|
60
|
+
3. Add a `BaseAccessory` subclass under `src/devices/`.
|
|
61
|
+
4. Extend `config.schema.json`, the config types, and `validateConfig` / `resolveAccessories`. Constrain the schema to what the plugin will accept. `required` must be an array of property names on the object (draft-07); a boolean on a field fails Homebridge verification CI.
|
|
62
|
+
5. Wire `attachHandler` in `platform.ts`. Extend the identity key only if the new accessory needs more than `kind`.
|
|
63
|
+
6. Tests for the parser, the client method, the accessory and the config path. A new response shape needs a recorded fixture.
|
|
64
|
+
7. Update PROTOCOL.md and [docs/FEATURES.md](docs/FEATURES.md).
|
|
65
|
+
8. `npm run build` and commit `dist/`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# homebridge-bluos
|
|
2
|
+
|
|
3
|
+
[](https://github.com/tbaur/homebridge-bluos/actions/workflows/test.yml) [](https://www.npmjs.com/package/homebridge-bluos) [](https://www.npmjs.com/package/homebridge-bluos) [](https://nodejs.org) [](https://homebridge.io) [](LICENSE)
|
|
4
|
+
|
|
5
|
+
**BluOS players in Apple HomeKit, over your LAN.** No cloud, no accounts, no polling loops hammering your speakers. Verified against NAD and Bluesound hardware; other BluOS brands (DALI, Monitor Audio, Roksan) run the same firmware and are expected to work, but are untested here.
|
|
6
|
+
|
|
7
|
+
The goal is a complete integration: everything about a BluOS player that HomeKit can express well, built one accessory at a time and verified against real hardware before it ships. Volume, mute, volume presets and battery are what exist today — the first set, not the ceiling. See the [roadmap](#roadmap) for what comes next.
|
|
8
|
+
|
|
9
|
+
What stays in the BluOS app is the part HomeKit has no vocabulary for: browsing, search, building a queue, artwork, and setting a player up in the first place. HomeKit has no way to render a library and no way to say "play the third album by that artist", so anything this plugin exposed there would be a worse version of an app you already have. Where HomeKit is genuinely better is the tile, the scene, the automation and the spoken command, and that is the line this plugin draws.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
### Accessories today, per zone
|
|
14
|
+
|
|
15
|
+
- **Volume slider** — 0–100, the same scale the BluOS app uses. Exposed as a fan by default (see [why](#why-is-my-volume-a-fan))
|
|
16
|
+
- **Mute switch** — On when the player is muted; unmuting restores the level the player remembered, not a guess
|
|
17
|
+
- **Grouping-aware** — A zone that is leading a group moves the whole group, exactly as its slider does in the BluOS app. Every other zone moves alone
|
|
18
|
+
- **Volume preset switches** — One switch per level, e.g. "Study Evening" at 15. Addressable by name with Siri, and far safer than a slider inside an automation
|
|
19
|
+
- **Battery sensor** — Charge level, charging state and low-battery warning for players with a battery pack fitted (PULSE FLEX with BP100, PULSE M)
|
|
20
|
+
- **Multi-zone chassis** — Each zone of a NAD CI-S2 or CI 580 is a separate player with its own accessories, discovered on its own port
|
|
21
|
+
- **Accessory Information** — Brand and model as the player reports them, the plugin version as firmware revision, and an opaque, stable serial number
|
|
22
|
+
|
|
23
|
+
### Reliability
|
|
24
|
+
|
|
25
|
+
- **Discovery in the settings page** — Finds zones over mDNS and writes the configuration for you; manual address entry for networks where multicast is filtered
|
|
26
|
+
- **Long-polling, not polling** — `/SyncStatus?timeout=100` with an `etag`, so a change made on the front panel, the remote or the BluOS app reaches HomeKit in about a second, without hammering the player
|
|
27
|
+
- **Follows the API's rate rules** — One second minimum between two requests for the same resource, per the BluOS specification; writes to one chassis are serialised, and separate chassis stay parallel
|
|
28
|
+
- **Survives a DHCP lease change** — Accessory identity is the player's MAC and zone port, never its address. If a player moves, the plugin re-resolves it and remembers the new address
|
|
29
|
+
- **Backs off when a player is off** — Exponential delay to a one-minute ceiling instead of dialling an absent player every second; the first failure warns, the rest go to debug
|
|
30
|
+
- **Answers HomeKit promptly** — A write returns inside HAP's patience window and finishes anything slower in the background, where the result still reaches HomeKit through the next poll
|
|
31
|
+
- **No volume jumps** — The pair of writes HomeKit sends when a slider leaves zero is coalesced into one command
|
|
32
|
+
- **Honest state** — An accessory reports No Response until the player has actually been read, and again once it stops answering, rather than showing a value it cannot confirm
|
|
33
|
+
- **Never loses your rooms** — A broken configuration disables the platform and leaves every accessory registered and showing No Response. Accessories are adopted by identity, never replaced
|
|
34
|
+
|
|
35
|
+
### Quality
|
|
36
|
+
|
|
37
|
+
- **Strict TypeScript** — `strict`, plus `noUncheckedIndexedAccess` and type-aware lint
|
|
38
|
+
- **Tested** — A behavioural Jest suite over 95% of statements, including XML fixtures recorded from real hardware (NAD C658, CI-S2, Bluesound P430, portable player) and the settings page that writes your configuration
|
|
39
|
+
- **CI** — Build, lint (warnings are failures), type-check and test on Node 20/22/24, a job against the oldest supported Homebridge, committed-`dist` drift check, dependency audit and OSV scanning
|
|
40
|
+
- **No analytics** — No tracking, no cloud, no accounts
|
|
41
|
+
|
|
42
|
+
## Quick Start
|
|
43
|
+
|
|
44
|
+
### 1. Install
|
|
45
|
+
|
|
46
|
+
**Homebridge UI** (recommended): Plugins → Search `homebridge-bluos` → Install
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install -g homebridge-bluos
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 2. Prepare your players
|
|
53
|
+
|
|
54
|
+
1. Give each player a static IP or a DHCP reservation. Not required — the plugin re-resolves addresses — but it makes logs easier to read
|
|
55
|
+
2. Nothing needs enabling on the player: the BluOS LAN API is always on
|
|
56
|
+
|
|
57
|
+
### 3. Configure
|
|
58
|
+
|
|
59
|
+
**Homebridge UI** (recommended): open the plugin settings and press **Discover Players**. Every zone that answers is listed with a suggested set of accessories; tick what you want and save.
|
|
60
|
+
|
|
61
|
+
Or in `config.json`:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"platforms": [
|
|
66
|
+
{
|
|
67
|
+
"platform": "BluOS",
|
|
68
|
+
"name": "BluOS",
|
|
69
|
+
"devices": [
|
|
70
|
+
{
|
|
71
|
+
"id": "90:56:82:0A:00:01:11000",
|
|
72
|
+
"name": "Study",
|
|
73
|
+
"host": "192.168.4.10",
|
|
74
|
+
"port": 11000,
|
|
75
|
+
"volumeSlider": true,
|
|
76
|
+
"mute": true,
|
|
77
|
+
"volumePresets": [
|
|
78
|
+
{ "name": "Study Quiet", "volume": 15 },
|
|
79
|
+
{ "name": "Study Loud", "volume": 70 }
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"id": "90:56:82:0A:00:02:11010",
|
|
84
|
+
"name": "Library",
|
|
85
|
+
"host": "192.168.4.11",
|
|
86
|
+
"port": 11010,
|
|
87
|
+
"volumeSlider": true
|
|
88
|
+
}
|
|
89
|
+
],
|
|
90
|
+
"options": {
|
|
91
|
+
"sliderService": "fan",
|
|
92
|
+
"discoveryTimeoutSec": 5
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 4. Restart Homebridge
|
|
100
|
+
|
|
101
|
+
Accessories appear in the Home app after restart, showing No Response for the second or two before the first read completes.
|
|
102
|
+
|
|
103
|
+
### Example: Apple Shortcuts
|
|
104
|
+
|
|
105
|
+
A "settle in" shortcut, using preset switches rather than the slider so the levels are exact:
|
|
106
|
+
|
|
107
|
+
1. Turn **Study Quiet** On
|
|
108
|
+
2. Turn **Library Quiet** On
|
|
109
|
+
3. Start your playlist in the BluOS app or with AirPlay
|
|
110
|
+
|
|
111
|
+
For "quiet, now", one **Mute** switch is faster than any slider.
|
|
112
|
+
|
|
113
|
+
### Example logs
|
|
114
|
+
|
|
115
|
+
Startup:
|
|
116
|
+
|
|
117
|
+
```text
|
|
118
|
+
[BluOS] Initializing BluOS platform
|
|
119
|
+
[BluOS] adding Study Volume
|
|
120
|
+
[BluOS] adding Study Mute
|
|
121
|
+
[BluOS] adding Study Quiet
|
|
122
|
+
[BluOS] adding Library Volume
|
|
123
|
+
[BluOS] BluOS is watching 2 zone(s) with 4 accessory(s)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
A player that moved to a new address:
|
|
127
|
+
|
|
128
|
+
```text
|
|
129
|
+
[BluOS] Library Volume [90:56:82:0A:00:02:11010] is not responding: connect ETIMEDOUT 192.168.4.11:11010
|
|
130
|
+
[BluOS] Library moved to 192.168.4.23:11010
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The identity in brackets is the player's `id`, so two rooms that happen to share a name are still tellable apart.
|
|
134
|
+
|
|
135
|
+
A HomeKit write that reached the player:
|
|
136
|
+
|
|
137
|
+
```text
|
|
138
|
+
[BluOS] Study Volume: SET 35
|
|
139
|
+
[BluOS] Study Mute: ON
|
|
140
|
+
[BluOS] Study Quiet: SET 15
|
|
141
|
+
[BluOS] Study Volume: SET 40 (group)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Mute `ON` means muted. `(group)` means that zone is leading a BluOS group, so the write carried the followers.
|
|
145
|
+
|
|
146
|
+
A configuration the plugin will not act on:
|
|
147
|
+
|
|
148
|
+
```text
|
|
149
|
+
[BluOS] devices[0] has no usable id (re-run discovery in the plugin settings); skipping devices[0]
|
|
150
|
+
[BluOS] all 1 configured device(s) were rejected; see the warnings above
|
|
151
|
+
[BluOS] BluOS is disabled until its configuration is fixed. Cached accessories are kept and will show as No Response, so rooms and automations are not lost.
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The platform only disables itself when *every* player was rejected. One bad entry among several is skipped with the first line and nothing else changes.
|
|
155
|
+
|
|
156
|
+
## Supported Devices
|
|
157
|
+
|
|
158
|
+
Any BluOS player running the Custom Integration API, which is all of them from BluOS 3.x onwards.
|
|
159
|
+
|
|
160
|
+
| Verified against | Notes |
|
|
161
|
+
|---|---|
|
|
162
|
+
| **NAD C658** | Streamer/preamp, firmware 4.16.6 |
|
|
163
|
+
| **NAD CI-S2** | Two independent zones on one chassis, ports 11000 and 11010 |
|
|
164
|
+
| **Bluesound P430** | Soundbar |
|
|
165
|
+
| **Bluesound PULSE-class portable** | Battery pack reporting |
|
|
166
|
+
|
|
167
|
+
Multi-zone chassis (CI-S2, CI 580) are supported by treating each zone as its own player. Fixed-output players are detected and get no slider, because writing a level to them does nothing.
|
|
168
|
+
|
|
169
|
+
## Roadmap
|
|
170
|
+
|
|
171
|
+
Ordered by how well HomeKit expresses the thing, not by how easy it is to build. Each one ships only after it has been verified against real hardware, with the protocol behaviour recorded in [docs/PROTOCOL.md](docs/PROTOCOL.md).
|
|
172
|
+
|
|
173
|
+
**Next** — group scenes (one switch that forms or breaks a named group, which the BluOS app can do by hand but cannot put into an automation), a chime (`/Doorbell`, so a HomeKit doorbell or door sensor can sound on your speakers), transport (play and pause as a switch a scene or a spoken command can drive, with skip and back), and station preset switches (recall a saved BluOS preset by name, addressable by Siri).
|
|
174
|
+
|
|
175
|
+
**Likely** — input switches for the physical inputs on players that have them, a playback sensor so "when music starts here" can trigger other accessories, relative volume nudges in dB for physical-button automations, and shuffle and repeat.
|
|
176
|
+
|
|
177
|
+
**Needs verification first** — a sleep timer. `/Status` reports the minutes remaining, but v1.7 documents no way to set it, so the endpoint has to be confirmed against hardware before anything is built on it.
|
|
178
|
+
|
|
179
|
+
**Being weighed** — a single media tile per player using HomeKit's `SmartSpeaker` or `Television` service instead of separate switches. It reads better in the Home app when it works, but its rendering varies by iOS version in ways that are worth confirming before anyone's rooms depend on it.
|
|
180
|
+
|
|
181
|
+
**Not planned** — browsing, search, queue editing, artwork and now-playing metadata, and player setup. HomeKit has no way to render a library or a queue, and no vocabulary for "play the third album by that artist". The BluOS app does these properly and this plugin will not pretend to.
|
|
182
|
+
|
|
183
|
+
## Configuration Options
|
|
184
|
+
|
|
185
|
+
Only one `BluOS` platform block is supported (`singular` in the schema); it can hold as many players as you like.
|
|
186
|
+
|
|
187
|
+
| Option | Required | Description |
|
|
188
|
+
|---|:-:|---|
|
|
189
|
+
| `name` | ✓ (UI) | Plugin instance name in the Homebridge log. Homebridge itself falls back to the platform alias, `BluOS`, if a hand-edited `config.json` omits it |
|
|
190
|
+
| `devices` | ✓ | List of players. An empty list is allowed and warns; a missing or non-list value is an error |
|
|
191
|
+
| `options.sliderService` | | `fan` (default) or `lightbulb`, for every slider |
|
|
192
|
+
| `options.discoveryTimeoutSec` | | mDNS listening window, 1–30 seconds (default 5) |
|
|
193
|
+
|
|
194
|
+
### `devices[]` entries
|
|
195
|
+
|
|
196
|
+
| Field | Required | Description |
|
|
197
|
+
|---|:-:|---|
|
|
198
|
+
| `id` | ✓ | Stable identity, normally `MAC:port`. Written by discovery. **Changing it detaches the accessories from their HomeKit rooms and automations** |
|
|
199
|
+
| `name` | ✓ | The room's name. Every accessory is named from it: `Study Volume`, `Study Mute`, `Study Battery`, and each preset's own name |
|
|
200
|
+
| `host` | ✓ | IP address or hostname |
|
|
201
|
+
| `port` | | Control port (default 11000). Extra zones of a multi-zone chassis use 11010, 11020, 11030 |
|
|
202
|
+
| `volumeSlider` | | Expose the slider (default true) |
|
|
203
|
+
| `sliderService` | | Override the platform slider style for this player. Empty means "use the platform setting". Changing it removes the old control from the accessory rather than leaving both |
|
|
204
|
+
| `mute` | | Expose a mute switch (default false) |
|
|
205
|
+
| `battery` | | Expose a battery sensor (default false; only meaningful with a battery pack) |
|
|
206
|
+
| `volumePresets[]` | | `{ "name": "...", "volume": 0-100 }`. Duplicate levels on one player are skipped with a warning |
|
|
207
|
+
|
|
208
|
+
An accessory's identity is the player's `id` plus its kind (and, for a preset, its level). It deliberately does **not** include the address, so changing `host` or `port` keeps your existing accessories intact. Renaming a player is applied in place. Changing a preset's `volume`, however, creates a **new** accessory and removes the old one, which loses its room assignment, scenes and automations — rename freely, but change preset levels only when you are ready to re-add them in the Home app.
|
|
209
|
+
|
|
210
|
+
Serial numbers in Accessory Information are opaque values generated once per accessory and kept in the Homebridge accessory cache. Clearing that cache issues new ones.
|
|
211
|
+
|
|
212
|
+
## Not Working?
|
|
213
|
+
|
|
214
|
+
1. **Nothing found by Discover Players?** mDNS is often filtered across VLANs and by some access points. Use the manual address entry, or add the player by hand in `config.json`
|
|
215
|
+
2. **Everything shows No Response right after a restart** — normal until the first read completes; the plugin reports unknown state rather than guessing
|
|
216
|
+
3. **Everything shows No Response and stays that way** — check the log for `BluOS is disabled until its configuration is fixed`. The plugin stays inert, without deleting anything, until the reported problem is fixed
|
|
217
|
+
4. **A player has no slider** — it reports a fixed output level (`volume="-1"`), so a level cannot be written to it. The log says so once
|
|
218
|
+
5. **The mute switch does not follow volume zero** — by design. Muting and setting level zero are different things to BluOS, and only mute remembers the level to come back to
|
|
219
|
+
6. **A zone on a multi-zone chassis is missing** — check the port. Zone two is 11010, not 11000
|
|
220
|
+
7. **The volume moved but the slider did not, for a second** — a change made on the player takes one long-poll round trip to arrive; a change made from HomeKit is immediate
|
|
221
|
+
8. **One zone's slider moved several rooms** — that zone is currently leading a BluOS group, so it carries its followers, the same as its slider in the BluOS app. Ungroup in the BluOS app and it goes back to moving alone
|
|
222
|
+
9. **100 is louder than you ever want** — set the limit on the player, in the BluOS app's settings for it (the wording varies by model: a volume limit on Bluesound players, a maximum volume on NAD amplifiers). The plugin deliberately has no ceiling of its own: the player maps 0–100 onto whatever range it is configured for and clamps anything above it, so a limit set there is enforced by the hardware for every controller — no HomeKit automation or misheard Siri phrase can exceed it, and a second limit here could only disagree with the first
|
|
223
|
+
10. Restart Homebridge after editing `config.json` by hand
|
|
224
|
+
|
|
225
|
+
### Why is my volume a fan?
|
|
226
|
+
|
|
227
|
+
HomeKit has no speaker volume characteristic that the Home app renders, so every plugin borrows another accessory type. This one uses a **fan** and its rotation speed: it looks like a slider, and it is not swept up by "Hey Siri, turn off all the lights" — which, with a lightbulb, would silence your speakers, or worse, set them to maximum. A lightbulb is available if you prefer it.
|
|
228
|
+
|
|
229
|
+
## Security
|
|
230
|
+
|
|
231
|
+
The plugin talks only to the addresses in your configuration, on your LAN. No cloud, no accounts, no credentials — which also means the BluOS API has no authentication: anyone who can reach a player on your network can already control it, so secure the network rather than the plugin. Responses are parsed by a size-, depth- and element-capped XML reader rather than a general-purpose parser, everything interpolated into a log line is sanitised, and manual probe targets are validated before a request is made. See [`SECURITY.md`](SECURITY.md).
|
|
232
|
+
|
|
233
|
+
## Requirements
|
|
234
|
+
|
|
235
|
+
- Homebridge 1.6.0+ or 2.0+
|
|
236
|
+
- Node.js 20+ (Homebridge 2.x itself requires Node 22+, so the Node 20 floor applies to Homebridge 1.x hosts)
|
|
237
|
+
- One or more BluOS players reachable on the same network
|
|
238
|
+
|
|
239
|
+
## More Info
|
|
240
|
+
|
|
241
|
+
- [Features](https://github.com/tbaur/homebridge-bluos/blob/main/docs/FEATURES.md)
|
|
242
|
+
- [Protocol reference](https://github.com/tbaur/homebridge-bluos/blob/main/docs/PROTOCOL.md) — hardware-verified BluOS behaviour
|
|
243
|
+
- [Development](https://github.com/tbaur/homebridge-bluos/blob/main/DEVELOPMENT.md)
|
|
244
|
+
- [Report Issues](https://github.com/tbaur/homebridge-bluos/issues)
|
|
245
|
+
- [Changelog](https://github.com/tbaur/homebridge-bluos/blob/main/CHANGELOG.md)
|
|
246
|
+
|
|
247
|
+
## License
|
|
248
|
+
|
|
249
|
+
Copyright 2026 tbaur
|
|
250
|
+
|
|
251
|
+
Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) file for details.
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
| Version | Supported |
|
|
6
|
+
| ------- | ------------------ |
|
|
7
|
+
| 0.x.x | ✅ Active support |
|
|
8
|
+
|
|
9
|
+
## Reporting a Vulnerability
|
|
10
|
+
|
|
11
|
+
Do not open a public issue. Use GitHub's [private vulnerability reporting](https://github.com/tbaur/homebridge-bluos/security/advisories/new), and include a description, how to reproduce it, and the impact.
|
|
12
|
+
|
|
13
|
+
## Security Measures
|
|
14
|
+
|
|
15
|
+
- **LAN only** — Plain HTTP to the addresses in your configuration. The BluOS API has no authentication; this plugin stores no credentials.
|
|
16
|
+
- **Input validation** — Config is checked at startup. A missing or non-list `devices` value disables the platform without unregistering accessories. A bad player or preset is skipped; hosts, ports and timeouts are rejected or clamped.
|
|
17
|
+
- **Log safety** — Values written to logs have control characters stripped and are length-limited.
|
|
18
|
+
- **Bounded I/O** — Connect and total timeouts on every request; responses capped at 128 KiB; XML parsed with size, depth and element caps.
|
|
19
|
+
- **Discovery** — mDNS errors are caught so they cannot take Homebridge down. Browse results are capped.
|
|
20
|
+
- **Settings probe** — Accepts only a private or local address and a documented BluOS port.
|
|
21
|
+
- **Dependencies** — CI runs `npm audit` on the runtime tree and OSV-Scanner on the full tree.
|
|
22
|
+
|
|
23
|
+
## Best Practices for Users
|
|
24
|
+
|
|
25
|
+
1. Keep Homebridge and this plugin updated.
|
|
26
|
+
2. Run Homebridge with minimal privileges, and do not expose it or player port 11000 to the internet.
|
|
27
|
+
3. The BluOS LAN API is unauthenticated — anyone who can reach a player can already control it.
|
|
28
|
+
4. Set volume limits in the BluOS app. The plugin has none of its own, so a limit there covers every controller.
|
|
29
|
+
5. A group leader's volume change applies to the whole group.
|
|
30
|
+
|
|
31
|
+
## Configuration Handling
|
|
32
|
+
|
|
33
|
+
Homebridge stores platform config in plain text on the host. Debug logs include request URLs (player addresses), not secrets. HomeKit serial numbers are opaque generated values, not MAC addresses.
|
|
34
|
+
|
|
35
|
+
## Response Timeline
|
|
36
|
+
|
|
37
|
+
- **Acknowledgment**: Within 48 hours
|
|
38
|
+
- **Initial assessment**: Within 1 week
|
|
39
|
+
- **Fix timeline**: Depends on severity
|
|
40
|
+
- Critical: 24-48 hours
|
|
41
|
+
- High: 1 week
|
|
42
|
+
- Medium: 2 weeks
|
|
43
|
+
- Low: Next release
|