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.
Files changed (64) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/DEVELOPMENT.md +65 -0
  3. package/LICENSE +202 -0
  4. package/README.md +251 -0
  5. package/SECURITY.md +43 -0
  6. package/config.schema.json +135 -0
  7. package/dist/api/client.d.ts +131 -0
  8. package/dist/api/client.js +226 -0
  9. package/dist/api/discovery.d.ts +136 -0
  10. package/dist/api/discovery.js +402 -0
  11. package/dist/api/http.d.ts +52 -0
  12. package/dist/api/http.js +136 -0
  13. package/dist/api/identity.d.ts +73 -0
  14. package/dist/api/identity.js +120 -0
  15. package/dist/api/index.d.ts +14 -0
  16. package/dist/api/index.js +30 -0
  17. package/dist/api/sync-status.d.ts +50 -0
  18. package/dist/api/sync-status.js +191 -0
  19. package/dist/api/xml.d.ts +76 -0
  20. package/dist/api/xml.js +365 -0
  21. package/dist/devices/base-accessory.d.ts +131 -0
  22. package/dist/devices/base-accessory.js +236 -0
  23. package/dist/devices/battery-accessory.d.ts +28 -0
  24. package/dist/devices/battery-accessory.js +85 -0
  25. package/dist/devices/host.d.ts +45 -0
  26. package/dist/devices/host.js +14 -0
  27. package/dist/devices/index.d.ts +14 -0
  28. package/dist/devices/index.js +30 -0
  29. package/dist/devices/mute-accessory.d.ts +35 -0
  30. package/dist/devices/mute-accessory.js +71 -0
  31. package/dist/devices/volume-accessory.d.ts +66 -0
  32. package/dist/devices/volume-accessory.js +218 -0
  33. package/dist/devices/volume-preset-accessory.d.ts +32 -0
  34. package/dist/devices/volume-preset-accessory.js +89 -0
  35. package/dist/index.d.ts +15 -0
  36. package/dist/index.js +19 -0
  37. package/dist/platform.d.ts +124 -0
  38. package/dist/platform.js +489 -0
  39. package/dist/poller.d.ts +109 -0
  40. package/dist/poller.js +300 -0
  41. package/dist/settings.d.ts +184 -0
  42. package/dist/settings.js +210 -0
  43. package/dist/types/index.d.ts +218 -0
  44. package/dist/types/index.js +38 -0
  45. package/dist/ui-api.d.ts +20 -0
  46. package/dist/ui-api.js +32 -0
  47. package/dist/utils/context.d.ts +18 -0
  48. package/dist/utils/context.js +56 -0
  49. package/dist/utils/errors.d.ts +37 -0
  50. package/dist/utils/errors.js +92 -0
  51. package/dist/utils/index.d.ts +13 -0
  52. package/dist/utils/index.js +29 -0
  53. package/dist/utils/serial.d.ts +22 -0
  54. package/dist/utils/serial.js +37 -0
  55. package/dist/utils/timing.d.ts +52 -0
  56. package/dist/utils/timing.js +74 -0
  57. package/dist/utils/validators.d.ts +99 -0
  58. package/dist/utils/validators.js +461 -0
  59. package/docs/FEATURES.md +91 -0
  60. package/docs/PROTOCOL.md +194 -0
  61. package/homebridge-ui/public/index.html +87 -0
  62. package/homebridge-ui/public/index.js +475 -0
  63. package/homebridge-ui/server.js +189 -0
  64. 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
+ [![Tests](https://github.com/tbaur/homebridge-bluos/actions/workflows/test.yml/badge.svg)](https://github.com/tbaur/homebridge-bluos/actions/workflows/test.yml) [![npm version](https://img.shields.io/npm/v/homebridge-bluos?style=flat-square)](https://www.npmjs.com/package/homebridge-bluos) [![npm downloads](https://img.shields.io/npm/dt/homebridge-bluos?label=downloads&style=flat-square)](https://www.npmjs.com/package/homebridge-bluos) [![Node.js](https://img.shields.io/badge/node-%3E%3D20-green)](https://nodejs.org) [![Homebridge](https://img.shields.io/badge/homebridge-%3E%3D1.6.0%20%7C%7C%202.x-purple)](https://homebridge.io) [![License](https://img.shields.io/badge/license-Apache--2.0-blue)](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