Skip to content

Commit 67a89ed

Browse files
authored
Add support for SwitchBot Hub3 device (#302)
1 parent 5de7059 commit 67a89ed

File tree

8 files changed

+288
-2
lines changed

8 files changed

+288
-2
lines changed

.github/copilot-instructions.md

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# Node-SwitchBot Development Instructions
2+
3+
Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.
4+
5+
## Working Effectively
6+
7+
### Bootstrap and Setup
8+
- Install Node.js (^20 || ^22 || ^24): Use the existing version if available
9+
- Install dependencies: `npm install` -- takes ~5-25 seconds (varies by cache). **NEVER CANCEL**
10+
- Build the project: `npm run build` -- takes ~5 seconds. **NEVER CANCEL**
11+
- Run tests: `npm run test` -- takes ~1 second with 12 tests. **NEVER CANCEL**
12+
13+
### Development Workflow
14+
- **ALWAYS run these commands in order when starting work:**
15+
1. `npm install`
16+
2. `npm run build`
17+
3. `npm run test`
18+
4. `npm run lint`
19+
- **Build and validate EVERY change**: After any code modification, always run `npm run build && npm run test && npm run lint`
20+
- **NEVER skip linting**: Run `npm run lint` before committing or the CI (.github/workflows/build.yml) will fail
21+
- **Use lint:fix for automatic fixes**: `npm run lint:fix` to automatically fix ESLint issues
22+
23+
### Platform Requirements and Constraints
24+
- **BLE functionality requires Linux-based OS only** (Raspbian, Ubuntu, etc.)
25+
- **Windows and macOS are NOT supported** for BLE operations (use OpenAPI instead)
26+
- **Node.js versions**: Must use ^20, ^22, or ^24 (currently using v20.19.4)
27+
- **ES Modules**: This project uses `"type": "module"` - always use ES import/export syntax
28+
29+
### Testing and Validation
30+
- **Basic functionality test**: After any changes to core classes, run this validation:
31+
```javascript
32+
const { SwitchBotBLE, SwitchBotOpenAPI } = require('./dist/index.js');
33+
const ble = new SwitchBotBLE(); // Should not throw
34+
const api = new SwitchBotOpenAPI('test', 'test'); // Should not throw
35+
```
36+
- **Complete test suite**: `npm run test` (12 tests) -- should always pass before committing
37+
- **Test coverage**: `npm run test-coverage` to see coverage report (~15% coverage is normal)
38+
- **Documentation generation**: `npm run docs` generates TypeDoc documentation in ./docs/
39+
40+
### Build and Timing Expectations
41+
- **npm install**: ~5-25 seconds (varies by cache) -- **NEVER CANCEL**. Set timeout to 5+ minutes for safety
42+
- **npm run build**: ~5 seconds -- **NEVER CANCEL**. Set timeout to 2+ minutes for safety
43+
- **npm run test**: ~1 second (12 tests) -- **NEVER CANCEL**. Set timeout to 2+ minutes for safety
44+
- **npm run lint**: ~3 seconds -- **NEVER CANCEL**. Set timeout to 2+ minutes for safety
45+
- **npm run docs**: ~2 seconds -- **NEVER CANCEL**. Set timeout to 2+ minutes for safety
46+
47+
## Project Structure and Key Files
48+
49+
### Source Code Organization
50+
- **src/index.ts**: Main export file - exports SwitchBotBLE, SwitchBotOpenAPI, and device classes
51+
- **src/switchbot-ble.ts**: Bluetooth Low Energy interface for direct device control
52+
- **src/switchbot-openapi.ts**: HTTP API interface for cloud-based SwitchBot control
53+
- **src/device.ts**: Individual device classes (WoHand, WoCurtain, WoSmartLock, etc.)
54+
- **src/types/**: TypeScript type definitions for all device interfaces
55+
- **src/settings.ts**: Configuration constants and API URLs
56+
- **dist/**: Compiled JavaScript output (generated by `npm run build`)
57+
58+
### Configuration Files
59+
- **package.json**: Main project config - scripts, dependencies, ES module config
60+
- **tsconfig.json**: TypeScript compilation settings (target: ES2022, module: ES2022)
61+
- **eslint.config.js**: ESLint configuration using @antfu/eslint-config
62+
- **jest.config.js**: Test configuration (uses Vitest, not Jest)
63+
- **.gitignore**: Excludes dist/, node_modules/, coverage/, and build artifacts
64+
65+
### Documentation
66+
- **README.md**: Main project documentation and installation instructions
67+
- **BLE.md**: Comprehensive BLE usage documentation and device examples
68+
- **OpenAPI.md**: OpenAPI usage documentation and authentication setup
69+
- **CHANGELOG.md**: Version history and release notes
70+
- **docs/**: Generated TypeDoc API documentation (HTML format)
71+
72+
## Common Development Tasks
73+
74+
### Adding New Device Support
75+
- **Add device class**: Create new class in src/device.ts extending SwitchbotDevice
76+
- **Update exports**: Add export to src/index.ts
77+
- **Add type definitions**: Create types in src/types/ if needed
78+
- **Test basic instantiation**: Ensure the device class can be imported and instantiated
79+
- **Always run full build and test cycle**: `npm run build && npm run test && npm run lint`
80+
81+
### API Changes and Extensions
82+
- **OpenAPI changes**: Modify src/switchbot-openapi.ts
83+
- **BLE changes**: Modify src/switchbot-ble.ts
84+
- **Update type definitions**: Modify corresponding files in src/types/
85+
- **Always verify exports**: Check that new functionality is exported in src/index.ts
86+
- **Test import functionality**: Verify new exports can be imported correctly
87+
88+
### Working with Dependencies
89+
- **Noble (BLE)**: @stoprocent/noble for Bluetooth functionality - Linux only
90+
- **HTTP requests**: Uses undici for HTTP calls (not axios or fetch)
91+
- **Async operations**: Uses async-mutex for concurrency control
92+
- **Adding dependencies**: Use `npm install --save` for runtime deps, `--save-dev` for dev deps
93+
94+
## Validation and Quality Assurance
95+
96+
### Pre-commit Checklist
97+
1. **Build succeeds**: `npm run build` completes without errors
98+
2. **All tests pass**: `npm run test` shows all 12 tests passing
99+
3. **Linting passes**: `npm run lint` shows no errors
100+
4. **Documentation builds**: `npm run docs` generates without warnings
101+
5. **Basic import works**: Can import and instantiate main classes
102+
103+
### Manual Testing Scenarios
104+
- **SwitchBotBLE instantiation**: `new SwitchBotBLE()` should not throw errors
105+
- **SwitchBotOpenAPI instantiation**: `new SwitchBotOpenAPI('token', 'secret')` should not throw
106+
- **Module exports**: All exported classes should be importable from main package
107+
- **TypeScript compilation**: No TypeScript errors in dist/ output
108+
- **Documentation completeness**: Check that new public APIs appear in generated docs
109+
110+
### CI/CD Integration
111+
- **GitHub Actions**: Build runs on push to 'latest' branch and PRs
112+
- **External workflows**: Uses OpenWonderLabs/.github/.github/workflows/nodejs-build-and-test.yml
113+
- **Required checks**: Build, test, and lint must all pass
114+
- **Coverage reporting**: Test coverage is tracked and reported
115+
116+
## Troubleshooting Common Issues
117+
118+
### BLE Not Working
119+
- **Check OS**: BLE only works on Linux-based systems
120+
- **Install noble prerequisites**: May need additional system libraries for @stoprocent/noble
121+
- **Use OpenAPI instead**: For Windows/macOS development, use SwitchBotOpenAPI class
122+
123+
### Build Failures
124+
- **Check Node.js version**: Must be ^20, ^22, or ^24
125+
- **Clean and rebuild**: `npm run clean && npm install && npm run build`
126+
- **TypeScript errors**: Check tsconfig.json settings and type definitions
127+
128+
### Test Failures
129+
- **Run individual tests**: Use `npm run test:watch` for interactive testing
130+
- **Check imports**: Ensure all imports use correct ES module syntax
131+
- **Verify exports**: Check that src/index.ts exports all necessary components
132+
133+
### Linting Errors
134+
- **Auto-fix**: Use `npm run lint:fix` to automatically fix many issues
135+
- **ESLint config**: Review eslint.config.js for current rules
136+
- **Import sorting**: ESLint enforces specific import order - use lint:fix
137+
138+
## Frequently Referenced Information
139+
140+
### Package Scripts (from package.json)
141+
```bash
142+
npm run build # Clean and compile TypeScript
143+
npm run test # Run test suite with Vitest
144+
npm run lint # Run ESLint on src/**/*.ts
145+
npm run lint:fix # Auto-fix ESLint issues
146+
npm run docs # Generate TypeDoc documentation
147+
npm run clean # Remove dist/ directory
148+
npm run watch # Build and link for development
149+
```
150+
151+
### Main Exports (from src/index.ts)
152+
- **SwitchBotBLE**: Bluetooth interface class
153+
- **SwitchBotOpenAPI**: HTTP API interface class
154+
- **SwitchbotDevice**: Base device class
155+
- **Device classes**: WoHand, WoCurtain, WoSmartLock, etc.
156+
- **Type definitions**: All device status and response types
157+
158+
### Dependencies Summary
159+
- **@stoprocent/noble**: BLE functionality (Linux only)
160+
- **undici**: HTTP client for API requests
161+
- **async-mutex**: Concurrency control
162+
- **TypeScript**: Language and compilation
163+
- **Vitest**: Testing framework
164+
- **ESLint**: Code linting with @antfu/eslint-config
165+
- **TypeDoc**: API documentation generation

docs/classes/WoHub3.html

Lines changed: 52 additions & 0 deletions
Large diffs are not rendered by default.

docs/types/hub3.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<!DOCTYPE html><html class="default" lang="en" data-base=".."><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>hub3 | node-switchbot</title><meta name="description" content="Documentation for node-switchbot"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script><script async src="../assets/hierarchy.js" id="tsd-hierarchy-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><div class="table-cell" id="tsd-search"><div class="field"><label for="tsd-search-field" class="tsd-widget tsd-toolbar-icon search no-caption"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></label><input type="text" id="tsd-search-field" aria-label="Search"/></div><div class="field"><div id="tsd-toolbar-links"></div></div><ul class="results"><li class="state loading">Preparing search index...</li><li class="state failure">The search index is not available</li></ul><a href="../index.html" class="title">node-switchbot</a></div><div class="table-cell" id="tsd-widgets"><a href="#" class="tsd-widget tsd-toolbar-icon menu no-caption" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb"><li><a href="../modules.html">node-switchbot</a></li><li><a href="hub3.html">hub3</a></li></ul><h1>Type Alias hub3</h1></div><div class="tsd-signature"><span class="tsd-kind-type-alias">hub3</span><span class="tsd-signature-symbol">:</span> <a href="../interfaces/device.html" class="tsd-signature-type tsd-kind-interface">device</a> <span class="tsd-signature-symbol">&amp;</span> <span class="tsd-signature-symbol">{}</span></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/OpenWonderLabs/node-switchbot/blob/4eede4f50c31e5a293d66bdf9ef84c8f6514029f/src/types/devicelist.ts#L38">types/devicelist.ts:38</a></li></ul></aside></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><h3><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div></div><div class="site-menu"><nav class="tsd-navigation"><a href="../modules.html">node-switchbot</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer></footer><div class="overlay"></div></body></html>

docs/types/hub3ServiceData.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<!DOCTYPE html><html class="default" lang="en" data-base=".."><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>hub3ServiceData | node-switchbot</title><meta name="description" content="Documentation for node-switchbot"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script><script async src="../assets/hierarchy.js" id="tsd-hierarchy-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><div class="table-cell" id="tsd-search"><div class="field"><label for="tsd-search-field" class="tsd-widget tsd-toolbar-icon search no-caption"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></label><input type="text" id="tsd-search-field" aria-label="Search"/></div><div class="field"><div id="tsd-toolbar-links"></div></div><ul class="results"><li class="state loading">Preparing search index...</li><li class="state failure">The search index is not available</li></ul><a href="../index.html" class="title">node-switchbot</a></div><div class="table-cell" id="tsd-widgets"><a href="#" class="tsd-widget tsd-toolbar-icon menu no-caption" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb"><li><a href="../modules.html">node-switchbot</a></li><li><a href="hub3ServiceData.html">hub3ServiceData</a></li></ul><h1>Type Alias hub3ServiceData</h1></div><div class="tsd-signature"><span class="tsd-kind-type-alias">hub3ServiceData</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">serviceData</span> <span class="tsd-signature-symbol">&amp;</span> <span class="tsd-signature-symbol">{</span><br/>    <span class="tsd-kind-property">celsius</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">;</span><br/>    <span class="tsd-kind-property">fahrenheit</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">;</span><br/>    <span class="tsd-kind-property">fahrenheit_mode</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">boolean</span><span class="tsd-signature-symbol">;</span><br/>    <span class="tsd-kind-property">humidity</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">;</span><br/>    <span class="tsd-kind-property">lightLevel</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">;</span><br/>    <span class="tsd-kind-property">model</span><span class="tsd-signature-symbol">:</span> <a href="../enums/SwitchBotBLEModel.html#hub3" class="tsd-signature-type tsd-kind-enum-member">Hub3</a><span class="tsd-signature-symbol">;</span><br/>    <span class="tsd-kind-property">modelFriendlyName</span><span class="tsd-signature-symbol">:</span> <a href="../enums/SwitchBotBLEModelFriendlyName.html#hub3" class="tsd-signature-type tsd-kind-enum-member">Hub3</a><span class="tsd-signature-symbol">;</span><br/>    <span class="tsd-kind-property">modelName</span><span class="tsd-signature-symbol">:</span> <a href="../enums/SwitchBotBLEModelName.html#hub3" class="tsd-signature-type tsd-kind-enum-member">Hub3</a><span class="tsd-signature-symbol">;</span><br/><span class="tsd-signature-symbol">}</span></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/OpenWonderLabs/node-switchbot/blob/4eede4f50c31e5a293d66bdf9ef84c8f6514029f/src/types/bledevicestatus.ts#L287">types/bledevicestatus.ts:287</a></li></ul></aside></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><h3><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div></div><div class="site-menu"><nav class="tsd-navigation"><a href="../modules.html">node-switchbot</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer></footer><div class="overlay"></div></body></html>

0 commit comments

Comments
 (0)