@switchbot/homebridge-switchbot 5.2.0 → 5.2.1-beta.1
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/.github/copilot-instructions.md +72 -169
- package/.github/npm-version-script-esm.js +100 -0
- package/.github/workflows/ci.yml +4 -1
- package/CHANGELOG.md +9 -0
- package/CLAUDE.md +13 -0
- package/README.md +2 -0
- package/config.schema.json +30 -0
- package/dist/SwitchBotHAPPlatform.d.ts.map +1 -1
- package/dist/SwitchBotHAPPlatform.js +32 -17
- package/dist/SwitchBotHAPPlatform.js.map +1 -1
- package/dist/devices/genericDevice.d.ts +2 -0
- package/dist/devices/genericDevice.d.ts.map +1 -1
- package/dist/devices/genericDevice.js +75 -0
- package/dist/devices/genericDevice.js.map +1 -1
- package/docs/index.html +2 -0
- package/docs/variables/default.html +1 -1
- package/package.json +1 -1
- package/src/SwitchBotHAPPlatform.ts +32 -16
- package/src/devices/genericDevice.ts +82 -1
- package/test/device/bot-hap.spec.ts +184 -0
- package/src/device-types.js.map +0 -1
- package/src/deviceCommandMapper.js +0 -319
- package/src/deviceCommandMapper.js.map +0 -1
- package/src/errors.js +0 -32
- package/src/errors.js.map +0 -1
- package/src/settings.js +0 -8
- package/src/settings.js.map +0 -1
- package/src/switchbotClient.js +0 -247
- package/src/switchbotClient.js.map +0 -1
|
@@ -150,192 +150,95 @@ node -p "const v=require('./package.json').version.split('.'); v[2]=parseInt(v[2
|
|
|
150
150
|
3. Proceed with patch increment as fallback
|
|
151
151
|
4. Create or target the appropriate beta branch based on version increment
|
|
152
152
|
|
|
153
|
+
## Following node-switchbot Changes
|
|
154
|
+
|
|
155
|
+
node-switchbot tracks [pySwitchbot](https://github.com/sblibs/pySwitchbot). After node-switchbot
|
|
156
|
+
ships a change, this repo usually needs a follow-up PR so the plugin can use it:
|
|
157
|
+
|
|
158
|
+
1. Read the node-switchbot PR/release and list what consumers see: new device types, renamed or
|
|
159
|
+
split model identities (`modelName`, OpenAPI `deviceType`), new status fields, new commands.
|
|
160
|
+
2. Update `src/device-types.ts` (`DEVICE_TYPES`, `DEVICE_TYPE_NORMALIZATION_MAP`, including
|
|
161
|
+
lowercase product codes such as `woplugjp`), `DEVICE_CLASS_MAP` in `src/deviceFactory.ts`, and
|
|
162
|
+
accessory behaviour in `src/devices/genericDevice.ts`.
|
|
163
|
+
3. Make sure no existing saved `configDeviceType` stops resolving (normalization map or
|
|
164
|
+
`src/homebridge-ui/utils/device-migration.ts`).
|
|
165
|
+
4. Bump `node-switchbot` in `package.json` only to a version already published on npm.
|
|
166
|
+
5. Add `createDevice` tests under `test/device/` asserting each affected type's accessory class.
|
|
167
|
+
6. Run build, test and lint; commit the regenerated `config.schema.json` if `DEVICE_TYPES` changed.
|
|
168
|
+
|
|
153
169
|
## Working Effectively
|
|
154
170
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
1. **Install dependencies:**
|
|
159
|
-
```bash
|
|
160
|
-
npm install --legacy-peer-deps
|
|
161
|
-
```
|
|
162
|
-
- CRITICAL: Must use `--legacy-peer-deps` flag due to TypeDoc dependency conflicts
|
|
163
|
-
- Takes approximately 30-45 seconds
|
|
164
|
-
- This is a known workaround documented in the codebase
|
|
165
|
-
|
|
166
|
-
2. **Build the project:**
|
|
167
|
-
```bash
|
|
168
|
-
npm run build
|
|
169
|
-
```
|
|
170
|
-
- Takes approximately 7-10 seconds
|
|
171
|
-
- Compiles TypeScript and copies UI files
|
|
172
|
-
- Output goes to `dist/` directory
|
|
173
|
-
|
|
174
|
-
3. **Run tests:**
|
|
175
|
-
```bash
|
|
176
|
-
npm run test
|
|
177
|
-
```
|
|
178
|
-
- Takes approximately 2-3 seconds
|
|
179
|
-
- Currently minimal test suite (1 test file)
|
|
180
|
-
- Use `npm run test:watch` for continuous testing during development
|
|
181
|
-
|
|
182
|
-
4. **Generate documentation:**
|
|
183
|
-
```bash
|
|
184
|
-
npm run docs
|
|
185
|
-
```
|
|
186
|
-
- Takes approximately 5-10 seconds
|
|
187
|
-
- Generates TypeDoc documentation in `docs/` directory
|
|
188
|
-
|
|
189
|
-
### Full Build and Validation Workflow
|
|
190
|
-
Complete validation workflow (approximately 30-45 seconds total):
|
|
191
|
-
```bash
|
|
192
|
-
npm install --legacy-peer-deps && npm run build && npm run test && npm run docs && npm run docs:lint && npm run docs:theme
|
|
193
|
-
```
|
|
194
|
-
- NEVER CANCEL: Set timeout to 90+ seconds for this complete workflow
|
|
195
|
-
- This validates TypeScript compilation, plugin loading, and documentation generation
|
|
196
|
-
|
|
197
|
-
### Development Commands
|
|
198
|
-
|
|
199
|
-
- **Watch mode for development:**
|
|
200
|
-
```bash
|
|
201
|
-
npm run watch
|
|
202
|
-
```
|
|
203
|
-
- Builds, links plugin locally, and starts nodemon for file watching
|
|
204
|
-
- Requires local Homebridge installation
|
|
205
|
-
|
|
206
|
-
- **Clean build artifacts:**
|
|
207
|
-
```bash
|
|
208
|
-
npm run clean
|
|
209
|
-
```
|
|
210
|
-
- Removes `dist/` directory
|
|
211
|
-
|
|
212
|
-
- **Check for outdated dependencies:**
|
|
213
|
-
```bash
|
|
214
|
-
npm run check
|
|
215
|
-
```
|
|
216
|
-
- WARNING: This will fail due to dependency conflicts - use with `--legacy-peer-deps`
|
|
217
|
-
|
|
218
|
-
### Known Issues and Workarounds
|
|
219
|
-
|
|
220
|
-
- **Linting fails:** `npm run lint` and `npm run lint:fix` currently fail due to ESLint configuration conflicts with import/extensions rule. Document any code style issues manually.
|
|
221
|
-
|
|
222
|
-
- **Dependency conflicts:** Always use `--legacy-peer-deps` flag for npm install due to TypeDoc theme dependency version mismatches.
|
|
223
|
-
|
|
224
|
-
- **Coverage and docs artifacts:** Add `coverage/` and `docs/` to `.gitignore` to prevent committing build artifacts.
|
|
225
|
-
|
|
226
|
-
## Validation Scenarios
|
|
227
|
-
|
|
228
|
-
### Manual Plugin Validation
|
|
229
|
-
After making changes, ALWAYS run these validation steps:
|
|
230
|
-
|
|
231
|
-
1. **Verify plugin builds and loads:**
|
|
232
|
-
```bash
|
|
233
|
-
npm run build && node -e "
|
|
234
|
-
try {
|
|
235
|
-
const plugin = require('./dist/index.js');
|
|
236
|
-
console.log('Plugin loads successfully');
|
|
237
|
-
console.log('Plugin type:', typeof plugin.default);
|
|
238
|
-
} catch (e) {
|
|
239
|
-
console.error('Plugin load failed:', e.message);
|
|
240
|
-
}"
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
2. **Test plugin registration:**
|
|
244
|
-
- Plugin exports a default function that registers with Homebridge
|
|
245
|
-
- Uses `PLUGIN_NAME = '@switchbot/homebridge-switchbot'` and `PLATFORM_NAME = 'SwitchBot'`
|
|
246
|
-
- Main platform class is `SwitchBotPlatform`
|
|
247
|
-
|
|
248
|
-
3. **Validate TypeScript compilation:**
|
|
249
|
-
- Must have no TypeScript errors
|
|
250
|
-
- Check `dist/` directory contains all expected `.js`, `.d.ts`, and `.map` files
|
|
251
|
-
|
|
252
|
-
4. **Test documentation generation:**
|
|
253
|
-
```bash
|
|
254
|
-
npm run docs:lint
|
|
255
|
-
```
|
|
256
|
-
- Must pass without warnings when using `--treatWarningsAsErrors`
|
|
257
|
-
|
|
258
|
-
### Code Coverage Validation
|
|
259
|
-
Run comprehensive test coverage analysis:
|
|
171
|
+
Node `^22 || ^24`. Run these in order; each must pass before you commit:
|
|
172
|
+
|
|
260
173
|
```bash
|
|
261
|
-
npm
|
|
174
|
+
npm ci # CI uses: npm ci --legacy-peer-deps
|
|
175
|
+
npm run build # syncs DEVICE_TYPES into config.schema.json, cleans dist/, tsc, copies + builds the plugin UI
|
|
176
|
+
npm run test # vitest run
|
|
177
|
+
npm run lint # eslint (src, test, UI html/js) then tsc --noEmit
|
|
262
178
|
```
|
|
263
|
-
|
|
264
|
-
-
|
|
179
|
+
|
|
180
|
+
- `npm run lint:fix` fixes most style issues.
|
|
181
|
+
- `npm run build` needs `rsync` (used by `plugin-ui`).
|
|
182
|
+
- `npm run docs` / `npm run docs:lint` generate and check TypeDoc output; `prepublishOnly` runs them.
|
|
183
|
+
- `npm run watch` builds, links the plugin and runs nodemon against a local Homebridge;
|
|
184
|
+
`npm run watch:ui` also starts a dev Homebridge with Config UI X.
|
|
185
|
+
|
|
186
|
+
### Known pitfalls
|
|
187
|
+
|
|
188
|
+
- **Never commit compiled output under `src/`.** `.gitignore` excludes `src/**/*.js` and
|
|
189
|
+
`src/**/*.js.map`. Vitest resolves `./foo.js` imports to a real `src/foo.js` before `src/foo.ts`,
|
|
190
|
+
so a stale compiled copy silently replaces the source in tests.
|
|
191
|
+
- **`config.schema.json` is generated** from `DEVICE_TYPES` in `src/device-types.ts` during
|
|
192
|
+
`npm run build`. When `DEVICE_TYPES` changes, commit the regenerated schema in the same change.
|
|
193
|
+
- **`DEVICE_CLASS_MAP` keys in `src/deviceFactory.ts` are lowercase.** Unmapped types fall back to
|
|
194
|
+
`GenericDevice`, so add a test for every new type.
|
|
265
195
|
|
|
266
196
|
## Codebase Navigation
|
|
267
197
|
|
|
268
198
|
### Project Structure
|
|
269
199
|
```
|
|
270
|
-
├── src/
|
|
271
|
-
│ ├──
|
|
272
|
-
│
|
|
273
|
-
│
|
|
274
|
-
│
|
|
275
|
-
│
|
|
276
|
-
│ ├──
|
|
277
|
-
│
|
|
278
|
-
│
|
|
279
|
-
│ │
|
|
280
|
-
│
|
|
281
|
-
│
|
|
282
|
-
│
|
|
283
|
-
│ ├──
|
|
284
|
-
│
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
├── config.schema.json
|
|
288
|
-
|
|
289
|
-
├── docs/ # Generated TypeDoc documentation
|
|
290
|
-
└── coverage/ # Test coverage reports
|
|
200
|
+
├── src/
|
|
201
|
+
│ ├── index.ts # Plugin entry point (registers one platform that proxies HAP or Matter)
|
|
202
|
+
│ ├── SwitchBotHAPPlatform.ts # HomeKit (HAP) platform
|
|
203
|
+
│ ├── SwitchBotMatterPlatform.ts # Matter platform
|
|
204
|
+
│ ├── switchbotClient.ts # Wrapper around node-switchbot (BLE + OpenAPI)
|
|
205
|
+
│ ├── deviceFactory.ts # Device type -> accessory class (DEVICE_CLASS_MAP, createDevice)
|
|
206
|
+
│ ├── device-types.ts # DEVICE_TYPES (valid types) + DEVICE_TYPE_NORMALIZATION_MAP (aliases)
|
|
207
|
+
│ ├── deviceCommandMapper.ts # Per-type command handlers
|
|
208
|
+
│ ├── devices/
|
|
209
|
+
│ │ ├── deviceBase.ts # Base accessory class
|
|
210
|
+
│ │ └── genericDevice.ts # GenericDevice and every concrete accessory class
|
|
211
|
+
│ ├── homebridge-ui/ # Config UI X custom UI (server, endpoints, utils, public/)
|
|
212
|
+
│ ├── settings.ts # Config interfaces and constants
|
|
213
|
+
│ ├── errors.ts
|
|
214
|
+
│ └── utils.ts
|
|
215
|
+
├── test/ # Vitest specs (client, device, platform, homebridge-ui, utils, e2e)
|
|
216
|
+
├── scripts/ # sync-device-types, build-ui, free-dev-ports
|
|
217
|
+
├── config.schema.json # Homebridge config schema (configDeviceType enum is generated)
|
|
218
|
+
└── dist/ # Build output (not committed)
|
|
291
219
|
```
|
|
292
220
|
|
|
293
|
-
### Key Development Areas
|
|
294
|
-
|
|
295
|
-
- **Adding new devices:** Extend `src/device/device.ts` base class, implement in new file under `src/device/`
|
|
296
|
-
- **Adding IR devices:** Extend `src/irdevice/irdevice.ts` base class, implement in new file under `src/irdevice/`
|
|
297
|
-
- **Configuration changes:** Update `src/settings.ts` and `config.schema.json`
|
|
298
|
-
- **Platform modifications:** Edit `src/platform.ts` (main class with 2800+ lines)
|
|
299
|
-
- **Utility functions:** Modify `src/utils.ts` for shared functionality
|
|
300
|
-
|
|
301
|
-
### Device Connection Types
|
|
302
|
-
This plugin supports two connection methods to SwitchBot devices:
|
|
303
|
-
1. **OpenAPI Connection:** Requires SwitchBot Cloud API token/secret, works through SwitchBot Hub
|
|
304
|
-
2. **BLE (Bluetooth Low Energy):** Direct device connection, requires device MAC addresses
|
|
305
|
-
|
|
306
221
|
### Common Development Tasks
|
|
307
222
|
|
|
308
|
-
- **
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
- **
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
-
|
|
315
|
-
-
|
|
316
|
-
- Device MAC address validation is handled in utils.ts (`formatDeviceIdAsMac`)
|
|
317
|
-
- BLE connection handling is in the base device classes
|
|
318
|
-
|
|
319
|
-
## Build Time Expectations
|
|
223
|
+
- **New device type or identity from node-switchbot:** add it to `DEVICE_TYPES` and/or
|
|
224
|
+
`DEVICE_TYPE_NORMALIZATION_MAP` in `src/device-types.ts`, map it in `DEVICE_CLASS_MAP` in
|
|
225
|
+
`src/deviceFactory.ts`, then add a `createDevice` test under `test/device/`.
|
|
226
|
+
- **New accessory behaviour:** add or extend a class in `src/devices/genericDevice.ts`.
|
|
227
|
+
- **Saved configs that would stop resolving:** handle them in the normalization map or
|
|
228
|
+
`src/homebridge-ui/utils/device-migration.ts`.
|
|
229
|
+
- **Config UI changes:** edit `src/homebridge-ui/`, then `npm run build`.
|
|
230
|
+
- **node-switchbot upgrades:** bump the dependency only to a version already published on npm.
|
|
320
231
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
- **npm run docs:** 5-10 seconds
|
|
325
|
-
- **Complete validation workflow:** 30-45 seconds total
|
|
326
|
-
- **NEVER CANCEL:** Always wait for builds to complete, set timeouts to 90+ seconds for safety
|
|
232
|
+
### Device Connection Types
|
|
233
|
+
1. **OpenAPI:** SwitchBot Cloud token/secret; works through a SwitchBot Hub.
|
|
234
|
+
2. **BLE:** direct Bluetooth connection via node-switchbot (Linux/macOS).
|
|
327
235
|
|
|
328
236
|
## CI/CD Integration
|
|
329
237
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
Always run the complete validation workflow locally before committing:
|
|
336
|
-
```bash
|
|
337
|
-
npm run build && npm run test && npm run docs:lint
|
|
338
|
-
```
|
|
238
|
+
- `.github/workflows/ci.yml`: build, test and lint on Node 22 and 24 for pushes to `latest`/`beta-*`
|
|
239
|
+
and PRs to `latest`/`beta-*`.
|
|
240
|
+
- `.github/workflows/release.yml`: pushes to `beta-*` publish a beta to npm; pushes to `latest`
|
|
241
|
+
publish a stable release.
|
|
339
242
|
|
|
340
243
|
## Complete Workflow Examples
|
|
341
244
|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This scripts queries the npm registry to pull out the latest version for a given tag.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import assert from 'node:assert'
|
|
8
|
+
import child_process from 'node:child_process'
|
|
9
|
+
import fs from 'node:fs'
|
|
10
|
+
import process from 'node:process'
|
|
11
|
+
|
|
12
|
+
const BRANCH_VERSION_PATTERN = /^([A-Z]+)-(\d+\.\d+\.\d+)$/i
|
|
13
|
+
|
|
14
|
+
// Load the contents of the package.json file
|
|
15
|
+
const packageJSON = JSON.parse(fs.readFileSync('package.json', 'utf8'))
|
|
16
|
+
|
|
17
|
+
const refArgument = process.argv[2]
|
|
18
|
+
const tagArgument = process.argv[3] || 'latest'
|
|
19
|
+
|
|
20
|
+
if (!refArgument) {
|
|
21
|
+
console.error('ref argument is missing')
|
|
22
|
+
console.error('Usage: npm-version-script-esm.js <ref> [tag]')
|
|
23
|
+
process.exit(1)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Queries the NPM registry for the latest version for the provided base version and tag.
|
|
28
|
+
* If the tag is latest, then the base version is returned if it exists. For other tags, the latest
|
|
29
|
+
* version found for that base version and tag is returned.
|
|
30
|
+
* @param baseVersion The base version to query for, e.g. 2.0.0
|
|
31
|
+
* @param tag The tag to query for, e.g. beta or latest
|
|
32
|
+
* @returns {string} Returns the version, or '' if not found
|
|
33
|
+
*/
|
|
34
|
+
function getTagVersionFromNpm(baseVersion, tag) {
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(child_process.execSync(`npm info ${packageJSON.name} versions --json`).toString('utf8').trim())
|
|
37
|
+
.filter(v => tag === 'latest' ? v === baseVersion : v.startsWith(`${baseVersion}-${tag}.`)) // find all published versions for this base version and tag
|
|
38
|
+
.reduce((_, current) => current, '') // choose the last as they're sorted in ascending order, or '' if there are none
|
|
39
|
+
} catch (e) {
|
|
40
|
+
console.error(`Failed to query the npm registry for the latest version for tag: ${tag}`, e)
|
|
41
|
+
// throw e;
|
|
42
|
+
return ''
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function desiredTargetVersion(ref) {
|
|
47
|
+
// ref is a GitHub action ref string
|
|
48
|
+
if (ref.startsWith('refs/pull/')) {
|
|
49
|
+
throw new Error('The version script was executed inside a PR!')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
assert(ref.startsWith('refs/heads/'))
|
|
53
|
+
const branchName = ref.slice('refs/heads/'.length)
|
|
54
|
+
|
|
55
|
+
const results = branchName.match(BRANCH_VERSION_PATTERN)
|
|
56
|
+
if (results !== null) {
|
|
57
|
+
if (results[1] !== tagArgument) {
|
|
58
|
+
console.warn(`The base branch name (${results[1]}) differs from the tag name ${tagArgument}`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return results[2]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
throw new Error(`Malformed branch name for ref: ${ref}. Can't derive the base version. Use a branch name like: beta-x.x.x or alpha-x.x.x`)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// derive the base version from the branch ref
|
|
68
|
+
const baseVersion = desiredTargetVersion(refArgument)
|
|
69
|
+
|
|
70
|
+
// query the npm registry for the latest version of the provided tag name
|
|
71
|
+
const latestReleasedVersion = getTagVersionFromNpm(baseVersion, tagArgument) // e.g. 0.7.0-beta.12
|
|
72
|
+
|
|
73
|
+
let publishTag
|
|
74
|
+
|
|
75
|
+
if (latestReleasedVersion) {
|
|
76
|
+
console.warn(`Latest published version for ${baseVersion} with tag ${tagArgument} is ${latestReleasedVersion}`)
|
|
77
|
+
publishTag = latestReleasedVersion // set this released beta or alpha to be incremented
|
|
78
|
+
} else {
|
|
79
|
+
console.warn(`No published versions for ${baseVersion} with tag ${tagArgument}`)
|
|
80
|
+
publishTag = baseVersion // start off with a new beta or alpha version
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (packageJSON.version !== publishTag) {
|
|
84
|
+
// report the change for CI
|
|
85
|
+
console.warn(`Changing version in package.json from ${packageJSON.version} to ${publishTag}`)
|
|
86
|
+
|
|
87
|
+
// save the package.json
|
|
88
|
+
// JSON.stringify does not end with a newline, but npm writes both of these
|
|
89
|
+
// files with one. Leaving it off makes the file fail an eol-last lint rule,
|
|
90
|
+
// which breaks any repo whose prepublishOnly lints the whole tree.
|
|
91
|
+
packageJSON.version = publishTag
|
|
92
|
+
fs.writeFileSync('package.json', `${JSON.stringify(packageJSON, null, 2)}\n`)
|
|
93
|
+
|
|
94
|
+
// perform the same change to the package-lock.json
|
|
95
|
+
const packageLockJSON = JSON.parse(fs.readFileSync('package-lock.json', 'utf8'))
|
|
96
|
+
packageLockJSON.version = publishTag
|
|
97
|
+
fs.writeFileSync('package-lock.json', `${JSON.stringify(packageLockJSON, null, 2)}\n`)
|
|
98
|
+
} else {
|
|
99
|
+
console.warn(`Leaving version in package.json at ${packageJSON.version}`)
|
|
100
|
+
}
|
package/.github/workflows/ci.yml
CHANGED
|
@@ -4,7 +4,7 @@ on:
|
|
|
4
4
|
push:
|
|
5
5
|
branches: ["main", "latest", "beta-*"]
|
|
6
6
|
pull_request:
|
|
7
|
-
branches: ["main", "latest"]
|
|
7
|
+
branches: ["main", "latest", "beta-*"]
|
|
8
8
|
|
|
9
9
|
permissions:
|
|
10
10
|
contents: read
|
|
@@ -33,3 +33,6 @@ jobs:
|
|
|
33
33
|
|
|
34
34
|
- name: Run tests
|
|
35
35
|
run: npm run test
|
|
36
|
+
|
|
37
|
+
- name: Lint
|
|
38
|
+
run: npm run lint
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
# [5.2.0](https://github.com/OpenWonderLabs/homebridge-switchbot/compare/v5.1.1...v5.2.0) (2026-10-09)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* add a CarbonDioxideSensor service for the Meter Pro (CO2) ([#1413](https://github.com/OpenWonderLabs/homebridge-switchbot/issues/1413)) ([588a59d](https://github.com/OpenWonderLabs/homebridge-switchbot/commit/588a59d8adcde2d77a6c3fb1bf703c1d5cf2cf01)), closes [#1197](https://github.com/OpenWonderLabs/homebridge-switchbot/issues/1197) [#1112](https://github.com/OpenWonderLabs/homebridge-switchbot/issues/1112) [#1197](https://github.com/OpenWonderLabs/homebridge-switchbot/issues/1197)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
1
10
|
# [5.2.0](https://github.com/OpenWonderLabs/homebridge-switchbot/compare/v5.1.0...v5.2.0) (2026-10-08)
|
|
2
11
|
|
|
3
12
|
### Bug Fixes
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# homebridge-switchbot
|
|
2
|
+
|
|
3
|
+
The project guide is shared with Copilot and imported here. It covers reference priority, branch
|
|
4
|
+
targeting, following node-switchbot changes, commands, structure and pitfalls:
|
|
5
|
+
|
|
6
|
+
@.github/copilot-instructions.md
|
|
7
|
+
|
|
8
|
+
## Claude-specific notes
|
|
9
|
+
|
|
10
|
+
- Conventional Commits (`feat:`, `fix:`, `docs:`, `test:`, `chore:`).
|
|
11
|
+
- If the right target branch is unclear, ask before opening the PR.
|
|
12
|
+
- PR description: what changed, why (link the node-switchbot PR/release), and any config migration
|
|
13
|
+
users need.
|
package/README.md
CHANGED
|
@@ -176,6 +176,8 @@ Click **Save Advanced Settings** to apply changes. These settings match the opti
|
|
|
176
176
|
- [SwitchBot Hub Mini](https://www.switch-bot.com/products/switchbot-hub-mini), [SwitchBot Hub 2](https://us.switch-bot.com/products/switchbot-hub-2), or [SwitchBot Hub 3](https://us.switch-bot.com/products/switchbot-hub-3) Required
|
|
177
177
|
- Enable Cloud Services for Device on SwitchBot App
|
|
178
178
|
- [SwitchBot Bot](https://www.switch-bot.com/products/switchbot-bot)
|
|
179
|
+
- **Bot HomeKit Display** (`botDisplay` in `config.json`) can show a Bot as a Switch, Door, Garage Door, Window, Window Covering, Lock, Outlet, Fan, Faucet, or Stateful Programmable Switch. The default is Switch. The legacy per-device `type` setting still works. Stateful Programmable Switch needs a third-party Home app.
|
|
180
|
+
- Set **Bot Physical Mode** (`mode`) to match the SwitchBot app: **Press** sends one `press` command when activated and returns HomeKit to its resting state; **Switch** sends `turnOn`/`turnOff`. If unset, Door uses Press and other displays use Switch. A Bot has no sensor for the door, window, lock, or other object it operates; their HomeKit state reflects only the Bot control, not the object's physical state.
|
|
179
181
|
- Supports OpenAPI & Bluetooth Low Energy (BLE) Connections
|
|
180
182
|
- If using OpenAPI:
|
|
181
183
|
- [SwitchBot Hub Mini](https://www.switch-bot.com/products/switchbot-hub-mini), [SwitchBot Hub 2](https://us.switch-bot.com/products/switchbot-hub-2), or [SwitchBot Hub 3](https://us.switch-bot.com/products/switchbot-hub-3) Required
|
package/config.schema.json
CHANGED
|
@@ -169,6 +169,36 @@
|
|
|
169
169
|
"remote with screen"
|
|
170
170
|
]
|
|
171
171
|
},
|
|
172
|
+
"botDisplay": {
|
|
173
|
+
"type": "string",
|
|
174
|
+
"title": "Bot HomeKit Display",
|
|
175
|
+
"description": "For a Bot only: choose its HomeKit service. This changes the Home app presentation, not the Bot's physical mode. The Bot cannot measure the position or state of the device it presses.",
|
|
176
|
+
"enum": [
|
|
177
|
+
"switch",
|
|
178
|
+
"door",
|
|
179
|
+
"garagedoor",
|
|
180
|
+
"window",
|
|
181
|
+
"windowcovering",
|
|
182
|
+
"lock",
|
|
183
|
+
"outlet",
|
|
184
|
+
"fan",
|
|
185
|
+
"faucet",
|
|
186
|
+
"stateful"
|
|
187
|
+
],
|
|
188
|
+
"default": "switch"
|
|
189
|
+
},
|
|
190
|
+
"mode": {
|
|
191
|
+
"type": "string",
|
|
192
|
+
"title": "Bot Physical Mode",
|
|
193
|
+
"description": "For a Bot only: match its mode in the SwitchBot app. Press sends one press and returns the HomeKit control to its resting state. Switch sends turnOn/turnOff. Legacy Multi-Press currently sends one press. If unset, Door uses Press and other displays use Switch.",
|
|
194
|
+
"enum": [
|
|
195
|
+
"",
|
|
196
|
+
"press",
|
|
197
|
+
"switch",
|
|
198
|
+
"multipress"
|
|
199
|
+
],
|
|
200
|
+
"default": ""
|
|
201
|
+
},
|
|
172
202
|
"connectionPreference": {
|
|
173
203
|
"type": "string",
|
|
174
204
|
"title": "Connection Preference",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SwitchBotHAPPlatform.d.ts","sourceRoot":"","sources":["../src/SwitchBotHAPPlatform.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAC1D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAO7D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,oBAAoB;IAC/B,8BAA8B;IAC9B,GAAG,EAAE,GAAG,GAAG,SAAS,CAAA;IACpB,iCAAiC;IACjC,GAAG,EAAE,MAAM,CAAA;IACX,2BAA2B;IAC3B,MAAM,EAAE,qBAAqB,CAAA;IAC7B,mCAAmC;IACnC,OAAO,EAAE,GAAG,EAAE,CAAK;IACnB,gDAAgD;IAChD,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC7B,sDAAsD;IACtD,OAAO,CAAC,cAAc,CAAa;IACnC,0CAA0C;IAC1C,OAAO,CAAC,oBAAoB,CAA8B;IAC1D,4EAA4E;IAC5E,OAAO,CAAC,cAAc,CAA0H;IAEhJ,4CAA4C;IAC5C,OAAO,CAAC,iBAAiB,CAAyC;IAClE,wCAAwC;IACxC,OAAO,CAAC,iBAAiB,CAA8B;IACvD,2CAA2C;IAC3C,OAAO,CAAC,oBAAoB,CAAI;IAChC,yDAAyD;IACzD,OAAO,CAAC,gBAAgB,CAAI;IAE5B;;;;;OAKG;gBACS,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,GAAG,CAAC,EAAE,GAAG;IA2C1D;;;;;;OAMG;IACG,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAgElC;;;;;;;;;;;;;;;;;;;;OAoBG;YACW,sBAAsB;
|
|
1
|
+
{"version":3,"file":"SwitchBotHAPPlatform.d.ts","sourceRoot":"","sources":["../src/SwitchBotHAPPlatform.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAC1D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAO7D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,oBAAoB;IAC/B,8BAA8B;IAC9B,GAAG,EAAE,GAAG,GAAG,SAAS,CAAA;IACpB,iCAAiC;IACjC,GAAG,EAAE,MAAM,CAAA;IACX,2BAA2B;IAC3B,MAAM,EAAE,qBAAqB,CAAA;IAC7B,mCAAmC;IACnC,OAAO,EAAE,GAAG,EAAE,CAAK;IACnB,gDAAgD;IAChD,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC7B,sDAAsD;IACtD,OAAO,CAAC,cAAc,CAAa;IACnC,0CAA0C;IAC1C,OAAO,CAAC,oBAAoB,CAA8B;IAC1D,4EAA4E;IAC5E,OAAO,CAAC,cAAc,CAA0H;IAEhJ,4CAA4C;IAC5C,OAAO,CAAC,iBAAiB,CAAyC;IAClE,wCAAwC;IACxC,OAAO,CAAC,iBAAiB,CAA8B;IACvD,2CAA2C;IAC3C,OAAO,CAAC,oBAAoB,CAAI;IAChC,yDAAyD;IACzD,OAAO,CAAC,gBAAgB,CAAI;IAE5B;;;;;OAKG;gBACS,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,GAAG,CAAC,EAAE,GAAG;IA2C1D;;;;;;OAMG;IACG,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAgElC;;;;;;;;;;;;;;;;;;;;OAoBG;YACW,sBAAsB;IA+KpC;;;OAGG;IACH,mBAAmB,IAAI,MAAM;IAI7B;;;OAGG;IACH,mBAAmB,IAAI,IAAI;IAS3B;;;OAGG;IACH,OAAO,CAAC,aAAa;IAYrB;;;;;;OAMG;YACW,qBAAqB;IAiCnC;;;;OAIG;IACH,QAAQ,IAAI,IAAI;IAQhB;;;;;OAKG;IACH;;;;;OAKG;YACW,gBAAgB;IA8B9B;;;OAGG;IACH,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,oBAAoB;IAoI5B;;;;OAIG;IACG,kBAAkB,CAAC,SAAS,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAUvD;;;;OAIG;IACH,wBAAwB,CAAC,CAAC,SAAS,EAAE,GAAG,GAAG,IAAI;CAShD;AAED,eAAe,oBAAoB,CAAA"}
|
|
@@ -285,9 +285,11 @@ export class SwitchBotHAPPlatform {
|
|
|
285
285
|
continue;
|
|
286
286
|
}
|
|
287
287
|
const service = accessory.getService(Service) || accessory.addService(Service);
|
|
288
|
-
const
|
|
288
|
+
const characteristicUUIDs = new Set(Object.keys(s.characteristics || {})
|
|
289
|
+
.map(name => hap.Characteristic[name]?.UUID)
|
|
290
|
+
.filter(Boolean));
|
|
289
291
|
for (const existingChar of service.characteristics.slice()) {
|
|
290
|
-
if (!
|
|
292
|
+
if (!characteristicUUIDs.has(existingChar.UUID)) {
|
|
291
293
|
service.removeCharacteristic(existingChar);
|
|
292
294
|
}
|
|
293
295
|
}
|
|
@@ -316,21 +318,32 @@ export class SwitchBotHAPPlatform {
|
|
|
316
318
|
service.getCharacteristic(Characteristic).onSet(async (value) => {
|
|
317
319
|
await getterSetter.set(value);
|
|
318
320
|
const refreshAfterSet = Array.isArray(getterSetter.refreshAfterSet) ? getterSetter.refreshAfterSet : [];
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
321
|
+
// HAP stores the requested value after onSet resolves. Refresh on the next tick
|
|
322
|
+
// so a momentary control can return to its resting value.
|
|
323
|
+
if (refreshAfterSet.length > 0 || typeof getterSetter.afterSet === 'function') {
|
|
324
|
+
setTimeout(async () => {
|
|
325
|
+
for (const refreshCharName of refreshAfterSet) {
|
|
326
|
+
const refreshGetterSetter = (s.characteristics || {})[refreshCharName];
|
|
327
|
+
const RefreshCharacteristic = hap.Characteristic[refreshCharName];
|
|
328
|
+
if (!RefreshCharacteristic || typeof refreshGetterSetter?.get !== 'function') {
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
try {
|
|
332
|
+
service.getCharacteristic(RefreshCharacteristic).updateValue(await refreshGetterSetter.get());
|
|
333
|
+
}
|
|
334
|
+
catch (e) {
|
|
335
|
+
this.log.debug?.(`Failed to refresh ${refreshCharName} after setting ${charName}`, e);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (typeof getterSetter.afterSet === 'function') {
|
|
339
|
+
try {
|
|
340
|
+
await getterSetter.afterSet(value, service, hap);
|
|
341
|
+
}
|
|
342
|
+
catch (e) {
|
|
343
|
+
this.log.debug?.(`Failed afterSet for ${charName}`, e);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}, 0);
|
|
334
347
|
}
|
|
335
348
|
});
|
|
336
349
|
}
|
|
@@ -387,6 +400,8 @@ export class SwitchBotHAPPlatform {
|
|
|
387
400
|
id: d.deviceId ?? d.id,
|
|
388
401
|
type: d.configDeviceType ?? d.type,
|
|
389
402
|
name: d.configDeviceName ?? d.name,
|
|
403
|
+
botDisplay: d.botDisplay ?? d.type,
|
|
404
|
+
mode: d.mode,
|
|
390
405
|
})));
|
|
391
406
|
}
|
|
392
407
|
/**
|