homebridge-yoto 0.0.27 → 0.0.31

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 CHANGED
@@ -1,609 +1,425 @@
1
- # Homebridge Yoto Plugin - Implementation Plan
1
+ # Homebridge Yoto Plugin - Implementation Status
2
2
 
3
- ## Overview
3
+ ## ✅ What's Implemented
4
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.
5
+ ### Architecture
6
+ - ✅ Platform plugin using `yoto-nodejs-client` for device management
7
+ - ✅ One accessory per Yoto device (published as external accessory for SmartSpeaker support)
8
+ - ✅ Real-time updates via MQTT + periodic HTTP polling fallback
9
+ - ✅ Offline detection and "No Response" status handling
10
+ - ✅ Capability-based service registration (v2/v3/mini device support)
6
11
 
7
- ## Current Status: Phase 1 Complete + Phase 2 Enhancements
12
+ ### Core Services (Always Present)
13
+ - ✅ **AccessoryInformation** - Device metadata (manufacturer, model, serial, firmware)
14
+ - ✅ **SmartSpeaker** (PRIMARY) - Playback control and volume
15
+ - ✅ **Battery** - Battery level, charging state, low battery indicator
16
+ - ✅ **ContactSensor (CardSlot)** - Card insertion detection
17
+ - ✅ **OccupancySensor (NightModeStatus)** - Day/night mode indicator
18
+ - ✅ **Switch (SleepTimer)** - Toggle sleep timer (30 min default)
19
+ - ✅ **Switch (Bluetooth)** - Toggle Bluetooth on/off
20
+ - ✅ **Fanv2 (DayMaxVolume)** - Day mode max volume limit control
21
+ - ✅ **Fanv2 (NightMaxVolume)** - Night mode max volume limit control
8
22
 
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.
23
+ ### Optional Services (Capability-Based)
24
+ - ✅ **TemperatureSensor** - Temperature reading (v3 only)
25
+ - ✅ **Lightbulb (DayNightlight)** - Day nightlight color/brightness control (v3 only)
26
+ - ✅ **Lightbulb (NightNightlight)** - Night nightlight color/brightness control (v3 only)
27
+ - ✅ **ContactSensor (NightlightActive)** - Live nightlight status (v3 only)
28
+ - ✅ **ContactSensor (DayNightlightActive)** - Day nightlight status (v3 only)
29
+ - ✅ **ContactSensor (NightNightlightActive)** - Night nightlight status (v3 only)
10
30
 
11
- ## Project Structure
31
+ ## Service & Characteristic Reference
12
32
 
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
- ```
33
+ All services are named consistently using `generateServiceName()` helper: `"[Device Name] [Service Name]"`
27
34
 
28
- ## Phase 1: Core Foundation (MVP) ✅ COMPLETE
35
+ ### Yoto Player Accessory
29
36
 
30
- ### 1.1 Authentication System
37
+ Each Yoto device is represented as a single HomeKit accessory with multiple services.
31
38
 
32
- **Status:** COMPLETE
39
+ **Category**: `SPEAKER` (required for SmartSpeaker service)
33
40
 
34
- **Goal:** Implement OAuth2 Device Authorization Flow for Yoto API
41
+ ---
35
42
 
36
- **Files:**
37
- - `lib/auth.js`
43
+ #### Service: AccessoryInformation (Required)
38
44
 
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
45
+ Standard HomeKit service providing device identification.
46
46
 
47
- **API Endpoints:**
48
- - `POST /oauth/device/code`
49
- - `POST /oauth/token` (with grant_type: 'device_code' and 'refresh_token')
47
+ **Characteristics:**
48
+ - `Manufacturer` (GET) - "Yoto Inc."
49
+ - `Model` (GET) - Device family (e.g., "v3", "v2", "mini")
50
+ - `SerialNumber` (GET) - Device ID
51
+ - `HardwareRevision` (GET) - Generation and form factor
52
+ - `FirmwareRevision` (GET) - Firmware version from device status
50
53
 
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
- ```
54
+ **Source:** `device` metadata + `status.firmwareVersion`
62
55
 
63
- ### 1.2 API Client ✅
56
+ ---
64
57
 
65
- **Status:** COMPLETE
58
+ #### Service: SmartSpeaker (PRIMARY)
66
59
 
67
- **Goal:** Create robust API wrapper for Yoto endpoints
60
+ Controls playback and volume. Marked as primary service for the accessory.
68
61
 
