homebridge-yoto 0.0.28 → 0.0.32

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/PLAN.md DELETED
@@ -1,609 +0,0 @@
1
- # Homebridge Yoto Plugin - Implementation Plan
2
-
3
- ## Overview
4
-
5
- A Homebridge plugin that integrates Yoto audio players with Apple HomeKit, providing comprehensive control over playback, volume, display settings, and status monitoring through MQTT.
6
-
7
- ## ✅ Current Status: Phase 1 Complete + Phase 2 Enhancements
8
-
9
- **MVP Complete!** All Phase 1 features implemented and tested. Additional Phase 2 features added including display brightness control, sleep timer, and advanced control switches.
10
-
11
- ## Project Structure
12
-
13
- ```
14
- homebridge-yoto/
15
- ├── lib/
16
- │ ├── platform.js # Main YotoPlatform class
17
- │ ├── playerAccessory.js # YotoPlayerAccessory handler
18
- │ ├── yotoApi.js # Yoto REST API client wrapper
19
- │ ├── yotoMqtt.js # MQTT client for real-time updates
20
- │ ├── auth.js # OAuth2 authentication handler
21
- │ ├── types.js # JSDoc type definitions
22
- │ └── constants.js # Plugin constants and defaults
23
- ├── index.js # Plugin entry point
24
- ├── config.schema.json # Homebridge config UI schema
25
- └── package.json
26
- ```
27
-
28
- ## Phase 1: Core Foundation (MVP) ✅ COMPLETE
29
-
30
- ### 1.1 Authentication System ✅
31
-
32
- **Status:** COMPLETE
33
-
34
- **Goal:** Implement OAuth2 Device Authorization Flow for Yoto API
35
-
36
- **Files:**
37
- - `lib/auth.js`
38
-
39
- **Implementation:**
40
- - Use `POST /oauth/device/code` to initiate device flow
41
- - Display user_code and verification_uri in Homebridge logs
42
- - Poll `POST /oauth/token` until user completes authorization
43
- - Store access_token and refresh_token in platform config
44
- - Implement automatic token refresh logic
45
- - Handle token expiration gracefully
46
-
47
- **API Endpoints:**
48
- - `POST /oauth/device/code`
49
- - `POST /oauth/token` (with grant_type: 'device_code' and 'refresh_token')
50
-
51
- **Config Structure:**
52
- ```javascript
53
- {
54
- "platform": "Yoto",
55
- "name": "Yoto",
56
- "clientId": "YOUR_CLIENT_ID",
57
- "accessToken": "(stored after auth)",
58
- "refreshToken": "(stored after auth)",
59
- "tokenExpiresAt": 0
60
- }
61
- ```
62
-
63
- ### 1.2 API Client ✅
64
-
65
- **Status:** COMPLETE
66
-
67
- **Goal:** Create robust API wrapper for Yoto endpoints
68
-
69
- **Files:**
70
- - `lib/yotoApi.js` (REST endpoints)
71
- - `lib/yotoMqtt.js` (MQTT client)
72
-
73
- **REST API Features:**
74
- - Centralized fetch wrapper with auth headers
75
- - Automatic token refresh on 401 responses
76
- - Error handling and retry logic
77
- - Rate limiting protection
78
- - Request/response logging for debugging
79
-
80
- **Key REST Methods:**
81
- ```javascript
82
- class YotoApi {
83
- async getDevices() // GET /device-v2/devices/mine
84
- async getDeviceConfig(deviceId) // GET /device-v2/{deviceId}/config
85
- async updateDeviceConfig(deviceId, config) // PUT /device-v2/{deviceId}/config
86
- async getContent(cardId) // GET /content/{cardId}
87
- async getLibraryGroups() // GET /card/family/library/groups
88
- }
89
- ```
90
-
91
- **MQTT Client Features:**
92
- - Real-time device status updates via `/device/{id}/data/status`
93
- - Real-time playback events via `/device/{id}/data/events`
94
- - Command publishing to control devices
95
- - Automatic reconnection on disconnect
96
- - Per-device topic subscriptions
97
-
98
- **Key MQTT Methods:**
99
- ```javascript
100
- class YotoMqtt {
101
- async connect(accessToken)
102
- async disconnect()
103
- subscribeToDevice(deviceId, callback)
104
- unsubscribeFromDevice(deviceId)
105
-
106
- // Command methods
107
- async setVolume(deviceId, volume)
108
- async startCard(deviceId, uri, options)
109
- async pauseCard(deviceId)
110
- async resumeCard(deviceId)
111
- async stopCard(deviceId)
112
- async setSleepTimer(deviceId, seconds)
113
- async setAmbientLight(deviceId, r, g, b)
114
- async requestStatus(deviceId)
115
- async requestEvents(deviceId)
116
- }
117
- ```
118
-
119
- ### 1.3 Platform Setup ✅
120
-
121
- **Status:** COMPLETE
122
-
123
- **Goal:** Implement DynamicPlatformPlugin for device discovery
124
-
125
- **Files:**
126
- - `lib/platform.js`
127
- - `index.js`
128
-
129
- **Implementation:**
130
- - Register platform in `index.js`
131
- - Implement `configureAccessory()` to restore cached accessories
132
- - Implement `discoverDevices()` called on `didFinishLaunching`
133
- - Create Map to track accessories by deviceId
134
- - Handle device addition/removal/updates
135
- - Store device metadata in `accessory.context`
136
-
137
- **Platform Lifecycle:**
138
- 1. Constructor: Initialize API client, Service/Characteristic refs
139
- 2. configureAccessory: Restore cached accessories from disk
140
- 3. didFinishLaunching: Discover devices and register new ones
141
- 4. Device sync: Update existing, add new, remove stale accessories
142
-
143
- ### 1.4 Player Accessory (Basic) ✅
144
-
145
- **Status:** COMPLETE
146
-
147
- **Goal:** Create accessory with essential playback controls
148
-
149
- **Files:**
150
- - `lib/playerAccessory.js`
151
-
152
- **Services (Phase 1):**
153
- 1. **AccessoryInformation** (required)
154
- - Manufacturer: "Yoto"
155
- - Model: from device.deviceType
156
- - SerialNumber: device.deviceId
157
- - FirmwareRevision: device.releaseChannel
158
-
159
- 2. **SmartSpeaker** (primary service)
160
- - CurrentMediaState: PLAYING/PAUSED/STOPPED (read-only)
161
- - TargetMediaState: PLAY/PAUSE/STOP (write)
162
- - Volume: 0-100 (read/write)
163
- - Mute: boolean (read/write)
164
-
165
- 3. **Battery**
166
- - BatteryLevel: 0-100
167
- - ChargingState: NOT_CHARGING/CHARGING
168
- - StatusLowBattery: NORMAL/LOW (< 20%)
169
-
170
- **Implementation:**
171
- - Constructor: Set up services and characteristics
172
- - Implement onGet/onSet handlers for each characteristic
173
- - Subscribe to MQTT topics for real-time updates
174
- - Use `updateCharacteristic()` when MQTT messages arrive
175
- - Cache last known state for onGet handlers
176
-
177
- ### 1.5 MQTT Real-Time Updates ✅
178
-
179
- **Status:** COMPLETE
180
-
181
- **Goal:** Keep HomeKit in sync with device state using MQTT
182
-
183
- **Implementation:**
184
- - Connect to Yoto MQTT broker on platform init
185
- - Subscribe to `/device/{id}/data/status` for each device
186
- - Subscribe to `/device/{id}/data/events` for playback events
187
- - Update characteristics immediately when MQTT messages arrive
188
- - Request initial status via `/device/{id}/command/status/request` on startup
189
- - Implement reconnection logic with exponential backoff
190
- - Handle offline devices (no recent MQTT messages)
191
-
192
- **MQTT Topic Subscriptions:**
193
- ```javascript
194
- // Per device subscriptions
195
- /device/{deviceId}/data/status → Battery, volume, config state
196
- /device/{deviceId}/data/events → Playback state, current track
197
- /device/{deviceId}/response → Command confirmations
198
- ```
199
-
200
- **Status Mapping:**
201
- ```javascript
202
- // MQTT data/status → HomeKit
203
- batteryLevel → BatteryLevel
204
- charging (0/1) → ChargingState (NOT_CHARGING/CHARGING)
205
- userVolume → Volume
206
- activeCard → CurrentMediaState context
207
-
208
- // MQTT data/events → HomeKit
209
- playbackStatus ("playing"/"paused"/"stopped") → CurrentMediaState
210
- volume → Volume (real-time)
211
- cardId → Active content tracking
212
- ```
213
-
214
- **Command Publishing:**
215
- ```javascript
216
- // HomeKit actions → MQTT commands
217
- Set Volume → /device/{id}/command/volume/set
218
- Play/Pause → /device/{id}/command/card/pause or /resume
219
- Stop → /device/{id}/command/card/stop
220
- ```
221
-
222
- ## Phase 2: Enhanced Controls (Partially Complete)
223
-
224
- ### 2.1 Display Control ✅
225
-
226
- **Status:** COMPLETE
227
-
228
- **Service:** Lightbulb (for brightness control)
229
-
230
- **Characteristics:**
231
- - On: Display on/off
232
- - Brightness: 0-100 mapped to display brightness settings
233
-
234
- **Implementation:**
235
- - Map to `dayDisplayBrightness` and `nightDisplayBrightness`
236
- - Handle "auto" mode as max brightness (100)
237
- - Numeric values map directly to percentage
238
- - Create separate services for day/night if needed
239
-
240
- ### 2.2 Temperature Sensor ✅
241
-
242
- **Status:** COMPLETE
243
-
244
- **Service:** TemperatureSensor (optional, enabled via config)
245
-
246
- **Characteristics:**
247
- - CurrentTemperature: From `temperatureCelcius`
248
-
249
- **Config:**
250
- ```javascript
251
- {
252
- "exposeTemperature": true
253
- }
254
- ```
255
-
256
- ### 2.3 Connection Status ✅
257
-
258
- **Status:** COMPLETE
259
-
260
- **Service:** ContactSensor or OccupancySensor
261
-
262
- **Characteristics:**
263
- - ContactSensorState or OccupancyDetected: Based on `isOnline`
264
- - StatusActive: `isOnline`
265
-
266
- **Use Cases:**
267
- - Automation triggers when player comes online
268
- - Notifications when player goes offline
269
- - Parent monitoring of device usage
270
-
271
- ### 2.4 Configuration Switches ✅
272
-
273
- **Status:** COMPLETE
274
-
275
- **Goal:** Expose device settings as HomeKit switches
276
-
277
- **Switches:**
278
- 1. Bluetooth Enabled → `bluetoothEnabled`
279
- 2. Bluetooth Headphones → `btHeadphonesEnabled`
280
- 3. Repeat All → `repeatAll`
281
-
282
- **Implementation:**
283
- - Create Switch service for each setting
284
- - onSet: Update via `PUT /device-v2/{deviceId}/config`
285
- - onGet: Read from cached config or status
286
- - Refresh config on status poll
287
-
288
- **Config:**
289
- ```javascript
290
- {
291
- "exposeAdvancedControls": true
292
- }
293
- ```
294
-
295
- ## Phase 3: Advanced Features
296
-
297
- ### 3.1 Volume Limits
298
-
299
- **Implementation Options:**
300
-
301
- **Option A: Custom Characteristics (via Eve)**
302
- - Requires homebridge-lib for custom characteristics
303
- - Create slider characteristics for max volume
304
-
305
- **Option B: Additional Lightbulb Services**
306
- - Use brightness as volume limit (0-16 → 0-100 scale)
307
- - Name: "Max Volume Day" and "Max Volume Night"
308
-
309
- **Settings:**
310
- - Max Volume Limit (Day): `maxVolumeLimit` (0-16)
311
- - Max Volume Limit (Night): `nightMaxVolumeLimit` (0-16)
312
-
313
- ### 3.2 Ambient Light Control
314
-
315
- **Service:** Lightbulb (with color)
316
-
317
- **Characteristics:**
318
- - On: Ambient light enabled
319
- - Brightness: Light intensity
320
- - Hue/Saturation: Parse `ambientColour` hex to HSV
321
-
322
- **Implementation:**
323
- - Parse hex color (e.g., "#ff3900") to Hue/Saturation
324
- - Separate services for day and night colors
325
- - Update via config: `ambientColour`, `nightAmbientColour`
326
-
327
- ### 3.3 Card Detection ✅
328
-
329
- **Status:** COMPLETE
330
-
331
- **Service:** ContactSensor
332
-
333
- **Characteristics:**
334
- - ContactSensorState: DETECTED when card inserted
335
- - StatusActive: true
336
-
337
- **Status Mapping:**
338
- ```javascript
339
- cardInsertionState === 0 → CONTACT_NOT_DETECTED
340
- cardInsertionState === 1 → CONTACT_DETECTED (physical)
341
- cardInsertionState === 2 → CONTACT_DETECTED (remote)
342
- ```
343
-
344
- **Use Cases:**
345
- - Trigger automations when card inserted
346
- - Parent monitoring of what's playing
347
- - Bedtime routine detection
348
-
349
- ### 3.4 Sleep Timer ✅
350
-
351
- **Status:** COMPLETE
352
-
353
- **Implementation:**
354
- - ✅ Uses Fanv2 service with rotation speed as timer minutes (0-120 minutes)
355
- - ✅ Active characteristic controls timer on/off
356
- - ✅ Real-time updates from MQTT events
357
- - Or use Valve service with duration
358
- - Map to `shutdownTimeout` setting (in seconds)
359
- - Convert minutes to seconds for API
360
-
361
- ## Phase 4: Content Integration
362
-
363
- ### 4.1 Active Content Info
364
-
365
- **Goal:** Display currently playing content information
366
-
367
- **Implementation:**
368
- - When `activeCard` changes, fetch via `GET /content/{cardId}`
369
- - Store card metadata in accessory context
370
- - Display title/author in logs or as custom characteristics
371
- - Update accessory display name with current content (optional)
372
-
373
- ### 4.2 Shortcuts (Future)
374
-
375
- **Goal:** Expose device shortcuts as programmable switches
376
-
377
- **API Endpoint:**
378
- - `PUT /device-v2/{deviceId}/shortcuts`
379
-
380
- **Service:** StatelessProgrammableSwitch
381
-
382
- **Implementation:**
383
- - Create button for each configured shortcut
384
- - Press triggers associated content/command
385
- - Separate buttons for day/night mode shortcuts
386
- - Read from device config: `dayYotoDaily`, `nightYotoRadio`, etc.
387
-
388
- **Note:** Library groups functionality has been removed from the plan as it's not essential for MVP.
389
-
390
- ## Configuration Schema
391
-
392
- ### Basic Config
393
- ```json
394
- {
395
- "platform": "Yoto",
396
- "name": "Yoto",
397
- "clientId": "",
398
- "accessToken": "",
399
- "refreshToken": "",
400
- "tokenExpiresAt": 0
401
- }
402
- ```
403
-
404
- ### Advanced Config
405
- ```json
406
- {
407
- "platform": "Yoto",
408
- "name": "Yoto",
409
- "clientId": "",
410
- "mqttBroker": "mqtt://mqtt.yotoplay.com:1883",
411
- "exposeTemperature": true,
412
- "exposeBattery": true,
413
- "exposeAdvancedControls": false,
414
- "exposeConnectionStatus": true,
415
- "exposeCardDetection": false,
416
- "volumeControlType": "speaker",
417
- "statusTimeoutSeconds": 120,
418
- "debug": false
419
- }
420
- ```
421
-
422
- ### config.schema.json Structure
423
- - OAuth setup instructions in description
424
- - clientId field (required)
425
- - MQTT broker URL (with default)
426
- - Feature toggles for optional sensors
427
- - Status timeout slider (60-300 seconds) - mark device offline if no updates
428
- - Debug mode toggle
429
-
430
- ## Error Handling Strategy
431
-
432
- ### API Errors
433
- - 401 Unauthorized → Trigger token refresh
434
- - 403 Forbidden → Log error, mark device unavailable
435
- - 404 Not Found → Device may be deleted, unregister accessory
436
- - 429 Too Many Requests → Implement exponential backoff
437
- - 500+ Server Errors → Retry with backoff, log for user
438
-
439
- ### Device Offline
440
- - Mark services as "No Response" in HomeKit if no MQTT updates for statusTimeoutSeconds
441
- - MQTT client will automatically attempt reconnection
442
- - Resume normal operation when MQTT messages resume
443
- - Log connection status changes
444
-
445
- ### MQTT Connection Issues
446
- - Implement exponential backoff for reconnection attempts
447
- - Use Last Will and Testament (LWT) if supported by broker
448
- - Maintain connection state per device
449
- - Gracefully handle broker unavailability
450
-
451
- ### Token Expiration
452
- - Proactively refresh before expiration
453
- - If refresh fails, require user re-authentication
454
- - Clear tokens from config on auth failure
455
- - Display clear instructions in logs
456
-
457
- ### Homebridge Crashes
458
- - Persist all state in accessory.context
459
- - Gracefully restore on restart
460
- - Re-establish API connection
461
- - Resume polling from last known state
462
-
463
- ## Testing Strategy
464
-
465
- ### Unit Tests
466
- - API client methods (mock fetch)
467
- - Token refresh logic
468
- - Status mapping functions
469
- - Config validation
470
-
471
- ### Integration Tests
472
- - OAuth flow (with mock server)
473
- - Device discovery
474
- - Characteristic updates
475
- - Error recovery
476
-
477
- ### Manual Testing Checklist
478
- - [ ] Initial OAuth setup flow
479
- - [ ] Device discovery and registration
480
- - [ ] Play/pause control
481
- - [ ] Volume adjustment
482
- - [ ] Battery status display
483
- - [ ] MQTT connection establishment
484
- - [ ] Real-time status updates via MQTT
485
- - [ ] Device offline handling
486
- - [ ] Token refresh
487
- - [ ] Homebridge restart recovery
488
- - [ ] Multiple device support
489
- - [ ] Device removal from account
490
-
491
- ## Documentation
492
-
493
- ### README.md
494
- - Overview and features
495
- - Prerequisites (Yoto account, Homebridge)
496
- - Installation instructions
497
- - OAuth setup guide with screenshots
498
- - Configuration options
499
- - Troubleshooting common issues
500
- - Supported devices
501
-
502
- ### CHANGELOG.md
503
- - Version history
504
- - Feature additions
505
- - Bug fixes
506
- - Breaking changes
507
-
508
- ### Contributing Guide
509
- - Development setup
510
- - Code style (JSDoc, ESLint)
511
- - Testing requirements
512
- - Pull request process
513
-
514
- ## Future Enhancements (Post-MVP)
515
-
516
- ### Content Management
517
- - [ ] Recently played content tracking
518
- - [ ] MYO card management
519
- - [ ] Active content information display
520
-
521
- ### Advanced Device Control
522
- - [ ] Alarm management
523
- - [ ] Clock face selection
524
- - [ ] Scheduled ambient light changes
525
- - [ ] Audio output routing
526
-
527
- ### Multi-Device Features
528
- - [ ] Synchronized playback
529
- - [ ] Family dashboard accessory
530
- - [ ] Group controls
531
-
532
- ### Parental Controls
533
- - [ ] Time-based restrictions
534
- - [ ] Content filtering
535
- - [ ] Usage monitoring
536
-
537
- ### Automation Integration
538
- - [ ] Bedtime scene triggers
539
- - [ ] Presence detection
540
- - [ ] Weather-based content
541
- - [ ] HomeKit Secure Video integration (if camera added)
542
-
543
- ## Success Metrics
544
-
545
- ### MVP Success Criteria
546
- - [ ] Successfully authenticate with Yoto API
547
- - [ ] Discover and register all user devices
548
- - [ ] Control play/pause from Home app
549
- - [ ] Adjust volume from Home app
550
- - [ ] Display accurate battery status
551
- - [ ] Status updates within 60 seconds
552
- - [ ] Survive Homebridge restart
553
- - [ ] Handle device offline gracefully
554
-
555
- ### Phase 2 Success Criteria
556
- - [ ] Brightness control functional
557
- - [ ] Temperature sensor (if enabled)
558
- - [ ] Connection status monitoring
559
- - [ ] Configuration switches work
560
-
561
- ### User Satisfaction Goals
562
- - Setup time under 5 minutes
563
- - No more than 2 second latency for controls
564
- - Clear error messages and recovery steps
565
- - Stable over 7 days continuous operation
566
-
567
- ## Development Phases Timeline
568
-
569
- ### Week 1: Foundation
570
- - Day 1-2: Project setup, REST API client, auth
571
- - Day 3-4: MQTT client implementation
572
- - Day 5-7: Platform implementation with MQTT integration
573
-
574
- ### Week 2: Core Features
575
- - Day 1-2: Basic accessory with playback/volume controls via MQTT
576
- - Day 3-4: Battery service, real-time status updates
577
- - Day 5-7: Display control, temperature sensor
578
-
579
- ### Week 3: Advanced Features
580
- - Day 1-3: Advanced controls, volume limits
581
- - Day 4-5: Card detection, connection status
582
- - Day 6-7: Integration testing, polish
583
-
584
- ### Week 4: Release Prep
585
- - Day 1-3: Documentation, examples
586
- - Day 4-5: Beta testing with users
587
- - Day 6-7: Bug fixes, npm publish
588
-
589
- ## Next Steps
590
-
591
- 1. ✅ Review and approve plan
592
- 2. ✅ Discover MQTT integration capability
593
- 3. ✅ Set up project structure and dependencies (including MQTT library)
594
- 4. ✅ Implement auth.js with OAuth flow
595
- 5. ✅ Create REST API client wrapper
596
- 6. ✅ Create MQTT client wrapper with subscriptions
597
- 7. ✅ Build platform foundation with MQTT connection
598
- 8. ✅ Implement basic player accessory with real-time updates
599
- 9. ✅ Add display brightness control
600
- 10. ✅ Add sleep timer control
601
- 11. ✅ Add advanced control switches
602
- 12. ✅ Improve MQTT reconnection logic
603
- 13. 🔄 Test with real Yoto devices (PRIORITY)
604
- 14. 🔄 Add volume limit controls (IN PROGRESS)
605
- 15. 🔄 Add ambient light color control (IN PROGRESS)
606
- 16. ⏳ Add active content information display
607
- 17. ⏳ Add shortcuts support (future)
608
- 18. ⏳ Publish to npm
609
- 19. ⏳ Iterate based on user feedback
@@ -1,15 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "declaration": true,
5
- "declarationMap": true,
6
- "noEmit": false,
7
- "emitDeclarationOnly": true
8
- },
9
- "exclude": [
10
- "**/*.test.js",
11
- "node_modules",
12
- "coverage",
13
- ".github"
14
- ]
15
- }
package/eslint.config.js DELETED
@@ -1,7 +0,0 @@
1
- import neostandard, { resolveIgnoresFromGitignore } from 'neostandard'
2
-
3
- export default neostandard({
4
- ignores: [
5
- ...resolveIgnoresFromGitignore(),
6
- ],
7
- })
package/index.test.js DELETED
@@ -1,7 +0,0 @@
1
- import { test } from 'node:test'
2
- import assert from 'node:assert'
3
- import homebridgeYoto from './index.js'
4
-
5
- test('exports default function', () => {
6
- assert.strictEqual(typeof homebridgeYoto, 'function')
7
- })