yoto-nodejs-client 0.0.5 → 0.0.7
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/README.md +73 -40
- package/bin/auth.js +4 -3
- package/bin/device-model.js +25 -5
- package/bin/device-tui.js +25 -3
- package/bin/devices.js +25 -9
- package/bin/lib/cli-helpers.d.ts.map +1 -1
- package/bin/lib/cli-helpers.js +3 -1
- package/bin/lib/token-helpers.d.ts +4 -2
- package/bin/lib/token-helpers.d.ts.map +1 -1
- package/bin/lib/token-helpers.js +9 -8
- package/bin/refresh-token.js +4 -2
- package/bin/token-info.js +2 -2
- package/lib/api-client.d.ts +11 -10
- package/lib/api-client.d.ts.map +1 -1
- package/lib/api-client.js +12 -14
- package/lib/api-endpoints/auth.test.js +4 -4
- package/lib/api-endpoints/content.test.js +32 -32
- package/lib/api-endpoints/devices.d.ts +4 -4
- package/lib/api-endpoints/devices.js +2 -2
- package/lib/api-endpoints/devices.test.js +45 -45
- package/lib/api-endpoints/endpoint-test-helpers.d.ts +3 -4
- package/lib/api-endpoints/endpoint-test-helpers.d.ts.map +1 -1
- package/lib/api-endpoints/endpoint-test-helpers.js +21 -5
- package/lib/api-endpoints/family-library-groups.d.ts +3 -3
- package/lib/api-endpoints/family-library-groups.d.ts.map +1 -1
- package/lib/api-endpoints/family-library-groups.js +3 -3
- package/lib/api-endpoints/family-library-groups.test.js +29 -29
- package/lib/api-endpoints/family.test.js +11 -11
- package/lib/api-endpoints/icons.test.js +14 -14
- package/lib/mqtt/client.d.ts +123 -48
- package/lib/mqtt/client.d.ts.map +1 -1
- package/lib/mqtt/client.js +131 -49
- package/lib/mqtt/factory.d.ts +12 -5
- package/lib/mqtt/factory.d.ts.map +1 -1
- package/lib/mqtt/factory.js +39 -11
- package/lib/mqtt/index.js +2 -1
- package/lib/mqtt/mqtt.test.js +25 -22
- package/lib/test-helpers/device-model-test-helpers.d.ts +29 -0
- package/lib/test-helpers/device-model-test-helpers.d.ts.map +1 -0
- package/lib/test-helpers/device-model-test-helpers.js +116 -0
- package/lib/token.d.ts +44 -2
- package/lib/token.d.ts.map +1 -1
- package/lib/token.js +142 -2
- package/lib/yoto-account.d.ts +339 -9
- package/lib/yoto-account.d.ts.map +1 -1
- package/lib/yoto-account.js +411 -39
- package/lib/yoto-account.test.js +139 -0
- package/lib/yoto-device.d.ts +418 -30
- package/lib/yoto-device.d.ts.map +1 -1
- package/lib/yoto-device.js +670 -104
- package/lib/yoto-device.test.js +88 -0
- package/package.json +1 -1
package/lib/yoto-device.js
CHANGED
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
+
* @import { IConnackPacket } from 'mqtt'
|
|
10
11
|
* @import { YotoClient } from './api-client.js'
|
|
11
12
|
* @import { YotoDevice, YotoDeviceConfig, YotoDeviceShortcuts, YotoDeviceFullStatus, YotoDeviceStatusResponse, YotoDeviceCommand, YotoDeviceCommandResponse } from './api-endpoints/devices.js'
|
|
12
|
-
* @import { YotoMqttClient, YotoMqttStatus, YotoEventsMessage, YotoLegacyStatus } from './mqtt/client.js'
|
|
13
|
-
* @import {
|
|
13
|
+
* @import { YotoMqttClient, YotoMqttStatus, YotoEventsMessage, YotoLegacyStatus, YotoStatusMessage, YotoStatusLegacyMessage, YotoResponseMessage, YotoMqttClientDisconnectMetadata, YotoMqttClientCloseMetadata, PlaybackStatus } from './mqtt/client.js'
|
|
14
|
+
* @import { YotoMqttOptions } from './mqtt/factory.js'
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
17
|
import { EventEmitter } from 'events'
|
|
@@ -80,6 +81,194 @@ function convertPowerSource (numericSource) {
|
|
|
80
81
|
* @typedef {'battery' | 'dock' | 'usb-c' | 'wireless'} PowerSource
|
|
81
82
|
*/
|
|
82
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Normalize "0"/"1" booleans from config to true/false.
|
|
86
|
+
* @param {string | boolean} value
|
|
87
|
+
* @returns {boolean}
|
|
88
|
+
*/
|
|
89
|
+
function parseConfigBoolean (value) {
|
|
90
|
+
if (typeof value === 'boolean') return value
|
|
91
|
+
return value === '1'
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Parse a numeric config field into a number.
|
|
96
|
+
* @param {string | number} value
|
|
97
|
+
* @param {number} fallback
|
|
98
|
+
* @returns {number}
|
|
99
|
+
*/
|
|
100
|
+
function parseConfigNumber (value, fallback) {
|
|
101
|
+
const parsed = typeof value === 'number' ? value : Number.parseInt(value, 10)
|
|
102
|
+
return Number.isFinite(parsed) ? parsed : fallback
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Parse brightness config values.
|
|
107
|
+
* @param {string} value
|
|
108
|
+
* @returns {{ auto: boolean, value: number | null }}
|
|
109
|
+
*/
|
|
110
|
+
function parseConfigBrightness (value) {
|
|
111
|
+
if (value === 'auto') return { auto: true, value: null }
|
|
112
|
+
const parsed = Number.parseInt(value, 10)
|
|
113
|
+
if (Number.isFinite(parsed)) return { auto: false, value: parsed }
|
|
114
|
+
return { auto: false, value: null }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Parse brightness status values (255 = auto).
|
|
119
|
+
* @param {number} value
|
|
120
|
+
* @returns {{ auto: boolean, value: number | null }}
|
|
121
|
+
*/
|
|
122
|
+
function parseStatusBrightness (value) {
|
|
123
|
+
if (value === 255) return { auto: true, value: null }
|
|
124
|
+
return { auto: false, value }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Parse "off" sentinel for night Yoto Radio.
|
|
129
|
+
* @param {string} value
|
|
130
|
+
* @returns {{ enabled: boolean, value: string | null }}
|
|
131
|
+
*/
|
|
132
|
+
function parseRadioSetting (value) {
|
|
133
|
+
if (value === '' || value === '0') return { enabled: false, value: null }
|
|
134
|
+
return { enabled: true, value }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Build a config update payload in API format.
|
|
139
|
+
* @param {Partial<YotoDeviceModelConfig>} configUpdate
|
|
140
|
+
* @param {YotoDeviceModelConfig} currentConfig
|
|
141
|
+
* @returns {Partial<YotoDeviceConfig>}
|
|
142
|
+
*/
|
|
143
|
+
function buildConfigUpdatePayload (configUpdate, currentConfig) {
|
|
144
|
+
/** @type {Partial<YotoDeviceConfig>} */
|
|
145
|
+
const update = {}
|
|
146
|
+
/**
|
|
147
|
+
* @param {keyof YotoDeviceModelConfig} key
|
|
148
|
+
* @returns {boolean}
|
|
149
|
+
*/
|
|
150
|
+
const has = (key) => Object.prototype.hasOwnProperty.call(configUpdate, key)
|
|
151
|
+
|
|
152
|
+
if (has('alarms') && configUpdate.alarms !== undefined) update.alarms = configUpdate.alarms
|
|
153
|
+
if (has('ambientColour') && configUpdate.ambientColour !== undefined) update.ambientColour = configUpdate.ambientColour
|
|
154
|
+
if (has('bluetoothEnabled') && configUpdate.bluetoothEnabled !== undefined) {
|
|
155
|
+
update.bluetoothEnabled = configUpdate.bluetoothEnabled ? '1' : '0'
|
|
156
|
+
}
|
|
157
|
+
if (has('btHeadphonesEnabled') && configUpdate.btHeadphonesEnabled !== undefined) {
|
|
158
|
+
update.btHeadphonesEnabled = configUpdate.btHeadphonesEnabled
|
|
159
|
+
}
|
|
160
|
+
if (has('clockFace') && configUpdate.clockFace !== undefined) update.clockFace = configUpdate.clockFace
|
|
161
|
+
if (has('dayTime') && configUpdate.dayTime !== undefined) update.dayTime = configUpdate.dayTime
|
|
162
|
+
if (has('dayYotoDaily') && configUpdate.dayYotoDaily !== undefined) update.dayYotoDaily = configUpdate.dayYotoDaily
|
|
163
|
+
if (has('dayYotoRadio') && configUpdate.dayYotoRadio !== undefined) update.dayYotoRadio = configUpdate.dayYotoRadio
|
|
164
|
+
if (has('daySoundsOff') && configUpdate.daySoundsOff !== undefined) {
|
|
165
|
+
update.daySoundsOff = configUpdate.daySoundsOff ? '1' : '0'
|
|
166
|
+
}
|
|
167
|
+
if (has('displayDimBrightness') && configUpdate.displayDimBrightness !== undefined) {
|
|
168
|
+
update.displayDimBrightness = String(configUpdate.displayDimBrightness)
|
|
169
|
+
}
|
|
170
|
+
if (has('displayDimTimeout') && configUpdate.displayDimTimeout !== undefined) {
|
|
171
|
+
update.displayDimTimeout = String(configUpdate.displayDimTimeout)
|
|
172
|
+
}
|
|
173
|
+
if (has('headphonesVolumeLimited') && configUpdate.headphonesVolumeLimited !== undefined) {
|
|
174
|
+
update.headphonesVolumeLimited = configUpdate.headphonesVolumeLimited
|
|
175
|
+
}
|
|
176
|
+
if (has('hourFormat') && configUpdate.hourFormat !== undefined) {
|
|
177
|
+
update.hourFormat = String(configUpdate.hourFormat)
|
|
178
|
+
}
|
|
179
|
+
if (has('locale') && configUpdate.locale !== undefined) update.locale = configUpdate.locale
|
|
180
|
+
if (has('logLevel') && configUpdate.logLevel !== undefined) update.logLevel = configUpdate.logLevel
|
|
181
|
+
if (has('maxVolumeLimit') && configUpdate.maxVolumeLimit !== undefined) {
|
|
182
|
+
update.maxVolumeLimit = String(configUpdate.maxVolumeLimit)
|
|
183
|
+
}
|
|
184
|
+
if (has('nightAmbientColour') && configUpdate.nightAmbientColour !== undefined) {
|
|
185
|
+
update.nightAmbientColour = configUpdate.nightAmbientColour
|
|
186
|
+
}
|
|
187
|
+
if (has('nightMaxVolumeLimit') && configUpdate.nightMaxVolumeLimit !== undefined) {
|
|
188
|
+
update.nightMaxVolumeLimit = String(configUpdate.nightMaxVolumeLimit)
|
|
189
|
+
}
|
|
190
|
+
if (has('nightTime') && configUpdate.nightTime !== undefined) update.nightTime = configUpdate.nightTime
|
|
191
|
+
if (has('nightYotoDaily') && configUpdate.nightYotoDaily !== undefined) update.nightYotoDaily = configUpdate.nightYotoDaily
|
|
192
|
+
if (has('nightSoundsOff') && configUpdate.nightSoundsOff !== undefined) {
|
|
193
|
+
update.nightSoundsOff = configUpdate.nightSoundsOff ? '1' : '0'
|
|
194
|
+
}
|
|
195
|
+
if (has('pausePowerButton') && configUpdate.pausePowerButton !== undefined) {
|
|
196
|
+
update.pausePowerButton = configUpdate.pausePowerButton
|
|
197
|
+
}
|
|
198
|
+
if (has('pauseVolumeDown') && configUpdate.pauseVolumeDown !== undefined) {
|
|
199
|
+
update.pauseVolumeDown = configUpdate.pauseVolumeDown
|
|
200
|
+
}
|
|
201
|
+
if (has('repeatAll') && configUpdate.repeatAll !== undefined) update.repeatAll = configUpdate.repeatAll
|
|
202
|
+
if (has('showDiagnostics') && configUpdate.showDiagnostics !== undefined) {
|
|
203
|
+
update.showDiagnostics = configUpdate.showDiagnostics
|
|
204
|
+
}
|
|
205
|
+
if (has('shutdownTimeout') && configUpdate.shutdownTimeout !== undefined) {
|
|
206
|
+
update.shutdownTimeout = String(configUpdate.shutdownTimeout)
|
|
207
|
+
}
|
|
208
|
+
if (has('systemVolume') && configUpdate.systemVolume !== undefined) {
|
|
209
|
+
update.systemVolume = String(configUpdate.systemVolume)
|
|
210
|
+
}
|
|
211
|
+
if (has('timezone') && configUpdate.timezone !== undefined) update.timezone = configUpdate.timezone
|
|
212
|
+
if (has('volumeLevel') && configUpdate.volumeLevel !== undefined) update.volumeLevel = configUpdate.volumeLevel
|
|
213
|
+
|
|
214
|
+
const hasDayBrightnessValue = has('dayDisplayBrightness')
|
|
215
|
+
const hasDayBrightnessAuto = has('dayDisplayBrightnessAuto')
|
|
216
|
+
if (hasDayBrightnessValue || hasDayBrightnessAuto) {
|
|
217
|
+
const autoValue = hasDayBrightnessAuto ? configUpdate.dayDisplayBrightnessAuto : undefined
|
|
218
|
+
const auto = autoValue !== undefined
|
|
219
|
+
? autoValue
|
|
220
|
+
: (hasDayBrightnessValue ? false : currentConfig.dayDisplayBrightnessAuto)
|
|
221
|
+
const value = hasDayBrightnessValue ? configUpdate.dayDisplayBrightness : currentConfig.dayDisplayBrightness
|
|
222
|
+
if (auto) {
|
|
223
|
+
update.dayDisplayBrightness = 'auto'
|
|
224
|
+
} else if (value !== null && value !== undefined) {
|
|
225
|
+
update.dayDisplayBrightness = String(value)
|
|
226
|
+
} else {
|
|
227
|
+
throw new Error('dayDisplayBrightness must be set when dayDisplayBrightnessAuto is false')
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const hasNightBrightnessValue = has('nightDisplayBrightness')
|
|
232
|
+
const hasNightBrightnessAuto = has('nightDisplayBrightnessAuto')
|
|
233
|
+
if (hasNightBrightnessValue || hasNightBrightnessAuto) {
|
|
234
|
+
const autoValue = hasNightBrightnessAuto ? configUpdate.nightDisplayBrightnessAuto : undefined
|
|
235
|
+
const auto = autoValue !== undefined
|
|
236
|
+
? autoValue
|
|
237
|
+
: (hasNightBrightnessValue ? false : currentConfig.nightDisplayBrightnessAuto)
|
|
238
|
+
const value = hasNightBrightnessValue ? configUpdate.nightDisplayBrightness : currentConfig.nightDisplayBrightness
|
|
239
|
+
if (auto) {
|
|
240
|
+
update.nightDisplayBrightness = 'auto'
|
|
241
|
+
} else if (value !== null && value !== undefined) {
|
|
242
|
+
update.nightDisplayBrightness = String(value)
|
|
243
|
+
} else {
|
|
244
|
+
throw new Error('nightDisplayBrightness must be set when nightDisplayBrightnessAuto is false')
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const hasNightRadioValue = has('nightYotoRadio')
|
|
249
|
+
const hasNightRadioEnabled = has('nightYotoRadioEnabled')
|
|
250
|
+
if (hasNightRadioValue || hasNightRadioEnabled) {
|
|
251
|
+
const enabledValue = hasNightRadioEnabled ? configUpdate.nightYotoRadioEnabled : undefined
|
|
252
|
+
const enabled = enabledValue !== undefined
|
|
253
|
+
? enabledValue
|
|
254
|
+
: (hasNightRadioValue
|
|
255
|
+
? configUpdate.nightYotoRadio !== null && configUpdate.nightYotoRadio !== undefined
|
|
256
|
+
: currentConfig.nightYotoRadioEnabled)
|
|
257
|
+
const radioValue = hasNightRadioValue ? configUpdate.nightYotoRadio : currentConfig.nightYotoRadio
|
|
258
|
+
if (enabled) {
|
|
259
|
+
if (radioValue && radioValue !== '0') {
|
|
260
|
+
update.nightYotoRadio = radioValue
|
|
261
|
+
} else {
|
|
262
|
+
throw new Error('nightYotoRadio must be set when nightYotoRadioEnabled is true')
|
|
263
|
+
}
|
|
264
|
+
} else {
|
|
265
|
+
update.nightYotoRadio = '0'
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return update
|
|
270
|
+
}
|
|
271
|
+
|
|
83
272
|
/**
|
|
84
273
|
* Official Yoto nightlight colors
|
|
85
274
|
*
|
|
@@ -120,7 +309,7 @@ export function getNightlightColorName (colorValue) {
|
|
|
120
309
|
* @property {boolean} isCharging - Whether device is currently charging
|
|
121
310
|
* @property {boolean} isOnline - Whether device is currently online
|
|
122
311
|
* @property {number} volume - User volume level (0-16 scale)
|
|
123
|
-
* @property {number} maxVolume - Maximum volume limit (0-16 scale)
|
|
312
|
+
* @property {number} maxVolume - Active Maximum volume (depending on day or night) limit (0-16 scale)
|
|
124
313
|
* @property {CardInsertionState} cardInsertionState - Card insertion state
|
|
125
314
|
* @property {DayMode} dayMode - Day mode status
|
|
126
315
|
* @property {PowerSource} powerSource - Power source
|
|
@@ -141,12 +330,57 @@ export function getNightlightColorName (colorValue) {
|
|
|
141
330
|
*/
|
|
142
331
|
export const YotoDeviceStatusType = {}
|
|
143
332
|
|
|
333
|
+
/**
|
|
334
|
+
* Canonical device config - normalized format for HTTP config payloads
|
|
335
|
+
*
|
|
336
|
+
* String booleans and numeric strings are normalized. Brightness "auto" values
|
|
337
|
+
* are split into value + boolean flag.
|
|
338
|
+
*
|
|
339
|
+
* @typedef {Object} YotoDeviceModelConfig
|
|
340
|
+
* @property {string[]} alarms - Alarm list entries
|
|
341
|
+
* @property {string} ambientColour - Ambient light color (hex code)
|
|
342
|
+
* @property {boolean} bluetoothEnabled - Bluetooth enabled state
|
|
343
|
+
* @property {boolean} btHeadphonesEnabled - Bluetooth headphones enabled
|
|
344
|
+
* @property {string} clockFace - Clock face style
|
|
345
|
+
* @property {number | null} dayDisplayBrightness - Day brightness (0-100), null when auto
|
|
346
|
+
* @property {boolean} dayDisplayBrightnessAuto - Whether day brightness is auto
|
|
347
|
+
* @property {string} dayTime - Day mode start time
|
|
348
|
+
* @property {string} dayYotoDaily - Day mode Yoto Daily card path
|
|
349
|
+
* @property {string} dayYotoRadio - Day mode Yoto Radio card path
|
|
350
|
+
* @property {boolean} daySoundsOff - Day sounds off
|
|
351
|
+
* @property {number} displayDimBrightness - Display dim brightness (0-100)
|
|
352
|
+
* @property {number} displayDimTimeout - Display dim timeout in seconds
|
|
353
|
+
* @property {boolean} headphonesVolumeLimited - Whether headphones volume is limited
|
|
354
|
+
* @property {12 | 24} hourFormat - Hour format
|
|
355
|
+
* @property {string} locale - Device locale
|
|
356
|
+
* @property {string} logLevel - Log level
|
|
357
|
+
* @property {number} maxVolumeLimit - Max volume limit (0-16)
|
|
358
|
+
* @property {string} nightAmbientColour - Night ambient light color (hex code)
|
|
359
|
+
* @property {number | null} nightDisplayBrightness - Night brightness (0-100), null when auto
|
|
360
|
+
* @property {boolean} nightDisplayBrightnessAuto - Whether night brightness is auto
|
|
361
|
+
* @property {number} nightMaxVolumeLimit - Night max volume limit (0-16)
|
|
362
|
+
* @property {string} nightTime - Night mode start time
|
|
363
|
+
* @property {string} nightYotoDaily - Night mode Yoto Daily card path
|
|
364
|
+
* @property {string | null} nightYotoRadio - Night mode Yoto Radio card path
|
|
365
|
+
* @property {boolean} nightYotoRadioEnabled - Whether night Yoto Radio is enabled
|
|
366
|
+
* @property {boolean} nightSoundsOff - Night sounds off
|
|
367
|
+
* @property {boolean} pausePowerButton - Pause on power button press
|
|
368
|
+
* @property {boolean} pauseVolumeDown - Pause on volume down press
|
|
369
|
+
* @property {boolean} repeatAll - Repeat all tracks
|
|
370
|
+
* @property {boolean} showDiagnostics - Show diagnostics
|
|
371
|
+
* @property {number} shutdownTimeout - Shutdown timeout in seconds
|
|
372
|
+
* @property {number} systemVolume - System volume level (0-100 percent)
|
|
373
|
+
* @property {string} timezone - Timezone setting (empty string if not set)
|
|
374
|
+
* @property {string} volumeLevel - Volume level preset
|
|
375
|
+
*/
|
|
376
|
+
export const YotoDeviceModelConfigType = {}
|
|
377
|
+
|
|
144
378
|
/**
|
|
145
379
|
* Playback state from MQTT events
|
|
146
380
|
* @typedef {Object} YotoPlaybackState
|
|
147
381
|
* @property {string | null} cardId - Currently playing card ID TODO: Figure out name of card
|
|
148
382
|
* @property {string | null} source - Playback source (e.g., 'card', 'remote', 'MQTT') TODO: Figure out what 'mqtt' source means. Card means card, remote means remotly played card.
|
|
149
|
-
* @property {
|
|
383
|
+
* @property {PlaybackStatus | null} playbackStatus - Playback status
|
|
150
384
|
* @property {string | null} trackTitle - Current track title
|
|
151
385
|
* @property {string | null} trackKey - Current track key
|
|
152
386
|
* @property {string | null} chapterTitle - Current chapter title
|
|
@@ -168,7 +402,7 @@ export const YotoPlaybackStateType = {}
|
|
|
168
402
|
* Complete device client state
|
|
169
403
|
* @typedef {Object} YotoDeviceClientState
|
|
170
404
|
* @property {YotoDevice} device - Basic device information
|
|
171
|
-
* @property {
|
|
405
|
+
* @property {YotoDeviceModelConfig} config - Device configuration (always initialized)
|
|
172
406
|
* @property {YotoDeviceShortcuts} shortcuts - Button shortcuts (always initialized)
|
|
173
407
|
* @property {YotoDeviceStatus} status - Current device status (always initialized)
|
|
174
408
|
* @property {YotoPlaybackState} playback - Current playback state (always initialized)
|
|
@@ -201,7 +435,7 @@ export const YotoPlaybackStateType = {}
|
|
|
201
435
|
/**
|
|
202
436
|
* Device client initialization options
|
|
203
437
|
* @typedef {Object} YotoDeviceModelOptions
|
|
204
|
-
* @property {
|
|
438
|
+
* @property {Omit<YotoMqttOptions, 'deviceId' | 'token' >} [yotoDeviceMqttOptions] - MQTT.js client options to pass through to factory
|
|
205
439
|
* @property {number} [httpPollIntervalMs=600000] - Background HTTP polling interval for config+status sync (default: 10 minutes)
|
|
206
440
|
*/
|
|
207
441
|
|
|
@@ -221,11 +455,26 @@ export const YotoPlaybackStateType = {}
|
|
|
221
455
|
* @property {string} [source] - Source of status update (only present for 'http-status' reason)
|
|
222
456
|
*/
|
|
223
457
|
|
|
458
|
+
/**
|
|
459
|
+
* MQTT disconnect event metadata (from MQTT disconnect packet)
|
|
460
|
+
* @typedef {YotoMqttClientDisconnectMetadata} YotoMqttDisconnectMetadata
|
|
461
|
+
*/
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* MQTT connect event metadata (from MQTT CONNACK)
|
|
465
|
+
* @typedef {IConnackPacket} YotoMqttConnectMetadata
|
|
466
|
+
*/
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* MQTT close event metadata (from connection close)
|
|
470
|
+
* @typedef {YotoMqttClientCloseMetadata} YotoMqttCloseMetadata
|
|
471
|
+
*/
|
|
472
|
+
|
|
224
473
|
/**
|
|
225
474
|
* Started event metadata
|
|
226
475
|
* @typedef {Object} YotoDeviceStartedMetadata
|
|
227
476
|
* @property {YotoDevice} device - Device information
|
|
228
|
-
* @property {
|
|
477
|
+
* @property {YotoDeviceModelConfig} config - Device configuration
|
|
229
478
|
* @property {YotoDeviceShortcuts} shortcuts - Device shortcuts
|
|
230
479
|
* @property {YotoDeviceStatus} status - Device status
|
|
231
480
|
* @property {YotoPlaybackState} playback - Playback state
|
|
@@ -239,12 +488,21 @@ export const YotoPlaybackStateType = {}
|
|
|
239
488
|
* 'started': [YotoDeviceStartedMetadata],
|
|
240
489
|
* 'stopped': [],
|
|
241
490
|
* 'statusUpdate': [YotoDeviceStatus, string, Set<keyof YotoDeviceStatus>],
|
|
242
|
-
* 'configUpdate': [
|
|
491
|
+
* 'configUpdate': [YotoDeviceModelConfig, Set<keyof YotoDeviceModelConfig>],
|
|
243
492
|
* 'playbackUpdate': [YotoPlaybackState, Set<keyof YotoPlaybackState>],
|
|
244
493
|
* 'online': [YotoDeviceOnlineMetadata],
|
|
245
494
|
* 'offline': [YotoDeviceOfflineMetadata],
|
|
246
|
-
* '
|
|
247
|
-
* '
|
|
495
|
+
* 'mqttConnect': [YotoMqttConnectMetadata],
|
|
496
|
+
* 'mqttDisconnect': [YotoMqttDisconnectMetadata],
|
|
497
|
+
* 'mqttClose': [YotoMqttCloseMetadata],
|
|
498
|
+
* 'mqttReconnect': [],
|
|
499
|
+
* 'mqttOffline': [],
|
|
500
|
+
* 'mqttEnd': [],
|
|
501
|
+
* 'mqttStatus': [string, YotoStatusMessage],
|
|
502
|
+
* 'mqttEvents': [string, YotoEventsMessage],
|
|
503
|
+
* 'mqttStatusLegacy': [string, YotoStatusLegacyMessage],
|
|
504
|
+
* 'mqttResponse': [string, YotoResponseMessage],
|
|
505
|
+
* 'mqttUnknown': [string, unknown],
|
|
248
506
|
* 'error': [Error]
|
|
249
507
|
* }} YotoDeviceModelEventMap
|
|
250
508
|
*/
|
|
@@ -271,8 +529,17 @@ export const YotoPlaybackStateType = {}
|
|
|
271
529
|
* - 'playbackUpdate' - Emitted when playback state changes, passes (playback, changedFields)
|
|
272
530
|
* - 'online' - Emitted when device comes online, passes metadata with reason and optional upTime
|
|
273
531
|
* - 'offline' - Emitted when device goes offline, passes metadata with reason and optional shutDownReason or timeSinceLastSeen
|
|
274
|
-
* - '
|
|
275
|
-
* - '
|
|
532
|
+
* - 'mqttConnect' - Emitted when MQTT client connects, passes CONNACK metadata
|
|
533
|
+
* - 'mqttDisconnect' - Emitted when MQTT disconnect packet received, passes metadata with disconnect packet
|
|
534
|
+
* - 'mqttClose' - Emitted when MQTT connection closes, passes metadata with close reason
|
|
535
|
+
* - 'mqttReconnect' - Emitted when MQTT client is reconnecting
|
|
536
|
+
* - 'mqttOffline' - Emitted when MQTT client goes offline
|
|
537
|
+
* - 'mqttEnd' - Emitted when MQTT client end is called
|
|
538
|
+
* - 'mqttStatus' - Emitted with raw MQTT status messages, passes (topic, message)
|
|
539
|
+
* - 'mqttEvents' - Emitted with raw MQTT events messages, passes (topic, message)
|
|
540
|
+
* - 'mqttStatusLegacy' - Emitted with raw legacy MQTT status messages, passes (topic, message)
|
|
541
|
+
* - 'mqttResponse' - Emitted with raw MQTT response messages, passes (topic, message)
|
|
542
|
+
* - 'mqttUnknown' - Emitted with unknown MQTT messages, passes (topic, message)
|
|
276
543
|
* - 'error' - Emitted when an error occurs, passes error
|
|
277
544
|
*
|
|
278
545
|
* @extends {EventEmitter<YotoDeviceModelEventMap>}
|
|
@@ -304,8 +571,8 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
304
571
|
}
|
|
305
572
|
|
|
306
573
|
// Only set mqttOptions if provided (exactOptionalPropertyTypes compatibility)
|
|
307
|
-
if (options.
|
|
308
|
-
this.#options.
|
|
574
|
+
if (options.yotoDeviceMqttOptions !== undefined) {
|
|
575
|
+
this.#options.yotoDeviceMqttOptions = options.yotoDeviceMqttOptions
|
|
309
576
|
}
|
|
310
577
|
|
|
311
578
|
// Initialize state
|
|
@@ -372,7 +639,7 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
372
639
|
|
|
373
640
|
/**
|
|
374
641
|
* Get device configuration
|
|
375
|
-
* @returns {
|
|
642
|
+
* @returns {YotoDeviceModelConfig}
|
|
376
643
|
*/
|
|
377
644
|
get config () { return this.#state.config }
|
|
378
645
|
|
|
@@ -500,10 +767,12 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
500
767
|
})
|
|
501
768
|
|
|
502
769
|
// Update state with config data
|
|
503
|
-
this.#
|
|
504
|
-
|
|
770
|
+
this.#updateConfigFromHttp(
|
|
771
|
+
configResponse.device.config,
|
|
772
|
+
configResponse.device.shortcuts
|
|
773
|
+
)
|
|
505
774
|
|
|
506
|
-
// Update device info with additional
|
|
775
|
+
// Update device info with additional detai ls from config
|
|
507
776
|
this.#state.device = {
|
|
508
777
|
...this.#state.device,
|
|
509
778
|
name: configResponse.device.name,
|
|
@@ -600,6 +869,197 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
600
869
|
return this.#mqttClient
|
|
601
870
|
}
|
|
602
871
|
|
|
872
|
+
// ==========================================================================
|
|
873
|
+
// Public API - MQTT Commands
|
|
874
|
+
// ==========================================================================
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* Request current events from device over MQTT
|
|
878
|
+
* @param {string} [body=''] - Optional request body for tracking/identification
|
|
879
|
+
* @returns {Promise<void>}
|
|
880
|
+
*/
|
|
881
|
+
async requestEvents (body = '') {
|
|
882
|
+
return await this.#mqttClient?.requestEvents(body)
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* Request current status from device over MQTT
|
|
887
|
+
* @param {string} [body=''] - Optional request body for tracking/identification
|
|
888
|
+
* @returns {Promise<void>}
|
|
889
|
+
*/
|
|
890
|
+
async requestStatus (body = '') {
|
|
891
|
+
return await this.#mqttClient?.requestStatus(body)
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* Set device volume over MQTT
|
|
896
|
+
* @param {number} volume - Volume level integer [0-16]
|
|
897
|
+
* @returns {Promise<void>}
|
|
898
|
+
*/
|
|
899
|
+
async setVolume (volume) {
|
|
900
|
+
const normalizedVolume = Math.min(16, Math.max(0, Math.round(volume)))
|
|
901
|
+
const volumePercentage = Math.round((normalizedVolume / 16) * 100)
|
|
902
|
+
return await this.#mqttClient?.setVolume(volumePercentage)
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/**
|
|
906
|
+
* Set ambient light color over MQTT
|
|
907
|
+
* @param {number} r - Red intensity [0-255]
|
|
908
|
+
* @param {number} g - Green intensity [0-255]
|
|
909
|
+
* @param {number} b - Blue intensity [0-255]
|
|
910
|
+
* @returns {Promise<void>}
|
|
911
|
+
*/
|
|
912
|
+
async setAmbient (r, g, b) {
|
|
913
|
+
return await this.#mqttClient?.setAmbient(r, g, b)
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/**
|
|
917
|
+
* Set ambient light color from hex over MQTT
|
|
918
|
+
* @param {string} hexColor - Hex color string (e.g., "#FF0000")
|
|
919
|
+
* @returns {Promise<void>}
|
|
920
|
+
*/
|
|
921
|
+
async setAmbientHex (hexColor) {
|
|
922
|
+
return await this.#mqttClient?.setAmbientHex(hexColor)
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* Set sleep timer over MQTT
|
|
927
|
+
* @param {number} seconds - Timer duration in seconds (0 to disable)
|
|
928
|
+
* @returns {Promise<void>}
|
|
929
|
+
*/
|
|
930
|
+
async setSleepTimer (seconds) {
|
|
931
|
+
return await this.#mqttClient?.setSleepTimer(seconds)
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* Reboot device over MQTT
|
|
936
|
+
* @returns {Promise<void>}
|
|
937
|
+
*/
|
|
938
|
+
async reboot () {
|
|
939
|
+
return await this.#mqttClient?.reboot()
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* Start card playback over MQTT
|
|
944
|
+
* @param {Object} options - Card start options
|
|
945
|
+
* @param {string} options.uri - Card URI (e.g., "https://yoto.io/<cardID>")
|
|
946
|
+
* @param {string} [options.chapterKey] - Chapter to start from
|
|
947
|
+
* @param {string} [options.trackKey] - Track to start from
|
|
948
|
+
* @param {number} [options.secondsIn] - Playback start offset in seconds
|
|
949
|
+
* @param {number} [options.cutOff] - Playback stop offset in seconds
|
|
950
|
+
* @param {boolean} [options.anyButtonStop] - Whether button press stops playback
|
|
951
|
+
* @returns {Promise<void>}
|
|
952
|
+
*/
|
|
953
|
+
async startCard (options) {
|
|
954
|
+
return await this.#mqttClient?.startCard(options)
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/**
|
|
958
|
+
* Stop card playback over MQTT
|
|
959
|
+
* @returns {Promise<void>}
|
|
960
|
+
*/
|
|
961
|
+
async stopCard () {
|
|
962
|
+
return await this.#mqttClient?.stopCard()
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
/**
|
|
966
|
+
* Pause card playback over MQTT
|
|
967
|
+
* @returns {Promise<void>}
|
|
968
|
+
*/
|
|
969
|
+
async pauseCard () {
|
|
970
|
+
return await this.#mqttClient?.pauseCard()
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/**
|
|
974
|
+
* Resume card playback over MQTT
|
|
975
|
+
* @returns {Promise<void>}
|
|
976
|
+
*/
|
|
977
|
+
async resumeCard () {
|
|
978
|
+
return await this.#mqttClient?.resumeCard()
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/**
|
|
982
|
+
* Turn Bluetooth on over MQTT
|
|
983
|
+
* @param {Object} [options] - Bluetooth options
|
|
984
|
+
* @param {string} [options.action] - Bluetooth action
|
|
985
|
+
* @param {boolean | string} [options.mode] - Bluetooth mode
|
|
986
|
+
* @param {number} [options.rssi] - RSSI threshold
|
|
987
|
+
* @param {string} [options.name] - Target device name
|
|
988
|
+
* @param {string} [options.mac] - Target device MAC
|
|
989
|
+
* @returns {Promise<void>}
|
|
990
|
+
*/
|
|
991
|
+
async bluetoothOn (options) {
|
|
992
|
+
return await this.#mqttClient?.bluetoothOn(options)
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* Turn Bluetooth off over MQTT
|
|
997
|
+
* @returns {Promise<void>}
|
|
998
|
+
*/
|
|
999
|
+
async bluetoothOff () {
|
|
1000
|
+
return await this.#mqttClient?.bluetoothOff()
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
/**
|
|
1004
|
+
* Enable Bluetooth speaker mode over MQTT
|
|
1005
|
+
* @returns {Promise<void>}
|
|
1006
|
+
*/
|
|
1007
|
+
async bluetoothSpeakerMode () {
|
|
1008
|
+
return await this.#mqttClient?.bluetoothSpeakerMode()
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
/**
|
|
1012
|
+
* Enable Bluetooth audio source mode over MQTT
|
|
1013
|
+
* @returns {Promise<void>}
|
|
1014
|
+
*/
|
|
1015
|
+
async bluetoothAudioSourceMode () {
|
|
1016
|
+
return await this.#mqttClient?.bluetoothAudioSourceMode()
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/**
|
|
1020
|
+
* Delete all Bluetooth bonds over MQTT
|
|
1021
|
+
* @returns {Promise<void>}
|
|
1022
|
+
*/
|
|
1023
|
+
async bluetoothDeleteBonds () {
|
|
1024
|
+
return await this.#mqttClient?.bluetoothDeleteBonds()
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
/**
|
|
1028
|
+
* Connect to Bluetooth device over MQTT
|
|
1029
|
+
* @returns {Promise<void>}
|
|
1030
|
+
*/
|
|
1031
|
+
async bluetoothConnect () {
|
|
1032
|
+
return await this.#mqttClient?.bluetoothConnect()
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
/**
|
|
1036
|
+
* Disconnect Bluetooth device over MQTT
|
|
1037
|
+
* @returns {Promise<void>}
|
|
1038
|
+
*/
|
|
1039
|
+
async bluetoothDisconnect () {
|
|
1040
|
+
return await this.#mqttClient?.bluetoothDisconnect()
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
/**
|
|
1044
|
+
* Get Bluetooth state over MQTT
|
|
1045
|
+
* @returns {Promise<void>}
|
|
1046
|
+
*/
|
|
1047
|
+
async bluetoothGetState () {
|
|
1048
|
+
return await this.#mqttClient?.bluetoothGetState()
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
/**
|
|
1052
|
+
* Preview display icon over MQTT
|
|
1053
|
+
* @param {Object} options - Display preview options
|
|
1054
|
+
* @param {string} options.uri - Icon URI
|
|
1055
|
+
* @param {number} options.timeout - Display duration in seconds
|
|
1056
|
+
* @param {boolean} options.animated - Whether icon is animated
|
|
1057
|
+
* @returns {Promise<void>}
|
|
1058
|
+
*/
|
|
1059
|
+
async displayPreview (options) {
|
|
1060
|
+
return await this.#mqttClient?.displayPreview(options)
|
|
1061
|
+
}
|
|
1062
|
+
|
|
603
1063
|
// ==========================================================================
|
|
604
1064
|
// Public API - Device Control
|
|
605
1065
|
// ==========================================================================
|
|
@@ -611,7 +1071,7 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
611
1071
|
*/
|
|
612
1072
|
/**
|
|
613
1073
|
* Refresh device config from HTTP API
|
|
614
|
-
* @returns {Promise<
|
|
1074
|
+
* @returns {Promise<YotoDeviceModelConfig>}
|
|
615
1075
|
*/
|
|
616
1076
|
async refreshConfig () {
|
|
617
1077
|
const configResponse = await this.#client.getDeviceConfig({
|
|
@@ -636,14 +1096,17 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
636
1096
|
|
|
637
1097
|
/**
|
|
638
1098
|
* Update device configuration
|
|
639
|
-
* @param {Partial<
|
|
1099
|
+
* @param {Partial<YotoDeviceModelConfig>} configUpdate - Configuration changes
|
|
640
1100
|
* @returns {Promise<void>}
|
|
641
1101
|
*/
|
|
642
1102
|
async updateConfig (configUpdate) {
|
|
1103
|
+
const payload = buildConfigUpdatePayload(configUpdate, this.#state.config)
|
|
1104
|
+
if (Object.keys(payload).length === 0) return
|
|
1105
|
+
|
|
643
1106
|
await this.#client.updateDeviceConfig({
|
|
644
1107
|
deviceId: this.#state.device.deviceId,
|
|
645
1108
|
configUpdate: {
|
|
646
|
-
config:
|
|
1109
|
+
config: payload
|
|
647
1110
|
}
|
|
648
1111
|
})
|
|
649
1112
|
|
|
@@ -675,7 +1138,7 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
675
1138
|
// Create MQTT client
|
|
676
1139
|
this.#mqttClient = await this.#client.createMqttClient({
|
|
677
1140
|
deviceId: this.#state.device.deviceId,
|
|
678
|
-
|
|
1141
|
+
...this.#options.yotoDeviceMqttOptions
|
|
679
1142
|
})
|
|
680
1143
|
|
|
681
1144
|
// Set up MQTT event handlers
|
|
@@ -697,23 +1160,41 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
697
1160
|
if (!this.#mqttClient) return
|
|
698
1161
|
|
|
699
1162
|
// Connection events
|
|
700
|
-
this.#mqttClient.on('
|
|
701
|
-
this.emit('
|
|
1163
|
+
this.#mqttClient.on('connect', (metadata) => {
|
|
1164
|
+
this.emit('mqttConnect', metadata)
|
|
702
1165
|
|
|
703
1166
|
// Request status and events after settling period
|
|
704
1167
|
this.#scheduleMqttRequests()
|
|
705
1168
|
})
|
|
706
1169
|
|
|
707
|
-
this.#mqttClient.on('
|
|
708
|
-
this.#
|
|
709
|
-
this.emit('
|
|
1170
|
+
this.#mqttClient.on('disconnect', (metadata) => {
|
|
1171
|
+
this.#clearMqttRequestTimers()
|
|
1172
|
+
this.emit('mqttDisconnect', metadata)
|
|
710
1173
|
|
|
711
1174
|
// Don't immediately mark as offline - MQTT may reconnect
|
|
712
1175
|
// Offline detection is based on lack of status updates, not connection state
|
|
713
1176
|
})
|
|
714
1177
|
|
|
715
|
-
this.#mqttClient.on('
|
|
716
|
-
|
|
1178
|
+
this.#mqttClient.on('close', (metadata) => {
|
|
1179
|
+
this.#clearMqttRequestTimers()
|
|
1180
|
+
this.emit('mqttClose', metadata)
|
|
1181
|
+
|
|
1182
|
+
// Don't immediately mark as offline - MQTT may reconnect
|
|
1183
|
+
// Offline detection is based on lack of status updates, not connection state
|
|
1184
|
+
})
|
|
1185
|
+
|
|
1186
|
+
this.#mqttClient.on('reconnect', () => {
|
|
1187
|
+
this.emit('mqttReconnect')
|
|
1188
|
+
})
|
|
1189
|
+
|
|
1190
|
+
this.#mqttClient.on('offline', () => {
|
|
1191
|
+
this.#clearMqttRequestTimers()
|
|
1192
|
+
this.emit('mqttOffline')
|
|
1193
|
+
})
|
|
1194
|
+
|
|
1195
|
+
this.#mqttClient.on('end', () => {
|
|
1196
|
+
this.#clearMqttRequestTimers()
|
|
1197
|
+
this.emit('mqttEnd')
|
|
717
1198
|
})
|
|
718
1199
|
|
|
719
1200
|
this.#mqttClient.on('error', (error) => {
|
|
@@ -721,8 +1202,9 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
721
1202
|
})
|
|
722
1203
|
|
|
723
1204
|
// Status updates - PRIMARY source for status after initialization
|
|
724
|
-
this.#mqttClient.on('status', (
|
|
1205
|
+
this.#mqttClient.on('status', (topic, message) => {
|
|
725
1206
|
this.#recordDeviceActivity()
|
|
1207
|
+
this.emit('mqttStatus', topic, message)
|
|
726
1208
|
this.#updateStatusFromDocumentedMqtt(message.status)
|
|
727
1209
|
})
|
|
728
1210
|
|
|
@@ -730,20 +1212,25 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
730
1212
|
// This does NOT respond to requestStatus() - only emits on real-time events or 5-minute periodic updates
|
|
731
1213
|
// NOTE: Don't call #recordDeviceActivity() here - #handleLegacyStatus handles online/offline transitions
|
|
732
1214
|
// based on actual power state (shutdown/startup) which is more reliable than just "activity"
|
|
733
|
-
this.#mqttClient.on('status-legacy', (
|
|
1215
|
+
this.#mqttClient.on('status-legacy', (topic, message) => {
|
|
1216
|
+
this.emit('mqttStatusLegacy', topic, message)
|
|
734
1217
|
this.#handleLegacyStatus(message.status)
|
|
735
1218
|
})
|
|
736
1219
|
|
|
737
1220
|
// Events updates (playback, volume, etc.)
|
|
738
|
-
this.#mqttClient.on('events', (
|
|
1221
|
+
this.#mqttClient.on('events', (topic, message) => {
|
|
739
1222
|
this.#recordDeviceActivity()
|
|
1223
|
+
this.emit('mqttEvents', topic, message)
|
|
740
1224
|
this.#handleEventMessage(message)
|
|
741
1225
|
})
|
|
742
1226
|
|
|
743
1227
|
// Response messages (for debugging/logging)
|
|
744
|
-
this.#mqttClient.on('response', (
|
|
745
|
-
|
|
746
|
-
|
|
1228
|
+
this.#mqttClient.on('response', (topic, message) => {
|
|
1229
|
+
this.emit('mqttResponse', topic, message)
|
|
1230
|
+
})
|
|
1231
|
+
|
|
1232
|
+
this.#mqttClient.on('unknown', (topic, message) => {
|
|
1233
|
+
this.emit('mqttUnknown', topic, message)
|
|
747
1234
|
})
|
|
748
1235
|
}
|
|
749
1236
|
|
|
@@ -752,7 +1239,7 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
752
1239
|
*/
|
|
753
1240
|
#scheduleMqttRequests () {
|
|
754
1241
|
// Clear any existing timers
|
|
755
|
-
this.#
|
|
1242
|
+
this.#clearMqttRequestTimers()
|
|
756
1243
|
|
|
757
1244
|
// Request status after settling delay
|
|
758
1245
|
this.#statusRequestTimer = setTimeout(async () => {
|
|
@@ -780,9 +1267,9 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
780
1267
|
}
|
|
781
1268
|
|
|
782
1269
|
/**
|
|
783
|
-
* Clear
|
|
1270
|
+
* Clear MQTT request timers (status/events) after connect
|
|
784
1271
|
*/
|
|
785
|
-
#
|
|
1272
|
+
#clearMqttRequestTimers () {
|
|
786
1273
|
if (this.#statusRequestTimer) {
|
|
787
1274
|
clearTimeout(this.#statusRequestTimer)
|
|
788
1275
|
this.#statusRequestTimer = null
|
|
@@ -792,13 +1279,26 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
792
1279
|
clearTimeout(this.#eventsRequestTimer)
|
|
793
1280
|
this.#eventsRequestTimer = null
|
|
794
1281
|
}
|
|
1282
|
+
}
|
|
795
1283
|
|
|
1284
|
+
/**
|
|
1285
|
+
* Stop background HTTP polling
|
|
1286
|
+
*/
|
|
1287
|
+
#stopBackgroundPolling () {
|
|
796
1288
|
if (this.#backgroundPollTimer) {
|
|
797
1289
|
clearInterval(this.#backgroundPollTimer)
|
|
798
1290
|
this.#backgroundPollTimer = null
|
|
799
1291
|
}
|
|
800
1292
|
}
|
|
801
1293
|
|
|
1294
|
+
/**
|
|
1295
|
+
* Clear all timers
|
|
1296
|
+
*/
|
|
1297
|
+
#clearAllTimers () {
|
|
1298
|
+
this.#clearMqttRequestTimers()
|
|
1299
|
+
this.#stopBackgroundPolling()
|
|
1300
|
+
}
|
|
1301
|
+
|
|
802
1302
|
// ==========================================================================
|
|
803
1303
|
// Private - Online/Offline Tracking
|
|
804
1304
|
// ==========================================================================
|
|
@@ -896,7 +1396,7 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
896
1396
|
const { status, config } = this.#state
|
|
897
1397
|
/** @type {Set<keyof YotoDeviceStatus>} */
|
|
898
1398
|
const changedFields = new Set()
|
|
899
|
-
/** @type {Set<keyof
|
|
1399
|
+
/** @type {Set<keyof YotoDeviceModelConfig>} */
|
|
900
1400
|
const configChangedFields = new Set()
|
|
901
1401
|
|
|
902
1402
|
/**
|
|
@@ -1001,9 +1501,15 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
1001
1501
|
}
|
|
1002
1502
|
case 'dayBright': {
|
|
1003
1503
|
if (fullStatus.dayBright !== null) {
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1504
|
+
// HTTP fullStatus represents 'auto' brightness as 255
|
|
1505
|
+
const brightness = parseStatusBrightness(fullStatus.dayBright)
|
|
1506
|
+
if (config.dayDisplayBrightnessAuto !== brightness.auto) {
|
|
1507
|
+
config.dayDisplayBrightnessAuto = brightness.auto
|
|
1508
|
+
configChangedFields.add('dayDisplayBrightnessAuto')
|
|
1509
|
+
configChanged = true
|
|
1510
|
+
}
|
|
1511
|
+
if (config.dayDisplayBrightness !== brightness.value) {
|
|
1512
|
+
config.dayDisplayBrightness = brightness.value
|
|
1007
1513
|
configChangedFields.add('dayDisplayBrightness')
|
|
1008
1514
|
configChanged = true
|
|
1009
1515
|
}
|
|
@@ -1093,9 +1599,15 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
1093
1599
|
}
|
|
1094
1600
|
case 'nightBright': {
|
|
1095
1601
|
if (fullStatus.nightBright !== null) {
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1602
|
+
// HTTP fullStatus represents 'auto' brightness as 255
|
|
1603
|
+
const brightness = parseStatusBrightness(fullStatus.nightBright)
|
|
1604
|
+
if (config.nightDisplayBrightnessAuto !== brightness.auto) {
|
|
1605
|
+
config.nightDisplayBrightnessAuto = brightness.auto
|
|
1606
|
+
configChangedFields.add('nightDisplayBrightnessAuto')
|
|
1607
|
+
configChanged = true
|
|
1608
|
+
}
|
|
1609
|
+
if (config.nightDisplayBrightness !== brightness.value) {
|
|
1610
|
+
config.nightDisplayBrightness = brightness.value
|
|
1099
1611
|
configChangedFields.add('nightDisplayBrightness')
|
|
1100
1612
|
configChanged = true
|
|
1101
1613
|
}
|
|
@@ -1555,7 +2067,7 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
1555
2067
|
let configChanged = false
|
|
1556
2068
|
|
|
1557
2069
|
const { config } = this.#state
|
|
1558
|
-
/** @type {Set<keyof
|
|
2070
|
+
/** @type {Set<keyof YotoDeviceModelConfig>} */
|
|
1559
2071
|
const changedFields = new Set()
|
|
1560
2072
|
|
|
1561
2073
|
/**
|
|
@@ -1582,8 +2094,9 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
1582
2094
|
break
|
|
1583
2095
|
}
|
|
1584
2096
|
case 'bluetoothEnabled': {
|
|
1585
|
-
|
|
1586
|
-
|
|
2097
|
+
const bluetoothEnabled = parseConfigBoolean(configData.bluetoothEnabled)
|
|
2098
|
+
if (config.bluetoothEnabled !== bluetoothEnabled) {
|
|
2099
|
+
config.bluetoothEnabled = bluetoothEnabled
|
|
1587
2100
|
changedFields.add('bluetoothEnabled')
|
|
1588
2101
|
configChanged = true
|
|
1589
2102
|
}
|
|
@@ -1606,8 +2119,14 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
1606
2119
|
break
|
|
1607
2120
|
}
|
|
1608
2121
|
case 'dayDisplayBrightness': {
|
|
1609
|
-
|
|
1610
|
-
|
|
2122
|
+
const brightness = parseConfigBrightness(configData.dayDisplayBrightness)
|
|
2123
|
+
if (config.dayDisplayBrightnessAuto !== brightness.auto) {
|
|
2124
|
+
config.dayDisplayBrightnessAuto = brightness.auto
|
|
2125
|
+
changedFields.add('dayDisplayBrightnessAuto')
|
|
2126
|
+
configChanged = true
|
|
2127
|
+
}
|
|
2128
|
+
if (config.dayDisplayBrightness !== brightness.value) {
|
|
2129
|
+
config.dayDisplayBrightness = brightness.value
|
|
1611
2130
|
changedFields.add('dayDisplayBrightness')
|
|
1612
2131
|
configChanged = true
|
|
1613
2132
|
}
|
|
@@ -1638,24 +2157,27 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
1638
2157
|
break
|
|
1639
2158
|
}
|
|
1640
2159
|
case 'daySoundsOff': {
|
|
1641
|
-
|
|
1642
|
-
|
|
2160
|
+
const daySoundsOff = parseConfigBoolean(configData.daySoundsOff)
|
|
2161
|
+
if (config.daySoundsOff !== daySoundsOff) {
|
|
2162
|
+
config.daySoundsOff = daySoundsOff
|
|
1643
2163
|
changedFields.add('daySoundsOff')
|
|
1644
2164
|
configChanged = true
|
|
1645
2165
|
}
|
|
1646
2166
|
break
|
|
1647
2167
|
}
|
|
1648
2168
|
case 'displayDimBrightness': {
|
|
1649
|
-
|
|
1650
|
-
|
|
2169
|
+
const displayDimBrightness = parseConfigNumber(configData.displayDimBrightness, config.displayDimBrightness)
|
|
2170
|
+
if (config.displayDimBrightness !== displayDimBrightness) {
|
|
2171
|
+
config.displayDimBrightness = displayDimBrightness
|
|
1651
2172
|
changedFields.add('displayDimBrightness')
|
|
1652
2173
|
configChanged = true
|
|
1653
2174
|
}
|
|
1654
2175
|
break
|
|
1655
2176
|
}
|
|
1656
2177
|
case 'displayDimTimeout': {
|
|
1657
|
-
|
|
1658
|
-
|
|
2178
|
+
const displayDimTimeout = parseConfigNumber(configData.displayDimTimeout, config.displayDimTimeout)
|
|
2179
|
+
if (config.displayDimTimeout !== displayDimTimeout) {
|
|
2180
|
+
config.displayDimTimeout = displayDimTimeout
|
|
1659
2181
|
changedFields.add('displayDimTimeout')
|
|
1660
2182
|
configChanged = true
|
|
1661
2183
|
}
|
|
@@ -1670,8 +2192,9 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
1670
2192
|
break
|
|
1671
2193
|
}
|
|
1672
2194
|
case 'hourFormat': {
|
|
1673
|
-
|
|
1674
|
-
|
|
2195
|
+
const hourFormat = parseConfigNumber(configData.hourFormat, config.hourFormat)
|
|
2196
|
+
if (config.hourFormat !== hourFormat) {
|
|
2197
|
+
config.hourFormat = /** @type {12 | 24} */ (hourFormat)
|
|
1675
2198
|
changedFields.add('hourFormat')
|
|
1676
2199
|
configChanged = true
|
|
1677
2200
|
}
|
|
@@ -1694,8 +2217,9 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
1694
2217
|
break
|
|
1695
2218
|
}
|
|
1696
2219
|
case 'maxVolumeLimit': {
|
|
1697
|
-
|
|
1698
|
-
|
|
2220
|
+
const maxVolumeLimit = parseConfigNumber(configData.maxVolumeLimit, config.maxVolumeLimit)
|
|
2221
|
+
if (config.maxVolumeLimit !== maxVolumeLimit) {
|
|
2222
|
+
config.maxVolumeLimit = maxVolumeLimit
|
|
1699
2223
|
changedFields.add('maxVolumeLimit')
|
|
1700
2224
|
configChanged = true
|
|
1701
2225
|
}
|
|
@@ -1710,16 +2234,23 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
1710
2234
|
break
|
|
1711
2235
|
}
|
|
1712
2236
|
case 'nightDisplayBrightness': {
|
|
1713
|
-
|
|
1714
|
-
|
|
2237
|
+
const brightness = parseConfigBrightness(configData.nightDisplayBrightness)
|
|
2238
|
+
if (config.nightDisplayBrightnessAuto !== brightness.auto) {
|
|
2239
|
+
config.nightDisplayBrightnessAuto = brightness.auto
|
|
2240
|
+
changedFields.add('nightDisplayBrightnessAuto')
|
|
2241
|
+
configChanged = true
|
|
2242
|
+
}
|
|
2243
|
+
if (config.nightDisplayBrightness !== brightness.value) {
|
|
2244
|
+
config.nightDisplayBrightness = brightness.value
|
|
1715
2245
|
changedFields.add('nightDisplayBrightness')
|
|
1716
2246
|
configChanged = true
|
|
1717
2247
|
}
|
|
1718
2248
|
break
|
|
1719
2249
|
}
|
|
1720
2250
|
case 'nightMaxVolumeLimit': {
|
|
1721
|
-
|
|
1722
|
-
|
|
2251
|
+
const nightMaxVolumeLimit = parseConfigNumber(configData.nightMaxVolumeLimit, config.nightMaxVolumeLimit)
|
|
2252
|
+
if (config.nightMaxVolumeLimit !== nightMaxVolumeLimit) {
|
|
2253
|
+
config.nightMaxVolumeLimit = nightMaxVolumeLimit
|
|
1723
2254
|
changedFields.add('nightMaxVolumeLimit')
|
|
1724
2255
|
configChanged = true
|
|
1725
2256
|
}
|
|
@@ -1742,16 +2273,23 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
1742
2273
|
break
|
|
1743
2274
|
}
|
|
1744
2275
|
case 'nightYotoRadio': {
|
|
1745
|
-
|
|
1746
|
-
|
|
2276
|
+
const radio = parseRadioSetting(configData.nightYotoRadio)
|
|
2277
|
+
if (config.nightYotoRadioEnabled !== radio.enabled) {
|
|
2278
|
+
config.nightYotoRadioEnabled = radio.enabled
|
|
2279
|
+
changedFields.add('nightYotoRadioEnabled')
|
|
2280
|
+
configChanged = true
|
|
2281
|
+
}
|
|
2282
|
+
if (config.nightYotoRadio !== radio.value) {
|
|
2283
|
+
config.nightYotoRadio = radio.value
|
|
1747
2284
|
changedFields.add('nightYotoRadio')
|
|
1748
2285
|
configChanged = true
|
|
1749
2286
|
}
|
|
1750
2287
|
break
|
|
1751
2288
|
}
|
|
1752
2289
|
case 'nightSoundsOff': {
|
|
1753
|
-
|
|
1754
|
-
|
|
2290
|
+
const nightSoundsOff = parseConfigBoolean(configData.nightSoundsOff)
|
|
2291
|
+
if (config.nightSoundsOff !== nightSoundsOff) {
|
|
2292
|
+
config.nightSoundsOff = nightSoundsOff
|
|
1755
2293
|
changedFields.add('nightSoundsOff')
|
|
1756
2294
|
configChanged = true
|
|
1757
2295
|
}
|
|
@@ -1790,16 +2328,18 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
1790
2328
|
break
|
|
1791
2329
|
}
|
|
1792
2330
|
case 'shutdownTimeout': {
|
|
1793
|
-
|
|
1794
|
-
|
|
2331
|
+
const shutdownTimeout = parseConfigNumber(configData.shutdownTimeout, config.shutdownTimeout)
|
|
2332
|
+
if (config.shutdownTimeout !== shutdownTimeout) {
|
|
2333
|
+
config.shutdownTimeout = shutdownTimeout
|
|
1795
2334
|
changedFields.add('shutdownTimeout')
|
|
1796
2335
|
configChanged = true
|
|
1797
2336
|
}
|
|
1798
2337
|
break
|
|
1799
2338
|
}
|
|
1800
2339
|
case 'systemVolume': {
|
|
1801
|
-
|
|
1802
|
-
|
|
2340
|
+
const systemVolume = parseConfigNumber(configData.systemVolume, config.systemVolume)
|
|
2341
|
+
if (config.systemVolume !== systemVolume) {
|
|
2342
|
+
config.systemVolume = systemVolume
|
|
1803
2343
|
changedFields.add('systemVolume')
|
|
1804
2344
|
configChanged = true
|
|
1805
2345
|
}
|
|
@@ -1889,7 +2429,7 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
1889
2429
|
const { status, config } = this.#state
|
|
1890
2430
|
/** @type {Set<keyof YotoDeviceStatus>} */
|
|
1891
2431
|
const changedFields = new Set()
|
|
1892
|
-
/** @type {Set<keyof
|
|
2432
|
+
/** @type {Set<keyof YotoDeviceModelConfig>} */
|
|
1893
2433
|
const configChangedFields = new Set()
|
|
1894
2434
|
|
|
1895
2435
|
/**
|
|
@@ -1996,18 +2536,30 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
1996
2536
|
break
|
|
1997
2537
|
}
|
|
1998
2538
|
case 'dayBright': {
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2539
|
+
// MQTT represents 'auto' brightness as 255
|
|
2540
|
+
const brightness = parseStatusBrightness(mqttStatus.dayBright)
|
|
2541
|
+
if (config.dayDisplayBrightnessAuto !== brightness.auto) {
|
|
2542
|
+
config.dayDisplayBrightnessAuto = brightness.auto
|
|
2543
|
+
configChangedFields.add('dayDisplayBrightnessAuto')
|
|
2544
|
+
configChanged = true
|
|
2545
|
+
}
|
|
2546
|
+
if (config.dayDisplayBrightness !== brightness.value) {
|
|
2547
|
+
config.dayDisplayBrightness = brightness.value
|
|
2002
2548
|
configChangedFields.add('dayDisplayBrightness')
|
|
2003
2549
|
configChanged = true
|
|
2004
2550
|
}
|
|
2005
2551
|
break
|
|
2006
2552
|
}
|
|
2007
2553
|
case 'nightBright': {
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2554
|
+
// MQTT represents 'auto' brightness as 255
|
|
2555
|
+
const brightness = parseStatusBrightness(mqttStatus.nightBright)
|
|
2556
|
+
if (config.nightDisplayBrightnessAuto !== brightness.auto) {
|
|
2557
|
+
config.nightDisplayBrightnessAuto = brightness.auto
|
|
2558
|
+
configChangedFields.add('nightDisplayBrightnessAuto')
|
|
2559
|
+
configChanged = true
|
|
2560
|
+
}
|
|
2561
|
+
if (config.nightDisplayBrightness !== brightness.value) {
|
|
2562
|
+
config.nightDisplayBrightness = brightness.value
|
|
2011
2563
|
configChangedFields.add('nightDisplayBrightness')
|
|
2012
2564
|
configChanged = true
|
|
2013
2565
|
}
|
|
@@ -2125,7 +2677,7 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
2125
2677
|
const { status, config } = this.#state
|
|
2126
2678
|
/** @type {Set<keyof YotoDeviceStatus>} */
|
|
2127
2679
|
const changedFields = new Set()
|
|
2128
|
-
/** @type {Set<keyof
|
|
2680
|
+
/** @type {Set<keyof YotoDeviceModelConfig>} */
|
|
2129
2681
|
const configChangedFields = new Set()
|
|
2130
2682
|
|
|
2131
2683
|
/**
|
|
@@ -2361,18 +2913,30 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
2361
2913
|
break
|
|
2362
2914
|
}
|
|
2363
2915
|
case 'dayBright': {
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2916
|
+
// MQTT represents 'auto' brightness as 255
|
|
2917
|
+
const brightness = parseStatusBrightness(legacyStatus.dayBright)
|
|
2918
|
+
if (config.dayDisplayBrightnessAuto !== brightness.auto) {
|
|
2919
|
+
config.dayDisplayBrightnessAuto = brightness.auto
|
|
2920
|
+
configChangedFields.add('dayDisplayBrightnessAuto')
|
|
2921
|
+
configChanged = true
|
|
2922
|
+
}
|
|
2923
|
+
if (config.dayDisplayBrightness !== brightness.value) {
|
|
2924
|
+
config.dayDisplayBrightness = brightness.value
|
|
2367
2925
|
configChangedFields.add('dayDisplayBrightness')
|
|
2368
2926
|
configChanged = true
|
|
2369
2927
|
}
|
|
2370
2928
|
break
|
|
2371
2929
|
}
|
|
2372
2930
|
case 'nightBright': {
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2931
|
+
// MQTT represents 'auto' brightness as 255
|
|
2932
|
+
const brightness = parseStatusBrightness(legacyStatus.nightBright)
|
|
2933
|
+
if (config.nightDisplayBrightnessAuto !== brightness.auto) {
|
|
2934
|
+
config.nightDisplayBrightnessAuto = brightness.auto
|
|
2935
|
+
configChangedFields.add('nightDisplayBrightnessAuto')
|
|
2936
|
+
configChanged = true
|
|
2937
|
+
}
|
|
2938
|
+
if (config.nightDisplayBrightness !== brightness.value) {
|
|
2939
|
+
config.nightDisplayBrightness = brightness.value
|
|
2376
2940
|
configChangedFields.add('nightDisplayBrightness')
|
|
2377
2941
|
configChanged = true
|
|
2378
2942
|
}
|
|
@@ -2523,8 +3087,8 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
2523
3087
|
* Uses exhaustive switch statement pattern to ensure all YotoEventsMessage fields are handled.
|
|
2524
3088
|
*
|
|
2525
3089
|
* Event fields are categorized as:
|
|
2526
|
-
* - STATUS: volume
|
|
2527
|
-
* - CONFIG: repeatAll
|
|
3090
|
+
* - STATUS: volume, volumeMax
|
|
3091
|
+
* - CONFIG: repeatAll
|
|
2528
3092
|
* - PLAYBACK: streaming, sleepTimerActive, sleepTimerSeconds, trackLength,
|
|
2529
3093
|
* position, cardId, source, playbackStatus, chapterTitle, chapterKey,
|
|
2530
3094
|
* trackTitle, trackKey
|
|
@@ -2540,7 +3104,7 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
2540
3104
|
const { status, config, playback } = this.#state
|
|
2541
3105
|
/** @type {Set<keyof YotoDeviceStatus>} */
|
|
2542
3106
|
const statusChangedFields = new Set()
|
|
2543
|
-
/** @type {Set<keyof
|
|
3107
|
+
/** @type {Set<keyof YotoDeviceModelConfig>} */
|
|
2544
3108
|
const configChangedFields = new Set()
|
|
2545
3109
|
/** @type {Set<keyof YotoPlaybackState>} */
|
|
2546
3110
|
const playbackChangedFields = new Set()
|
|
@@ -2571,7 +3135,6 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
2571
3135
|
}
|
|
2572
3136
|
case 'volumeMax': {
|
|
2573
3137
|
// volumeMax is the current effective max volume limit (config-derived)
|
|
2574
|
-
// Store as string to match config type (maxVolumeLimit is string)
|
|
2575
3138
|
if (eventsMessage.volumeMax !== undefined && status.maxVolume !== eventsMessage.volumeMax) {
|
|
2576
3139
|
status.maxVolume = eventsMessage.volumeMax
|
|
2577
3140
|
statusChangedFields.add('maxVolume')
|
|
@@ -2750,40 +3313,43 @@ function createEmptyPlaybackState () {
|
|
|
2750
3313
|
|
|
2751
3314
|
/**
|
|
2752
3315
|
* Create an empty device config object
|
|
2753
|
-
* @returns {
|
|
3316
|
+
* @returns {YotoDeviceModelConfig}
|
|
2754
3317
|
*/
|
|
2755
3318
|
function createEmptyDeviceConfig () {
|
|
2756
3319
|
return {
|
|
2757
3320
|
alarms: [],
|
|
2758
3321
|
ambientColour: '#000000',
|
|
2759
|
-
bluetoothEnabled:
|
|
3322
|
+
bluetoothEnabled: false,
|
|
2760
3323
|
btHeadphonesEnabled: false,
|
|
2761
3324
|
clockFace: 'digital-sun',
|
|
2762
|
-
dayDisplayBrightness:
|
|
3325
|
+
dayDisplayBrightness: null,
|
|
3326
|
+
dayDisplayBrightnessAuto: true,
|
|
2763
3327
|
dayTime: '07:00',
|
|
2764
3328
|
dayYotoDaily: '',
|
|
2765
3329
|
dayYotoRadio: '',
|
|
2766
|
-
daySoundsOff:
|
|
2767
|
-
displayDimBrightness:
|
|
2768
|
-
displayDimTimeout:
|
|
3330
|
+
daySoundsOff: false,
|
|
3331
|
+
displayDimBrightness: 0,
|
|
3332
|
+
displayDimTimeout: 30,
|
|
2769
3333
|
headphonesVolumeLimited: false,
|
|
2770
|
-
hourFormat:
|
|
3334
|
+
hourFormat: 12,
|
|
2771
3335
|
locale: 'en',
|
|
2772
3336
|
logLevel: 'none',
|
|
2773
|
-
maxVolumeLimit:
|
|
3337
|
+
maxVolumeLimit: 16,
|
|
2774
3338
|
nightAmbientColour: '#000000',
|
|
2775
|
-
nightDisplayBrightness:
|
|
2776
|
-
|
|
3339
|
+
nightDisplayBrightness: null,
|
|
3340
|
+
nightDisplayBrightnessAuto: true,
|
|
3341
|
+
nightMaxVolumeLimit: 10,
|
|
2777
3342
|
nightTime: '19:00',
|
|
2778
3343
|
nightYotoDaily: '',
|
|
2779
|
-
nightYotoRadio:
|
|
2780
|
-
|
|
3344
|
+
nightYotoRadio: null,
|
|
3345
|
+
nightYotoRadioEnabled: false,
|
|
3346
|
+
nightSoundsOff: false,
|
|
2781
3347
|
pausePowerButton: false,
|
|
2782
3348
|
pauseVolumeDown: false,
|
|
2783
3349
|
repeatAll: false,
|
|
2784
3350
|
showDiagnostics: false,
|
|
2785
|
-
shutdownTimeout:
|
|
2786
|
-
systemVolume:
|
|
3351
|
+
shutdownTimeout: 900,
|
|
3352
|
+
systemVolume: 100,
|
|
2787
3353
|
timezone: '',
|
|
2788
3354
|
volumeLevel: 'safe'
|
|
2789
3355
|
}
|