69
- **Files:**
70
- - `lib/yotoApi.js` (REST endpoints)
71
- - `lib/yotoMqtt.js` (MQTT client)
62
+ **Characteristics:**
63
+ - `CurrentMediaState` (GET) - PLAY/PAUSE/STOP based on playback status
64
+ - `TargetMediaState` (GET/SET) - Control playback (play/pause/stop)
65
+ - `Volume` (GET/SET) - Volume level (0-16 native scale, dynamic max based on volume limit)
66
+ - `Mute` (GET/SET) - Mute state (derived from volume === 0)
67
+ - `StatusActive` (GET) - Online/offline indicator
72
68
 
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
69
+ **Source:** `status.volume`, `playback.playbackStatus`
70
+ **Control:** `sendCommand({ action: 'play' | 'pause' | 'stop' | 'volume', volume: N })`
79
71
 
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
- ```
72
+ ---
90
73
 
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
74
+ #### Service: Battery
97
75
 
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
- ```
76
+ Battery status information.
118
77
 
119
- ### 1.3 Platform Setup ✅
78
+ **Characteristics:**
79
+ - `BatteryLevel` (GET) - Battery percentage (0-100)
80
+ - `ChargingState` (GET) - CHARGING or NOT_CHARGING
81
+ - `StatusLowBattery` (GET) - LOW when ≤20%, NORMAL otherwise
82
+ - `StatusActive` (GET) - Online/offline indicator
120
83
 
121
- **Status:** COMPLETE
84
+ **Source:** `status.batteryLevelPercentage`, `status.isCharging`
122
85
 
123
- **Goal:** Implement DynamicPlatformPlugin for device discovery
86
+ ---
124
87
 
125
- **Files:**
126
- - `lib/platform.js`
127
- - `index.js`
88
+ #### Service: TemperatureSensor (OPTIONAL - v3 only)
128
89
 
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`
90
+ Temperature reading from device sensor.
136
91
 
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
92
+ **Characteristics:**
93
+ - `CurrentTemperature` (GET) - Temperature in Celsius
94
+ - `StatusFault` (GET) - NO_FAULT (only shown when temperature available)
95
+ - `StatusActive` (GET) - Online/offline indicator
142
96
 
143
- ### 1.4 Player Accessory (Basic) ✅
97
+ **Source:** `status.temperatureCelsius`
98
+ **Availability:** `deviceModel.capabilities.hasTemperatureSensor`
144
99
 
145
- **Status:** COMPLETE
100
+ ---
146
101
 
147
- **Goal:** Create accessory with essential playback controls
102
+ #### Service: Lightbulb (subtype: "DayNightlight") - OPTIONAL - v3 only
148
103
 
149
- **Files:**
150
- - `lib/playerAccessory.js`
104
+ Day mode nightlight color and brightness control (config-based).
151
105
 
152
- **Services (Phase 1):**
153
- 1. **AccessoryInformation** (required)
154
- - Manufacturer: "Yoto"
155
- - Model: from device.deviceType
156
- - SerialNumber: device.deviceId
157
- - FirmwareRevision: device.releaseChannel
106
+ **Characteristics:**
107
+ - `On` (GET/SET) - Turn nightlight on/off (off states: '0x000000', 'off')
108
+ - `Brightness` (GET/SET) - Screen brightness 0-100% (or 'auto')
109
+ - `Hue` (GET/SET) - Color hue 0-360° (derived from hex color)
110
+ - `Saturation` (GET/SET) - Color saturation 0-100% (derived from hex color)
158
111
 
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)
112
+ **Source:** `config.ambientColour`, `config.dayDisplayBrightness`
113
+ **Control:** `updateConfig({ ambientColour: '0xRRGGBB', dayDisplayBrightness: 'N' })`
114
+ **Availability:** `deviceModel.capabilities.hasColoredNightlight`
164
115
 
165
- 3. **Battery**
166
- - BatteryLevel: 0-100
167
- - ChargingState: NOT_CHARGING/CHARGING
168
- - StatusLowBattery: NORMAL/LOW (< 20%)
116
+ **Color Conversion:** Uses `color-convert` library for hex ↔ HSV conversion
169
117
 
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
118
+ ---
176
119
 
177
- ### 1.5 MQTT Real-Time Updates
120
+ #### Service: Lightbulb (subtype: "NightNightlight") - OPTIONAL - v3 only
178
121
 
179
- **Status:** COMPLETE
122
+ Night mode nightlight color and brightness control (config-based).
180
123
 
181
- **Goal:** Keep HomeKit in sync with device state using MQTT
124
+ **Characteristics:**
125
+ - `On` (GET/SET) - Turn nightlight on/off (off states: '0x000000', 'off')
126
+ - `Brightness` (GET/SET) - Screen brightness 0-100% (or 'auto')
127
+ - `Hue` (GET/SET) - Color hue 0-360° (derived from hex color)
128
+ - `Saturation` (GET/SET) - Color saturation 0-100% (derived from hex color)
182
129
 
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)
130
+ **Source:** `config.nightAmbientColour`, `config.nightDisplayBrightness`
131
+ **Control:** `updateConfig({ nightAmbientColour: '0xRRGGBB', nightDisplayBrightness: 'N' })`
132
+ **Availability:** `deviceModel.capabilities.hasColoredNightlight`
191
133
 
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
- ```
134
+ ---
199
135
 
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
- ```
136
+ #### Service: ContactSensor (subtype: "NightlightActive") - OPTIONAL - v3 only
213
137
 
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
- ```
138
+ Shows if nightlight is currently active (live device state).
221
139
 
