├── .vscode └── settings.json ├── .gitattributes ├── branding ├── icon.png └── switchbot.png ├── docs ├── .nojekyll ├── assets │ ├── hierarchy.js │ ├── highlight.css │ └── navigation.js ├── types │ ├── MacAddress.html │ ├── bot.html │ ├── hub2.html │ ├── plug.html │ ├── meter.html │ ├── remote.html │ ├── meterPro.html │ ├── plugMini.html │ ├── colorBulb.html │ ├── indoorCam.html │ ├── meterPlus.html │ ├── humidifier.html │ ├── pantiltCam.html │ ├── stripLight.html │ ├── airPurifier.html │ ├── ceilingLight.html │ ├── motionSensor.html │ ├── outdoorMeter.html │ ├── pantiltCam2k.html │ ├── contactSensor.html │ ├── airPurifierVOC.html │ ├── ceilingLightPro.html │ ├── airPurifierTable.html │ ├── waterLeakDetector.html │ ├── airPurifierTableVOC.html │ ├── batteryCirculatorFan.html │ ├── robotVacuumCleanerS1.html │ ├── floorCleaningRobotS10.html │ ├── robotVacuumCleanerS1Plus.html │ ├── CommandType.html │ ├── panTiltCamWebhookContext.html │ ├── plugWebhookContext.html │ ├── indoorCameraWebhookContext.html │ ├── plugStatus.html │ ├── plugMiniJPWebhookContext.html │ ├── plugMiniUSWebhookContext.html │ ├── relaySwitch1Status.html │ ├── waterLeakDetectorStatus.html │ ├── keypad.html │ ├── keypadTouch.html │ └── hub2Status.html └── variables │ └── parameterChecker.html ├── .github ├── workflows │ ├── stale.yml │ ├── beta-release.yml │ └── release.yml ├── dependabot.yml └── labeler.yml ├── typedoc.json ├── src ├── index.test.ts ├── index.ts ├── types │ ├── ble-guards.ts │ └── ble-guards.test.ts ├── switchbot-ble.test.ts ├── switchbot-openapi.test.ts ├── settings.ts ├── parameter-checker.test.ts └── settings.test.ts ├── tsconfig.json ├── LICENSE ├── eslint.config.js ├── .eslintrc ├── README.md ├── package.json ├── .gitignore └── .npmignore /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /branding/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenWonderLabs/node-switchbot/HEAD/branding/icon.png -------------------------------------------------------------------------------- /branding/switchbot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenWonderLabs/node-switchbot/HEAD/branding/switchbot.png -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Stale workflow 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '45 11 * * *' 7 | 8 | jobs: 9 | stale: 10 | uses: OpenWonderLabs/.github/.github/workflows/stale.yml@latest 11 | secrets: 12 | token: ${{ secrets.GITHUB_TOKEN }} 13 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "out": "docs", 3 | "exclude": ["src/**/*.spec.ts"], 4 | "entryPoints": [ 5 | "src/index.ts" 6 | ], 7 | "excludePrivate": true, 8 | "excludeProtected": true, 9 | "excludeExternals": true, 10 | "hideGenerator": true, 11 | "includeVersion": false, 12 | "validation": { 13 | "invalidLink": true, 14 | "notExported": false 15 | }, 16 | "inlineTags": ["@link", "@see"] 17 | } 18 | -------------------------------------------------------------------------------- /src/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | import * as index from './index.js' 4 | 5 | describe('index module exports', () => { 6 | it('should export switchbot-ble', () => { 7 | expect(index.SwitchBotBLE).toBeDefined() 8 | }) 9 | 10 | it('should export switchbot-openapi', () => { 11 | expect(index.SwitchBotOpenAPI).toBeDefined() 12 | }) 13 | 14 | it('should export SwitchbotDevice', () => { 15 | expect(index.SwitchbotDevice).toBeDefined() 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /* Copyright(C) 2024, donavanbecker (https://github.com/donavanbecker). All rights reserved. 2 | * 3 | * index.ts: Switchbot BLE API registration. 4 | */ 5 | // Primary module exports 6 | export * from './device.js' 7 | export { ParameterChecker, parameterChecker } from './parameter-checker.js' 8 | export { updateBaseURL, urls } from './settings.js' 9 | export * from './switchbot-ble.js' 10 | export * from './switchbot-openapi.js' 11 | 12 | // Type definitions 13 | export * from './types/ble.js' 14 | export * from './types/openapi.js' 15 | -------------------------------------------------------------------------------- /src/types/ble-guards.ts: -------------------------------------------------------------------------------- 1 | import type { SwitchBotBLEModel } from '../device' 2 | import type { BLEDeviceServiceData } from './ble.js' 3 | 4 | /** 5 | * Generic type guard for BLE device service data by model. 6 | * @param data The BLE service data object. 7 | * @param model The SwitchBotBLEModel enum to check against. 8 | * @returns True if data.model matches, and narrows to the specific service data type. 9 | */ 10 | export function isServiceDataOfModel( 11 | data: BLEDeviceServiceData, 12 | model: M, 13 | ): data is Extract { 14 | return data.model === model 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "lib": [ 5 | "DOM", 6 | "ES2022" 7 | ], 8 | "rootDir": "src", 9 | "module": "ES2022", 10 | "moduleResolution": "bundler", 11 | "strict": true, 12 | "noImplicitAny": false, 13 | "declaration": true, 14 | "declarationMap": true, 15 | "outDir": "dist", 16 | "sourceMap": true, 17 | "allowSyntheticDefaultImports": true, 18 | "esModuleInterop": true, 19 | "forceConsistentCasingInFileNames": true 20 | }, 21 | "include": [ 22 | "src" 23 | ], 24 | "exclude": [ 25 | "**/*.spec.ts" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License (ISC) 2 | Copyright (c) 2020 SwitchBot 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any 5 | purpose with or without fee is hereby granted, provided that the above 6 | copyright notice and this permission notice appear in all copies. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 9 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 10 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 11 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 12 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 13 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 14 | PERFORMANCE OF THIS SOFTWARE. 15 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: 'npm' # See documentation for possible values 9 | directory: '/' # Location of package manifests 10 | target-branch: 'beta-*.*.*' 11 | schedule: 12 | interval: 'daily' 13 | - package-ecosystem: 'github-actions' # See documentation for possible values 14 | directory: '/' # Location of package manifests 15 | target-branch: 'beta-*.*.*' 16 | schedule: 17 | interval: 'daily' 18 | -------------------------------------------------------------------------------- /docs/assets/hierarchy.js: -------------------------------------------------------------------------------- 1 | window.hierarchyData = "eJyVlsFO3DAQht/F56FkbCexc2vpgbYgoi6IA+KQTQxr4U2Q47RCaN+9iqIucQqxe1rt7p/vi0cee16J7TrXk+KOJVQAlek9EKsejKqd7tqeFK+EynT8aKu9IgXZ/Nau3m0791X90rUiQJ5025ACqQAyWEMKUpuq71V/uoh+2rm9ITD9TQri+uZkfPZk+gFIvdOmsaolxR3HDNJEQCoFZDKFPEtAcAqSJiBFCpikCSAyBkgxA6SCAjIuADlyQC4ZYJpTwIzlgDkmgLlggGKMSGSAMhdAE86AYiKBjhTKKAPKx69c4v0BCMdstvTb7ovRbXOtjVtb9iwWWPIBSJoI3zCYbQA+mG0EV/rcM6WNbh8v9OMu8PLzZNiTeZvjtjvrWlfVIcUUCtPzLPHpg3WVbgP0KRSmC049+nnVNuvoMRHmSuq/9fmwpQHusKURXJEuuSzIZWHu2EwL8F6HwHsdAUbG/gEHS7HXEbUY295Df7vaqLbv7PX5Ov8tFyMR/g75oV6eq8AemTIRcMb9Fr1Q1dM6ekxEgDlyD1ya4fFSt/p7uY5/y8VIJHtXcrOJk9xsIiRp7te/tKpX7fqt85aKEGQs9wQ/lalepnsL1yXzZIQox+RDUXkZryovY2SCLWT7zgWKNmUi4GKxbePa7j+aTiJ7V1CaoY+TjMkYUf7+SkrbRXpsF9aMQ8ZHmrOrwGnoZyNkmEhftq+su+jqwMlyjEUolifv8dlw3WbJCBGji8I5q58DhjESgeaLMn3WthysftDKrgtmwRiNxI8019XWBFpymQ4LxyF+Jmz8EZ2m2V+Jbp2yD1Wt+tNmfTg/Jv0BnSFj44TM/Ct+gm1c5Wa9uuadohH2w+HwB3cNBpI=" -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | # Add 'branding' label to any changes within 'docs' folder or any subfolders 2 | branding: 3 | - changed-files: 4 | - any-glob-to-any-file: branding/** 5 | 6 | # Add 'docs' label to any change to .md files within the entire repository 7 | docs: 8 | - changed-files: 9 | - any-glob-to-any-file: '**/*.md' 10 | 11 | # Add 'enhancement' label to any change to src files within the source dir EXCEPT for the docs sub-folder 12 | enhancement: 13 | - changed-files: 14 | - any-glob-to-any-file: 'src/**/*' 15 | - any-glob-to-any-file: 'config.schema.json' 16 | 17 | # Add 'dependencies' label to any change to src files within the source dir EXCEPT for the docs sub-folder 18 | dependencies: 19 | - changed-files: 20 | - any-glob-to-any-file: 'package.json' 21 | - any-glob-to-any-file: 'package-lock.json' 22 | 23 | # Add 'beta' label to any PR that is opened against the `beta` branch 24 | beta: 25 | - base-branch: 'beta*' 26 | 27 | # Add 'alpha' label to any PR that is opened against the `alpha` branch 28 | alpha: 29 | - base-branch: 'alpha*' 30 | 31 | # Add 'latest' label to any PR that is opened against the `latest` branch 32 | latest: 33 | - base-branch: 'latest' 34 | 35 | # Add 'workflow' to any changes within 'workflow' folder or any subfolders 36 | workflow: 37 | - changed-files: 38 | - any-glob-to-any-file: .github/** -------------------------------------------------------------------------------- /src/switchbot-ble.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | import { LogLevel, SwitchBotBLE } from './switchbot-ble' 4 | 5 | describe('switchBotBLE', () => { 6 | describe('constructor', () => { 7 | it('should create an instance without parameters', () => { 8 | const switchbot = new SwitchBotBLE() 9 | expect(switchbot).toBeInstanceOf(SwitchBotBLE) 10 | expect(switchbot.nobleInitialized).toBeInstanceOf(Promise) 11 | }) 12 | 13 | it('should create an instance with parameters', () => { 14 | const params = { duration: 5000 } 15 | const switchbot = new SwitchBotBLE(params) 16 | expect(switchbot).toBeInstanceOf(SwitchBotBLE) 17 | expect(switchbot.nobleInitialized).toBeInstanceOf(Promise) 18 | }) 19 | }) 20 | 21 | it('should emit log events asynchronously', async () => { 22 | const sw = new SwitchBotBLE() 23 | // Listen for a single 'log' event 24 | const eventPromise = new Promise<{ level: string, message: string }>((resolve) => { 25 | sw.once('log', (event) => { 26 | resolve(event) 27 | }) 28 | }) 29 | sw.log(LogLevel.INFO, 'test message') 30 | const event = (await eventPromise) as { level: string, message: string } 31 | expect(event.level).toBe('info') 32 | expect(event.message).toBe('test message') 33 | expect(event.message).toBe('test message') 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import antfu from '@antfu/eslint-config' 2 | 3 | export default antfu( 4 | { 5 | ignores: ['dist', 'docs'], 6 | jsx: false, 7 | typescript: true, 8 | formatters: { 9 | markdown: true, 10 | }, 11 | rules: { 12 | 'curly': ['error', 'multi-line'], 13 | 'jsdoc/check-alignment': 'error', 14 | 'jsdoc/check-line-alignment': 'error', 15 | 'perfectionist/sort-exports': 'error', 16 | 'perfectionist/sort-imports': [ 17 | 'error', 18 | { 19 | groups: [ 20 | 'builtin-type', 21 | 'external-type', 22 | 'internal-type', 23 | ['parent-type', 'sibling-type', 'index-type'], 24 | 'builtin', 25 | 'external', 26 | 'internal', 27 | ['parent', 'sibling', 'index'], 28 | 'object', 29 | 'unknown', 30 | ], 31 | order: 'asc', 32 | type: 'natural', 33 | }, 34 | ], 35 | 'perfectionist/sort-named-exports': 'error', 36 | 'perfectionist/sort-named-imports': 'error', 37 | 'sort-imports': 0, 38 | 'style/brace-style': ['error', '1tbs', { allowSingleLine: true }], 39 | 'style/quote-props': ['error', 'consistent-as-needed'], 40 | 'test/no-only-tests': 'error', 41 | 'unicorn/no-useless-spread': 'error', 42 | 'unused-imports/no-unused-vars': ['error', { caughtErrors: 'none' }], 43 | 'no-new': 0, // Disable the no-new rule 44 | 'new-cap': 0, // Disable the new-cap rule 45 | 'no-undef': 0, // Disable the no-undef rule 46 | }, 47 | }, 48 | ) 49 | -------------------------------------------------------------------------------- /src/switchbot-openapi.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/order */ 2 | /* eslint-disable */ 3 | import { afterEach, beforeEach, describe, expect, it, vi, type Mock } from 'vitest' 4 | 5 | // Mock undici.request in ESM via module mock 6 | vi.mock('undici', async () => { 7 | const actual = await vi.importActual('undici') 8 | return { ...actual, request: vi.fn() } 9 | }) 10 | import * as undici from 'undici' 11 | 12 | // Alias the mocked request function 13 | const requestMock = undici.request as Mock 14 | 15 | import { SwitchBotOpenAPI } from './switchbot-openapi' 16 | 17 | describe('switchBotOpenAPI', () => { 18 | beforeEach(() => { 19 | // Reset module-level undici.request mock 20 | requestMock.mockReset() 21 | }) 22 | 23 | afterEach(() => { 24 | vi.resetAllMocks() 25 | }) 26 | 27 | it('controlDevice should send correct request and parse response', async () => { 28 | const fakeBody = { json: vi.fn().mockResolvedValue({ commandId: 'abc123' }) } 29 | const statusCode = 200 30 | requestMock.mockResolvedValue({ body: fakeBody, statusCode }) 31 | 32 | const api = new SwitchBotOpenAPI('my-token', 'my-secret') 33 | const result = await api.controlDevice('dev123', 'turnOn', 'default') 34 | 35 | expect(requestMock).toHaveBeenCalledWith( 36 | expect.stringContaining('/dev123/commands'), 37 | expect.objectContaining({ 38 | method: 'POST', 39 | headers: expect.any(Object), 40 | body: JSON.stringify({ command: 'turnOn', parameter: 'default', commandType: 'command' }), 41 | }), 42 | ) 43 | expect(result.response).toEqual({ commandId: 'abc123' }) 44 | expect(result.statusCode).toBe(statusCode) 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /.github/workflows/beta-release.yml: -------------------------------------------------------------------------------- 1 | name: Beta Release 2 | 3 | on: 4 | push: 5 | branches: [beta-*.*.*, beta] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build_and_test: 10 | uses: homebridge/.github/.github/workflows/nodejs-build-and-test.yml@latest 11 | with: 12 | enable_coverage: false 13 | secrets: 14 | token: ${{ secrets.GITHUB_TOKEN }} 15 | lint: 16 | needs: build_and_test 17 | uses: homebridge/.github/.github/workflows/eslint.yml@latest 18 | 19 | publish: 20 | needs: lint 21 | permissions: 22 | id-token: write 23 | uses: homebridge/.github/.github/workflows/npm-publish-esm.yml@latest 24 | with: 25 | tag: 'beta' 26 | dynamically_adjust_version: true 27 | npm_version_command: 'pre' 28 | pre_id: 'beta' 29 | secrets: 30 | npm_auth_token: ${{ secrets.npm_token }} 31 | 32 | pre-release: 33 | needs: publish 34 | uses: homebridge/.github/.github/workflows/pre-release.yml@latest 35 | with: 36 | npm_version: ${{ needs.publish.outputs.NPM_VERSION }} 37 | body: | 38 | **Beta Release** 39 | **Version**: v${{ needs.publish.outputs.NPM_VERSION }} 40 | [How To Test Beta Releases](https://github.com/OpenWonderLabs/homebridge-air/wiki/Beta-Version) 41 | 42 | github-releases-to-discord: 43 | name: Discord Webhooks 44 | needs: [build_and_test,publish] 45 | uses: homebridge/.github/.github/workflows/discord-webhooks.yml@latest 46 | with: 47 | title: "Node-SwitchBot Module Beta Release" 48 | description: | 49 | Version `v${{ needs.publish.outputs.NPM_VERSION }}` 50 | url: "https://github.com/OpenWonderLabs/homebridge-air/releases/tag/v${{ needs.publish.outputs.NPM_VERSION }}" 51 | secrets: 52 | DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_URL_LATEST }} -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": [ 4 | "eslint:recommended", 5 | "plugin:@typescript-eslint/eslint-recommended", 6 | "plugin:@typescript-eslint/recommended", // uses the recommended rules from the @typescript-eslint/eslint-plugin 7 | "plugin:jest/recommended" // enables eslint-plugin-jest 8 | ], 9 | "parserOptions": { 10 | "ecmaVersion": 2021, 11 | "sourceType": "module" 12 | }, 13 | "ignorePatterns": [ 14 | "dist", 15 | "server" 16 | ], 17 | "rules": { 18 | "quotes": [ 19 | "warn", 20 | "single" 21 | ], 22 | "indent": [ 23 | "warn", 24 | 2, 25 | { 26 | "SwitchCase": 1 27 | } 28 | ], 29 | "linebreak-style": [ 30 | "warn", 31 | "unix" 32 | ], 33 | "semi": [ 34 | "warn", 35 | "always" 36 | ], 37 | "comma-dangle": [ 38 | "warn", 39 | "always-multiline" 40 | ], 41 | "dot-notation": "off", 42 | "eqeqeq": "warn", 43 | "curly": [ 44 | "warn", 45 | "all" 46 | ], 47 | "brace-style": [ 48 | "warn" 49 | ], 50 | "prefer-arrow-callback": [ 51 | "warn" 52 | ], 53 | "max-len": [ 54 | "warn", 55 | 150 56 | ], // use the provided log method instead 57 | "no-non-null-assertion": [ 58 | "off" 59 | ], 60 | "comma-spacing": [ 61 | "error" 62 | ], 63 | "no-multi-spaces": [ 64 | "warn", 65 | { 66 | "ignoreEOLComments": true 67 | } 68 | ], 69 | "no-trailing-spaces": [ 70 | "warn" 71 | ], 72 | "lines-between-class-members": [ 73 | "warn", 74 | "always", 75 | { 76 | "exceptAfterSingleLine": true 77 | } 78 | ], 79 | "@typescript-eslint/explicit-function-return-type": "off", 80 | "@typescript-eslint/no-non-null-assertion": "off", 81 | "@typescript-eslint/explicit-module-boundary-types": "off", 82 | "@typescript-eslint/no-explicit-any": "error" 83 | } 84 | } -------------------------------------------------------------------------------- /docs/assets/highlight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-hl-0: #795E26; 3 | --dark-hl-0: #DCDCAA; 4 | --light-hl-1: #000000; 5 | --dark-hl-1: #D4D4D4; 6 | --light-hl-2: #A31515; 7 | --dark-hl-2: #CE9178; 8 | --light-hl-3: #0000FF; 9 | --dark-hl-3: #569CD6; 10 | --light-hl-4: #0070C1; 11 | --dark-hl-4: #4FC1FF; 12 | --light-hl-5: #008000; 13 | --dark-hl-5: #6A9955; 14 | --light-hl-6: #001080; 15 | --dark-hl-6: #9CDCFE; 16 | --light-code-background: #FFFFFF; 17 | --dark-code-background: #1E1E1E; 18 | } 19 | 20 | @media (prefers-color-scheme: light) { :root { 21 | --hl-0: var(--light-hl-0); 22 | --hl-1: var(--light-hl-1); 23 | --hl-2: var(--light-hl-2); 24 | --hl-3: var(--light-hl-3); 25 | --hl-4: var(--light-hl-4); 26 | --hl-5: var(--light-hl-5); 27 | --hl-6: var(--light-hl-6); 28 | --code-background: var(--light-code-background); 29 | } } 30 | 31 | @media (prefers-color-scheme: dark) { :root { 32 | --hl-0: var(--dark-hl-0); 33 | --hl-1: var(--dark-hl-1); 34 | --hl-2: var(--dark-hl-2); 35 | --hl-3: var(--dark-hl-3); 36 | --hl-4: var(--dark-hl-4); 37 | --hl-5: var(--dark-hl-5); 38 | --hl-6: var(--dark-hl-6); 39 | --code-background: var(--dark-code-background); 40 | } } 41 | 42 | :root[data-theme='light'] { 43 | --hl-0: var(--light-hl-0); 44 | --hl-1: var(--light-hl-1); 45 | --hl-2: var(--light-hl-2); 46 | --hl-3: var(--light-hl-3); 47 | --hl-4: var(--light-hl-4); 48 | --hl-5: var(--light-hl-5); 49 | --hl-6: var(--light-hl-6); 50 | --code-background: var(--light-code-background); 51 | } 52 | 53 | :root[data-theme='dark'] { 54 | --hl-0: var(--dark-hl-0); 55 | --hl-1: var(--dark-hl-1); 56 | --hl-2: var(--dark-hl-2); 57 | --hl-3: var(--dark-hl-3); 58 | --hl-4: var(--dark-hl-4); 59 | --hl-5: var(--dark-hl-5); 60 | --hl-6: var(--dark-hl-6); 61 | --code-background: var(--dark-code-background); 62 | } 63 | 64 | .hl-0 { color: var(--hl-0); } 65 | .hl-1 { color: var(--hl-1); } 66 | .hl-2 { color: var(--hl-2); } 67 | .hl-3 { color: var(--hl-3); } 68 | .hl-4 { color: var(--hl-4); } 69 | .hl-5 { color: var(--hl-5); } 70 | .hl-6 { color: var(--hl-6); } 71 | pre, code { background: var(--code-background); } 72 | -------------------------------------------------------------------------------- /src/settings.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Switchbot BLE API registration settings. 3 | * 4 | * © 2024, donavanbecker (https://github.com/donavanbecker). All rights reserved. 5 | */ 6 | let baseURL = 'https://api.switch-bot.com' 7 | 8 | // API version used for endpoint paths 9 | const API_VERSION = '/v1.1' 10 | 11 | // URLs are generated dynamically via getters below 12 | 13 | /** 14 | * Updates the base URL for the SwitchBot API endpoints. 15 | * @param {string} newBaseURL - The new base URL to use. 16 | */ 17 | export function updateBaseURL(newBaseURL: string): void { 18 | baseURL = newBaseURL 19 | } 20 | 21 | export const urls = { 22 | get baseURL() { 23 | return baseURL 24 | }, 25 | get devicesURL() { 26 | return `${baseURL}${API_VERSION}/devices` 27 | }, 28 | get setupWebhook() { 29 | return `${baseURL}${API_VERSION}/webhook/setupWebhook` 30 | }, 31 | get queryWebhook() { 32 | return `${baseURL}${API_VERSION}/webhook/queryWebhook` 33 | }, 34 | get updateWebhook() { 35 | return `${baseURL}${API_VERSION}/webhook/updateWebhook` 36 | }, 37 | get deleteWebhook() { 38 | return `${baseURL}${API_VERSION}/webhook/deleteWebhook` 39 | }, 40 | } 41 | 42 | /** 43 | * constants used to access SwitchBot BLE API 44 | */ 45 | export const SERV_UUID_PRIMARY = 'cba20d00224d11e69fb80002a5d5c51b' 46 | export const CHAR_UUID_WRITE = 'cba20002224d11e69fb80002a5d5c51b' 47 | export const CHAR_UUID_NOTIFY = 'cba20003224d11e69fb80002a5d5c51b' 48 | export const CHAR_UUID_DEVICE = '2a00' 49 | 50 | export const READ_TIMEOUT_MSEC = 3000 51 | export const WRITE_TIMEOUT_MSEC = 3000 52 | export const COMMAND_TIMEOUT_MSEC = 3000 53 | 54 | export const DEFAULT_DISCOVERY_DURATION = 5000 55 | export const PRIMARY_SERVICE_UUID_LIST = [] 56 | 57 | export enum WoSmartLockProCommands { 58 | GET_CKIV = '570f2103', 59 | LOCK_INFO = '570f4f8102', 60 | UNLOCK = '570f4e0101000080', 61 | UNLOCK_NO_UNLATCH = '570f4e01010000a0', 62 | LOCK = '570f4e0101000000', 63 | ENABLE_NOTIFICATIONS = '570e01001e00008101', 64 | DISABLE_NOTIFICATIONS = '570e00', 65 | } 66 | 67 | export enum WoSmartLockCommands { 68 | GET_CKIV = '570f2103', 69 | LOCK_INFO = '570f4f8101', 70 | UNLOCK = '570f4e01011080', 71 | UNLOCK_NO_UNLATCH = '570f4e010110a0', 72 | LOCK = '570f4e01011000', 73 | ENABLE_NOTIFICATIONS = '570e01001e00008101', 74 | DISABLE_NOTIFICATIONS = '570e00', 75 | } 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ![node-switchbot](https://raw.githubusercontent.com/OpenWonderLabs/node-switchbot/latest/branding/Node_x_SwitchBot.svg?sanitize=true) 4 | 5 | # Node-SwitchBot 6 | 7 | [![npm version](https://badgen.net/npm/v/node-switchbot)](https://www.npmjs.com/package/node-switchbot) 8 | [![npm downloads](https://badgen.net/npm/dt/node-switchbot)](https://www.npmjs.com/package/node-switchbot) 9 | 10 |
11 | 12 | The `node-switchbot` is a Node.js module that allows you to interact with various SwitchBot devices. You can control your [SwitchBot (Bot)'s](https://www.switch-bot.com/bot) arm, operate your [SwitchBot Curtain](https://www.switch-bot.com/products/switchbot-curtain), and manage your [SwitchBot Lock](https://www.switch-bot.com/products/switchbot-lock). Additionally, you can monitor temperature and humidity using the [SwitchBot Thermometer & Hygrometer (Meter)](https://www.switch-bot.com/meter), and check the status of the [SwitchBot Motion Sensor](https://www.switch-bot.com/products/motion-sensor) and [SwitchBot Contact Sensor](https://www.switch-bot.com/products/contact-sensor). 13 | 14 | This module now supports both Bluetooth Low Energy (BLE) and the SwitchBot OpenAPI, providing more flexibility and options for interacting with your devices. 15 | 16 | Please note that most of this module was developed by referencing the official [BLE API](https://github.com/OpenWonderLabs/SwitchBotAPI-BLE) and [OpenAPI](https://github.com/OpenWonderLabs/SwitchBotAPI) documentation. However, some functionalities were developed through trial and error, so there might be inaccuracies in the information obtained from this module. 17 | 18 | --- 19 | 20 | ## [Installation](https://npmjs.org/node-switchbot) 21 | 22 | To install the `node-switchbot` module within your project, use the following command: 23 | 24 | ```sh 25 | $ npm install --save node-switchbot 26 | ``` 27 | 28 | ## [BLE (Bluetooth Low Energy)](BLE.md) 29 | 30 | To see a breakdown of how to use the BLE functionality of this project, visit the [BLE (Bluetooth Low Energy)](BLE.md) documentation. 31 | 32 | ## [OpenAPI](OpenAPI.md) 33 | 34 | To see a breakdown of how to use the OpenAPI functionality of this project, visit the [OpenAPI](OpenAPI.md) documentation. 35 | 36 | ## References 37 | 38 | - [SwitchBot (Official website)](https://www.switch-bot.com/) 39 | - [Facebook @SwitchBotRobot](https://www.facebook.com/SwitchBotRobot/) 40 | - [Twitter @SwitchBot](https://twitter.com/switchbot) 41 | -------------------------------------------------------------------------------- /src/types/ble-guards.test.ts: -------------------------------------------------------------------------------- 1 | import type { ceilingLightServiceData, colorBulbServiceData, meterServiceData } from './ble' 2 | 3 | import { describe, expect, it } from 'vitest' 4 | 5 | import { SwitchBotBLEModel, SwitchBotBLEModelFriendlyName, SwitchBotBLEModelName } from '../device' 6 | import { isServiceDataOfModel } from './ble-guards' 7 | 8 | describe('ble service data guards', () => { 9 | it('identifies colorBulbServiceData correctly', () => { 10 | const sample: colorBulbServiceData = { 11 | model: SwitchBotBLEModel.ColorBulb, 12 | modelName: SwitchBotBLEModelName.ColorBulb, 13 | modelFriendlyName: SwitchBotBLEModelFriendlyName.ColorBulb, 14 | color_temperature: 3000, 15 | power: true, 16 | state: false, 17 | red: 255, 18 | green: 200, 19 | blue: 150, 20 | brightness: 50, 21 | delay: 0, 22 | preset: 1, 23 | color_mode: 0, 24 | speed: 10, 25 | loop_index: 0, 26 | } 27 | expect(isServiceDataOfModel(sample, SwitchBotBLEModel.ColorBulb)).toBe(true) 28 | expect(isServiceDataOfModel(sample, SwitchBotBLEModel.CeilingLight)).toBe(false) 29 | }) 30 | 31 | it('identifies ceilingLightServiceData correctly', () => { 32 | const sample: ceilingLightServiceData = { 33 | model: SwitchBotBLEModel.CeilingLight, 34 | modelName: SwitchBotBLEModelName.CeilingLight, 35 | modelFriendlyName: SwitchBotBLEModelFriendlyName.CeilingLight, 36 | color_temperature: 3500, 37 | power: false, 38 | state: true, 39 | red: 100, 40 | green: 150, 41 | blue: 200, 42 | brightness: 75, 43 | delay: 5, 44 | preset: 2, 45 | color_mode: 1, 46 | speed: 5, 47 | loop_index: 1, 48 | } 49 | expect(isServiceDataOfModel(sample, SwitchBotBLEModel.CeilingLight)).toBe(true) 50 | expect(isServiceDataOfModel(sample, SwitchBotBLEModel.Meter)).toBe(false) 51 | }) 52 | 53 | it('identifies meterServiceData correctly', () => { 54 | const sample: meterServiceData = { 55 | model: SwitchBotBLEModel.Meter, 56 | modelName: SwitchBotBLEModelName.Meter, 57 | modelFriendlyName: SwitchBotBLEModelFriendlyName.Meter, 58 | celsius: 22.5, 59 | fahrenheit: 72.5, 60 | fahrenheit_mode: false, 61 | humidity: 45, 62 | battery: 90, 63 | } 64 | expect(isServiceDataOfModel(sample, SwitchBotBLEModel.Meter)).toBe(true) 65 | expect(isServiceDataOfModel(sample, SwitchBotBLEModel.ColorBulb)).toBe(false) 66 | }) 67 | }) 68 | -------------------------------------------------------------------------------- /src/parameter-checker.test.ts: -------------------------------------------------------------------------------- 1 | import type { Rule } from './device.js' 2 | 3 | import { describe, expect, it } from 'vitest' 4 | 5 | import { ParameterChecker } from './parameter-checker.js' 6 | 7 | describe('parameterChecker', () => { 8 | it('should pass with empty rules and no required object', async () => { 9 | const checker = new ParameterChecker() 10 | const result = await checker.check({}, {}, false) 11 | expect(result).toBe(true) 12 | expect(checker.error).toBeNull() 13 | }) 14 | 15 | it('should fail when required object is missing', async () => { 16 | const checker = new ParameterChecker() 17 | const result = await checker.check(undefined as any, {}, true) 18 | expect(result).toBe(false) 19 | expect(checker.error?.code).toBe('MISSING_REQUIRED') 20 | }) 21 | 22 | it('should fail when required field is missing', async () => { 23 | const checker = new ParameterChecker() 24 | const rules: Record = { 25 | name: { type: 'string', required: true }, 26 | } 27 | const result = await checker.check({}, rules) 28 | expect(result).toBe(false) 29 | expect(checker.error?.code).toBe('MISSING_REQUIRED') 30 | }) 31 | 32 | it('should fail on type mismatch for integer', async () => { 33 | const checker = new ParameterChecker() 34 | const rules: Record = { 35 | age: { type: 'integer', required: true }, 36 | } 37 | const result = await checker.check({ age: 25.5 }, rules) 38 | expect(result).toBe(false) 39 | expect(checker.error?.code).toBe('TYPE_INVALID') 40 | }) 41 | 42 | it('should enforce float min and max', async () => { 43 | const checker = new ParameterChecker() 44 | const rules: Record = { 45 | temperature: { type: 'float', min: 0, max: 100 }, 46 | } 47 | let result = await checker.check({ temperature: -5 }, rules) 48 | expect(result).toBe(false) 49 | expect(checker.error?.code).toBe('VALUE_UNDERFLOW') 50 | 51 | result = await checker.check({ temperature: 150 }, rules) 52 | expect(result).toBe(false) 53 | expect(checker.error?.code).toBe('VALUE_OVERFLOW') 54 | }) 55 | 56 | it('should ignore extra parameters', async () => { 57 | const checker = new ParameterChecker() 58 | const rules: Record = { 59 | id: { type: 'integer' }, 60 | } 61 | const result = await checker.check({ id: 1, extra: 'ignored' }, rules) 62 | expect(result).toBe(true) 63 | expect(checker.error).toBeNull() 64 | }) 65 | }) 66 | -------------------------------------------------------------------------------- /docs/assets/navigation.js: -------------------------------------------------------------------------------- 1 | window.navigationData = "eJylnE1z2zYQhv+Lzpk6lpu0zc2Wk0lSKdJYsnPo9ACRsISaBBQQdOrp5L93IOqDJBa7C+bKffdZ7C4/QAjUX/+NnPzXjd6NpmYzlc+yGL0a7YTbjt6NpK7L6uJ4/JetK73xSel89O73H69OnsvvymXbG+Nupu9nJg8RgSCB9cEqqfPi5YsoJcltixNisNhMJl4AIvvr/FlapyqlN2dEVoiqktVFy9glXI7bjPfWGnvvVFGFiLMNIyyEFaV00k62MnuSNuT0FRitXciQ1LbSlLVxt/JZZTIGOglYI5rvpL5efEJGdVBgtAdRqFw4ZXSk6D0BxvpqrpVd1FY9KqjsHTObsxLrAihYqMGJN4XS+UoVDkKdjASjLtage12scc+JVIXSm6nabMEBtO0EyWgnMhjSmAj/2jqhNOjfmHD/j0LnkLM/TnjW6zHoWa/HpOdVxPOK8iwV7Fkq2jMy3FIR4/00X0pdGbv6CAHOVpzyp3zZCbDWjQX3nkrxBPn647jnoqg3M6XV5wXkf7byKPdLjHK/JChWVlJDN8yzDSfcyUK8NDfES4jStvNJixnFWswoWmkcmFdjwb2x84t3dh1Vi6IGbvpdO5NkDQqyhs2ZzMFLr6sgaKWwbmoy8CI4GZmMWGotO0FyVu1AhDdgvu07gNJO2keRyeqif/mP37wFJmKylNrdCidARqDCkGuTv4AUb6AcJ1uhNzLq3pgxyGQrbAX67y2oqymM3T9Yl9L6uZXP9EZU8HCiaixELgvp5Fe53hrzdCerndERPKjE0d35YocVzhQh56mqHALwZhoCF/9go92XTrgaYzQCLuhOfqslmlVHR2MPHUGABwUb5Sdj/jBJPAgx8P69Z77+R2Ywr2XHMEo/WmFl3jxgoqdFKEOhFjlFj0YM4O+f/gJrXW8gC9Bh2C9mXcjVyy5y5p7NGGT/oggDGhPqfJjjcG47ES2G39XVFrsOWnYag9yy2gIu6Cb2sOiLMOC3WtoXzk0VEmLgu7qAQd6AOVKnKPPUrKSrd5zEICE6vtZqRH+hoT3MQMaCLjOhdfu9HkIeRBhwJcudtMLVVnIujbgcC1LvcsF7JINKDH2Q3konVAEiOwoM9f0YNX4ZdyUYTEBrL87f4i5EbN1l/PqP3y7fjGHKYjZ+E3uaBdxQnBAJvLCCEPHLC2X3ph4hFpp6YMTeilQABFajaF5SsSMeqTF5Ze9Lk6NQDWipUtkP8wkFfphPBlB5gz4JB0RIa3bgkBoxLdzgWHhD0nrBacOQDqQU/2fqzg6SFmEtnJP2ZaJsVhfCGftB6D4c0gzhIncIQj4oGtjtuHJIDLwptAcV8zSnQkoHachcwt8vDkOGf7tACFhTAQ2fC7eva2bTiEbBMpJuQpLheGE1M0nVMrE6GW6FDFWbvoAiZuCvUw0si/4yhXM6S6YhKlgxJWlIB+LK1BhgZ0BRIhnvGCpOicQs0sAK0eVJrw2/MIlVoR8sgCSJSgx9+DMk84vQ3R+bD8yjgU3ATghAw+fCFe2a2TSikrCMppel0Llf5AuJJxNN2f+o3vz0FHJaxiQS2hdYl8aP9CeQJFGpPkWlZJT+roQDEdqREPW+irhfcf2xpoQSLpUoGqhisukBp483cua0jUwSK/G0vB8LY+ykkEIrvbkzfs50+bpPBkWDyHAtEOmgKHidGC5U1G1n502D3Qa7bmA/5BTrmVk0sKBnC4eBlytUMJjYld8z07RS5eAi7NnCZ+Dlh1Qp7EgzeoIEItWaiJAfgVWOQdWgipFaC24p0irxSef+biDKPvBkoAjqKJRW4GOMK6kYT72daw3vCdi1FvO9lU5mDp0mRYW8CCtTZ1uYuTclUPAyRoW8CBx4GrfobIpqOEWwFwr2A97qC2j/U9QbaWio4DLBS7djZJLwYoMiDplIOjHjeLr8XOlE07Kciew6z62sgnGdLRRjvyO/774/yPLsbmRseYc7GBEC0ipIw+eCTeuZ2TS8fREZjx5e4MfjXP/JHJu1wKoUNlLJliCByKkmIORGYBRjQCXwMqTVgFeAAdlTqafmjSSdkDEj3cRcjf88Bl4iattSOFjpYFkSHS5koEhhEmWNKqkYRov2RuU+uGemabmqMvMcPmrOFpJROz81nkEPrLYthYP0OyJLooP9DhUpTLzfcSUVYye0U4UDXnLOFj5jHMx12zYGZ9Vo8WxjOpJf1JuAVdQbjp/fLwr5+uNc/88L5LwDRXwyUbGIjsuHz+iulcu6XzKq0BHxybwq9HUcfrwC/Ozp0aWNy7a+gIpAAUkKdTFjcE+iNDJyFkSFiRHAnkGaFC5z3ANHTY+ZO+LuF29HTvi1W8wXzbMnIIl+Jf1BZHVd7hfXpV1eBlBAM4QLvZrGdEP5kS6h6qGx8LsGz2tIbH6Ow/NLz+2n80LOakxMRar8p4Xgnp6zhc9ARgmKEshgV/t2Pg/vYExH8b8LJ63/ePq44twHB4JkIlJiTJseByx4RJZMx8tPyOn5eOy/PZ6FVX7nsp+LY//ucdXG1bb9jxdnhD+Oue0/GfGfoNzfTc/+j7XO/Evu8ZuSg6ALevvrj7//B84qPlk=" -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-switchbot", 3 | "type": "module", 4 | "version": "3.6.0", 5 | "description": "The node-switchbot is a Node.js module which allows you to control your Switchbot Devices through Bluetooth (BLE).", 6 | "author": "OpenWonderLabs (https://github.com/OpenWonderLabs)", 7 | "license": "MIT", 8 | "homepage": "https://github.com/OpenWonderLabs/node-switchbot", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/OpenWonderLabs/node-switchbot.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/OpenWonderLabs/node-switchbot/issues" 15 | }, 16 | "keywords": [ 17 | "switchbot", 18 | "bot", 19 | "meter", 20 | "temperature", 21 | "humidity", 22 | "curtain", 23 | "blind", 24 | "BLE", 25 | "Bluetooth Low Energy", 26 | "Bluetooth smart", 27 | "Bluetooth", 28 | "OpenAPI", 29 | "OpenWonderLabs", 30 | "Switchbot API" 31 | ], 32 | "main": "dist/index.js", 33 | "types": "dist/index.d.ts", 34 | "engines": { 35 | "node": "^20 || ^22 || ^24" 36 | }, 37 | "scripts": { 38 | "check": "npm install && npm outdated", 39 | "lint": "eslint src/**/*.ts", 40 | "lint:fix": "eslint src/**/*.ts --fix", 41 | "watch": "npm run build && npm link && nodemon", 42 | "build": "npm run clean && tsc", 43 | "prepublishOnly": "npm run lint && npm run build && npm run docs && npm run lint-docs", 44 | "postpublish": "npm run clean && npm ci", 45 | "clean": "shx rm -rf ./dist", 46 | "test": "vitest run", 47 | "test:watch": "vitest watch", 48 | "test-coverage": "npm run test -- --coverage", 49 | "docs": "typedoc", 50 | "lint-docs": "typedoc --emit none --treatWarningsAsErrors" 51 | }, 52 | "readmeFilename": "README.md", 53 | "dependencies": { 54 | "@stoprocent/noble": "^2.3.5", 55 | "async-mutex": "^0.5.0", 56 | "undici": "^7.15.0" 57 | }, 58 | "optionalDependencies": { 59 | "@stoprocent/bluetooth-hci-socket": "^2.2.3" 60 | }, 61 | "devDependencies": { 62 | "@antfu/eslint-config": "^5.2.1", 63 | "@types/aes-js": "^3.1.4", 64 | "@types/debug": "^4.1.12", 65 | "@types/fs-extra": "^11.0.4", 66 | "@types/mdast": "^4.0.4", 67 | "@types/node": "^24.3.0", 68 | "@types/semver": "^7.7.0", 69 | "@types/source-map-support": "^0.5.10", 70 | "@vitest/coverage-v8": "^3.2.4", 71 | "eslint": "^9.34.0", 72 | "eslint-plugin-format": "^1.0.1", 73 | "eslint-plugin-import": "^2.32.0", 74 | "eslint-plugin-import-x": "^4.16.1", 75 | "nodemon": "^3.1.10", 76 | "shx": "^0.4.0", 77 | "ts-node": "^10.9.2", 78 | "typedoc": "^0.28.11", 79 | "typescript": "^5.9.2", 80 | "vitest": "^3.2.4" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore compiled code 2 | 3 | dist 4 | 5 | # ------------- Defaults ------------- 6 | 7 | # Logs 8 | 9 | logs 10 | _.log 11 | npm-debug.log_ 12 | yarn-debug.log* 13 | yarn-error.log* 14 | lerna-debug.log\* 15 | 16 | # Diagnostic reports (https://nodejs.org/api/report.html) 17 | 18 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 19 | 20 | # Runtime data 21 | 22 | pids 23 | _.pid 24 | _.seed 25 | \*.pid.lock 26 | 27 | # Directory for instrumented libs generated by jscoverage/JSCover 28 | 29 | lib-cov 30 | 31 | # Coverage directory used by tools like istanbul 32 | 33 | coverage 34 | \*.lcov 35 | 36 | # nyc test coverage 37 | 38 | .nyc_output 39 | 40 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 41 | 42 | .grunt 43 | 44 | # Bower dependency directory (https://bower.io/) 45 | 46 | bower_components 47 | 48 | # node-waf configuration 49 | 50 | .lock-wscript 51 | 52 | # Compiled binary addons (https://nodejs.org/api/addons.html) 53 | 54 | build/Release 55 | 56 | # Dependency directories 57 | 58 | node_modules/ 59 | jspm_packages/ 60 | 61 | # Snowpack dependency directory (https://snowpack.dev/) 62 | 63 | web_modules/ 64 | 65 | # TypeScript cache 66 | 67 | \*.tsbuildinfo 68 | 69 | # Optional npm cache directory 70 | 71 | .npm 72 | 73 | # Optional eslint cache 74 | 75 | .eslintcache 76 | 77 | # Microbundle cache 78 | 79 | .rpt2_cache/ 80 | .rts2_cache_cjs/ 81 | .rts2_cache_es/ 82 | .rts2_cache_umd/ 83 | 84 | # Optional REPL history 85 | 86 | .node_repl_history 87 | 88 | # Output of 'npm pack' 89 | 90 | \*.tgz 91 | 92 | # Yarn Integrity file 93 | 94 | .yarn-integrity 95 | 96 | # dotenv environment variables file 97 | 98 | .env 99 | .env.test 100 | 101 | # parcel-bundler cache (https://parceljs.org/) 102 | 103 | .cache 104 | .parcel-cache 105 | 106 | # Next.js build output 107 | 108 | .next 109 | 110 | # Nuxt.js build / generate output 111 | 112 | .nuxt 113 | dist 114 | 115 | # Gatsby files 116 | 117 | .cache/ 118 | 119 | # Comment in the public line in if your project uses Gatsby and not Next.js 120 | 121 | # https://nextjs.org/blog/next-9-1#public-directory-support 122 | 123 | # public 124 | 125 | # vuepress build output 126 | 127 | .vuepress/dist 128 | 129 | # Serverless directories 130 | 131 | .serverless/ 132 | 133 | # FuseBox cache 134 | 135 | .fusebox/ 136 | 137 | # DynamoDB Local files 138 | 139 | .dynamodb/ 140 | 141 | # TernJS port file 142 | 143 | .tern-port 144 | 145 | # Stores VSCode versions used for testing VSCode extensions 146 | 147 | .vscode-test 148 | 149 | # yarn v2 150 | 151 | .yarn/cache 152 | .yarn/unplugged 153 | .yarn/build-state.yml 154 | .pnp.\* 155 | 156 | # others 157 | .DS_Store 158 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Ignore source code 2 | 3 | src 4 | 5 | # ------------- Defaults ------------- 6 | 7 | # eslint 8 | 9 | .eslintrc 10 | 11 | # typescript 12 | 13 | tsconfig.json 14 | 15 | # vscode 16 | 17 | .vscode 18 | 19 | # nodemon 20 | 21 | nodemon.json 22 | 23 | # Logs 24 | 25 | logs 26 | _.log 27 | npm-debug.log_ 28 | yarn-debug.log* 29 | yarn-error.log* 30 | lerna-debug.log\* 31 | 32 | # Diagnostic reports (https://nodejs.org/api/report.html) 33 | 34 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 35 | 36 | # Runtime data 37 | 38 | pids 39 | _.pid 40 | _.seed 41 | \*.pid.lock 42 | 43 | # Directory for instrumented libs generated by jscoverage/JSCover 44 | 45 | lib-cov 46 | 47 | # Coverage directory used by tools like istanbul 48 | 49 | coverage 50 | \*.lcov 51 | 52 | # nyc test coverage 53 | 54 | .nyc_output 55 | 56 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 57 | 58 | .grunt 59 | 60 | # Bower dependency directory (https://bower.io/) 61 | 62 | bower_components 63 | 64 | # node-waf configuration 65 | 66 | .lock-wscript 67 | 68 | # Compiled binary addons (https://nodejs.org/api/addons.html) 69 | 70 | build/Release 71 | 72 | # Dependency directories 73 | 74 | node_modules/ 75 | jspm_packages/ 76 | 77 | # Snowpack dependency directory (https://snowpack.dev/) 78 | 79 | web_modules/ 80 | 81 | # TypeScript cache 82 | 83 | \*.tsbuildinfo 84 | 85 | # Optional npm cache directory 86 | 87 | .npm 88 | 89 | # Optional eslint cache 90 | 91 | .eslintcache 92 | 93 | # Microbundle cache 94 | 95 | .rpt2_cache/ 96 | .rts2_cache_cjs/ 97 | .rts2_cache_es/ 98 | .rts2_cache_umd/ 99 | 100 | # Optional REPL history 101 | 102 | .node_repl_history 103 | 104 | # Output of 'npm pack' 105 | 106 | \*.tgz 107 | 108 | # Yarn Integrity file 109 | 110 | .yarn-integrity 111 | 112 | # dotenv environment variables file 113 | 114 | .env 115 | .env.test 116 | 117 | # parcel-bundler cache (https://parceljs.org/) 118 | 119 | .cache 120 | .parcel-cache 121 | 122 | # Next.js build output 123 | 124 | .next 125 | 126 | # Nuxt.js build / generate output 127 | 128 | .nuxt 129 | dist 130 | 131 | # Gatsby files 132 | 133 | .cache/ 134 | 135 | # Comment in the public line in if your project uses Gatsby and not Next.js 136 | 137 | # https://nextjs.org/blog/next-9-1#public-directory-support 138 | 139 | # public 140 | 141 | # vuepress build output 142 | 143 | .vuepress/dist 144 | 145 | # Serverless directories 146 | 147 | .serverless/ 148 | 149 | # FuseBox cache 150 | 151 | .fusebox/ 152 | 153 | # DynamoDB Local files 154 | 155 | .dynamodb/ 156 | 157 | # TernJS port file 158 | 159 | .tern-port 160 | 161 | # Stores VSCode versions used for testing VSCode extensions 162 | 163 | .vscode-test 164 | 165 | # yarn v2 166 | 167 | .yarn/cache 168 | .yarn/unplugged 169 | .yarn/build-state.yml 170 | .pnp.\* 171 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Unified Release 2 | 3 | on: 4 | push: 5 | branches: 6 | #- "alpha-*" 7 | #- "beta-*" 8 | - latest 9 | workflow_dispatch: 10 | 11 | jobs: 12 | # 1️⃣ Determine release type, ESM status, and branch name 13 | determine-release-type: 14 | uses: homebridge/.github/.github/workflows/determine-release-type.yml@latest 15 | with: 16 | ref_name: ${{ github.ref_name }} 17 | 18 | # 2️⃣ Update version and changelog using the scripts 19 | update-version: 20 | needs: determine-release-type 21 | uses: homebridge/.github/.github/workflows/update-version.yml@latest 22 | with: 23 | release_type: ${{ needs.determine-release-type.outputs.release_type }} 24 | is_esm: ${{ needs.determine-release-type.outputs.is_esm == 'true' }} 25 | secrets: 26 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 27 | 28 | # 3️⃣ Publish to NPM and create GitHub release 29 | publish-release: 30 | needs: [determine-release-type, update-version] 31 | permissions: 32 | id-token: write 33 | contents: write 34 | uses: homebridge/.github/.github/workflows/publish-release.yml@latest 35 | with: 36 | release_type: ${{ needs.determine-release-type.outputs.release_type }} 37 | version: ${{ needs.update-version.outputs.version }} 38 | is_esm: ${{ needs.determine-release-type.outputs.is_esm == 'true' }} 39 | secrets: 40 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 41 | 42 | # 4️⃣ Promote branch if this is a prerelease (alpha/beta) 43 | promote-branch: 44 | needs: [determine-release-type, publish-release] 45 | if: ${{ needs.determine-release-type.outputs.release_type != 'latest' && needs.determine-release-type.outputs.release_type != 'skip' }} 46 | uses: homebridge/.github/.github/workflows/promote-branch.yml@latest 47 | with: 48 | branch_name: ${{ needs.determine-release-type.outputs.branch_name }} 49 | release_type: ${{ needs.determine-release-type.outputs.release_type }} 50 | is_esm: ${{ needs.determine-release-type.outputs.is_esm == 'true' }} 51 | secrets: 52 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 53 | 54 | # 5️⃣ Notify if any previous job fails 55 | workflow-failure: 56 | if: ${{ failure() }} 57 | needs: [determine-release-type, update-version, publish-release, promote-branch] 58 | uses: homebridge/.github/.github/workflows/report-failure.yml@latest 59 | with: 60 | workflow_name: ${{ github.workflow }} 61 | job_name: ${{ github.job }} 62 | run_url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 63 | 64 | # 6️⃣ Post to Discord 65 | github-releases-to-discord: 66 | name: Discord Webhooks 67 | needs: [determine-release-type, update-version, publish-release] 68 | uses: homebridge/.github/.github/workflows/discord-webhooks.yml@latest 69 | with: 70 | title: "Node-SwitchBot Module Release" 71 | description: | 72 | Version `v${{ needs.update-version.outputs.version }}` 73 | url: "https://github.com/OpenWonderLabs/node-switchbot/releases/tag/v${{ needs.update-version.outputs.version }}" 74 | secrets: 75 | DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_URL_LATEST }} 76 | -------------------------------------------------------------------------------- /src/settings.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | import { 4 | CHAR_UUID_DEVICE, 5 | CHAR_UUID_NOTIFY, 6 | CHAR_UUID_WRITE, 7 | COMMAND_TIMEOUT_MSEC, 8 | READ_TIMEOUT_MSEC, 9 | SERV_UUID_PRIMARY, 10 | updateBaseURL, 11 | urls, 12 | WoSmartLockCommands, 13 | WoSmartLockProCommands, 14 | WRITE_TIMEOUT_MSEC, 15 | } from './settings.js' 16 | 17 | describe('switchBot API Settings', () => { 18 | it('should have correct Devices URL', () => { 19 | expect(urls.devicesURL).toBe('https://api.switch-bot.com/v1.1/devices') 20 | }) 21 | 22 | it('should have correct setupWebhook URL', () => { 23 | expect(urls.setupWebhook).toBe('https://api.switch-bot.com/v1.1/webhook/setupWebhook') 24 | }) 25 | 26 | it('should have correct queryWebhook URL', () => { 27 | expect(urls.queryWebhook).toBe('https://api.switch-bot.com/v1.1/webhook/queryWebhook') 28 | }) 29 | 30 | it('should have correct updateWebhook URL', () => { 31 | expect(urls.updateWebhook).toBe('https://api.switch-bot.com/v1.1/webhook/updateWebhook') 32 | }) 33 | 34 | it('should have correct deleteWebhook URL', () => { 35 | expect(urls.deleteWebhook).toBe('https://api.switch-bot.com/v1.1/webhook/deleteWebhook') 36 | }) 37 | 38 | it('should have correct BLE API constants', () => { 39 | expect(SERV_UUID_PRIMARY).toBe('cba20d00224d11e69fb80002a5d5c51b') 40 | expect(CHAR_UUID_WRITE).toBe('cba20002224d11e69fb80002a5d5c51b') 41 | expect(CHAR_UUID_NOTIFY).toBe('cba20003224d11e69fb80002a5d5c51b') 42 | expect(CHAR_UUID_DEVICE).toBe('2a00') 43 | }) 44 | 45 | it('should have correct timeout constants', () => { 46 | expect(READ_TIMEOUT_MSEC).toBe(3000) 47 | expect(WRITE_TIMEOUT_MSEC).toBe(3000) 48 | expect(COMMAND_TIMEOUT_MSEC).toBe(3000) 49 | }) 50 | 51 | it('should have correct WoSmartLockProCommands', () => { 52 | expect(WoSmartLockProCommands.GET_CKIV).toBe('570f2103') 53 | expect(WoSmartLockProCommands.LOCK_INFO).toBe('570f4f8102') 54 | expect(WoSmartLockProCommands.UNLOCK).toBe('570f4e0101000080') 55 | expect(WoSmartLockProCommands.UNLOCK_NO_UNLATCH).toBe('570f4e01010000a0') 56 | expect(WoSmartLockProCommands.LOCK).toBe('570f4e0101000000') 57 | expect(WoSmartLockProCommands.ENABLE_NOTIFICATIONS).toBe('570e01001e00008101') 58 | expect(WoSmartLockProCommands.DISABLE_NOTIFICATIONS).toBe('570e00') 59 | }) 60 | 61 | it('should have correct WoSmartLockCommands', () => { 62 | expect(WoSmartLockCommands.GET_CKIV).toBe('570f2103') 63 | expect(WoSmartLockCommands.LOCK_INFO).toBe('570f4f8101') 64 | expect(WoSmartLockCommands.UNLOCK).toBe('570f4e01011080') 65 | expect(WoSmartLockCommands.UNLOCK_NO_UNLATCH).toBe('570f4e010110a0') 66 | expect(WoSmartLockCommands.LOCK).toBe('570f4e01011000') 67 | expect(WoSmartLockCommands.ENABLE_NOTIFICATIONS).toBe('570e01001e00008101') 68 | expect(WoSmartLockCommands.DISABLE_NOTIFICATIONS).toBe('570e00') 69 | }) 70 | 71 | it('should update URLs when baseURL is changed', () => { 72 | const original = urls.baseURL 73 | const custom = 'https://custom.api' 74 | updateBaseURL(custom) 75 | expect(urls.devicesURL).toBe(`${custom}/v1.1/devices`) 76 | expect(urls.setupWebhook).toBe(`${custom}/v1.1/webhook/setupWebhook`) 77 | expect(urls.queryWebhook).toBe(`${custom}/v1.1/webhook/queryWebhook`) 78 | expect(urls.updateWebhook).toBe(`${custom}/v1.1/webhook/updateWebhook`) 79 | expect(urls.deleteWebhook).toBe(`${custom}/v1.1/webhook/deleteWebhook`) 80 | // restore original baseURL 81 | updateBaseURL(original) 82 | }) 83 | }) 84 | -------------------------------------------------------------------------------- /docs/types/MacAddress.html: -------------------------------------------------------------------------------- 1 | MacAddress | node-switchbot
node-switchbot
    Preparing search index...

    Type Alias MacAddress

    MacAddress: string
    2 | -------------------------------------------------------------------------------- /docs/types/bot.html: -------------------------------------------------------------------------------- 1 | bot | node-switchbot
    node-switchbot
      Preparing search index...

      Type Alias bot

      bot: device
      2 | -------------------------------------------------------------------------------- /docs/types/hub2.html: -------------------------------------------------------------------------------- 1 | hub2 | node-switchbot
      node-switchbot
        Preparing search index...

        Type Alias hub2

        hub2: device
        2 | -------------------------------------------------------------------------------- /docs/types/plug.html: -------------------------------------------------------------------------------- 1 | plug | node-switchbot
        node-switchbot
          Preparing search index...

          Type Alias plug

          plug: device
          2 | -------------------------------------------------------------------------------- /docs/types/meter.html: -------------------------------------------------------------------------------- 1 | meter | node-switchbot
          node-switchbot
            Preparing search index...

            Type Alias meter

            meter: device
            2 | -------------------------------------------------------------------------------- /docs/types/remote.html: -------------------------------------------------------------------------------- 1 | remote | node-switchbot
            node-switchbot
              Preparing search index...

              Type Alias remote

              remote: device
              2 | -------------------------------------------------------------------------------- /docs/types/meterPro.html: -------------------------------------------------------------------------------- 1 | meterPro | node-switchbot
              node-switchbot
                Preparing search index...

                Type Alias meterPro

                meterPro: device
                2 | -------------------------------------------------------------------------------- /docs/types/plugMini.html: -------------------------------------------------------------------------------- 1 | plugMini | node-switchbot
                node-switchbot
                  Preparing search index...

                  Type Alias plugMini

                  plugMini: device
                  2 | -------------------------------------------------------------------------------- /docs/types/colorBulb.html: -------------------------------------------------------------------------------- 1 | colorBulb | node-switchbot
                  node-switchbot
                    Preparing search index...

                    Type Alias colorBulb

                    colorBulb: device
                    2 | -------------------------------------------------------------------------------- /docs/types/indoorCam.html: -------------------------------------------------------------------------------- 1 | IndoorCam | node-switchbot
                    node-switchbot
                      Preparing search index...

                      Type Alias IndoorCam

                      IndoorCam: device
                      2 | -------------------------------------------------------------------------------- /docs/types/meterPlus.html: -------------------------------------------------------------------------------- 1 | meterPlus | node-switchbot
                      node-switchbot
                        Preparing search index...

                        Type Alias meterPlus

                        meterPlus: device
                        2 | -------------------------------------------------------------------------------- /docs/types/humidifier.html: -------------------------------------------------------------------------------- 1 | humidifier | node-switchbot
                        node-switchbot
                          Preparing search index...

                          Type Alias humidifier

                          humidifier: device
                          2 | -------------------------------------------------------------------------------- /docs/types/pantiltCam.html: -------------------------------------------------------------------------------- 1 | pantiltCam | node-switchbot
                          node-switchbot
                            Preparing search index...

                            Type Alias pantiltCam

                            pantiltCam: device
                            2 | -------------------------------------------------------------------------------- /docs/types/stripLight.html: -------------------------------------------------------------------------------- 1 | stripLight | node-switchbot
                            node-switchbot
                              Preparing search index...

                              Type Alias stripLight

                              stripLight: device
                              2 | -------------------------------------------------------------------------------- /docs/types/airPurifier.html: -------------------------------------------------------------------------------- 1 | airPurifier | node-switchbot
                              node-switchbot
                                Preparing search index...

                                Type Alias airPurifier

                                airPurifier: device
                                2 | -------------------------------------------------------------------------------- /docs/types/ceilingLight.html: -------------------------------------------------------------------------------- 1 | ceilingLight | node-switchbot
                                node-switchbot
                                  Preparing search index...

                                  Type Alias ceilingLight

                                  ceilingLight: device
                                  2 | -------------------------------------------------------------------------------- /docs/types/motionSensor.html: -------------------------------------------------------------------------------- 1 | motionSensor | node-switchbot
                                  node-switchbot
                                    Preparing search index...

                                    Type Alias motionSensor

                                    motionSensor: device
                                    2 | -------------------------------------------------------------------------------- /docs/types/outdoorMeter.html: -------------------------------------------------------------------------------- 1 | outdoorMeter | node-switchbot
                                    node-switchbot
                                      Preparing search index...

                                      Type Alias outdoorMeter

                                      outdoorMeter: device
                                      2 | -------------------------------------------------------------------------------- /docs/types/pantiltCam2k.html: -------------------------------------------------------------------------------- 1 | pantiltCam2k | node-switchbot
                                      node-switchbot
                                        Preparing search index...

                                        Type Alias pantiltCam2k

                                        pantiltCam2k: device
                                        2 | -------------------------------------------------------------------------------- /docs/types/contactSensor.html: -------------------------------------------------------------------------------- 1 | contactSensor | node-switchbot
                                        node-switchbot
                                          Preparing search index...

                                          Type Alias contactSensor

                                          contactSensor: device
                                          2 | -------------------------------------------------------------------------------- /docs/types/airPurifierVOC.html: -------------------------------------------------------------------------------- 1 | airPurifierVOC | node-switchbot
                                          node-switchbot
                                            Preparing search index...

                                            Type Alias airPurifierVOC

                                            airPurifierVOC: device
                                            2 | -------------------------------------------------------------------------------- /docs/types/ceilingLightPro.html: -------------------------------------------------------------------------------- 1 | ceilingLightPro | node-switchbot
                                            node-switchbot
                                              Preparing search index...

                                              Type Alias ceilingLightPro

                                              ceilingLightPro: device
                                              2 | -------------------------------------------------------------------------------- /docs/types/airPurifierTable.html: -------------------------------------------------------------------------------- 1 | airPurifierTable | node-switchbot
                                              node-switchbot
                                                Preparing search index...

                                                Type Alias airPurifierTable

                                                airPurifierTable: device
                                                2 | -------------------------------------------------------------------------------- /docs/types/waterLeakDetector.html: -------------------------------------------------------------------------------- 1 | waterLeakDetector | node-switchbot
                                                node-switchbot
                                                  Preparing search index...

                                                  Type Alias waterLeakDetector

                                                  waterLeakDetector: device
                                                  2 | -------------------------------------------------------------------------------- /docs/types/airPurifierTableVOC.html: -------------------------------------------------------------------------------- 1 | airPurifierTableVOC | node-switchbot
                                                  node-switchbot
                                                    Preparing search index...

                                                    Type Alias airPurifierTableVOC

                                                    airPurifierTableVOC: device
                                                    2 | -------------------------------------------------------------------------------- /docs/types/batteryCirculatorFan.html: -------------------------------------------------------------------------------- 1 | batteryCirculatorFan | node-switchbot
                                                    node-switchbot
                                                      Preparing search index...

                                                      Type Alias batteryCirculatorFan

                                                      batteryCirculatorFan: device
                                                      2 | -------------------------------------------------------------------------------- /docs/types/robotVacuumCleanerS1.html: -------------------------------------------------------------------------------- 1 | robotVacuumCleanerS1 | node-switchbot
                                                      node-switchbot
                                                        Preparing search index...

                                                        Type Alias robotVacuumCleanerS1

                                                        robotVacuumCleanerS1: device
                                                        2 | -------------------------------------------------------------------------------- /docs/types/floorCleaningRobotS10.html: -------------------------------------------------------------------------------- 1 | floorCleaningRobotS10 | node-switchbot
                                                        node-switchbot
                                                          Preparing search index...

                                                          Type Alias floorCleaningRobotS10

                                                          floorCleaningRobotS10: device
                                                          2 | -------------------------------------------------------------------------------- /docs/types/robotVacuumCleanerS1Plus.html: -------------------------------------------------------------------------------- 1 | robotVacuumCleanerS1Plus | node-switchbot
                                                          node-switchbot
                                                            Preparing search index...

                                                            Type Alias robotVacuumCleanerS1Plus

                                                            robotVacuumCleanerS1Plus: device
                                                            2 | -------------------------------------------------------------------------------- /docs/variables/parameterChecker.html: -------------------------------------------------------------------------------- 1 | parameterChecker | node-switchbot
                                                            node-switchbot
                                                              Preparing search index...

                                                              Variable parameterCheckerConst

                                                              parameterChecker: ParameterChecker = ...
                                                              2 | -------------------------------------------------------------------------------- /docs/types/CommandType.html: -------------------------------------------------------------------------------- 1 | commandType | node-switchbot
                                                              node-switchbot
                                                                Preparing search index...

                                                                Type Alias commandType

                                                                commandType: "command" | "customize"

                                                                Allowed command types for device control.

                                                                2 |
                                                                3 | -------------------------------------------------------------------------------- /docs/types/panTiltCamWebhookContext.html: -------------------------------------------------------------------------------- 1 | panTiltCamWebhookContext | node-switchbot
                                                                node-switchbot
                                                                  Preparing search index...

                                                                  Type Alias panTiltCamWebhookContext

                                                                  panTiltCamWebhookContext: deviceWebhookContext & { detectionState: "DETECTED" }
                                                                  2 | -------------------------------------------------------------------------------- /docs/types/plugWebhookContext.html: -------------------------------------------------------------------------------- 1 | plugWebhookContext | node-switchbot
                                                                  node-switchbot
                                                                    Preparing search index...

                                                                    Type Alias plugWebhookContext

                                                                    plugWebhookContext: deviceWebhookContext & { powerState: "ON" | "OFF" }
                                                                    2 | -------------------------------------------------------------------------------- /docs/types/indoorCameraWebhookContext.html: -------------------------------------------------------------------------------- 1 | indoorCameraWebhookContext | node-switchbot
                                                                    node-switchbot
                                                                      Preparing search index...

                                                                      Type Alias indoorCameraWebhookContext

                                                                      indoorCameraWebhookContext: deviceWebhookContext & {
                                                                          detectionState: "DETECTED";
                                                                      }
                                                                      2 | -------------------------------------------------------------------------------- /docs/types/plugStatus.html: -------------------------------------------------------------------------------- 1 | plugStatus | node-switchbot
                                                                      node-switchbot
                                                                        Preparing search index...

                                                                        Type Alias plugStatus

                                                                        plugStatus: deviceStatus & { power: string; version: string }
                                                                        2 | -------------------------------------------------------------------------------- /docs/types/plugMiniJPWebhookContext.html: -------------------------------------------------------------------------------- 1 | plugMiniJPWebhookContext | node-switchbot
                                                                        node-switchbot
                                                                          Preparing search index...

                                                                          Type Alias plugMiniJPWebhookContext

                                                                          plugMiniJPWebhookContext: deviceWebhookContext & { powerState: "ON" | "OFF" }
                                                                          2 | -------------------------------------------------------------------------------- /docs/types/plugMiniUSWebhookContext.html: -------------------------------------------------------------------------------- 1 | plugMiniUSWebhookContext | node-switchbot
                                                                          node-switchbot
                                                                            Preparing search index...

                                                                            Type Alias plugMiniUSWebhookContext

                                                                            plugMiniUSWebhookContext: deviceWebhookContext & { powerState: "ON" | "OFF" }
                                                                            2 | -------------------------------------------------------------------------------- /docs/types/relaySwitch1Status.html: -------------------------------------------------------------------------------- 1 | relaySwitch1Status | node-switchbot
                                                                            node-switchbot
                                                                              Preparing search index...

                                                                              Type Alias relaySwitch1Status

                                                                              relaySwitch1Status: deviceStatus & { switchStatus: 0 | 1; version: string }
                                                                              2 | -------------------------------------------------------------------------------- /docs/types/waterLeakDetectorStatus.html: -------------------------------------------------------------------------------- 1 | waterLeakDetectorStatus | node-switchbot
                                                                              node-switchbot
                                                                                Preparing search index...

                                                                                Type Alias waterLeakDetectorStatus

                                                                                waterLeakDetectorStatus: deviceStatus & { battery: number; status: 0 | 1 }
                                                                                2 | -------------------------------------------------------------------------------- /docs/types/keypad.html: -------------------------------------------------------------------------------- 1 | keypad | node-switchbot
                                                                                node-switchbot
                                                                                  Preparing search index...

                                                                                  Type Alias keypad

                                                                                  keypad: device & { keyList: keyList; lockDeviceId: string; remoteType: string }
                                                                                  2 | -------------------------------------------------------------------------------- /docs/types/keypadTouch.html: -------------------------------------------------------------------------------- 1 | keypadTouch | node-switchbot
                                                                                  node-switchbot
                                                                                    Preparing search index...

                                                                                    Type Alias keypadTouch

                                                                                    keypadTouch: device & {
                                                                                        keyList: keyList;
                                                                                        lockDeviceId: string;
                                                                                        remoteType: string;
                                                                                    }
                                                                                    2 | -------------------------------------------------------------------------------- /docs/types/hub2Status.html: -------------------------------------------------------------------------------- 1 | hub2Status | node-switchbot
                                                                                    node-switchbot
                                                                                      Preparing search index...

                                                                                      Type Alias hub2Status

                                                                                      hub2Status: deviceStatus & {
                                                                                          humidity: number;
                                                                                          lightLevel: number;
                                                                                          temperature: number;
                                                                                      }
                                                                                      2 | --------------------------------------------------------------------------------