homebridge-yoto 0.0.28 → 0.0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @fileoverview Utility functions for the plugin
3
+ *
4
+ * Includes code adapted from `homebridge-plugin-utils`:
5
+ * - Source: https://github.com/hjdhjd/homebridge-plugin-utils/blob/main/src/util.ts
6
+ *
7
+ * ISC License
8
+ * ===========
9
+ *
10
+ * Copyright (c) 2017-2025, HJD https://github.com/hjdhjd
11
+ *
12
+ * Permission to use, copy, modify, and/or distribute this software for any purpose
13
+ * with or without fee is hereby granted, provided that the above copyright notice
14
+ * and this permission notice appear in all copies.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
17
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
18
+ * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
19
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
20
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
21
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
22
+ * THIS SOFTWARE.
23
+ */
24
+
25
+ /**
26
+ * Sanitize an accessory/service name according to HomeKit naming conventions.
27
+ *
28
+ * Starts and ends with a letter or number. Exception: may end with a period.
29
+ * May have the following special characters: -"',.#&.
30
+ * Must not include emojis.
31
+ *
32
+ * @param {string} name - The name to sanitize
33
+ * @returns {string} The HomeKit-sanitized version of the name
34
+ */
35
+ export function sanitizeName (name) {
36
+ return name
37
+ // Replace any disallowed char (including emojis) with a space.
38
+ .replace(/[^\p{L}\p{N}\-"'.,#&\s]/gu, ' ')
39
+ // Collapse multiple spaces to one.
40
+ .replace(/\s+/g, ' ')
41
+ // Trim spaces at the beginning and end of the string.
42
+ .trim()
43
+ // Strip any leading non-letter/number.
44
+ .replace(/^[^\p{L}\p{N}]+/u, '')
45
+ // Collapse two or more trailing periods into one.
46
+ .replace(/\.{2,}$/g, '.')
47
+ // Remove any other trailing char that's not letter/number/period.
48
+ .replace(/[^\p{L}\p{N}.]$/u, '')
49
+ }
@@ -0,0 +1,16 @@
1
+ import { configSchema } from '../config.schema.cjs'
2
+
3
+ /**
4
+ * This is the name of the platform that users will use to register the plugin in the Homebridge config.json
5
+ */
6
+ export const PLATFORM_NAME = 'Yoto'
7
+
8
+ /**
9
+ * This must match the name of your plugin as defined the package.json `name` property
10
+ */
11
+ export const PLUGIN_NAME = 'homebridge-yoto'
12
+
13
+ /**
14
+ * Default OAuth Client ID from config schema
15
+ */
16
+ export const DEFAULT_CLIENT_ID = configSchema.schema.properties.clientId.default
@@ -0,0 +1,34 @@
1
+ /** @import { Service, Characteristic } from 'homebridge' */
2
+ import { sanitizeName } from './sanitize-name.js'
3
+
4
+ /**
5
+ * Apply HomeKit-visible naming to a service.
6
+ *
7
+ * We set both `Name` and `ConfiguredName` on every service we manage so HomeKit tiles are consistently labeled.
8
+ *
9
+ * @param {object} params
10
+ * @param {Service} params.service
11
+ * @param {string} params.name
12
+ * @param {typeof Characteristic} params.Characteristic
13
+ * @returns {void}
14
+ */
15
+ export function syncServiceNames ({
16
+ Characteristic,
17
+ service,
18
+ name
19
+ }) {
20
+ const sanitizedName = sanitizeName(name)
21
+ service.displayName = sanitizedName
22
+
23
+ service.updateCharacteristic(Characteristic.Name, sanitizedName)
24
+
25
+ // Set ConfiguredName on all services, not just ones that say they support it.
26
+ // This is the only way to set the service name inside an accessory.
27
+ // const hasConfiguredNameCharacteristic = service.characteristics.some(c => c.UUID === Characteristic.ConfiguredName.UUID)
28
+ // const hasConfiguredNameOptional = service.optionalCharacteristics.some(c => c.UUID === Characteristic.ConfiguredName.UUID)
29
+ // if (!hasConfiguredNameCharacteristic && !hasConfiguredNameOptional) {
30
+ // service.addOptionalCharacteristic(Characteristic.ConfiguredName)
31
+ // }
32
+
33
+ service.updateCharacteristic(Characteristic.ConfiguredName, sanitizedName)
34
+ }
package/package.json CHANGED
@@ -1,28 +1,30 @@
1
1
  {
2
2
  "name": "homebridge-yoto",
3
3
  "description": "Control your Yoto players through Apple HomeKit with real-time MQTT updates",
4
- "version": "0.0.28",
4
+ "version": "0.0.32",
5
5
  "author": "Bret Comnes <bcomnes@gmail.com> (https://bret.io)",
6
6
  "bugs": {
7
7
  "url": "https://github.com/bcomnes/homebridge-yoto/issues"
8
8
  },
9
9
  "dependencies": {
10
- "homebridge-lib": "^7.2.0",
11
- "mqtt": "^5.14.1"
10
+ "@homebridge/plugin-ui-utils": "^2.1.2",
11
+ "color-convert": "3.1.3",
12
+ "yoto-nodejs-client": "^0.0.7"
12
13
  },
13
14
  "devDependencies": {
14
15
  "@types/node": "^25.0.0",
15
16
  "@voxpelli/tsconfig": "^16.1.0",
16
17
  "auto-changelog": "^2.0.0",
17
18
  "c8": "^10.0.0",
19
+ "eslint": "^9.39.2",
18
20
  "gh-release": "^7.0.0",
19
- "homebridge": "^1.6.1",
21
+ "homebridge": "^2.0.0-beta.66",
20
22
  "neostandard": "^0.12.0",
21
23
  "npm-run-all2": "^8.0.1",
22
24
  "typescript": "~5.9.3"
23
25
  },
24
26
  "engines": {
25
- "node": ">=20",
27
+ "node": ">=22",
26
28
  "npm": ">=10",
27
29
  "homebridge": "^1.8.0 || ^2.0.0-beta.0"
28
30
  },
@@ -36,26 +38,21 @@
36
38
  "module": "index.js",
37
39
  "main": "index.js",
38
40
  "types": "index.d.ts",
41
+ "files": [
42
+ "index.js",
43
+ "index.d.ts",
44
+ "index.d.ts.map",
45
+ "config.schema.json",
46
+ "config.schema.cjs",
47
+ "lib/",
48
+ "homebridge-ui/",
49
+ "README.md",
50
+ "LICENSE"
51
+ ],
39
52
  "repository": {
40
53
  "type": "git",
41
54
  "url": "https://github.com/bcomnes/homebridge-yoto.git"
42
55
  },
43
- "scripts": {
44
- "prepublishOnly": "npm run build && git push --follow-tags && gh-release -y",
45
- "postpublish": "npm run clean",
46
- "test": "run-s test:*",
47
- "test:lint": "eslint",
48
- "test:tsc": "tsc",
49
- "test:node-test": "c8 node --test --test-reporter spec",
50
- "version": "run-s version:*",
51
- "version:changelog": "auto-changelog -p --template keepachangelog auto-changelog --breaking-pattern 'BREAKING CHANGE:'",
52
- "version:git": "git add CHANGELOG.md",
53
- "build": "npm run clean && run-p build:*",
54
- "build:declaration": "tsc -p declaration.tsconfig.json",
55
- "clean": "run-p clean:*",
56
- "clean:declarations-top": "rm -rf $(find . -maxdepth 1 -type f -name '*.d.ts*')",
57
- "clean:declarations-lib": "rm -rf $(find lib -type f -name '*.d.ts*' ! -name '*-types.d.ts')"
58
- },
59
56
  "funding": {
60
57
  "type": "individual",
61
58
  "url": "https://github.com/sponsors/bcomnes"
@@ -65,5 +62,14 @@
65
62
  "lcov",
66
63
  "text"
67
64
  ]
65
+ },
66
+ "scripts": {
67
+ "test": "run-s test:*",
68
+ "test:lint": "eslint",
69
+ "test:tsc": "tsc",
70
+ "test:node-test": "c8 node --test --test-reporter spec",
71
+ "version": "run-s version:*",
72
+ "version:changelog": "auto-changelog -p --template keepachangelog auto-changelog --breaking-pattern 'BREAKING CHANGE:'",
73
+ "version:git": "git add CHANGELOG.md"
68
74
  }
69
- }
75
+ }
@@ -1,18 +0,0 @@
1
- # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
2
- version: 2
3
- updates:
4
- - package-ecosystem: "npm"
5
- directory: "/"
6
- # Check the npm registry for updates every day (weekdays)
7
- schedule:
8
- interval: "daily"
9
- groups:
10
- typescript:
11
- patterns:
12
- - "typescript"
13
- - "@types/node"
14
- - "@voxpelli/tsconfig"
15
- - package-ecosystem: "github-actions"
16
- directory: "/"
17
- schedule:
18
- interval: "daily"
@@ -1,4 +0,0 @@
1
- # These are supported funding model platforms
2
-
3
- github: ['bcomnes']
4
- custom: ['https://bret.io']
@@ -1,41 +0,0 @@
1
- name: npm bump
2
-
3
- on:
4
- workflow_dispatch:
5
- inputs:
6
- newversion:
7
- description: 'npm version {major,minor,patch}'
8
- required: true
9
-
10
- env:
11
- FORCE_COLOR: 1
12
-
13
- concurrency: # prevent concurrent releases
14
- group: npm-bump
15
- cancel-in-progress: true
16
-
17
- jobs:
18
- version_and_release:
19
- runs-on: ubuntu-latest
20
- steps:
21
- - uses: actions/checkout@v6
22
- with:
23
- # fetch full history so things like auto-changelog work properly
24
- fetch-depth: 0
25
- - name: Use Node.js ${{ env.node }}
26
- uses: actions/setup-node@v6
27
- with:
28
- node-version-file: package.json
29
- # setting a registry enables the NODE_AUTH_TOKEN env variable where we can set an npm token. REQUIRED
30
- registry-url: 'https://registry.npmjs.org'
31
- - run: npm i
32
- - run: npm test
33
- - name: npm version && npm publish
34
- uses: bcomnes/npm-bump@v2
35
- with:
36
- git_email: bcomnes@gmail.com
37
- git_username: ${{ github.actor }}
38
- newversion: ${{ github.event.inputs.newversion }}
39
- github_token: ${{ secrets.GITHUB_TOKEN }} # built in actions token. Passed tp gh-release if in use.
40
- npm_token: ${{ secrets.NPM_TOKEN }} # user set secret token generated at npm
41
-
@@ -1,37 +0,0 @@
1
- name: tests
2
-
3
- on: [pull_request, push]
4
-
5
- env:
6
- FORCE_COLOR: 1
7
-
8
- jobs:
9
- test:
10
- runs-on: ${{ matrix.os }}
11
-
12
- strategy:
13
- fail-fast: false
14
- matrix:
15
- os: [ubuntu-latest]
16
- node: ['lts/*']
17
-
18
- steps:
19
- - uses: actions/checkout@v6
20
- - name: Use Node.js ${{ matrix.node }}
21
- uses: actions/setup-node@v6
22
- with:
23
- node-version: ${{ matrix.node }}
24
- - run: npm i
25
- - run: npm test --color=always
26
-
27
- automerge:
28
- needs: test
29
- runs-on: ubuntu-latest
30
- permissions:
31
- pull-requests: write
32
- contents: write
33
- steps:
34
- - uses: fastify/github-action-merge-dependabot@v3
35
- if: ${{ github.actor == 'dependabot[bot]' && github.event_name == 'pull_request' && contains(github.head_ref, 'dependabot/github_actions') }}
36
- with:
37
- github-token: ${{secrets.github_token}}
package/AGENTS.md DELETED
@@ -1,253 +0,0 @@
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`
package/CHANGELOG.md DELETED
@@ -1,8 +0,0 @@
1
- # homebridge-yoto Change Log
2
- All notable changes to this project will be documented in this file.
3
- This project adheres to [Semantic Versioning](http://semver.org/).
4
-
5
- ## Unreleased
6
-
7
- ## 1.0.0 - {{date}}
8
- * ...
package/CONTRIBUTING.md DELETED
@@ -1,34 +0,0 @@
1
- # Contributing
2
-
3
- ## Releasing
4
-
5
- Changelog and releasing are automated with npm scripts and actions. To create a release:
6
-
7
- - Navigate to the Actions tab.
8
- - Select the `npm bump` action.
9
- - Trigger the action, specifying the semantic version bump that is needed.
10
- - Changelog, GitHub release, and npm publish are handled by the action.
11
- - An in-depth review of this system is documented here: [bret.io/projects/package-automation](https://bret.io/projects/package-automation/).
12
-
13
- If for some reason this isn't working or a local release is preferred, follow these steps:
14
-
15
- - Ensure a clean working git workspace.
16
- - Run `npm version {patch, minor, major}`.
17
- - This will update the version number and generate the changelog.
18
- - Run `npm publish`.
19
- - This will push your local git branch and tags to the default remote, perform a [gh-release](https://ghub.io/gh-release), and create an npm publication.
20
-
21
- ## Guidelines
22
-
23
- - Patches, ideas, and changes are welcome.
24
- - Fixes are almost always welcome.
25
- - Features are sometimes welcome.
26
- - Please open an issue to discuss the idea prior to spending lots of time on the problem.
27
- - It may be rejected.
28
- - If you don't want to wait for the discussion to commence and you really want to jump into the implementation work, be prepared to fork the project if the idea is respectfully declined.
29
- - Try to stay within the style of the existing code.
30
- - All tests must pass.
31
- - Additional features or code paths must be tested.
32
- - Aim for 100% test coverage.
33
- - Questions are welcome. However, unless there is an official support contract established between the maintainers and the requester, support is not guaranteed.
34
- - Contributors reserve the right to walk away from this project at any moment, with or without notice.