222
- ## Phase 2: Enhanced Controls (Partially Complete)
140
+ **Characteristics:**
141
+ - `ContactSensorState` (GET) - CONTACT_DETECTED when nightlight on
142
+ - `StatusActive` (GET) - Online/offline indicator
143
+
144
+ **Source:** `status.nightlightMode !== 'off'`
145
+ **Availability:** `deviceModel.capabilities.hasColoredNightlight`
223
146
 
224
- ### 2.1 Display Control ✅
147
+ ---
225
148
 
226
- **Status:** COMPLETE
149
+ #### Service: ContactSensor (subtype: "DayNightlightActive") - OPTIONAL - v3 only
227
150
 
228
- **Service:** Lightbulb (for brightness control)
151
+ Shows if day nightlight is currently active and showing.
229
152
 
230
153
  **Characteristics:**
231
- - On: Display on/off
232
- - Brightness: 0-100 mapped to display brightness settings
154
+ - `ContactSensorState` (GET) - CONTACT_DETECTED when day mode + nightlight on
155
+ - `StatusActive` (GET) - Online/offline indicator
233
156
 
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
157
+ **Source:** `status.dayMode === 'day' && status.nightlightMode !== 'off'`
158
+ **Availability:** `deviceModel.capabilities.hasColoredNightlight`
239
159
 
240
- ### 2.2 Temperature Sensor ✅
160
+ ---
241
161
 
242
- **Status:** COMPLETE
162
+ #### Service: ContactSensor (subtype: "NightNightlightActive") - OPTIONAL - v3 only
243
163
 
244
- **Service:** TemperatureSensor (optional, enabled via config)
164
+ Shows if night nightlight is currently active and showing.
245
165
 
246
166
  **Characteristics:**
247
- - CurrentTemperature: From `temperatureCelcius`
167
+ - `ContactSensorState` (GET) - CONTACT_DETECTED when night mode + nightlight on
168
+ - `StatusActive` (GET) - Online/offline indicator
248
169
 
249
- **Config:**
250
- ```javascript
251
- {
252
- "exposeTemperature": true
253
- }
254
- ```
170
+ **Source:** `status.dayMode === 'night' && status.nightlightMode !== 'off'`
171
+ **Availability:** `deviceModel.capabilities.hasColoredNightlight`
255
172
 
256
- ### 2.3 Connection Status ✅
173
+ ---
257
174
 
258
- **Status:** COMPLETE
175
+ #### Service: ContactSensor (subtype: "CardSlot")
259
176
 
260
- **Service:** ContactSensor or OccupancySensor
177
+ Shows if a card is currently inserted.
261
178
 
262
179
  **Characteristics:**
263
- - ContactSensorState or OccupancyDetected: Based on `isOnline`
264
- - StatusActive: `isOnline`
180
+ - `ContactSensorState` (GET) - CONTACT_DETECTED when card present
181
+ - `StatusActive` (GET) - Online/offline indicator
265
182
 
266
- **Use Cases:**
267
- - Automation triggers when player comes online
268
- - Notifications when player goes offline
269
- - Parent monitoring of device usage
183
+ **Source:** `status.cardInsertionState !== 'none'`
270
184
 
271
- ### 2.4 Configuration Switches ✅
185
+ ---
272
186
 
273
- **Status:** COMPLETE
187
+ #### Service: OccupancySensor (subtype: "NightModeStatus")
274
188
 
