homebridge-yoto 0.0.1 → 0.0.3
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/AGENTS.md +253 -0
- package/PLAN.md +609 -0
- package/README.md +315 -6
- package/config.schema.json +222 -0
- package/index.js +15 -2
- package/index.test.js +4 -4
- package/lib/auth.js +220 -0
- package/lib/constants.js +157 -0
- package/lib/platform.js +269 -0
- package/lib/playerAccessory.js +1452 -0
- package/lib/types.js +233 -0
- package/lib/yotoApi.js +260 -0
- package/lib/yotoMqtt.js +521 -0
- package/package.json +14 -7
- package/lib/.keep +0 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# Agent Development Notes
|
|
2
|
+
|
|
3
|
+
This document contains patterns, conventions, and guidelines for developing the homebridge-yoto plugin.
|
|
4
|
+
|
|
5
|
+
## JSDoc Typing Patterns
|
|
6
|
+
|
|
7
|
+
### Use TypeScript-in-JavaScript (ts-in-js)
|
|
8
|
+
|
|
9
|
+
All source files use `.js` extensions with JSDoc comments for type safety. This provides type checking without TypeScript compilation overhead.
|
|
10
|
+
|
|
11
|
+
### Avoid `any` types
|
|
12
|
+
|
|
13
|
+
Always provide specific types. Use `unknown` when the type is truly unknown, then narrow it with type guards.
|
|
14
|
+
|
|
15
|
+
**Bad:**
|
|
16
|
+
```javascript
|
|
17
|
+
/**
|
|
18
|
+
* @param {any} data
|
|
19
|
+
*/
|
|
20
|
+
function processData(data) {
|
|
21
|
+
return data.value;
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Good:**
|
|
26
|
+
```javascript
|
|
27
|
+
/**
|
|
28
|
+
* @param {YotoDeviceStatus} status
|
|
29
|
+
* @returns {number}
|
|
30
|
+
*/
|
|
31
|
+
function getBatteryLevel(status) {
|
|
32
|
+
return status.batteryLevelPercentage;
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Use @ts-expect-error over @ts-ignore
|
|
37
|
+
|
|
38
|
+
When you must suppress a TypeScript error, use `@ts-expect-error` with a comment explaining why. This will error if the issue is fixed, prompting cleanup.
|
|
39
|
+
|
|
40
|
+
**Bad:**
|
|
41
|
+
```javascript
|
|
42
|
+
// @ts-ignore
|
|
43
|
+
const value = accessory.context.device.unknownProperty;
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Good:**
|
|
47
|
+
```javascript
|
|
48
|
+
// @ts-expect-error - API may return undefined for offline devices
|
|
49
|
+
const lastSeen = accessory.context.device.lastSeenAt;
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Use newer @import syntax in jsdoc/ts-in-js for types only
|
|
53
|
+
|
|
54
|
+
Import types using the `@import` JSDoc tag to avoid runtime imports of type-only dependencies.
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
/** @import { API, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service, Characteristic } from 'homebridge' */
|
|
58
|
+
/** @import { YotoDevice, YotoDeviceStatus, YotoDeviceConfig } from './types.js' */
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @param {Logger} log
|
|
62
|
+
* @param {PlatformConfig} config
|
|
63
|
+
* @param {API} api
|
|
64
|
+
*/
|
|
65
|
+
export function YotoPlatform(log, config, api) {
|
|
66
|
+
this.log = log;
|
|
67
|
+
this.config = config;
|
|
68
|
+
this.api = api;
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Import Consolidation
|
|
73
|
+
|
|
74
|
+
Keep regular imports and type imports separate. Use single-line imports for types when possible.
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
import { EventEmitter } from 'events';
|
|
78
|
+
|
|
79
|
+
/** @import { YotoDevice } from './types.js' */
|
|
80
|
+
/** @import { API, PlatformAccessory } from 'homebridge' */
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Homebridge Type Import Patterns
|
|
84
|
+
|
|
85
|
+
Homebridge types are available from the `homebridge` package:
|
|
86
|
+
|
|
87
|
+
```javascript
|
|
88
|
+
/** @import { API, Characteristic, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service } from 'homebridge' */
|
|
89
|
+
/** @import { CharacteristicValue } from 'homebridge' */
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
For HAP (HomeKit Accessory Protocol) types:
|
|
93
|
+
|
|
94
|
+
```javascript
|
|
95
|
+
/** @import { HAPStatus } from 'homebridge' */
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @throws {import('homebridge').HapStatusError}
|
|
99
|
+
*/
|
|
100
|
+
function throwNotResponding() {
|
|
101
|
+
throw new this.api.hap.HapStatusError(this.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE);
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Prefer Schema-Based Types
|
|
106
|
+
|
|
107
|
+
Define types for API responses and configuration objects using JSDoc typedef.
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
/**
|
|
111
|
+
* @typedef {Object} YotoDeviceStatus
|
|
112
|
+
* @property {string} deviceId
|
|
113
|
+
* @property {number} batteryLevelPercentage
|
|
114
|
+
* @property {boolean} isCharging
|
|
115
|
+
* @property {boolean} isOnline
|
|
116
|
+
* @property {string | null} activeCard
|
|
117
|
+
* @property {number} userVolumePercentage
|
|
118
|
+
* @property {number} systemVolumePercentage
|
|
119
|
+
* @property {number} temperatureCelcius
|
|
120
|
+
* @property {number} wifiStrength
|
|
121
|
+
* @property {0 | 1 | 2} cardInsertionState - 0=none, 1=physical, 2=remote
|
|
122
|
+
* @property {-1 | 0 | 1} dayMode - -1=unknown, 0=night, 1=day
|
|
123
|
+
* @property {0 | 1 | 2 | 3} powerSource - 0=battery, 1=V2 dock, 2=USB-C, 3=Qi
|
|
124
|
+
*/
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @typedef {Object} YotoDevice
|
|
128
|
+
* @property {string} deviceId
|
|
129
|
+
* @property {string} name
|
|
130
|
+
* @property {string} description
|
|
131
|
+
* @property {boolean} online
|
|
132
|
+
* @property {string} releaseChannel
|
|
133
|
+
* @property {string} deviceType
|
|
134
|
+
* @property {string} deviceFamily
|
|
135
|
+
* @property {string} deviceGroup
|
|
136
|
+
*/
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @typedef {Object} YotoDeviceConfig
|
|
140
|
+
* @property {string} name
|
|
141
|
+
* @property {YotoDeviceConfigSettings} config
|
|
142
|
+
*/
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @typedef {Object} YotoDeviceConfigSettings
|
|
146
|
+
* @property {any[]} alarms
|
|
147
|
+
* @property {string} ambientColour
|
|
148
|
+
* @property {string} bluetoothEnabled
|
|
149
|
+
* @property {boolean} btHeadphonesEnabled
|
|
150
|
+
* @property {string} clockFace
|
|
151
|
+
* @property {string} dayDisplayBrightness
|
|
152
|
+
* @property {string} dayTime
|
|
153
|
+
* @property {string} maxVolumeLimit
|
|
154
|
+
* @property {string} nightAmbientColour
|
|
155
|
+
* @property {string} nightDisplayBrightness
|
|
156
|
+
* @property {string} nightMaxVolumeLimit
|
|
157
|
+
* @property {string} nightTime
|
|
158
|
+
* @property {boolean} repeatAll
|
|
159
|
+
* @property {string} shutdownTimeout
|
|
160
|
+
* @property {string} volumeLevel
|
|
161
|
+
*/
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### API Response Typing
|
|
165
|
+
|
|
166
|
+
Type API responses explicitly:
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
/**
|
|
170
|
+
* @typedef {Object} YotoApiDevicesResponse
|
|
171
|
+
* @property {YotoDevice[]} devices
|
|
172
|
+
*/
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get all devices for authenticated user
|
|
176
|
+
* @returns {Promise<YotoDevice[]>}
|
|
177
|
+
*/
|
|
178
|
+
async function getDevices() {
|
|
179
|
+
const response = await fetch('https://api.yotoplay.com/device-v2/devices/mine', {
|
|
180
|
+
headers: { 'Authorization': `Bearer ${this.accessToken}` }
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
/** @type {YotoApiDevicesResponse} */
|
|
184
|
+
const data = await response.json();
|
|
185
|
+
return data.devices;
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Platform Accessory Context Typing
|
|
190
|
+
|
|
191
|
+
Define the context object structure stored in accessories:
|
|
192
|
+
|
|
193
|
+
```javascript
|
|
194
|
+
/**
|
|
195
|
+
* @typedef {Object} YotoAccessoryContext
|
|
196
|
+
* @property {YotoDevice} device
|
|
197
|
+
* @property {YotoDeviceStatus | null} lastStatus
|
|
198
|
+
* @property {number} lastUpdate
|
|
199
|
+
*/
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* @param {PlatformAccessory<YotoAccessoryContext>} accessory
|
|
203
|
+
*/
|
|
204
|
+
function configureAccessory(accessory) {
|
|
205
|
+
const device = accessory.context.device;
|
|
206
|
+
this.log.info('Restoring device:', device.name);
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Nullable Fields in API Responses
|
|
211
|
+
|
|
212
|
+
Use union types with `null` for fields that may be absent:
|
|
213
|
+
|
|
214
|
+
```javascript
|
|
215
|
+
/**
|
|
216
|
+
* @typedef {Object} YotoCardContent
|
|
217
|
+
* @property {string} cardId
|
|
218
|
+
* @property {string} title
|
|
219
|
+
* @property {string | null} author
|
|
220
|
+
* @property {string | null} description
|
|
221
|
+
* @property {YotoChapter[] | null} chapters
|
|
222
|
+
*/
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Optional vs Nullable
|
|
226
|
+
|
|
227
|
+
Distinguish between optional fields (may not exist) and nullable fields (exists but may be null):
|
|
228
|
+
|
|
229
|
+
```javascript
|
|
230
|
+
/**
|
|
231
|
+
* @typedef {Object} YotoPlayerState
|
|
232
|
+
* @property {string} deviceId - Always present
|
|
233
|
+
* @property {string | null} activeCard - Present but may be null
|
|
234
|
+
* @property {string} [lastPlayedCard] - May not be present in response
|
|
235
|
+
*/
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Changelog Management
|
|
239
|
+
|
|
240
|
+
**NEVER manually edit CHANGELOG.md**
|
|
241
|
+
|
|
242
|
+
The changelog is automatically generated using `auto-changelog` during the version bump process:
|
|
243
|
+
|
|
244
|
+
- When running `npm version [patch|minor|major]`, the `version:changelog` script runs automatically
|
|
245
|
+
- It uses the keepachangelog template
|
|
246
|
+
- Detects breaking changes via `BREAKING CHANGE:` pattern in commit messages
|
|
247
|
+
- Generates entries from git commits
|
|
248
|
+
|
|
249
|
+
To ensure proper changelog generation:
|
|
250
|
+
- Write meaningful git commit messages
|
|
251
|
+
- Use conventional commit format when possible
|
|
252
|
+
- Mark breaking changes with `BREAKING CHANGE:` in commit body
|
|
253
|
+
- Let the automation handle changelog updates during `npm version`
|