275
- **Goal:** Expose device settings as HomeKit switches
189
+ Shows if device is in night mode (vs day mode).
276
190
 
277
- **Switches:**
278
- 1. Bluetooth Enabled `bluetoothEnabled`
279
- 2. Bluetooth Headphones `btHeadphonesEnabled`
280
- 3. Repeat All → `repeatAll`
191
+ **Characteristics:**
192
+ - `OccupancyDetected` (GET) - OCCUPANCY_DETECTED when in night mode
193
+ - `StatusActive` (GET) - Online/offline indicator
281
194
 
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
195
+ **Source:** `status.dayMode === 'night'`
287
196
 
288
- **Config:**
289
- ```javascript
290
- {
291
- "exposeAdvancedControls": true
292
- }
293
- ```
197
+ **Use Case:** Trigger automations based on device's day/night schedule
294
198
 
295
- ## Phase 3: Advanced Features
199
+ ---
296
200
 
297
- ### 3.1 Volume Limits
201
+ #### Service: Switch (subtype: "SleepTimer")
298
202
 
299
- **Implementation Options:**
203
+ Toggle sleep timer on/off.
300
204
 
301
- **Option A: Custom Characteristics (via Eve)**
302
- - Requires homebridge-lib for custom characteristics
303
- - Create slider characteristics for max volume
205
+ **Characteristics:**
206
+ - `On` (GET/SET) - Sleep timer active state
207
+ - `StatusActive` (GET) - Online/offline indicator
304
208
 
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"
209
+ **Source:** `playback.sleepTimerActive`
210
+ **Control:** `sendCommand({ action: 'sleep-timer', minutes: 30 })` (on) or `minutes: 0` (off)
308
211
 
309
- **Settings:**
310
- - Max Volume Limit (Day): `maxVolumeLimit` (0-16)
311
- - Max Volume Limit (Night): `nightMaxVolumeLimit` (0-16)
212
+ ---
312
213
 
313
- ### 3.2 Ambient Light Control
214
+ #### Service: Switch (subtype: "Bluetooth")
314
215
 
315
- **Service:** Lightbulb (with color)
216
+ Toggle Bluetooth on/off (config-based, works offline).
316
217
 
317
218
  **Characteristics:**
318
- - On: Ambient light enabled
319
- - Brightness: Light intensity
320
- - Hue/Saturation: Parse `ambientColour` hex to HSV
219
+ - `On` (GET/SET) - Bluetooth enabled state
321
220
 
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`
221
+ **Source:** `config.bluetoothEnabled` (string '0' or '1')
222
+ **Control:** `updateConfig({ bluetoothEnabled: '1' | '0' })`
326
223
 
327
- ### 3.3 Card Detection
224
+ **Note:** No StatusActive - config-based services work offline
328
225
 
329
- **Status:** COMPLETE
226
+ ---
330
227
 
331
- **Service:** ContactSensor
228
+ #### Service: Fanv2 (subtype: "DayMaxVolume")
229
+
230
+ Control day mode maximum volume limit (config-based, works offline).
332
231
 
333
232
  **Characteristics:**
334
- - ContactSensorState: DETECTED when card inserted
335
- - StatusActive: true
233
+ - `Active` (GET) - Always ACTIVE
234
+ - `RotationSpeed` (GET/SET) - Volume limit 0-100%
336
235
 
337
- **Status Mapping:**
338
- ```javascript
339
- cardInsertionState === 0 CONTACT_NOT_DETECTED
340
- cardInsertionState === 1 → CONTACT_DETECTED (physical)
341
- cardInsertionState === 2 → CONTACT_DETECTED (remote)
342
- ```
236
+ **Source:** `config.maxVolumeLimit` (device: 0-16, HomeKit: 0-100%)
237
+ **Control:** `updateConfig({ maxVolumeLimit: 'N' })`
238
+ **Conversion:** `percentage = (limit / 16) * 100`
343
239
 
344
- **Use Cases:**
345
- - Trigger automations when card inserted
346
- - Parent monitoring of what's playing
347
- - Bedtime routine detection
240
+ ---
348
241
 
349
- ### 3.4 Sleep Timer
242
+ #### Service: Fanv2 (subtype: "NightMaxVolume")
350
243
 
351
- **Status:** COMPLETE
244
+ Control night mode maximum volume limit (config-based, works offline).
352
245
 
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
246
+ **Characteristics:**
247
+ - `Active` (GET) - Always ACTIVE
248
+ - `RotationSpeed` (GET/SET) - Volume limit 0-100%
249
+
250
+ **Source:** `config.nightMaxVolumeLimit` (device: 0-16, HomeKit: 0-100%)
251
+ **Control:** `updateConfig({ nightMaxVolumeLimit: 'N' })`
252
+ **Conversion:** `percentage = (limit / 16) * 100`
253
+
254
+ ---
255
+
256
+ #### Service: StatelessProgrammableSwitch (DYNAMIC) - NOT YET IMPLEMENTED
257
+
258
+ One service per shortcut configured on device. Trigger shortcuts from HomeKit.
259
+
260
+ **Characteristics:**
261
+ - `ProgrammableSwitchEvent` (READ/NOTIFY) - SINGLE_PRESS event
262
+ - `ServiceLabelIndex` (GET) - Index in shortcuts array
263
+
264
+ **Source:** `config.shortcuts.modes.day.content[]` and `config.shortcuts.modes.night.content[]`
265
+ **Control:** `sendCommand({ action: 'play', shortcut: X })`
266
+
267
+ **Note:** Dynamic services - created based on device configuration
268
+
269
+ ---
270
+
271
+ ### Service Availability Matrix
272
+
273
+ | Service | v2 | v3 | mini |
274
+ |---------|----|----|------|
275
+ | AccessoryInformation | ✅ | ✅ | ✅ |
276
+ | SmartSpeaker | ✅ | ✅ | ✅ |
277
+ | Battery | ✅ | ✅ | ✅ |
278
+ | ContactSensor (CardSlot) | ✅ | ✅ | ✅ |
279
+ | OccupancySensor (NightMode) | ✅ | ✅ | ✅ |
280
+ | Switch (SleepTimer) | ✅ | ✅ | ✅ |
281
+ | Switch (Bluetooth) | ✅ | ✅ | ✅ |
282
+ | Fanv2 (Volume Limits) | ✅ | ✅ | ✅ |
283
+ | TemperatureSensor | ❌ | ✅ | ❌ |
284
+ | Lightbulb (Nightlights) | ❌ | ✅ | ❌ |
285
+ | ContactSensor (Nightlight Status) | ❌ | ✅ | ❌ |
286
+ | StatelessProgrammableSwitch | 🚧 | 🚧 | 🚧 |
287
+
288
+ ---
289
+
290
+ ## Offline Behavior
291
+
292
+ ### What Gets Marked as "No Response"
293
+
294
+ Services are categorized by data source:
360
295
 
361
- ## Phase 4: Content Integration
296
+ **Device State Services** (require online connection):
297
+ - SmartSpeaker (playback, volume)
298
+ - Battery (level, charging)
299
+ - TemperatureSensor (temperature)
300
+ - ContactSensor - all variants (card, nightlight status)
301
+ - OccupancySensor (night mode)
302
+ - Switch (SleepTimer) - reads playback state
362
303
 
363
- ### 4.1 Active Content Info
304
+ **Config-Based Services** (work offline):
305
+ - Lightbulb (nightlight color/brightness)
306
+ - Fanv2 (volume limits)
307
+ - Switch (Bluetooth)
308
+ - StatelessProgrammableSwitch (shortcuts)
364
309
 
365
- **Goal:** Display currently playing content information
310
+ ### Implementation
366
311
 
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)
312
+ Device state services use `StatusActive` characteristic to indicate offline status. When `StatusActive` is false, HomeKit shows "No Response" for the service.
372
313
 
373
- ### 4.2 Shortcuts (Future)
314
+ Config-based services do NOT have `StatusActive` and remain accessible when offline since they only read/write device configuration (cached in `deviceModel.config`).
374
315
 
375
- **Goal:** Expose device shortcuts as programmable switches
316
+ ---
376
317
 
377
- **API Endpoint:**
378
- - `PUT /device-v2/{deviceId}/shortcuts`
318
+ ## ❌ What's Left to Implement
379
319
 
380
- **Service:** StatelessProgrammableSwitch
320
+ ### StatelessProgrammableSwitch Services (Shortcuts)
381
321
 
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.
322
+ Dynamic services created based on `config.shortcuts` configuration.
387
323
 
388
- **Note:** Library groups functionality has been removed from the plan as it's not essential for MVP.
324
+ **Implementation Notes:**
325
+ - Parse `config.shortcuts.modes.day.content[]` and `config.shortcuts.modes.night.content[]`
326
+ - Create one service per unique shortcut
327
+ - Handle shortcuts refresh when config changes
328
+ - Trigger with `sendCommand({ action: 'play', shortcut: X })`
389
329
 
390
- ## Configuration Schema
330
+ **Complexity:** Dynamic service lifecycle management, shortcut identification
391
331
 
392
- ### Basic Config
393
- ```json
394
- {
395
- "platform": "Yoto",
396
- "name": "Yoto",
397
- "clientId": "",
398
- "accessToken": "",
399
- "refreshToken": "",
400
- "tokenExpiresAt": 0
401
- }
332
+ ---
333
+
334
+ ## API Reference Documentation
335
+
336
+ ### YotoDeviceModel - State & Events
337
+
338
+ **State Properties:**
339
+ - `device` - Device metadata (deviceId, name, deviceType, etc.)
340
+ - `status` - Live device status (volume, battery, temperature, etc.)
341
+ - `config` - Device configuration (nightlight colors, volume limits, etc.)
342
+ - `shortcuts` - Configured shortcuts
343
+ - `playback` - Playback state (status, track, sleep timer, etc.)
344
+
345
+ **Events:**
346
+ - `statusUpdate(status, source, changedFields)` - Device status changed
347
+ - `configUpdate(config, changedFields)` - Configuration changed
348
+ - `playbackUpdate(playback, changedFields)` - Playback state changed
349
+ - `online({ reason })` - Device came online
350
+ - `offline({ reason })` - Device went offline
351
+
352
+ ### Device API Endpoints
353
+
354
+ - `GET /devices` - List all devices
355
+ - `GET /devices/:deviceId/config` - Get device config
356
+ - `GET /devices/:deviceId/status` - Get device status
357
+ - `POST /devices/:deviceId/config` - Update device config
358
+ - `POST /devices/:deviceId/mq` - Send device command
359
+
360
+ ### Command Examples
361
+
362
+ ```javascript
363
+ // Playback control
364
+ await deviceModel.sendCommand({ action: 'play' })
365
+ await deviceModel.sendCommand({ action: 'pause' })
366
+ await deviceModel.sendCommand({ action: 'stop' })
367
+
368
+ // Volume control
369
+ await deviceModel.sendCommand({ action: 'volume', volume: 10 })
370
+
371
+ // Sleep timer
372
+ await deviceModel.sendCommand({ action: 'sleep-timer', minutes: 30 })
402
373
  ```
403
374
 
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
- }
375
+ ### Config Update Examples
376
+
377
+ ```javascript
378
+ // Nightlight colors
379
+ await deviceModel.updateConfig({ ambientColour: '0xff5733' })
380
+ await deviceModel.updateConfig({ nightAmbientColour: '0x3366ff' })
381
+
382
+ // Volume limits
383
+ await deviceModel.updateConfig({ maxVolumeLimit: '12' })
384
+ await deviceModel.updateConfig({ nightMaxVolumeLimit: '8' })
385
+
386
+ // Bluetooth
387
+ await deviceModel.updateConfig({ bluetoothEnabled: '1' })
420
388
  ```
421
389
 
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
390
+ ---
391
+
392
+ ## Testing Checklist
393
+
394
+ ### Basic Functionality
395
+ - [ ] Device discovery and accessory creation
396
+ - [ ] Playback control (play/pause/stop)
397
+ - [ ] Volume control (0-16 range)
398
+ - [ ] Battery status updates
399
+ - [ ] Online/offline detection
400
+ - [ ] Firmware version display
401
+
402
+ ### Optional Services
403
+ - [ ] Temperature sensor (v3 only)
404
+ - [ ] Nightlight color control (v3 only)
405
+ - [ ] Nightlight brightness control (v3 only)
406
+ - [ ] Nightlight status sensors (v3 only)
407
+ - [ ] Card slot detection (all devices)
408
+ - [ ] Night mode detection (all devices)
409
+ - [ ] Sleep timer control (all devices)
410
+ - [ ] Bluetooth toggle (all devices)
411
+ - [ ] Volume limit controls (all devices)
412
+
413
+ ### Edge Cases
414
+ - [ ] Device goes offline during operation
415
+ - [ ] MQTT disconnection with HTTP fallback
416
+ - [ ] Multiple devices in one account
417
+ - [ ] Device rename handling
418
+ - [ ] Config changes from Yoto app
419
+
420
+ ### Automations
421
+ - [ ] Trigger on card insertion
422
+ - [ ] Trigger on night mode change
423
+ - [ ] Trigger on nightlight activation
424
+ - [ ] Volume limit changes based on time
425
+ - [ ] Sleep timer activation