diff --git a/.github/stale.yml b/.github/stale.yml index 0f806531a..39b958202 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,7 +1,7 @@ # Configuration for probot-stale - https://github.com/probot/stale # Number of days of inactivity before an Issue or Pull Request becomes stale -daysUntilStale: 60 +daysUntilStale: 23 # Number of days of inactivity before a stale Issue or Pull Request is closed daysUntilClose: 7 # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 387a39c37..647a41d2e 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -26,9 +26,27 @@ jobs: run: | npm ci npm run build --if-present - npm test + npm test-coverage env: CI: true + - name: Coveralls Parallel + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.github_token }} + flag-name: run-${{ matrix.node-version }} + parallel: true + + finish: + needs: build + + runs-on: ubuntu-latest + + steps: + - name: Coveralls Finished + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.github_token }} + parallel-finished: true publish-npm: # publish only if we are on our own repo, event was 'create' (a tag was created) and the tag starts with "v" (aka version tag) diff --git a/jest.config.js b/jest.config.js index 91a2d2c0d..eee1665b7 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,4 +1,11 @@ module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', -}; \ No newline at end of file + preset: "ts-jest", + testEnvironment: "node", + coverageReporters: ["lcov"], + collectCoverageFrom: [ + "src/**", + "!src/accessories/**", + "!src/lib/definitions/generate-definitions.ts", + "!src/lib/definitions/generator-configuration.ts" + ], +}; diff --git a/package-lock.json b/package-lock.json index 4de4cce0c..5fc687fe4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,13 +14,13 @@ } }, "@babel/core": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.5.tgz", - "integrity": "sha512-fsEANVOcZHzrsV6dMVWqpSeXClq3lNbYrfFGme6DE25FQWe7pyeYpXyx9guqUnpy466JLzZ8z4uwSr2iv60V5Q==", + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz", + "integrity": "sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==", "dev": true, "requires": { "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.11.5", + "@babel/generator": "^7.11.6", "@babel/helper-module-transforms": "^7.11.0", "@babel/helpers": "^7.10.4", "@babel/parser": "^7.11.5", @@ -34,7 +34,7 @@ "lodash": "^4.17.19", "resolve": "^1.3.2", "semver": "^5.4.1", - "source-map": "^0.6.1" + "source-map": "^0.5.0" }, "dependencies": { "semver": { @@ -42,18 +42,32 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true } } }, "@babel/generator": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.5.tgz", - "integrity": "sha512-9UqHWJ4IwRTy4l0o8gq2ef8ws8UPzvtMkVKjTLAiRmza9p9V6Z+OfuNd9fB1j5Q67F+dVJtPC2sZXI8NM9br4g==", + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", "dev": true, "requires": { "@babel/types": "^7.11.5", "jsesc": "^2.5.1", - "source-map": "^0.6.1" + "source-map": "^0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } } }, "@babel/helper-function-name": { @@ -396,19 +410,14 @@ } }, "@homebridge/ciao": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@homebridge/ciao/-/ciao-1.0.5.tgz", - "integrity": "sha512-aqggqXJ6ynEE3UuZz9X3WrP+46OsLRhwMoFuo7RHZ2MK0WOciPsw3u55rndaAHM/NtJGuDMs0oqOKN9UeeXu9g==", + "version": "1.1.0-beta.23", + "resolved": "https://registry.npmjs.org/@homebridge/ciao/-/ciao-1.1.0-beta.23.tgz", + "integrity": "sha512-d0ocWR58RojA7pBJWMQtHNjsO3aen2byoM/ZDJ0bsZU3jdrZaF4/WxO8QYWMv+BKg5T7+gnYFZPTdQGyj/YJNg==", "requires": { "debug": "^4.1.1", - "fast-deep-equal": "^3.1.3" - }, - "dependencies": { - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - } + "fast-deep-equal": "^3.1.3", + "source-map-support": "^0.5.19", + "tslib": "^2.0.1" } }, "@istanbuljs/load-nyc-config": { @@ -431,23 +440,23 @@ "dev": true }, "@jest/console": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.3.0.tgz", - "integrity": "sha512-/5Pn6sJev0nPUcAdpJHMVIsA8sKizL2ZkcKPE5+dJrCccks7tcM7c9wbgHudBJbxXLoTbqsHkG1Dofoem4F09w==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.5.2.tgz", + "integrity": "sha512-lJELzKINpF1v74DXHbCRIkQ/+nUV1M+ntj+X1J8LxCgpmJZjfLmhFejiMSbjjD66fayxl5Z06tbs3HMyuik6rw==", "dev": true, "requires": { - "@jest/types": "^26.3.0", + "@jest/types": "^26.5.2", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^26.3.0", - "jest-util": "^26.3.0", + "jest-message-util": "^26.5.2", + "jest-util": "^26.5.2", "slash": "^3.0.0" }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -475,38 +484,52 @@ "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } + }, + "jest-util": { + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.5.2.tgz", + "integrity": "sha512-WTL675bK+GSSAYgS8z9FWdCT2nccO1yTIplNLPlP0OD8tUk/H5IrWKMMRudIQQ0qp8bb4k+1Qa8CxGKq9qnYdg==", + "dev": true, + "requires": { + "@jest/types": "^26.5.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + } } } }, "@jest/core": { - "version": "26.4.2", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.4.2.tgz", - "integrity": "sha512-sDva7YkeNprxJfepOctzS8cAk9TOekldh+5FhVuXS40+94SHbiicRO1VV2tSoRtgIo+POs/Cdyf8p76vPTd6dg==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.5.2.tgz", + "integrity": "sha512-LLTo1LQMg7eJjG/+P1NYqFof2B25EV1EqzD5FonklihG4UJKiK2JBIvWonunws6W7e+DhNLoFD+g05tCY03eyA==", "dev": true, "requires": { - "@jest/console": "^26.3.0", - "@jest/reporters": "^26.4.1", - "@jest/test-result": "^26.3.0", - "@jest/transform": "^26.3.0", - "@jest/types": "^26.3.0", + "@jest/console": "^26.5.2", + "@jest/reporters": "^26.5.2", + "@jest/test-result": "^26.5.2", + "@jest/transform": "^26.5.2", + "@jest/types": "^26.5.2", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.4", - "jest-changed-files": "^26.3.0", - "jest-config": "^26.4.2", - "jest-haste-map": "^26.3.0", - "jest-message-util": "^26.3.0", + "jest-changed-files": "^26.5.2", + "jest-config": "^26.5.2", + "jest-haste-map": "^26.5.2", + "jest-message-util": "^26.5.2", "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.4.0", - "jest-resolve-dependencies": "^26.4.2", - "jest-runner": "^26.4.2", - "jest-runtime": "^26.4.2", - "jest-snapshot": "^26.4.2", - "jest-util": "^26.3.0", - "jest-validate": "^26.4.2", - "jest-watcher": "^26.3.0", + "jest-resolve": "^26.5.2", + "jest-resolve-dependencies": "^26.5.2", + "jest-runner": "^26.5.2", + "jest-runtime": "^26.5.2", + "jest-snapshot": "^26.5.2", + "jest-util": "^26.5.2", + "jest-validate": "^26.5.2", + "jest-watcher": "^26.5.2", "micromatch": "^4.0.2", "p-each-series": "^2.1.0", "rimraf": "^3.0.0", @@ -515,9 +538,9 @@ }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -545,25 +568,39 @@ "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } + }, + "jest-util": { + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.5.2.tgz", + "integrity": "sha512-WTL675bK+GSSAYgS8z9FWdCT2nccO1yTIplNLPlP0OD8tUk/H5IrWKMMRudIQQ0qp8bb4k+1Qa8CxGKq9qnYdg==", + "dev": true, + "requires": { + "@jest/types": "^26.5.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + } } } }, "@jest/environment": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.3.0.tgz", - "integrity": "sha512-EW+MFEo0DGHahf83RAaiqQx688qpXgl99wdb8Fy67ybyzHwR1a58LHcO376xQJHfmoXTu89M09dH3J509cx2AA==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.5.2.tgz", + "integrity": "sha512-YjhCD/Zhkz0/1vdlS/QN6QmuUdDkpgBdK4SdiVg4Y19e29g4VQYN5Xg8+YuHjdoWGY7wJHMxc79uDTeTOy9Ngw==", "dev": true, "requires": { - "@jest/fake-timers": "^26.3.0", - "@jest/types": "^26.3.0", + "@jest/fake-timers": "^26.5.2", + "@jest/types": "^26.5.2", "@types/node": "*", - "jest-mock": "^26.3.0" + "jest-mock": "^26.5.2" }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -595,23 +632,23 @@ } }, "@jest/fake-timers": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.3.0.tgz", - "integrity": "sha512-ZL9ytUiRwVP8ujfRepffokBvD2KbxbqMhrXSBhSdAhISCw3gOkuntisiSFv+A6HN0n0fF4cxzICEKZENLmW+1A==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.5.2.tgz", + "integrity": "sha512-09Hn5Oraqt36V1akxQeWMVL0fR9c6PnEhpgLaYvREXZJAh2H2Y+QLCsl0g7uMoJeoWJAuz4tozk1prbR1Fc1sw==", "dev": true, "requires": { - "@jest/types": "^26.3.0", + "@jest/types": "^26.5.2", "@sinonjs/fake-timers": "^6.0.1", "@types/node": "*", - "jest-message-util": "^26.3.0", - "jest-mock": "^26.3.0", - "jest-util": "^26.3.0" + "jest-message-util": "^26.5.2", + "jest-mock": "^26.5.2", + "jest-util": "^26.5.2" }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -639,24 +676,38 @@ "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } + }, + "jest-util": { + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.5.2.tgz", + "integrity": "sha512-WTL675bK+GSSAYgS8z9FWdCT2nccO1yTIplNLPlP0OD8tUk/H5IrWKMMRudIQQ0qp8bb4k+1Qa8CxGKq9qnYdg==", + "dev": true, + "requires": { + "@jest/types": "^26.5.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + } } } }, "@jest/globals": { - "version": "26.4.2", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.4.2.tgz", - "integrity": "sha512-Ot5ouAlehhHLRhc+sDz2/9bmNv9p5ZWZ9LE1pXGGTCXBasmi5jnYjlgYcYt03FBwLmZXCZ7GrL29c33/XRQiow==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.5.2.tgz", + "integrity": "sha512-9PmnFsAUJxpPt1s/stq02acS1YHliVBDNfAWMe1bwdRr1iTCfhbNt3ERQXrO/ZfZSweftoA26Q/2yhSVSWQ3sw==", "dev": true, "requires": { - "@jest/environment": "^26.3.0", - "@jest/types": "^26.3.0", - "expect": "^26.4.2" + "@jest/environment": "^26.5.2", + "@jest/types": "^26.5.2", + "expect": "^26.5.2" }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -688,16 +739,16 @@ } }, "@jest/reporters": { - "version": "26.4.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.4.1.tgz", - "integrity": "sha512-aROTkCLU8++yiRGVxLsuDmZsQEKO6LprlrxtAuzvtpbIFl3eIjgIf3EUxDKgomkS25R9ZzwGEdB5weCcBZlrpQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.5.2.tgz", + "integrity": "sha512-zvq6Wvy6MmJq/0QY0YfOPb49CXKSf42wkJbrBPkeypVa8I+XDxijvFuywo6TJBX/ILPrdrlE/FW9vJZh6Rf9vA==", "dev": true, "requires": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^26.3.0", - "@jest/test-result": "^26.3.0", - "@jest/transform": "^26.3.0", - "@jest/types": "^26.3.0", + "@jest/console": "^26.5.2", + "@jest/test-result": "^26.5.2", + "@jest/transform": "^26.5.2", + "@jest/types": "^26.5.2", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", @@ -708,10 +759,10 @@ "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.0.2", - "jest-haste-map": "^26.3.0", - "jest-resolve": "^26.4.0", - "jest-util": "^26.3.0", - "jest-worker": "^26.3.0", + "jest-haste-map": "^26.5.2", + "jest-resolve": "^26.5.2", + "jest-util": "^26.5.2", + "jest-worker": "^26.5.0", "node-notifier": "^8.0.0", "slash": "^3.0.0", "source-map": "^0.6.0", @@ -721,9 +772,9 @@ }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -751,13 +802,27 @@ "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } + }, + "jest-util": { + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.5.2.tgz", + "integrity": "sha512-WTL675bK+GSSAYgS8z9FWdCT2nccO1yTIplNLPlP0OD8tUk/H5IrWKMMRudIQQ0qp8bb4k+1Qa8CxGKq9qnYdg==", + "dev": true, + "requires": { + "@jest/types": "^26.5.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + } } } }, "@jest/source-map": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.3.0.tgz", - "integrity": "sha512-hWX5IHmMDWe1kyrKl7IhFwqOuAreIwHhbe44+XH2ZRHjrKIh0LO5eLQ/vxHFeAfRwJapmxuqlGAEYLadDq6ZGQ==", + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.5.0.tgz", + "integrity": "sha512-jWAw9ZwYHJMe9eZq/WrsHlwF8E3hM9gynlcDpOyCb9bR8wEd9ZNBZCi7/jZyzHxC7t3thZ10gO2IDhu0bPKS5g==", "dev": true, "requires": { "callsites": "^3.0.0", @@ -766,21 +831,21 @@ } }, "@jest/test-result": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.3.0.tgz", - "integrity": "sha512-a8rbLqzW/q7HWheFVMtghXV79Xk+GWwOK1FrtimpI5n1la2SY0qHri3/b0/1F0Ve0/yJmV8pEhxDfVwiUBGtgg==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.5.2.tgz", + "integrity": "sha512-E/Zp6LURJEGSCWpoMGmCFuuEI1OWuI3hmZwmULV0GsgJBh7u0rwqioxhRU95euUuviqBDN8ruX/vP/4bwYolXw==", "dev": true, "requires": { - "@jest/console": "^26.3.0", - "@jest/types": "^26.3.0", + "@jest/console": "^26.5.2", + "@jest/types": "^26.5.2", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -812,34 +877,34 @@ } }, "@jest/test-sequencer": { - "version": "26.4.2", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.4.2.tgz", - "integrity": "sha512-83DRD8N3M0tOhz9h0bn6Kl6dSp+US6DazuVF8J9m21WAp5x7CqSMaNycMP0aemC/SH/pDQQddbsfHRTBXVUgog==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.5.2.tgz", + "integrity": "sha512-XmGEh7hh07H2B8mHLFCIgr7gA5Y6Hw1ZATIsbz2fOhpnQ5AnQtZk0gmP0Q5/+mVB2xygO64tVFQxOajzoptkNA==", "dev": true, "requires": { - "@jest/test-result": "^26.3.0", + "@jest/test-result": "^26.5.2", "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.3.0", - "jest-runner": "^26.4.2", - "jest-runtime": "^26.4.2" + "jest-haste-map": "^26.5.2", + "jest-runner": "^26.5.2", + "jest-runtime": "^26.5.2" } }, "@jest/transform": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.3.0.tgz", - "integrity": "sha512-Isj6NB68QorGoFWvcOjlUhpkT56PqNIsXKR7XfvoDlCANn/IANlh8DrKAA2l2JKC3yWSMH5wS0GwuQM20w3b2A==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.5.2.tgz", + "integrity": "sha512-AUNjvexh+APhhmS8S+KboPz+D3pCxPvEAGduffaAJYxIFxGi/ytZQkrqcKDUU0ERBAo5R7087fyOYr2oms1seg==", "dev": true, "requires": { "@babel/core": "^7.1.0", - "@jest/types": "^26.3.0", + "@jest/types": "^26.5.2", "babel-plugin-istanbul": "^6.0.0", "chalk": "^4.0.0", "convert-source-map": "^1.4.0", "fast-json-stable-stringify": "^2.0.0", "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.3.0", + "jest-haste-map": "^26.5.2", "jest-regex-util": "^26.0.0", - "jest-util": "^26.3.0", + "jest-util": "^26.5.2", "micromatch": "^4.0.2", "pirates": "^4.0.1", "slash": "^3.0.0", @@ -848,9 +913,9 @@ }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -878,6 +943,20 @@ "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } + }, + "jest-util": { + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.5.2.tgz", + "integrity": "sha512-WTL675bK+GSSAYgS8z9FWdCT2nccO1yTIplNLPlP0OD8tUk/H5IrWKMMRudIQQ0qp8bb4k+1Qa8CxGKq9qnYdg==", + "dev": true, + "requires": { + "@jest/types": "^26.5.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + } } } }, @@ -912,9 +991,9 @@ } }, "@types/babel__core": { - "version": "7.1.9", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.9.tgz", - "integrity": "sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw==", + "version": "7.1.10", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.10.tgz", + "integrity": "sha512-x8OM8XzITIMyiwl5Vmo2B1cR1S1Ipkyv4mdlbJjMa1lmuKvKY9FrBbEANIaMlnWn5Rf7uO+rC/VgYabNkE17Hw==", "dev": true, "requires": { "@babel/parser": "^7.1.0", @@ -925,18 +1004,18 @@ } }, "@types/babel__generator": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.1.tgz", - "integrity": "sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.2.tgz", + "integrity": "sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ==", "dev": true, "requires": { "@babel/types": "^7.0.0" } }, "@types/babel__template": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz", - "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.3.tgz", + "integrity": "sha512-uCoznIPDmnickEi6D0v11SBpW0OuVqHJCa7syXqQHy5uktSCreIlt0iglsCnmvz8yCb38hGcWeseA8cWJSwv5Q==", "dev": true, "requires": { "@babel/parser": "^7.1.0", @@ -944,9 +1023,9 @@ } }, "@types/babel__traverse": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.13.tgz", - "integrity": "sha512-i+zS7t6/s9cdQvbqKDARrcbrPvtJGlbYsMkazo03nTAK3RX9FNrLllXys22uiTGJapPOTZTQ35nHh4ISph4SLQ==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.15.tgz", + "integrity": "sha512-Pzh9O3sTK8V6I1olsXpCfj2k/ygO2q1X0vhhnDrEQyYLHZesWz+zMZMVcwXLCYf0U36EtmyYaFGPfXlTtDHe3A==", "dev": true, "requires": { "@babel/types": "^7.3.0" @@ -999,9 +1078,9 @@ } }, "@types/jest": { - "version": "26.0.13", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.13.tgz", - "integrity": "sha512-sCzjKow4z9LILc6DhBvn5AkIfmQzDZkgtVVKmGwVrs5tuid38ws281D4l+7x1kP487+FlKDh5kfMZ8WSPAdmdA==", + "version": "26.0.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.14.tgz", + "integrity": "sha512-Hz5q8Vu0D288x3iWXePSn53W7hAjP0H7EQ6QvDO9c7t46mR0lNOLlfuwQ+JkVxuhygHzlzPX+0jKdA3ZgSh+Vg==", "dev": true, "requires": { "jest-diff": "^25.2.1", @@ -1021,15 +1100,15 @@ "dev": true }, "@types/prettier": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.1.0.tgz", - "integrity": "sha512-hiYA88aHiEIgDmeKlsyVsuQdcFn3Z2VuFd/Xm/HCnGnPD8UFU5BM128uzzRVVGEzKDKYUrRsRH9S2o+NUy/3IA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.1.1.tgz", + "integrity": "sha512-2zs+O+UkDsJ1Vcp667pd3f8xearMdopz/z54i99wtRDI5KLmngk7vlrYZD0ZjKHaROR03EznlBbVY9PfAEyJIQ==", "dev": true }, "@types/stack-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", - "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", + "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", "dev": true }, "@types/yargs": { @@ -1048,15 +1127,15 @@ "dev": true }, "abab": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.4.tgz", - "integrity": "sha512-Eu9ELJWCz/c1e9gTiCY+FceWxcqzjYEbqMgtndnuSqZSUCOL73TWNK2mHfIj4Cw2E/ongOp+JISVNCmovt2KYQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", + "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", "dev": true }, "acorn": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", - "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true }, "acorn-globals": { @@ -1076,9 +1155,9 @@ "dev": true }, "ajv": { - "version": "6.12.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", - "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", + "version": "6.12.5", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.5.tgz", + "integrity": "sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -1215,25 +1294,25 @@ "dev": true }, "babel-jest": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.3.0.tgz", - "integrity": "sha512-sxPnQGEyHAOPF8NcUsD0g7hDCnvLL2XyblRBcgrzTWBB/mAIpWow3n1bEL+VghnnZfreLhFSBsFluRoK2tRK4g==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.5.2.tgz", + "integrity": "sha512-U3KvymF3SczA3vOL/cgiUFOznfMET+XDIXiWnoJV45siAp2pLMG8i2+/MGZlAC3f/F6Q40LR4M4qDrWZ9wkK8A==", "dev": true, "requires": { - "@jest/transform": "^26.3.0", - "@jest/types": "^26.3.0", + "@jest/transform": "^26.5.2", + "@jest/types": "^26.5.2", "@types/babel__core": "^7.1.7", "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^26.3.0", + "babel-preset-jest": "^26.5.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.4", "slash": "^3.0.0" }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -1278,9 +1357,9 @@ } }, "babel-plugin-jest-hoist": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.2.0.tgz", - "integrity": "sha512-B/hVMRv8Nh1sQ1a3EY8I0n4Y1Wty3NrR5ebOyVT302op+DOAau+xNEImGMsUWOC3++ZlMooCytKz+NgN8aKGbA==", + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.5.0.tgz", + "integrity": "sha512-ck17uZFD3CDfuwCLATWZxkkuGGFhMij8quP8CNhwj8ek1mqFgbFzRJ30xwC04LLscj/aKsVFfRST+b5PT7rSuw==", "dev": true, "requires": { "@babel/template": "^7.3.3", @@ -1290,9 +1369,9 @@ } }, "babel-preset-current-node-syntax": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.3.tgz", - "integrity": "sha512-uyexu1sVwcdFnyq9o8UQYsXwXflIh8LvrF5+cKrYam93ned1CStffB3+BEcsxGSgagoA3GEyjDqO4a/58hyPYQ==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.4.tgz", + "integrity": "sha512-5/INNCYhUGqw7VbVjT/hb3ucjgkVHKXY7lX3ZjlN4gm565VyFmJUrJ/h+h16ECVB38R/9SF6aACydpKMLZ/c9w==", "dev": true, "requires": { "@babel/plugin-syntax-async-generators": "^7.8.4", @@ -1309,12 +1388,12 @@ } }, "babel-preset-jest": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.3.0.tgz", - "integrity": "sha512-5WPdf7nyYi2/eRxCbVrE1kKCWxgWY4RsPEbdJWFm7QsesFGqjdkyLeu1zRkwM1cxK6EPIlNd6d2AxLk7J+t4pw==", + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.5.0.tgz", + "integrity": "sha512-F2vTluljhqkiGSJGBg/jOruA8vIIIL11YrxRcO7nviNTMbbofPSHwnm8mgP7d/wS7wRSexRoI6X1A6T74d4LQA==", "dev": true, "requires": { - "babel-plugin-jest-hoist": "^26.2.0", + "babel-plugin-jest-hoist": "^26.5.0", "babel-preset-current-node-syntax": "^0.1.3" } }, @@ -1472,8 +1551,7 @@ "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, "cache-base": { "version": "1.0.1", @@ -1621,6 +1699,12 @@ "delayed-stream": "~1.0.0" } }, + "commander": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.1.0.tgz", + "integrity": "sha512-wl7PNrYWd2y5mp1OK/LhTlv8Ff4kQJQRXXAvF+uU/TPNiVJUxZLRYGj/B0y/lPGAVcSbJqH2Za/cvHmrPMC8mA==", + "dev": true + }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -1640,14 +1724,6 @@ "dev": true, "requires": { "safe-buffer": "~5.1.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } } }, "copy-descriptor": { @@ -1727,11 +1803,11 @@ } }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha1-O3ImAlUQnGtYnO4FDx1RYTlmR5E=", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.0.tgz", + "integrity": "sha512-jjO6JD2rKfiZQnBoRzhRTbXjHLGLfH+UtGkWLc/UXAh/rzZMyjbgn0NcfFpqT8nd1kTtFnDiJcrIFkq4UKeJVg==", "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "decamelize": { @@ -1741,9 +1817,10 @@ "dev": true }, "decimal.js": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.0.tgz", - "integrity": "sha1-OUZhE6ngNhEdAvgkibX9awte0jE=" + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", + "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==", + "dev": true }, "decode-uri-component": { "version": "0.2.0", @@ -2000,23 +2077,23 @@ } }, "expect": { - "version": "26.4.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-26.4.2.tgz", - "integrity": "sha512-IlJ3X52Z0lDHm7gjEp+m76uX46ldH5VpqmU0006vqDju/285twh7zaWMRhs67VpQhBwjjMchk+p5aA0VkERCAA==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/expect/-/expect-26.5.2.tgz", + "integrity": "sha512-ccTGrXZd8DZCcvCz4htGXTkd/LOoy6OEtiDS38x3/VVf6E4AQL0QoeksBiw7BtGR5xDNiRYPB8GN6pfbuTOi7w==", "dev": true, "requires": { - "@jest/types": "^26.3.0", + "@jest/types": "^26.5.2", "ansi-styles": "^4.0.0", "jest-get-type": "^26.3.0", - "jest-matcher-utils": "^26.4.2", - "jest-message-util": "^26.3.0", + "jest-matcher-utils": "^26.5.2", + "jest-message-util": "^26.5.2", "jest-regex-util": "^26.0.0" }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -2152,10 +2229,9 @@ "dev": true }, "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", - "dev": true + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-json-stable-stringify": { "version": "2.1.0", @@ -2728,20 +2804,20 @@ } }, "jest": { - "version": "26.4.2", - "resolved": "https://registry.npmjs.org/jest/-/jest-26.4.2.tgz", - "integrity": "sha512-LLCjPrUh98Ik8CzW8LLVnSCfLaiY+wbK53U7VxnFSX7Q+kWC4noVeDvGWIFw0Amfq1lq2VfGm7YHWSLBV62MJw==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest/-/jest-26.5.2.tgz", + "integrity": "sha512-4HFabJVwsgDwul/7rhXJ3yFAF/aUkVIXiJWmgFxb+WMdZG39fVvOwYAs8/3r4AlFPc4m/n5sTMtuMbOL3kNtrQ==", "dev": true, "requires": { - "@jest/core": "^26.4.2", + "@jest/core": "^26.5.2", "import-local": "^3.0.2", - "jest-cli": "^26.4.2" + "jest-cli": "^26.5.2" }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -2771,43 +2847,57 @@ } }, "jest-cli": { - "version": "26.4.2", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.4.2.tgz", - "integrity": "sha512-zb+lGd/SfrPvoRSC/0LWdaWCnscXc1mGYW//NP4/tmBvRPT3VntZ2jtKUONsRi59zc5JqmsSajA9ewJKFYp8Cw==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.5.2.tgz", + "integrity": "sha512-usm48COuUvRp8YEG5OWOaxbSM0my7eHn3QeBWxiGUuFhvkGVBvl1fic4UjC02EAEQtDv8KrNQUXdQTV6ZZBsoA==", "dev": true, "requires": { - "@jest/core": "^26.4.2", - "@jest/test-result": "^26.3.0", - "@jest/types": "^26.3.0", + "@jest/core": "^26.5.2", + "@jest/test-result": "^26.5.2", + "@jest/types": "^26.5.2", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.4", "import-local": "^3.0.2", "is-ci": "^2.0.0", - "jest-config": "^26.4.2", - "jest-util": "^26.3.0", - "jest-validate": "^26.4.2", + "jest-config": "^26.5.2", + "jest-util": "^26.5.2", + "jest-validate": "^26.5.2", "prompts": "^2.0.1", - "yargs": "^15.3.1" + "yargs": "^15.4.1" + } + }, + "jest-util": { + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.5.2.tgz", + "integrity": "sha512-WTL675bK+GSSAYgS8z9FWdCT2nccO1yTIplNLPlP0OD8tUk/H5IrWKMMRudIQQ0qp8bb4k+1Qa8CxGKq9qnYdg==", + "dev": true, + "requires": { + "@jest/types": "^26.5.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" } } } }, "jest-changed-files": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.3.0.tgz", - "integrity": "sha512-1C4R4nijgPltX6fugKxM4oQ18zimS7LqQ+zTTY8lMCMFPrxqBFb7KJH0Z2fRQJvw2Slbaipsqq7s1mgX5Iot+g==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.5.2.tgz", + "integrity": "sha512-qSmssmiIdvM5BWVtyK/nqVpN3spR5YyvkvPqz1x3BR1bwIxsWmU/MGwLoCrPNLbkG2ASAKfvmJpOduEApBPh2w==", "dev": true, "requires": { - "@jest/types": "^26.3.0", + "@jest/types": "^26.5.2", "execa": "^4.0.0", "throat": "^5.0.0" }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -2921,35 +3011,35 @@ } }, "jest-config": { - "version": "26.4.2", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.4.2.tgz", - "integrity": "sha512-QBf7YGLuToiM8PmTnJEdRxyYy3mHWLh24LJZKVdXZ2PNdizSe1B/E8bVm+HYcjbEzGuVXDv/di+EzdO/6Gq80A==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.5.2.tgz", + "integrity": "sha512-dqJOnSegNdE5yDiuGHsjTM5gec7Z4AcAMHiW+YscbOYJAlb3LEtDSobXCq0or9EmGQI5SFmKy4T7P1FxetJOfg==", "dev": true, "requires": { "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^26.4.2", - "@jest/types": "^26.3.0", - "babel-jest": "^26.3.0", + "@jest/test-sequencer": "^26.5.2", + "@jest/types": "^26.5.2", + "babel-jest": "^26.5.2", "chalk": "^4.0.0", "deepmerge": "^4.2.2", "glob": "^7.1.1", "graceful-fs": "^4.2.4", - "jest-environment-jsdom": "^26.3.0", - "jest-environment-node": "^26.3.0", + "jest-environment-jsdom": "^26.5.2", + "jest-environment-node": "^26.5.2", "jest-get-type": "^26.3.0", - "jest-jasmine2": "^26.4.2", + "jest-jasmine2": "^26.5.2", "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.4.0", - "jest-util": "^26.3.0", - "jest-validate": "^26.4.2", + "jest-resolve": "^26.5.2", + "jest-util": "^26.5.2", + "jest-validate": "^26.5.2", "micromatch": "^4.0.2", - "pretty-format": "^26.4.2" + "pretty-format": "^26.5.2" }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -2984,13 +3074,27 @@ "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", "dev": true }, + "jest-util": { + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.5.2.tgz", + "integrity": "sha512-WTL675bK+GSSAYgS8z9FWdCT2nccO1yTIplNLPlP0OD8tUk/H5IrWKMMRudIQQ0qp8bb4k+1Qa8CxGKq9qnYdg==", + "dev": true, + "requires": { + "@jest/types": "^26.5.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + } + }, "pretty-format": { - "version": "26.4.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.4.2.tgz", - "integrity": "sha512-zK6Gd8zDsEiVydOCGLkoBoZuqv8VTiHyAbKznXe/gaph/DAeZOmit9yMfgIz5adIgAMMs5XfoYSwAX3jcCO1tA==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.5.2.tgz", + "integrity": "sha512-VizyV669eqESlkOikKJI8Ryxl/kPpbdLwNdPs2GrbQs18MpySB5S0Yo0N7zkg2xTRiFq4CFw8ct5Vg4a0xP0og==", "dev": true, "requires": { - "@jest/types": "^26.3.0", + "@jest/types": "^26.5.2", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^16.12.0" @@ -3020,22 +3124,22 @@ } }, "jest-each": { - "version": "26.4.2", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.4.2.tgz", - "integrity": "sha512-p15rt8r8cUcRY0Mvo1fpkOGYm7iI8S6ySxgIdfh3oOIv+gHwrHTy5VWCGOecWUhDsit4Nz8avJWdT07WLpbwDA==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.5.2.tgz", + "integrity": "sha512-w7D9FNe0m2D3yZ0Drj9CLkyF/mGhmBSULMQTypzAKR746xXnjUrK8GUJdlLTWUF6dd0ks3MtvGP7/xNFr9Aphg==", "dev": true, "requires": { - "@jest/types": "^26.3.0", + "@jest/types": "^26.5.2", "chalk": "^4.0.0", "jest-get-type": "^26.3.0", - "jest-util": "^26.3.0", - "pretty-format": "^26.4.2" + "jest-util": "^26.5.2", + "pretty-format": "^26.5.2" }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -3070,13 +3174,27 @@ "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", "dev": true }, + "jest-util": { + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.5.2.tgz", + "integrity": "sha512-WTL675bK+GSSAYgS8z9FWdCT2nccO1yTIplNLPlP0OD8tUk/H5IrWKMMRudIQQ0qp8bb4k+1Qa8CxGKq9qnYdg==", + "dev": true, + "requires": { + "@jest/types": "^26.5.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + } + }, "pretty-format": { - "version": "26.4.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.4.2.tgz", - "integrity": "sha512-zK6Gd8zDsEiVydOCGLkoBoZuqv8VTiHyAbKznXe/gaph/DAeZOmit9yMfgIz5adIgAMMs5XfoYSwAX3jcCO1tA==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.5.2.tgz", + "integrity": "sha512-VizyV669eqESlkOikKJI8Ryxl/kPpbdLwNdPs2GrbQs18MpySB5S0Yo0N7zkg2xTRiFq4CFw8ct5Vg4a0xP0og==", "dev": true, "requires": { - "@jest/types": "^26.3.0", + "@jest/types": "^26.5.2", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^16.12.0" @@ -3085,24 +3203,24 @@ } }, "jest-environment-jsdom": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.3.0.tgz", - "integrity": "sha512-zra8He2btIMJkAzvLaiZ9QwEPGEetbxqmjEBQwhH3CA+Hhhu0jSiEJxnJMbX28TGUvPLxBt/zyaTLrOPF4yMJA==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.5.2.tgz", + "integrity": "sha512-fWZPx0bluJaTQ36+PmRpvUtUlUFlGGBNyGX1SN3dLUHHMcQ4WseNEzcGGKOw4U5towXgxI4qDoI3vwR18H0RTw==", "dev": true, "requires": { - "@jest/environment": "^26.3.0", - "@jest/fake-timers": "^26.3.0", - "@jest/types": "^26.3.0", + "@jest/environment": "^26.5.2", + "@jest/fake-timers": "^26.5.2", + "@jest/types": "^26.5.2", "@types/node": "*", - "jest-mock": "^26.3.0", - "jest-util": "^26.3.0", - "jsdom": "^16.2.2" + "jest-mock": "^26.5.2", + "jest-util": "^26.5.2", + "jsdom": "^16.4.0" }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -3130,27 +3248,41 @@ "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } + }, + "jest-util": { + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.5.2.tgz", + "integrity": "sha512-WTL675bK+GSSAYgS8z9FWdCT2nccO1yTIplNLPlP0OD8tUk/H5IrWKMMRudIQQ0qp8bb4k+1Qa8CxGKq9qnYdg==", + "dev": true, + "requires": { + "@jest/types": "^26.5.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + } } } }, "jest-environment-node": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.3.0.tgz", - "integrity": "sha512-c9BvYoo+FGcMj5FunbBgtBnbR5qk3uky8PKyRVpSfe2/8+LrNQMiXX53z6q2kY+j15SkjQCOSL/6LHnCPLVHNw==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.5.2.tgz", + "integrity": "sha512-YHjnDsf/GKFCYMGF1V+6HF7jhY1fcLfLNBDjhAOvFGvt6d8vXvNdJGVM7uTZ2VO/TuIyEFhPGaXMX5j3h7fsrA==", "dev": true, "requires": { - "@jest/environment": "^26.3.0", - "@jest/fake-timers": "^26.3.0", - "@jest/types": "^26.3.0", + "@jest/environment": "^26.5.2", + "@jest/fake-timers": "^26.5.2", + "@jest/types": "^26.5.2", "@types/node": "*", - "jest-mock": "^26.3.0", - "jest-util": "^26.3.0" + "jest-mock": "^26.5.2", + "jest-util": "^26.5.2" }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -3178,6 +3310,20 @@ "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } + }, + "jest-util": { + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.5.2.tgz", + "integrity": "sha512-WTL675bK+GSSAYgS8z9FWdCT2nccO1yTIplNLPlP0OD8tUk/H5IrWKMMRudIQQ0qp8bb4k+1Qa8CxGKq9qnYdg==", + "dev": true, + "requires": { + "@jest/types": "^26.5.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + } } } }, @@ -3188,12 +3334,12 @@ "dev": true }, "jest-haste-map": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.3.0.tgz", - "integrity": "sha512-DHWBpTJgJhLLGwE5Z1ZaqLTYqeODQIZpby0zMBsCU9iRFHYyhklYqP4EiG73j5dkbaAdSZhgB938mL51Q5LeZA==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.5.2.tgz", + "integrity": "sha512-lJIAVJN3gtO3k4xy+7i2Xjtwh8CfPcH08WYjZpe9xzveDaqGw9fVNCpkYu6M525wKFVkLmyi7ku+DxCAP1lyMA==", "dev": true, "requires": { - "@jest/types": "^26.3.0", + "@jest/types": "^26.5.2", "@types/graceful-fs": "^4.1.2", "@types/node": "*", "anymatch": "^3.0.3", @@ -3201,18 +3347,18 @@ "fsevents": "^2.1.2", "graceful-fs": "^4.2.4", "jest-regex-util": "^26.0.0", - "jest-serializer": "^26.3.0", - "jest-util": "^26.3.0", - "jest-worker": "^26.3.0", + "jest-serializer": "^26.5.0", + "jest-util": "^26.5.2", + "jest-worker": "^26.5.0", "micromatch": "^4.0.2", "sane": "^4.0.3", "walker": "^1.0.7" }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -3240,39 +3386,53 @@ "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } + }, + "jest-util": { + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.5.2.tgz", + "integrity": "sha512-WTL675bK+GSSAYgS8z9FWdCT2nccO1yTIplNLPlP0OD8tUk/H5IrWKMMRudIQQ0qp8bb4k+1Qa8CxGKq9qnYdg==", + "dev": true, + "requires": { + "@jest/types": "^26.5.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + } } } }, "jest-jasmine2": { - "version": "26.4.2", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.4.2.tgz", - "integrity": "sha512-z7H4EpCldHN1J8fNgsja58QftxBSL+JcwZmaXIvV9WKIM+x49F4GLHu/+BQh2kzRKHAgaN/E82od+8rTOBPyPA==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.5.2.tgz", + "integrity": "sha512-2J+GYcgLVPTkpmvHEj0/IDTIAuyblGNGlyGe4fLfDT2aktEPBYvoxUwFiOmDDxxzuuEAD2uxcYXr0+1Yw4tjFA==", "dev": true, "requires": { "@babel/traverse": "^7.1.0", - "@jest/environment": "^26.3.0", - "@jest/source-map": "^26.3.0", - "@jest/test-result": "^26.3.0", - "@jest/types": "^26.3.0", + "@jest/environment": "^26.5.2", + "@jest/source-map": "^26.5.0", + "@jest/test-result": "^26.5.2", + "@jest/types": "^26.5.2", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", - "expect": "^26.4.2", + "expect": "^26.5.2", "is-generator-fn": "^2.0.0", - "jest-each": "^26.4.2", - "jest-matcher-utils": "^26.4.2", - "jest-message-util": "^26.3.0", - "jest-runtime": "^26.4.2", - "jest-snapshot": "^26.4.2", - "jest-util": "^26.3.0", - "pretty-format": "^26.4.2", + "jest-each": "^26.5.2", + "jest-matcher-utils": "^26.5.2", + "jest-message-util": "^26.5.2", + "jest-runtime": "^26.5.2", + "jest-snapshot": "^26.5.2", + "jest-util": "^26.5.2", + "pretty-format": "^26.5.2", "throat": "^5.0.0" }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -3301,13 +3461,27 @@ "supports-color": "^7.1.0" } }, + "jest-util": { + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.5.2.tgz", + "integrity": "sha512-WTL675bK+GSSAYgS8z9FWdCT2nccO1yTIplNLPlP0OD8tUk/H5IrWKMMRudIQQ0qp8bb4k+1Qa8CxGKq9qnYdg==", + "dev": true, + "requires": { + "@jest/types": "^26.5.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + } + }, "pretty-format": { - "version": "26.4.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.4.2.tgz", - "integrity": "sha512-zK6Gd8zDsEiVydOCGLkoBoZuqv8VTiHyAbKznXe/gaph/DAeZOmit9yMfgIz5adIgAMMs5XfoYSwAX3jcCO1tA==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.5.2.tgz", + "integrity": "sha512-VizyV669eqESlkOikKJI8Ryxl/kPpbdLwNdPs2GrbQs18MpySB5S0Yo0N7zkg2xTRiFq4CFw8ct5Vg4a0xP0og==", "dev": true, "requires": { - "@jest/types": "^26.3.0", + "@jest/types": "^26.5.2", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^16.12.0" @@ -3316,19 +3490,19 @@ } }, "jest-leak-detector": { - "version": "26.4.2", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.4.2.tgz", - "integrity": "sha512-akzGcxwxtE+9ZJZRW+M2o+nTNnmQZxrHJxX/HjgDaU5+PLmY1qnQPnMjgADPGCRPhB+Yawe1iij0REe+k/aHoA==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.5.2.tgz", + "integrity": "sha512-h7ia3dLzBFItmYERaLPEtEKxy3YlcbcRSjj0XRNJgBEyODuu+3DM2o62kvIFvs3PsaYoIIv+e+nLRI61Dj1CNw==", "dev": true, "requires": { "jest-get-type": "^26.3.0", - "pretty-format": "^26.4.2" + "pretty-format": "^26.5.2" }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -3364,12 +3538,12 @@ "dev": true }, "pretty-format": { - "version": "26.4.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.4.2.tgz", - "integrity": "sha512-zK6Gd8zDsEiVydOCGLkoBoZuqv8VTiHyAbKznXe/gaph/DAeZOmit9yMfgIz5adIgAMMs5XfoYSwAX3jcCO1tA==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.5.2.tgz", + "integrity": "sha512-VizyV669eqESlkOikKJI8Ryxl/kPpbdLwNdPs2GrbQs18MpySB5S0Yo0N7zkg2xTRiFq4CFw8ct5Vg4a0xP0og==", "dev": true, "requires": { - "@jest/types": "^26.3.0", + "@jest/types": "^26.5.2", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^16.12.0" @@ -3378,21 +3552,21 @@ } }, "jest-matcher-utils": { - "version": "26.4.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.4.2.tgz", - "integrity": "sha512-KcbNqWfWUG24R7tu9WcAOKKdiXiXCbMvQYT6iodZ9k1f7065k0keUOW6XpJMMvah+hTfqkhJhRXmA3r3zMAg0Q==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.5.2.tgz", + "integrity": "sha512-W9GO9KBIC4gIArsNqDUKsLnhivaqf8MSs6ujO/JDcPIQrmY+aasewweXVET8KdrJ6ADQaUne5UzysvF/RR7JYA==", "dev": true, "requires": { "chalk": "^4.0.0", - "jest-diff": "^26.4.2", + "jest-diff": "^26.5.2", "jest-get-type": "^26.3.0", - "pretty-format": "^26.4.2" + "pretty-format": "^26.5.2" }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -3422,21 +3596,21 @@ } }, "diff-sequences": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.3.0.tgz", - "integrity": "sha512-5j5vdRcw3CNctePNYN0Wy2e/JbWT6cAYnXv5OuqPhDpyCGc0uLu2TK0zOCJWNB9kOIfYMSpIulRaDgIi4HJ6Ig==", + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.5.0.tgz", + "integrity": "sha512-ZXx86srb/iYy6jG71k++wBN9P9J05UNQ5hQHQd9MtMPvcqXPx/vKU69jfHV637D00Q2gSgPk2D+jSx3l1lDW/Q==", "dev": true }, "jest-diff": { - "version": "26.4.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.4.2.tgz", - "integrity": "sha512-6T1XQY8U28WH0Z5rGpQ+VqZSZz8EN8rZcBtfvXaOkbwxIEeRre6qnuZQlbY1AJ4MKDxQF8EkrCvK+hL/VkyYLQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.5.2.tgz", + "integrity": "sha512-HCSWDUGwsov5oTlGzrRM+UPJI/Dpqi9jzeV0fdRNi3Ch5bnoXhnyJMmVg2juv9081zLIy3HGPI5mcuGgXM2xRA==", "dev": true, "requires": { "chalk": "^4.0.0", - "diff-sequences": "^26.3.0", + "diff-sequences": "^26.5.0", "jest-get-type": "^26.3.0", - "pretty-format": "^26.4.2" + "pretty-format": "^26.5.2" } }, "jest-get-type": { @@ -3446,12 +3620,12 @@ "dev": true }, "pretty-format": { - "version": "26.4.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.4.2.tgz", - "integrity": "sha512-zK6Gd8zDsEiVydOCGLkoBoZuqv8VTiHyAbKznXe/gaph/DAeZOmit9yMfgIz5adIgAMMs5XfoYSwAX3jcCO1tA==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.5.2.tgz", + "integrity": "sha512-VizyV669eqESlkOikKJI8Ryxl/kPpbdLwNdPs2GrbQs18MpySB5S0Yo0N7zkg2xTRiFq4CFw8ct5Vg4a0xP0og==", "dev": true, "requires": { - "@jest/types": "^26.3.0", + "@jest/types": "^26.5.2", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^16.12.0" @@ -3460,14 +3634,14 @@ } }, "jest-message-util": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.3.0.tgz", - "integrity": "sha512-xIavRYqr4/otGOiLxLZGj3ieMmjcNE73Ui+LdSW/Y790j5acqCsAdDiLIbzHCZMpN07JOENRWX5DcU+OQ+TjTA==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.5.2.tgz", + "integrity": "sha512-Ocp9UYZ5Jl15C5PNsoDiGEk14A4NG0zZKknpWdZGoMzJuGAkVt10e97tnEVMYpk7LnQHZOfuK2j/izLBMcuCZw==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@jest/types": "^26.3.0", - "@types/stack-utils": "^1.0.1", + "@jest/types": "^26.5.2", + "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.4", "micromatch": "^4.0.2", @@ -3476,9 +3650,9 @@ }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -3510,19 +3684,19 @@ } }, "jest-mock": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.3.0.tgz", - "integrity": "sha512-PeaRrg8Dc6mnS35gOo/CbZovoDPKAeB1FICZiuagAgGvbWdNNyjQjkOaGUa/3N3JtpQ/Mh9P4A2D4Fv51NnP8Q==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.5.2.tgz", + "integrity": "sha512-9SiU4b5PtO51v0MtJwVRqeGEroH66Bnwtq4ARdNP7jNXbpT7+ByeWNAk4NeT/uHfNSVDXEXgQo1XRuwEqS6Rdw==", "dev": true, "requires": { - "@jest/types": "^26.3.0", + "@jest/types": "^26.5.2", "@types/node": "*" }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -3566,25 +3740,25 @@ "dev": true }, "jest-resolve": { - "version": "26.4.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.4.0.tgz", - "integrity": "sha512-bn/JoZTEXRSlEx3+SfgZcJAVuTMOksYq9xe9O6s4Ekg84aKBObEaVXKOEilULRqviSLAYJldnoWV9c07kwtiCg==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.5.2.tgz", + "integrity": "sha512-XsPxojXGRA0CoDD7Vis59ucz2p3cQFU5C+19tz3tLEAlhYKkK77IL0cjYjikY9wXnOaBeEdm1rOgSJjbZWpcZg==", "dev": true, "requires": { - "@jest/types": "^26.3.0", + "@jest/types": "^26.5.2", "chalk": "^4.0.0", "graceful-fs": "^4.2.4", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^26.3.0", + "jest-util": "^26.5.2", "read-pkg-up": "^7.0.1", "resolve": "^1.17.0", "slash": "^3.0.0" }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -3612,24 +3786,38 @@ "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } + }, + "jest-util": { + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.5.2.tgz", + "integrity": "sha512-WTL675bK+GSSAYgS8z9FWdCT2nccO1yTIplNLPlP0OD8tUk/H5IrWKMMRudIQQ0qp8bb4k+1Qa8CxGKq9qnYdg==", + "dev": true, + "requires": { + "@jest/types": "^26.5.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + } } } }, "jest-resolve-dependencies": { - "version": "26.4.2", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.4.2.tgz", - "integrity": "sha512-ADHaOwqEcVc71uTfySzSowA/RdxUpCxhxa2FNLiin9vWLB1uLPad3we+JSSROq5+SrL9iYPdZZF8bdKM7XABTQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.5.2.tgz", + "integrity": "sha512-LLkc8LuRtxqOx0AtX/Npa2C4I23WcIrwUgNtHYXg4owYF/ZDQShcwBAHjYZIFR06+HpQcZ43+kCTMlQ3aDCYTg==", "dev": true, "requires": { - "@jest/types": "^26.3.0", + "@jest/types": "^26.5.2", "jest-regex-util": "^26.0.0", - "jest-snapshot": "^26.4.2" + "jest-snapshot": "^26.5.2" }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -3661,37 +3849,37 @@ } }, "jest-runner": { - "version": "26.4.2", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.4.2.tgz", - "integrity": "sha512-FgjDHeVknDjw1gRAYaoUoShe1K3XUuFMkIaXbdhEys+1O4bEJS8Avmn4lBwoMfL8O5oFTdWYKcf3tEJyyYyk8g==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.5.2.tgz", + "integrity": "sha512-GKhYxtSX5+tXZsd2QwfkDqPIj5C2HqOdXLRc2x2qYqWE26OJh17xo58/fN/mLhRkO4y6o60ZVloan7Kk5YA6hg==", "dev": true, "requires": { - "@jest/console": "^26.3.0", - "@jest/environment": "^26.3.0", - "@jest/test-result": "^26.3.0", - "@jest/types": "^26.3.0", + "@jest/console": "^26.5.2", + "@jest/environment": "^26.5.2", + "@jest/test-result": "^26.5.2", + "@jest/types": "^26.5.2", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.7.1", "exit": "^0.1.2", "graceful-fs": "^4.2.4", - "jest-config": "^26.4.2", + "jest-config": "^26.5.2", "jest-docblock": "^26.0.0", - "jest-haste-map": "^26.3.0", - "jest-leak-detector": "^26.4.2", - "jest-message-util": "^26.3.0", - "jest-resolve": "^26.4.0", - "jest-runtime": "^26.4.2", - "jest-util": "^26.3.0", - "jest-worker": "^26.3.0", + "jest-haste-map": "^26.5.2", + "jest-leak-detector": "^26.5.2", + "jest-message-util": "^26.5.2", + "jest-resolve": "^26.5.2", + "jest-runtime": "^26.5.2", + "jest-util": "^26.5.2", + "jest-worker": "^26.5.0", "source-map-support": "^0.5.6", "throat": "^5.0.0" }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -3719,47 +3907,61 @@ "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } + }, + "jest-util": { + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.5.2.tgz", + "integrity": "sha512-WTL675bK+GSSAYgS8z9FWdCT2nccO1yTIplNLPlP0OD8tUk/H5IrWKMMRudIQQ0qp8bb4k+1Qa8CxGKq9qnYdg==", + "dev": true, + "requires": { + "@jest/types": "^26.5.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + } } } }, "jest-runtime": { - "version": "26.4.2", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.4.2.tgz", - "integrity": "sha512-4Pe7Uk5a80FnbHwSOk7ojNCJvz3Ks2CNQWT5Z7MJo4tX0jb3V/LThKvD9tKPNVNyeMH98J/nzGlcwc00R2dSHQ==", - "dev": true, - "requires": { - "@jest/console": "^26.3.0", - "@jest/environment": "^26.3.0", - "@jest/fake-timers": "^26.3.0", - "@jest/globals": "^26.4.2", - "@jest/source-map": "^26.3.0", - "@jest/test-result": "^26.3.0", - "@jest/transform": "^26.3.0", - "@jest/types": "^26.3.0", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.5.2.tgz", + "integrity": "sha512-zArr4DatX/Sn0wswX/AnAuJgmwgAR5rNtrUz36HR8BfMuysHYNq5sDbYHuLC4ICyRdy5ae/KQ+sczxyS9G6Qvw==", + "dev": true, + "requires": { + "@jest/console": "^26.5.2", + "@jest/environment": "^26.5.2", + "@jest/fake-timers": "^26.5.2", + "@jest/globals": "^26.5.2", + "@jest/source-map": "^26.5.0", + "@jest/test-result": "^26.5.2", + "@jest/transform": "^26.5.2", + "@jest/types": "^26.5.2", "@types/yargs": "^15.0.0", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", "glob": "^7.1.3", "graceful-fs": "^4.2.4", - "jest-config": "^26.4.2", - "jest-haste-map": "^26.3.0", - "jest-message-util": "^26.3.0", - "jest-mock": "^26.3.0", + "jest-config": "^26.5.2", + "jest-haste-map": "^26.5.2", + "jest-message-util": "^26.5.2", + "jest-mock": "^26.5.2", "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.4.0", - "jest-snapshot": "^26.4.2", - "jest-util": "^26.3.0", - "jest-validate": "^26.4.2", + "jest-resolve": "^26.5.2", + "jest-snapshot": "^26.5.2", + "jest-util": "^26.5.2", + "jest-validate": "^26.5.2", "slash": "^3.0.0", "strip-bom": "^4.0.0", - "yargs": "^15.3.1" + "yargs": "^15.4.1" }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -3787,13 +3989,27 @@ "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } + }, + "jest-util": { + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.5.2.tgz", + "integrity": "sha512-WTL675bK+GSSAYgS8z9FWdCT2nccO1yTIplNLPlP0OD8tUk/H5IrWKMMRudIQQ0qp8bb4k+1Qa8CxGKq9qnYdg==", + "dev": true, + "requires": { + "@jest/types": "^26.5.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + } } } }, "jest-serializer": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.3.0.tgz", - "integrity": "sha512-IDRBQBLPlKa4flg77fqg0n/pH87tcRKwe8zxOVTWISxGpPHYkRZ1dXKyh04JOja7gppc60+soKVZ791mruVdow==", + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.5.0.tgz", + "integrity": "sha512-+h3Gf5CDRlSLdgTv7y0vPIAoLgX/SI7T4v6hy+TEXMgYbv+ztzbg5PSN6mUXAT/hXYHvZRWm+MaObVfqkhCGxA==", "dev": true, "requires": { "@types/node": "*", @@ -3801,32 +4017,33 @@ } }, "jest-snapshot": { - "version": "26.4.2", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.4.2.tgz", - "integrity": "sha512-N6Uub8FccKlf5SBFnL2Ri/xofbaA68Cc3MGjP/NuwgnsvWh+9hLIR/DhrxbSiKXMY9vUW5dI6EW1eHaDHqe9sg==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.5.2.tgz", + "integrity": "sha512-MkXIDvEefzDubI/WaDVSRH4xnkuirP/Pz8LhAIDXcVQTmcEfwxywj5LGwBmhz+kAAIldA7XM4l96vbpzltSjqg==", "dev": true, "requires": { "@babel/types": "^7.0.0", - "@jest/types": "^26.3.0", + "@jest/types": "^26.5.2", + "@types/babel__traverse": "^7.0.4", "@types/prettier": "^2.0.0", "chalk": "^4.0.0", - "expect": "^26.4.2", + "expect": "^26.5.2", "graceful-fs": "^4.2.4", - "jest-diff": "^26.4.2", + "jest-diff": "^26.5.2", "jest-get-type": "^26.3.0", - "jest-haste-map": "^26.3.0", - "jest-matcher-utils": "^26.4.2", - "jest-message-util": "^26.3.0", - "jest-resolve": "^26.4.0", + "jest-haste-map": "^26.5.2", + "jest-matcher-utils": "^26.5.2", + "jest-message-util": "^26.5.2", + "jest-resolve": "^26.5.2", "natural-compare": "^1.4.0", - "pretty-format": "^26.4.2", + "pretty-format": "^26.5.2", "semver": "^7.3.2" }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -3856,21 +4073,21 @@ } }, "diff-sequences": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.3.0.tgz", - "integrity": "sha512-5j5vdRcw3CNctePNYN0Wy2e/JbWT6cAYnXv5OuqPhDpyCGc0uLu2TK0zOCJWNB9kOIfYMSpIulRaDgIi4HJ6Ig==", + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.5.0.tgz", + "integrity": "sha512-ZXx86srb/iYy6jG71k++wBN9P9J05UNQ5hQHQd9MtMPvcqXPx/vKU69jfHV637D00Q2gSgPk2D+jSx3l1lDW/Q==", "dev": true }, "jest-diff": { - "version": "26.4.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.4.2.tgz", - "integrity": "sha512-6T1XQY8U28WH0Z5rGpQ+VqZSZz8EN8rZcBtfvXaOkbwxIEeRre6qnuZQlbY1AJ4MKDxQF8EkrCvK+hL/VkyYLQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.5.2.tgz", + "integrity": "sha512-HCSWDUGwsov5oTlGzrRM+UPJI/Dpqi9jzeV0fdRNi3Ch5bnoXhnyJMmVg2juv9081zLIy3HGPI5mcuGgXM2xRA==", "dev": true, "requires": { "chalk": "^4.0.0", - "diff-sequences": "^26.3.0", + "diff-sequences": "^26.5.0", "jest-get-type": "^26.3.0", - "pretty-format": "^26.4.2" + "pretty-format": "^26.5.2" } }, "jest-get-type": { @@ -3880,12 +4097,12 @@ "dev": true }, "pretty-format": { - "version": "26.4.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.4.2.tgz", - "integrity": "sha512-zK6Gd8zDsEiVydOCGLkoBoZuqv8VTiHyAbKznXe/gaph/DAeZOmit9yMfgIz5adIgAMMs5XfoYSwAX3jcCO1tA==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.5.2.tgz", + "integrity": "sha512-VizyV669eqESlkOikKJI8Ryxl/kPpbdLwNdPs2GrbQs18MpySB5S0Yo0N7zkg2xTRiFq4CFw8ct5Vg4a0xP0og==", "dev": true, "requires": { - "@jest/types": "^26.3.0", + "@jest/types": "^26.5.2", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^16.12.0" @@ -3942,23 +4159,23 @@ } }, "jest-validate": { - "version": "26.4.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.4.2.tgz", - "integrity": "sha512-blft+xDX7XXghfhY0mrsBCYhX365n8K5wNDC4XAcNKqqjEzsRUSXP44m6PL0QJEW2crxQFLLztVnJ4j7oPlQrQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.5.2.tgz", + "integrity": "sha512-FmJks0zY36mp6Af/5sqO6CTL9bNMU45yKCJk3hrz8d2aIqQIlN1pr9HPIwZE8blLaewOla134nt5+xAmWsx3SQ==", "dev": true, "requires": { - "@jest/types": "^26.3.0", + "@jest/types": "^26.5.2", "camelcase": "^6.0.0", "chalk": "^4.0.0", "jest-get-type": "^26.3.0", "leven": "^3.1.0", - "pretty-format": "^26.4.2" + "pretty-format": "^26.5.2" }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -4000,12 +4217,12 @@ "dev": true }, "pretty-format": { - "version": "26.4.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.4.2.tgz", - "integrity": "sha512-zK6Gd8zDsEiVydOCGLkoBoZuqv8VTiHyAbKznXe/gaph/DAeZOmit9yMfgIz5adIgAMMs5XfoYSwAX3jcCO1tA==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.5.2.tgz", + "integrity": "sha512-VizyV669eqESlkOikKJI8Ryxl/kPpbdLwNdPs2GrbQs18MpySB5S0Yo0N7zkg2xTRiFq4CFw8ct5Vg4a0xP0og==", "dev": true, "requires": { - "@jest/types": "^26.3.0", + "@jest/types": "^26.5.2", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^16.12.0" @@ -4014,24 +4231,24 @@ } }, "jest-watcher": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.3.0.tgz", - "integrity": "sha512-XnLdKmyCGJ3VoF6G/p5ohbJ04q/vv5aH9ENI+i6BL0uu9WWB6Z7Z2lhQQk0d2AVZcRGp1yW+/TsoToMhBFPRdQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.5.2.tgz", + "integrity": "sha512-i3m1NtWzF+FXfJ3ljLBB/WQEp4uaNhX7QcQUWMokcifFTUQBDFyUMEwk0JkJ1kopHbx7Een3KX0Q7+9koGM/Pw==", "dev": true, "requires": { - "@jest/test-result": "^26.3.0", - "@jest/types": "^26.3.0", + "@jest/test-result": "^26.5.2", + "@jest/types": "^26.5.2", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", - "jest-util": "^26.3.0", + "jest-util": "^26.5.2", "string-length": "^4.0.1" }, "dependencies": { "@jest/types": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", - "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.2.tgz", + "integrity": "sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -4059,13 +4276,27 @@ "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } + }, + "jest-util": { + "version": "26.5.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.5.2.tgz", + "integrity": "sha512-WTL675bK+GSSAYgS8z9FWdCT2nccO1yTIplNLPlP0OD8tUk/H5IrWKMMRudIQQ0qp8bb4k+1Qa8CxGKq9qnYdg==", + "dev": true, + "requires": { + "@jest/types": "^26.5.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + } } } }, "jest-worker": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.3.0.tgz", - "integrity": "sha512-Vmpn2F6IASefL+DVBhPzI2J9/GJUsqzomdeN+P+dK8/jKxbh8R3BtFnx3FIta7wYlPU62cpJMJQo4kuOowcMnw==", + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.5.0.tgz", + "integrity": "sha512-kTw66Dn4ZX7WpjZ7T/SUDgRhapFRKWmisVAF0Rv4Fu8SLFD7eLbqpLvbxVqYhSgaWa7I+bW7pHnbyfNsH6stug==", "dev": true, "requires": { "@types/node": "*", @@ -4979,9 +5210,9 @@ "dev": true }, "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, "safe-regex": { @@ -5213,9 +5444,9 @@ "dev": true }, "simple-plist": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.1.0.tgz", - "integrity": "sha1-g1SrY+s5IqBUx4zpbCCcUy6QeiM=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.1.1.tgz", + "integrity": "sha512-pKMCVKvZbZTsqYR6RKgLfBHkh2cV89GXcA/0CVPje3sOiNOnXA8+rp/ciAMZ7JRaUdLzlEM6JFfUn+fS6Nt3hg==", "dev": true, "requires": { "bplist-creator": "0.0.8", @@ -5366,8 +5597,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "source-map-resolve": { "version": "0.5.3", @@ -5386,7 +5616,6 @@ "version": "0.5.19", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -5425,9 +5654,9 @@ } }, "spdx-license-ids": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz", + "integrity": "sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==", "dev": true }, "split-string": { @@ -5694,22 +5923,22 @@ } }, "ts-jest": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.3.0.tgz", - "integrity": "sha512-Jq2uKfx6bPd9+JDpZNMBJMdMQUC3sJ08acISj8NXlVgR2d5OqslEHOR2KHMgwymu8h50+lKIm0m0xj/ioYdW2Q==", + "version": "26.4.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.4.1.tgz", + "integrity": "sha512-F4aFq01aS6mnAAa0DljNmKr/Kk9y4HVZ1m6/rtJ0ED56cuxINGq3Q9eVAh+z5vcYKe5qnTMvv90vE8vUMFxomg==", "dev": true, "requires": { "@types/jest": "26.x", "bs-logger": "0.x", "buffer-from": "1.x", "fast-json-stable-stringify": "2.x", - "jest-util": "26.x", + "jest-util": "^26.1.0", "json5": "2.x", "lodash.memoize": "4.x", "make-error": "1.x", "mkdirp": "1.x", "semver": "7.x", - "yargs-parser": "18.x" + "yargs-parser": "20.x" }, "dependencies": { "mkdirp": { @@ -5717,6 +5946,12 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true + }, + "yargs-parser": { + "version": "20.2.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.0.tgz", + "integrity": "sha512-2agPoRFPoIcFzOIp6656gcvsg2ohtscpw2OINr/q46+Sq41xz2OYLqx5HRHabmFU1OARIPAYH5uteICE7mn/5A==", + "dev": true } } }, @@ -5733,6 +5968,11 @@ "yn": "3.1.1" } }, + "tslib": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.2.tgz", + "integrity": "sha512-wAH28hcEKwna96/UacuWaVspVLkg4x1aDM9JlzqaQTOFczCktkVAb5fmXChgandR1EraDPs2w8P+ozM+oafwxg==" + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -5778,9 +6018,9 @@ } }, "typescript": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz", - "integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.3.tgz", + "integrity": "sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==", "dev": true }, "union-value": { @@ -5857,9 +6097,9 @@ "dev": true }, "uuid": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz", - "integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==", + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", + "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==", "dev": true, "optional": true }, @@ -5952,9 +6192,9 @@ "dev": true }, "whatwg-url": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.2.1.tgz", - "integrity": "sha512-ZmVCr6nfBeaMxEHALLEGy0LszYjpJqf6PVNQUQ1qd9Et+q7Jpygd4rGGDXgHjD8e99yLFseD69msHDM4YwPZ4A==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.3.0.tgz", + "integrity": "sha512-BQRf/ej5Rp3+n7k0grQXZj9a1cHtsp4lqj01p59xBWFKdezR8sO37XnpafwNqiFac/v2Il12EIMjX/Y4VZtT8Q==", "dev": true, "requires": { "lodash.sortby": "^4.7.0", diff --git a/package.json b/package.json index a08870c16..9d44f2902 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "hap-nodejs", "version": "0.8.2", + "betaVersion": "0.9.0", "description": "HAP-NodeJS is a Node.js implementation of HomeKit Accessory Server.", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -11,11 +12,12 @@ "homepage": "https://github.com/homebridge/HAP-NodeJS", "license": "Apache-2.0", "scripts": { - "clean": "rimraf dist/", - "build": "rimraf dist/ && tsc && node .github/workflows/node-persist-ignore.js", + "clean": "rimraf dist && rimraf coverage", + "build": "rimraf dist && tsc && node .github/workflows/node-persist-ignore.js", "prepublishOnly": "npm run build", "postpublish": "npm run clean", "test": "jest", + "test-coverage": "jest --coverage", "start": "node dist/BridgedCore.js" }, "keywords": [ @@ -50,25 +52,27 @@ "@types" ], "dependencies": { - "@homebridge/ciao": "~1.0.5", + "@homebridge/ciao": "~1.1.0-beta.23", "fast-srp-hap": "2.0.2", "tweetnacl": "^1.0.3", - "debug": "^4.1.1", - "decimal.js": "^10.2.0", + "debug": "^4.3.0", "ip": "^1.1.3", "node-persist": "^0.0.11", - "futoin-hkdf": "~1.3.2" + "futoin-hkdf": "~1.3.2", + "source-map-support": "^0.5.19", + "tslib": "^2.0.2" }, "devDependencies": { "@types/debug": "^4.1.5", - "@types/jest": "^26.0.13", + "@types/jest": "^26.0.14", "@types/node": "^10.17.20", - "jest": "^26.4.2", + "commander": "^6.1.0", + "jest": "^26.5.2", "rimraf": "^3.0.2", "semver": "^7.3.2", - "simple-plist": "^1.1.0", - "ts-jest": "^26.3.0", + "simple-plist": "^1.1.1", + "ts-jest": "^26.4.1", "ts-node": "^9.0.0", - "typescript": "^4.0.2" + "typescript": "^4.0.3" } } diff --git a/src/BridgedCore.ts b/src/BridgedCore.ts index 9a9fd2cfa..0d977ba3d 100644 --- a/src/BridgedCore.ts +++ b/src/BridgedCore.ts @@ -22,8 +22,8 @@ bridge.on(AccessoryEventTypes.IDENTIFY, (paired: boolean, callback: VoidCallback }); // Load up all accessories in the /accessories folder -var dir = path.join(__dirname, "accessories"); -var accessories = AccessoryLoader.loadDirectory(dir); +const dir = path.join(__dirname, "accessories"); +const accessories = AccessoryLoader.loadDirectory(dir); // Add them all to the bridge accessories.forEach((accessory: Accessory) => { @@ -37,7 +37,7 @@ bridge.publish({ category: Categories.BRIDGE }); -var signals = { 'SIGINT': 2, 'SIGTERM': 15 } as Record; +const signals = {'SIGINT': 2, 'SIGTERM': 15} as Record; Object.keys(signals).forEach((signal: any) => { process.on(signal, function () { bridge.unpublish(); diff --git a/src/Core.ts b/src/Core.ts index fc46f29d4..1fb2e71a2 100644 --- a/src/Core.ts +++ b/src/Core.ts @@ -14,11 +14,11 @@ console.warn("DEPRECATION NOTICE: The use of Core and BridgeCore are deprecated storage.initSync(); // Our Accessories will each have their own HAP server; we will assign ports sequentially -var targetPort = 51826; +let targetPort = 51826; // Load up all accessories in the /accessories folder -var dir = path.join(__dirname, "accessories"); -var accessories = AccessoryLoader.loadDirectory(dir); +const dir = path.join(__dirname, "accessories"); +const accessories = AccessoryLoader.loadDirectory(dir); // Publish them all separately (as opposed to BridgedCore which publishes them behind a single Bridge accessory) accessories.forEach((accessory) => { @@ -41,10 +41,10 @@ accessories.forEach((accessory) => { }); }); -var signals = { 'SIGINT': 2, 'SIGTERM': 15 } as Record; +const signals = {'SIGINT': 2, 'SIGTERM': 15} as Record; Object.keys(signals).forEach((signal: any) => { process.on(signal, () => { - for (var i = 0; i < accessories.length; i++) { + for (let i = 0; i < accessories.length; i++) { accessories[i].unpublish(); } diff --git a/src/accessories/AirConditioner_accessory.ts b/src/accessories/AirConditioner_accessory.ts index 470227d8e..09c92399f 100644 --- a/src/accessories/AirConditioner_accessory.ts +++ b/src/accessories/AirConditioner_accessory.ts @@ -1,4 +1,4 @@ -//In This example we create an Airconditioner Accessory that Has a Thermostat linked to a Fan Service. +//In This example we create an air conditioner Accessory that Has a Thermostat linked to a Fan Service. //For example, I've also put a Light Service that should be hidden to represent a light in the closet that is part of the AC. It is to show how to hide services. //The linking and Hiding does NOT appear to be reflected in Home @@ -8,14 +8,16 @@ import { AccessoryEventTypes, Categories, Characteristic, - CharacteristicEventTypes, CharacteristicGetCallback, CharacteristicSetCallback, + CharacteristicEventTypes, + CharacteristicGetCallback, + CharacteristicSetCallback, CharacteristicValue, Service, uuid, } from '..'; -import { NodeCallback, VoidCallback } from '../types'; +import { VoidCallback } from '../types'; -var ACTest_data: Record = { +const ACTest_data: Record = { fanPowerOn: false, rSpeed: 100, CurrentHeatingCoolingState: 1, @@ -24,10 +26,10 @@ var ACTest_data: Record = { TargetTemperature: 32, TemperatureDisplayUnits: 1, LightOn: false -} +}; // This is the Accessory that we'll return to HAP-NodeJS that represents our fake fan. -var ACTest = exports.accessory = new Accessory('Air Conditioner', uuid.generate('hap-nodejs:accessories:airconditioner')); +const ACTest = exports.accessory = new Accessory('Air Conditioner', uuid.generate('hap-nodejs:accessories:airconditioner')); // Add properties for publishing (in case we're using Core.js and not BridgedCore.js) // @ts-ignore @@ -49,9 +51,8 @@ ACTest.on(AccessoryEventTypes.IDENTIFY, (paired: boolean, callback: VoidCallback }); // Add the actual Fan Service and listen for change events from iOS. -// We can see the complete list of Services and Characteristics in `lib/gen/HomeKit.ts` -var FanService = ACTest.addService(Service.Fan, "Blower") // services exposed to the user should have "names" like "Fake Light" for us +const FanService = ACTest.addService(Service.Fan, "Blower"); // services exposed to the user should have "names" like "Fake Light" for us FanService.getCharacteristic(Characteristic.On)! .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { console.log("Fan Power Changed To "+value); @@ -68,7 +69,7 @@ FanService.getCharacteristic(Characteristic.On)! // the fan hardware itself to find this out, then call the callback. But if you take longer than a // few seconds to respond, Siri will give up. - var err = null; // in case there were any problems + const err = null; // in case there were any problems if (ACTest_data.fanPowerOn) { callback(err, true); @@ -90,7 +91,7 @@ FanService.addCharacteristic(Characteristic.RotationSpeed) callback(); }) -var ThermostatService = ACTest.addService(Service.Thermostat,"Thermostat"); +const ThermostatService = ACTest.addService(Service.Thermostat, "Thermostat"); ThermostatService.addLinkedService(FanService); ThermostatService.setPrimaryService(); @@ -146,8 +147,7 @@ ThermostatService.getCharacteristic(Characteristic.CurrentHeatingCoolingState)! }); - -var LightService = ACTest.addService(Service.Lightbulb, 'AC Light'); +const LightService = ACTest.addService(Service.Lightbulb, 'AC Light'); LightService.getCharacteristic(Characteristic.On)! .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { callback(null, ACTest_data.LightOn); diff --git a/src/accessories/Fan_accessory.ts b/src/accessories/Fan_accessory.ts index de9d3b5b2..b7e17b8b0 100644 --- a/src/accessories/Fan_accessory.ts +++ b/src/accessories/Fan_accessory.ts @@ -4,23 +4,20 @@ import { AccessoryEventTypes, Categories, Characteristic, - CharacteristicEventTypes, CharacteristicSetCallback, CharacteristicValue, - NodeCallback, Service, uuid, VoidCallback } from '..'; -var FAKE_FAN: Record = { +const FAKE_FAN: Record = { powerOn: false, rSpeed: 100, setPowerOn: (on: CharacteristicValue) => { - if(on){ + if (on) { //put your code here to turn on the fan FAKE_FAN.powerOn = on; - } - else{ + } else { //put your code here to turn off the fan FAKE_FAN.powerOn = on; } @@ -34,10 +31,10 @@ var FAKE_FAN: Record = { //put your code here to identify the fan console.log("Fan Identified!"); } -} +}; // This is the Accessory that we'll return to HAP-NodeJS that represents our fake fan. -var fan = exports.accessory = new Accessory('Fan', uuid.generate('hap-nodejs:accessories:Fan')); +const fan = exports.accessory = new Accessory('Fan', uuid.generate('hap-nodejs:accessories:Fan')); // Add properties for publishing (in case we're using Core.js and not BridgedCore.js) // @ts-ignore @@ -59,13 +56,11 @@ fan.on(AccessoryEventTypes.IDENTIFY, (paired: boolean, callback: VoidCallback) = }); // Add the actual Fan Service and listen for change events from iOS. -// We can see the complete list of Services and Characteristics in `lib/gen/HomeKit.ts` fan .addService(Service.Fan, "Fan") // services exposed to the user should have "names" like "Fake Light" for us .getCharacteristic(Characteristic.On)! - .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { + .onSet((value) => { FAKE_FAN.setPowerOn(value); - callback(); // Our fake Fan is synchronous - this value has been successfully set }); // We want to intercept requests for our current power state so we can query the hardware itself instead of @@ -73,19 +68,16 @@ fan fan .getService(Service.Fan)! .getCharacteristic(Characteristic.On)! - .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { + .onGet(() => { // this event is emitted when you ask Siri directly whether your fan is on or not. you might query // the fan hardware itself to find this out, then call the callback. But if you take longer than a // few seconds to respond, Siri will give up. - var err = null; // in case there were any problems - if (FAKE_FAN.powerOn) { - callback(err, true); - } - else { - callback(err, false); + return true; + } else { + return false; } }); @@ -93,10 +85,9 @@ fan fan .getService(Service.Fan)! .addCharacteristic(Characteristic.RotationSpeed) - .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { - callback(null, FAKE_FAN.rSpeed); + .onGet(async () => { + return FAKE_FAN.rSpeed; }) - .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { + .onSet(async (value) => { FAKE_FAN.setSpeed(value); - callback(); }) diff --git a/src/accessories/GarageDoorOpener_accessory.ts b/src/accessories/GarageDoorOpener_accessory.ts index faa33620d..c3074bb72 100644 --- a/src/accessories/GarageDoorOpener_accessory.ts +++ b/src/accessories/GarageDoorOpener_accessory.ts @@ -10,7 +10,7 @@ import { VoidCallback } from '..'; -var FAKE_GARAGE = { +const FAKE_GARAGE = { opened: false, open: () => { console.log("Opening the Garage!"); @@ -26,15 +26,15 @@ var FAKE_GARAGE = { //add your code here which allows the garage to be identified console.log("Identify the Garage"); }, - status: () =>{ + status: () => { //use this section to get sensor values. set the boolean FAKE_GARAGE.opened with a sensor value. console.log("Sensor queried!"); //FAKE_GARAGE.opened = true/false; } }; -var garageUUID = uuid.generate('hap-nodejs:accessories:'+'GarageDoor'); -var garage = exports.accessory = new Accessory('Garage Door', garageUUID); +const garageUUID = uuid.generate('hap-nodejs:accessories:' + 'GarageDoor'); +const garage = exports.accessory = new Accessory('Garage Door', garageUUID); // Add properties for publishing (in case we're using Core.js and not BridgedCore.js) // @ts-ignore @@ -83,7 +83,7 @@ garage .getCharacteristic(Characteristic.CurrentDoorState)! .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { - var err = null; + const err = null; FAKE_GARAGE.status(); if (FAKE_GARAGE.opened) { diff --git a/src/accessories/Light_accessory.ts b/src/accessories/Light_accessory.ts index 1cd4f6522..fb6d01264 100644 --- a/src/accessories/Light_accessory.ts +++ b/src/accessories/Light_accessory.ts @@ -77,10 +77,10 @@ const LightController = new LightControllerClass(); // Generate a consistent UUID for our light Accessory that will remain the same even when // restarting our server. We use the `uuid.generate` helper function to create a deterministic // UUID based on an arbitrary "namespace" and the word "light". -var lightUUID = uuid.generate('hap-nodejs:accessories:light' + LightController.name); +const lightUUID = uuid.generate('hap-nodejs:accessories:light' + LightController.name); // This is the Accessory that we'll return to HAP-NodeJS that represents our light. -var lightAccessory = exports.accessory = new Accessory(LightController.name as string, lightUUID); +const lightAccessory = exports.accessory = new Accessory(LightController.name as string, lightUUID); // Add properties for publishing (in case we're using Core.js and not BridgedCore.js) // @ts-ignore @@ -104,7 +104,6 @@ lightAccessory.on(AccessoryEventTypes.IDENTIFY, (paired: boolean, callback: Void }); // Add the actual Lightbulb Service and listen for change events from iOS. -// We can see the complete list of Services and Characteristics in `lib/gen/HomeKit.ts` lightAccessory .addService(Service.Lightbulb, LightController.name) // services exposed to the user should have "names" like "Light" for this case .getCharacteristic(Characteristic.On)! diff --git a/src/accessories/Lock_accessory.ts b/src/accessories/Lock_accessory.ts index de3713ce0..2e708e4b0 100644 --- a/src/accessories/Lock_accessory.ts +++ b/src/accessories/Lock_accessory.ts @@ -3,15 +3,13 @@ import { AccessoryEventTypes, Categories, Characteristic, - CharacteristicEventTypes, CharacteristicSetCallback, - CharacteristicValue, + CharacteristicEventTypes, Service, uuid } from '../'; -import { NodeCallback, VoidCallback } from '../types'; // here's a fake hardware device that we'll expose to HomeKit -var FAKE_LOCK = { +const FAKE_LOCK = { locked: false, lock: () => { console.log("Locking the lock!"); @@ -24,15 +22,15 @@ var FAKE_LOCK = { identify: () => { console.log("Identify the lock!"); } -} +}; // Generate a consistent UUID for our Lock Accessory that will remain the same even when // restarting our server. We use the `uuid.generate` helper function to create a deterministic // UUID based on an arbitrary "namespace" and the word "lock". -var lockUUID = uuid.generate('hap-nodejs:accessories:lock'); +const lockUUID = uuid.generate('hap-nodejs:accessories:lock'); // This is the Accessory that we'll return to HAP-NodeJS that represents our fake lock. -var lock = exports.accessory = new Accessory('Lock', lockUUID); +const lock = exports.accessory = new Accessory('Lock', lockUUID); // Add properties for publishing (in case we're using Core.js and not BridgedCore.js) // @ts-ignore @@ -45,62 +43,53 @@ lock.category = Categories.DOOR_LOCK; // set some basic properties (these values are arbitrary and setting them is optional) lock .getService(Service.AccessoryInformation)! - .setCharacteristic(Characteristic.Manufacturer, "Oltica") - .setCharacteristic(Characteristic.Model, "Rev-1") - .setCharacteristic(Characteristic.SerialNumber, "A1S2NASF88EW"); + .setCharacteristic(Characteristic.Manufacturer, "Lock Manufacturer") + .setCharacteristic(Characteristic.Model, "Rev-2") + .setCharacteristic(Characteristic.SerialNumber, "MY-Serial-Number"); // listen for the "identify" event for this Accessory -lock.on(AccessoryEventTypes.IDENTIFY, (paired: boolean, callback: VoidCallback) => { +lock.on(AccessoryEventTypes.IDENTIFY, (paired, callback) => { FAKE_LOCK.identify(); callback(); // success }); +const service = new Service.LockMechanism("Fake Lock"); + // Add the actual Door Lock Service and listen for change events from iOS. -// We can see the complete list of Services and Characteristics in `lib/gen/HomeKit.ts` -lock - .addService(Service.LockMechanism, "Fake Lock") // services exposed to the user should have "names" like "Fake Light" for us - .getCharacteristic(Characteristic.LockTargetState)! - .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { +service.getCharacteristic(Characteristic.LockTargetState) + .on(CharacteristicEventTypes.SET, (value, callback) => { if (value == Characteristic.LockTargetState.UNSECURED) { FAKE_LOCK.unlock(); callback(); // Our fake Lock is synchronous - this value has been successfully set // now we want to set our lock's "actual state" to be unsecured so it shows as unlocked in iOS apps - lock - .getService(Service.LockMechanism)! - .setCharacteristic(Characteristic.LockCurrentState, Characteristic.LockCurrentState.UNSECURED); - } - else if (value == Characteristic.LockTargetState.SECURED) { + service.updateCharacteristic(Characteristic.LockCurrentState, Characteristic.LockCurrentState.UNSECURED); + } else if (value == Characteristic.LockTargetState.SECURED) { FAKE_LOCK.lock(); callback(); // Our fake Lock is synchronous - this value has been successfully set // now we want to set our lock's "actual state" to be locked so it shows as open in iOS apps - lock - .getService(Service.LockMechanism)! - .setCharacteristic(Characteristic.LockCurrentState, Characteristic.LockCurrentState.SECURED); + service.updateCharacteristic(Characteristic.LockCurrentState, Characteristic.LockCurrentState.SECURED); } }); // We want to intercept requests for our current state so we can query the hardware itself instead of // allowing HAP-NodeJS to return the cached Characteristic.value. -lock - .getService(Service.LockMechanism)! - .getCharacteristic(Characteristic.LockCurrentState)! - .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { +service.getCharacteristic(Characteristic.LockCurrentState) + .on(CharacteristicEventTypes.GET, callback => { // this event is emitted when you ask Siri directly whether your lock is locked or not. you might query // the lock hardware itself to find this out, then call the callback. But if you take longer than a // few seconds to respond, Siri will give up. - var err = null; // in case there were any problems - if (FAKE_LOCK.locked) { console.log("Are we locked? Yes."); - callback(err, Characteristic.LockCurrentState.SECURED); - } - else { + callback(undefined, Characteristic.LockCurrentState.SECURED); + } else { console.log("Are we locked? No."); - callback(err, Characteristic.LockCurrentState.UNSECURED); + callback(undefined, Characteristic.LockCurrentState.UNSECURED); } }); + +lock.addService(service); diff --git a/src/accessories/MotionSensor_accessory.ts b/src/accessories/MotionSensor_accessory.ts index e97093354..954e3a88a 100644 --- a/src/accessories/MotionSensor_accessory.ts +++ b/src/accessories/MotionSensor_accessory.ts @@ -11,7 +11,7 @@ import { uuid, VoidCallback } from '..'; -var MOTION_SENSOR = { +const MOTION_SENSOR = { motionDetected: false, getStatus: () => { @@ -21,15 +21,15 @@ var MOTION_SENSOR = { identify: () => { console.log("Identify the motion sensor!"); } -} +}; // Generate a consistent UUID for our Motion Sensor Accessory that will remain the same even when // restarting our server. We use the `uuid.generate` helper function to create a deterministic // UUID based on an arbitrary "namespace" and the word "motionsensor". -var motionSensorUUID = uuid.generate('hap-nodejs:accessories:motionsensor'); +const motionSensorUUID = uuid.generate('hap-nodejs:accessories:motionsensor'); // This is the Accessory that we'll return to HAP-NodeJS that represents our fake motionSensor. -var motionSensor = exports.accessory = new Accessory('Motion Sensor', motionSensorUUID); +const motionSensor = exports.accessory = new Accessory('Motion Sensor', motionSensorUUID); // Add properties for publishing (in case we're using Core.js and not BridgedCore.js) // @ts-ignore diff --git a/src/accessories/Outlet_accessory.ts b/src/accessories/Outlet_accessory.ts index 82f8179a5..3e56634c7 100644 --- a/src/accessories/Outlet_accessory.ts +++ b/src/accessories/Outlet_accessory.ts @@ -14,32 +14,36 @@ import { Nullable } from '../types'; let err: Nullable = null; // in case there were any problems // here's a fake hardware device that we'll expose to HomeKit -var FAKE_OUTLET = { +const FAKE_OUTLET = { powerOn: false, - setPowerOn: (on: CharacteristicValue) => { + setPowerOn: (on: CharacteristicValue) => { console.log("Turning the outlet %s!...", on ? "on" : "off"); if (on) { - FAKE_OUTLET.powerOn = true; - if(err) { return console.log(err); } - console.log("...outlet is now on."); + FAKE_OUTLET.powerOn = true; + if (err) { + return console.log(err); + } + console.log("...outlet is now on."); } else { - FAKE_OUTLET.powerOn = false; - if(err) { return console.log(err); } - console.log("...outlet is now off."); + FAKE_OUTLET.powerOn = false; + if (err) { + return console.log(err); + } + console.log("...outlet is now off."); } }, - identify: function() { + identify: function () { console.log("Identify the outlet."); - } -} + } +}; // Generate a consistent UUID for our outlet Accessory that will remain the same even when // restarting our server. We use the `uuid.generate` helper function to create a deterministic // UUID based on an arbitrary "namespace" and the accessory name. -var outletUUID = uuid.generate('hap-nodejs:accessories:Outlet'); +const outletUUID = uuid.generate('hap-nodejs:accessories:Outlet'); // This is the Accessory that we'll return to HAP-NodeJS that represents our fake light. -var outlet = exports.accessory = new Accessory('Outlet', outletUUID); +const outlet = exports.accessory = new Accessory('Outlet', outletUUID); // Add properties for publishing (in case we're using Core.js and not BridgedCore.js) // @ts-ignore @@ -63,7 +67,6 @@ outlet.on(AccessoryEventTypes.IDENTIFY, function(paired: boolean, callback: Void }); // Add the actual outlet Service and listen for change events from iOS. -// We can see the complete list of Services and Characteristics in `lib/gen/HomeKit.ts` outlet .addService(Service.Outlet, "Fake Outlet") // services exposed to the user should have "names" like "Fake Light" for us .getCharacteristic(Characteristic.On)! @@ -83,7 +86,7 @@ outlet // the light hardware itself to find this out, then call the callback. But if you take longer than a // few seconds to respond, Siri will give up. - var err = null; // in case there were any problems + const err = null; // in case there were any problems if (FAKE_OUTLET.powerOn) { console.log("Are we on? Yes."); diff --git a/src/accessories/SmartSpeaker_accessory.ts b/src/accessories/SmartSpeaker_accessory.ts index edee0c4e2..079e79b72 100644 --- a/src/accessories/SmartSpeaker_accessory.ts +++ b/src/accessories/SmartSpeaker_accessory.ts @@ -1,13 +1,14 @@ import { - Accessory, - Categories, - Characteristic, - CharacteristicEventTypes, - CharacteristicGetCallback, CharacteristicSetCallback, CharacteristicValue, - Service, - uuid + Accessory, + Categories, + Characteristic, + CharacteristicEventTypes, + CharacteristicGetCallback, + CharacteristicSetCallback, + CharacteristicValue, + Service, + uuid } from ".."; -import {CurrentMediaState, TargetMediaState} from "../lib/gen/HomeKit-TV"; const speakerUUID = uuid.generate('hap-nodejs:accessories:smart-speaker'); const speaker = exports.accessory = new Accessory('SmartSpeaker', speakerUUID); @@ -20,8 +21,8 @@ speaker.category = Categories.SPEAKER; const service = new Service.SmartSpeaker('Smart Speaker', ''); -let currentMediaState: number = CurrentMediaState.PAUSE; -let targetMediaState: number = TargetMediaState.PAUSE; +let currentMediaState: number = Characteristic.CurrentMediaState.PAUSE; +let targetMediaState: number = Characteristic.TargetMediaState.PAUSE; // ConfigureName is used to listen for Name changes inside the Home App. // A device manufacturer would probably need to adjust the name of the device in the AirPlay 2 protocol (or something) @@ -30,31 +31,33 @@ service.setCharacteristic(Characteristic.Mute, false); service.setCharacteristic(Characteristic.Volume, 100); service.getCharacteristic(Characteristic.CurrentMediaState)! - .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { - console.log("Reading CurrentMediaState: " + currentMediaState); - callback(undefined, currentMediaState); - }).getValue(); + .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { + console.log("Reading CurrentMediaState: " + currentMediaState); + callback(undefined, currentMediaState); + }) + .updateValue(currentMediaState); // init value service.getCharacteristic(Characteristic.TargetMediaState)! - .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { - console.log("Setting TargetMediaState to: " + value); - targetMediaState = value as number; - currentMediaState = targetMediaState; + .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { + console.log("Setting TargetMediaState to: " + value); + targetMediaState = value as number; + currentMediaState = targetMediaState; - callback(); + callback(); - service.setCharacteristic(Characteristic.CurrentMediaState, targetMediaState); - }) - .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { - console.log("Reading TargetMediaState: " + targetMediaState); - callback(undefined, targetMediaState); - }).getValue(); + service.setCharacteristic(Characteristic.CurrentMediaState, targetMediaState); + }) + .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { + console.log("Reading TargetMediaState: " + targetMediaState); + callback(undefined, targetMediaState); + }) + .updateValue(targetMediaState); service.getCharacteristic(Characteristic.ConfiguredName)! - .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { - console.log(`Name was changed to: '${value}'`); - callback(); - }); + .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { + console.log(`Name was changed to: '${value}'`); + callback(); + }); speaker.addService(service); diff --git a/src/accessories/Sprinkler_accessory.ts b/src/accessories/Sprinkler_accessory.ts index b515258f5..027947063 100644 --- a/src/accessories/Sprinkler_accessory.ts +++ b/src/accessories/Sprinkler_accessory.ts @@ -11,7 +11,7 @@ import { uuid } from '..'; -var SPRINKLER: any = { +const SPRINKLER: any = { active: false, name: "Garten Hinten", timerEnd: 0, @@ -25,16 +25,16 @@ var SPRINKLER: any = { identify: () => { console.log("Identify the sprinkler!"); } -} +}; // Generate a consistent UUID for our Motion Sensor Accessory that will remain the same even when // restarting our server. We use the `uuid.generate` helper function to create a deterministic // UUID based on an arbitrary "namespace" and the word "motionsensor". -var sprinklerUUID = uuid.generate('hap-nodejs:accessories:sprinkler'); +const sprinklerUUID = uuid.generate('hap-nodejs:accessories:sprinkler'); // This is the Accessory that we'll return to HAP-NodeJS that represents our fake motionSensor. -var sprinkler = exports.accessory = new Accessory('💦 Sprinkler', sprinklerUUID); +const sprinkler = exports.accessory = new Accessory('💦 Sprinkler', sprinklerUUID); // Add properties for publishing (in case we're using Core.js and not BridgedCore.js) // @ts-ignore @@ -45,24 +45,21 @@ sprinkler.pincode = "123-44-567"; sprinkler.category = Categories.SPRINKLER; // Add the actual Valve Service and listen for change events from iOS. -// We can see the complete list of Services and Characteristics in `lib/gen/HomeKit.ts` -var sprinklerService = sprinkler.addService(Service.Valve, "💦 Sprinkler") +const sprinklerService = sprinkler.addService(Service.Valve, "💦 Sprinkler"); // set some basic properties (these values are arbitrary and setting them is optional) -sprinkler - .getService(Service.Valve)! +sprinklerService .setCharacteristic(Characteristic.ValveType, "1") // IRRIGATION/SPRINKLER = 1; SHOWER_HEAD = 2; WATER_FAUCET = 3; .setCharacteristic(Characteristic.Name, SPRINKLER.name) ; -sprinkler - .getService(Service.Valve)! +sprinklerService .getCharacteristic(Characteristic.Active)! .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { console.log("get Active"); - var err = null; // in case there were any problems + const err = null; // in case there were any problems if (SPRINKLER.active) { callback(err, true); @@ -118,12 +115,11 @@ sprinkler }); -sprinkler - .getService(Service.Valve)! +sprinklerService .getCharacteristic(Characteristic.InUse)! .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { console.log("get In_Use"); - var err = null; // in case there were any problems + const err = null; // in case there were any problems if (SPRINKLER.active) { callback(err, true); @@ -134,19 +130,19 @@ sprinkler }) .on(CharacteristicEventTypes.SET, (newValue: CharacteristicValue, callback: CharacteristicSetCallback) => { console.log("set In_Use => NewValue: " + newValue); + callback(); }); - sprinkler - .getService(Service.Valve)! +sprinklerService .getCharacteristic(Characteristic.RemainingDuration)! .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { - var err = null; // in case there were any problems + const err = null; // in case there were any problems if (SPRINKLER.active) { - var duration = SPRINKLER.timerEnd - Math.floor(new Date().getTime() / 1000); + const duration = SPRINKLER.timerEnd - Math.floor(new Date().getTime() / 1000); console.log("RemainingDuration: " + duration) callback(err, duration); } @@ -155,14 +151,10 @@ sprinkler } }); - - sprinkler - .getService(Service.Valve)! +sprinklerService .getCharacteristic(Characteristic.SetDuration)! .on(CharacteristicEventTypes.SET, (newValue: CharacteristicValue, callback: CharacteristicSetCallback) => { console.log("SetDuration => NewValue: " + newValue); - - var err = null; // in case there were any problems SPRINKLER.defaultDuration = newValue; callback(); }); diff --git a/src/accessories/TV_accessory.ts b/src/accessories/TV_accessory.ts index 7a22a79f0..f6a74696e 100644 --- a/src/accessories/TV_accessory.ts +++ b/src/accessories/TV_accessory.ts @@ -15,10 +15,10 @@ import { // Generate a consistent UUID for TV that will remain the same even when // restarting our server. We use the `uuid.generate` helper function to create a deterministic // UUID based on an arbitrary "namespace" and the word "tv". -var tvUUID = uuid.generate('hap-nodejs:accessories:tv'); +const tvUUID = uuid.generate('hap-nodejs:accessories:tv'); // This is the Accessory that we'll return to HAP-NodeJS. -var tv = exports.accessory = new Accessory('TV', tvUUID); +const tv = exports.accessory = new Accessory('TV', tvUUID); // Add properties for publishing (in case we're using Core.js and not BridgedCore.js) // @ts-ignore @@ -29,8 +29,7 @@ tv.pincode = "031-45-154"; tv.category = Categories.TELEVISION; // Add the actual TV Service and listen for change events from iOS. -// We can see the complete list of Services and Characteristics in `lib/gen/HomeKit.ts` -var televisionService = tv.addService(Service.Television, "Television", "Television"); +const televisionService = tv.addService(Service.Television, "Television", "Television"); televisionService .setCharacteristic(Characteristic.ConfiguredName, "Television"); @@ -81,7 +80,7 @@ televisionService // Speaker -var speakerService = tv.addService(Service.TelevisionSpeaker) +const speakerService = tv.addService(Service.TelevisionSpeaker); speakerService .setCharacteristic(Characteristic.Active, Characteristic.Active.ACTIVE) @@ -95,7 +94,7 @@ speakerService.getCharacteristic(Characteristic.VolumeSelector)! // HDMI 1 -var inputHDMI1 = tv.addService(Service.InputSource, "hdmi1", "HDMI 1"); +const inputHDMI1 = tv.addService(Service.InputSource, "hdmi1", "HDMI 1"); inputHDMI1 .setCharacteristic(Characteristic.Identifier, 1) @@ -105,7 +104,7 @@ inputHDMI1 // HDMI 2 -var inputHDMI2 = tv.addService(Service.InputSource, "hdmi2", "HDMI 2"); +const inputHDMI2 = tv.addService(Service.InputSource, "hdmi2", "HDMI 2"); inputHDMI2 .setCharacteristic(Characteristic.Identifier, 2) @@ -115,7 +114,7 @@ inputHDMI2 // Netflix -var inputNetflix = tv.addService(Service.InputSource, "netflix", "Netflix"); +const inputNetflix = tv.addService(Service.InputSource, "netflix", "Netflix"); inputNetflix .setCharacteristic(Characteristic.Identifier, 3) diff --git a/src/accessories/TemperatureSensor_accessory.ts b/src/accessories/TemperatureSensor_accessory.ts index 2441615f6..38574c9bc 100644 --- a/src/accessories/TemperatureSensor_accessory.ts +++ b/src/accessories/TemperatureSensor_accessory.ts @@ -1,26 +1,26 @@ // here's a fake temperature sensor device that we'll expose to HomeKit import { Accessory, Categories, Characteristic, CharacteristicEventTypes, CharacteristicValue, NodeCallback, Service, uuid } from '..'; -var FAKE_SENSOR = { +const FAKE_SENSOR = { currentTemperature: 50, - getTemperature: function() { + getTemperature: function () { console.log("Getting the current temperature!"); return FAKE_SENSOR.currentTemperature; }, - randomizeTemperature: function() { + randomizeTemperature: function () { // randomize temperature to a value between 0 and 100 FAKE_SENSOR.currentTemperature = Math.round(Math.random() * 100); } -} +}; // Generate a consistent UUID for our Temperature Sensor Accessory that will remain the same // even when restarting our server. We use the `uuid.generate` helper function to create // a deterministic UUID based on an arbitrary "namespace" and the string "temperature-sensor". -var sensorUUID = uuid.generate('hap-nodejs:accessories:temperature-sensor'); +const sensorUUID = uuid.generate('hap-nodejs:accessories:temperature-sensor'); // This is the Accessory that we'll return to HAP-NodeJS that represents our fake lock. -var sensor = exports.accessory = new Accessory('Temperature Sensor', sensorUUID); +const sensor = exports.accessory = new Accessory('Temperature Sensor', sensorUUID); // Add properties for publishing (in case we're using Core.js and not BridgedCore.js) // @ts-ignore @@ -31,7 +31,6 @@ sensor.pincode = "031-45-154"; sensor.category = Categories.SENSOR; // Add the actual TemperatureSensor Service. -// We can see the complete list of Services and Characteristics in `lib/gen/HomeKit.ts` sensor .addService(Service.TemperatureSensor)! .getCharacteristic(Characteristic.CurrentTemperature)! diff --git a/src/accessories/Thermostat_accessory.ts b/src/accessories/Thermostat_accessory.ts deleted file mode 100644 index 42db18b61..000000000 --- a/src/accessories/Thermostat_accessory.ts +++ /dev/null @@ -1,148 +0,0 @@ -// HomeKit types required -import * as types from "./types"; -import { Categories, CharacteristicValue } from '..'; - -const execute = (accessory: string, characteristic: string, value: CharacteristicValue) => { - console.log("executed accessory: " + accessory + ", and characteristic: " + characteristic + ", with value: " + value + "."); -} - -export const accessory = { - displayName: "Thermostat 1", - username: "CA:3E:BC:4D:5E:FF", - pincode: "031-45-154", - category: Categories.THERMOSTAT, - services: [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Thermostat 1", - supportEvents: false, - supportBonjour: false, - manfDescription: "Bla", - designedMaxLength: 255 - },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Oltica", - supportEvents: false, - supportBonjour: false, - manfDescription: "Bla", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Rev-1", - supportEvents: false, - supportBonjour: false, - manfDescription: "Bla", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "A1S2NASF88EW", - supportEvents: false, - supportBonjour: false, - manfDescription: "Bla", - designedMaxLength: 255 - },{ - cType: types.FIRMWARE_REVISION_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "1.0.0", - supportEvents: false, - supportBonjour: false, - manfDescription: "Bla", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: null, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - }] - },{ - sType: types.THERMOSTAT_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Thermostat Control", - supportEvents: false, - supportBonjour: false, - manfDescription: "Bla", - designedMaxLength: 255 - },{ - cType: types.CURRENTHEATINGCOOLING_CTYPE, - onUpdate: (value: CharacteristicValue) => { console.log("Change:",value); execute("Thermostat", "Current HC", value); }, - perms: ["pr","ev"], - format: "uint8", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Current Mode", - designedMaxLength: 1, - designedMinValue: 0, - designedMaxValue: 2, - designedMinStep: 1, - },{ - cType: types.TARGETHEATINGCOOLING_CTYPE, - onUpdate: (value: CharacteristicValue) => { console.log("Change:",value); execute("Thermostat", "Target HC", value); }, - perms: ["pw","pr","ev"], - format: "uint8", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Target Mode", - designedMinValue: 0, - designedMaxValue: 3, - designedMinStep: 1, - },{ - cType: types.CURRENT_TEMPERATURE_CTYPE, - onUpdate: (value: CharacteristicValue) => { console.log("Change:",value); execute("Thermostat", "Current Temperature", value); }, - perms: ["pr","ev"], - format: "float", - initialValue: 20, - supportEvents: false, - supportBonjour: false, - manfDescription: "Current Temperature", - unit: "celsius" - },{ - cType: types.TARGET_TEMPERATURE_CTYPE, - onUpdate: (value: CharacteristicValue) => { console.log("Change:",value); execute("Thermostat", "Target Temperature", value); }, - perms: ["pw","pr","ev"], - format: "float", - initialValue: 20, - supportEvents: false, - supportBonjour: false, - manfDescription: "Target Temperature", - designedMinValue: 16, - designedMaxValue: 38, - designedMinStep: 1, - unit: "celsius" - },{ - cType: types.TEMPERATURE_UNITS_CTYPE, - onUpdate: (value: CharacteristicValue) => { console.log("Change:",value); execute("Thermostat", "Unit", value); }, - perms: ["pw","pr","ev"], - format: "uint8", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Unit" - }] - }] -} diff --git a/src/accessories/Wi-FiRouter_accessory.ts b/src/accessories/Wi-FiRouter_accessory.ts index b39840503..552136ef8 100644 --- a/src/accessories/Wi-FiRouter_accessory.ts +++ b/src/accessories/Wi-FiRouter_accessory.ts @@ -1,12 +1,4 @@ -import { - Accessory, - AccessoryEventTypes, - Categories, - Characteristic, - Service, - uuid, - VoidCallback, -} from '..'; +import { Accessory, AccessoryEventTypes, Categories, Service, uuid, VoidCallback, } from '..'; const UUID = uuid.generate('hap-nodejs:accessories:wifi-router'); export const accessory = new Accessory('Wi-Fi Router', UUID); diff --git a/src/accessories/Wi-FiSatellite_accessory.ts b/src/accessories/Wi-FiSatellite_accessory.ts index 2d665509d..43274fb68 100644 --- a/src/accessories/Wi-FiSatellite_accessory.ts +++ b/src/accessories/Wi-FiSatellite_accessory.ts @@ -1,12 +1,4 @@ -import { - Accessory, - AccessoryEventTypes, - Categories, - Characteristic, - Service, - uuid, - VoidCallback, -} from '..'; +import { Accessory, AccessoryEventTypes, Categories, Characteristic, Service, uuid, VoidCallback, } from '..'; const UUID = uuid.generate('hap-nodejs:accessories:wifi-satellite'); export const accessory = new Accessory('Wi-Fi Satellite', UUID); diff --git a/src/accessories/gstreamer-audioProducer.ts b/src/accessories/gstreamer-audioProducer.ts index 4c7eadd8c..0eada5d3e 100644 --- a/src/accessories/gstreamer-audioProducer.ts +++ b/src/accessories/gstreamer-audioProducer.ts @@ -41,7 +41,7 @@ export type GStreamerOptions = { * * This producer is mainly tested on a RaspberryPi, but should also work on other linux based devices using alsa. * - * This producer requires some packages to be installed. It is adviced to install the following (for example via apt-get): + * This producer requires some packages to be installed. It is advised to install the following (for example via apt-get): * gstreamer1.0-plugins-base, gstreamer1.0-x, gstreamer1.0-tools, libgstreamer1.0-dev, gstreamer1.0-doc, * gstreamer1.0-plugins-good, gstreamer1.0-plugins- ugly, gstreamer1.0-plugins-bad, gstreamer1.0-alsa * diff --git a/src/index.ts b/src/index.ts index d24f7086d..d2114066f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ -import './lib/gen'; +import "source-map-support/register"; // registering node-source-map-support for typescript stack traces +import './lib/definitions'; // must be loaded before Characteristic and Service class import * as accessoryLoader from './lib/AccessoryLoader'; import * as uuidFunctions from './lib/util/uuid'; import * as legacyTypes from './accessories/types'; @@ -16,7 +17,6 @@ export * from './lib/AccessoryLoader'; export * from './lib/camera'; export * from './lib/tv/AccessControlManagement'; export * from './lib/HAPServer'; -export * from './lib/gen'; export * from './lib/datastream'; export * from './lib/controller'; @@ -24,6 +24,7 @@ export * from './lib/util/clone'; export * from './lib/util/once'; export * from './lib/util/setupcode'; export * from './lib/util/tlv'; +export * from './lib/util/hapStatusError'; export * from './types'; export const LegacyTypes = legacyTypes; @@ -35,6 +36,9 @@ export const LegacyTypes = legacyTypes; * storage path location, please use {@link HAPStorage.setCustomStoragePath} directly. */ export function init(storagePath?: string) { + console.log("DEPRECATED: The need to manually initialize HAP (by calling the init method) was removed. " + + "If you want to set a custom storage path location, please ust HAPStorage.setCustomStoragePath directly. " + + "This method will be removed in the next major update!"); if (storagePath) { HAPStorage.setCustomStoragePath(storagePath); } diff --git a/src/internal-types.ts b/src/internal-types.ts new file mode 100644 index 000000000..444bf29cf --- /dev/null +++ b/src/internal-types.ts @@ -0,0 +1,203 @@ +import { Formats, Perms, Units } from "./lib/Characteristic"; +import { HAPStatus } from "./lib/HAPServer"; +import { CharacteristicValue, Nullable } from "./types"; + +/* +type HAPProps = Pick + & { + "valid-values"?: number[], + "valid-values-range"?: [number, number], +} +export type HapCharacteristic = HAPProps & { + iid: number; + type: string; + value: string | number | {} | null; +} +export type HapService = { + iid: number; + type: string; + + characteristics: HapCharacteristic[]; + primary: boolean; + hidden: boolean; + linked: number[]; +} + */ +export interface CharacteristicJsonObject { + type: string, // uuid or short uuid + iid: number, + value?: Nullable, // undefined for non readable characteristics + + perms: Perms[], + format: Formats | string, + + description?: string, + + unit?: Units | string, + minValue?: number, + maxValue?: number, + minStep?: number, + maxLen?: number, + maxDataLen?: number, + "valid-values"?: number[], + "valid-values-range"?: [min: number, max: number], +} + +export interface ServiceJsonObject { + type: string, + iid: number, + characteristics: CharacteristicJsonObject[], // must not be empty, max 100 characteristics + hidden?: boolean, + primary?: boolean, + linked?: number[], // iid array +} + +export interface AccessoryJsonObject { + aid: number, + services: ServiceJsonObject[], // must not be empty, max 100 services +} + +export interface AccessoriesResponse { + accessories: AccessoryJsonObject[], +} + +export interface CharacteristicId { + aid: number, + iid: number, +} + +export interface CharacteristicsReadRequest { + ids: CharacteristicId[], + includeMeta: boolean; + includePerms: boolean, + includeType: boolean, + includeEvent: boolean, +} + +export interface PartialCharacteristicReadDataValue { + value: CharacteristicValue | null, + + status?: HAPStatus.SUCCESS, + + // type + type?: string, // characteristics uuid + + // metadata + format?: string, + unit?: string, + minValue?: number, + maxValue?: number, + minStep?: number, + maxLen?: number, + + // perms + perms?: Perms[], + + // event + ev?: boolean, +} + +export interface PartialCharacteristicReadError { + status: HAPStatus, +} + +export interface CharacteristicReadDataValue extends PartialCharacteristicReadDataValue { + aid: number, + iid: number, +} + +export interface CharacteristicReadError extends PartialCharacteristicReadError { + aid: number, + iid: number, +} + +export type PartialCharacteristicReadData = PartialCharacteristicReadDataValue | PartialCharacteristicReadError; +export type CharacteristicReadData = CharacteristicReadDataValue | CharacteristicReadError; + +export interface CharacteristicsReadResponse { + characteristics: CharacteristicReadData[], +} + +export interface CharacteristicWrite { + aid: number, + iid: number, + + value?: CharacteristicValue, + ev?: boolean, // enable/disable event notifications for the accessory + + authData?: string, // base64 encoded string used for custom authorisation + /** + * @deprecated This indicated if access was done via the old iCloud relay + */ + remote?: boolean, // remote access used + r?: boolean, // write response +} + +export interface CharacteristicsWriteRequest { + characteristics: CharacteristicWrite[], + pid?: number +} + +export interface PartialCharacteristicWriteDataValue { + value?: CharacteristicValue | null, + ev?: boolean, // event + + status?: HAPStatus.SUCCESS, +} + +export interface PartialCharacteristicWriteError { + status: HAPStatus, + + value?: undefined, // defined to make things easier +} + +export interface CharacteristicWriteDataValue extends PartialCharacteristicWriteDataValue{ + aid: number, + iid: number, +} + +export interface CharacteristicWriteError extends PartialCharacteristicWriteError { + aid: number, + iid: number, +} + +export type PartialCharacteristicWriteData = PartialCharacteristicWriteDataValue | PartialCharacteristicWriteError; +export type CharacteristicWriteData = CharacteristicWriteDataValue | CharacteristicWriteError; + +export interface CharacteristicsWriteResponse { + characteristics: CharacteristicWriteData[], +} + +export type PrepareWriteRequest = { + ttl: number, + pid: number +} + +export const enum ResourceRequestType { + IMAGE = "image", +} + +export interface ResourceRequest { + aid?: number; + "image-height": number; + "image-width": number; + "resource-type": ResourceRequestType; +} + +export interface EventNotification { + characteristics: CharacteristicEventNotification[], +} + +export interface CharacteristicEventNotification { + aid: number, + iid: number, + value: Nullable, +} + +export function consideredTrue(input: string | null): boolean { + if (!input) { + return false; + } + + return input === "true" || input === "1"; +} diff --git a/src/lib/Accessory.spec.ts b/src/lib/Accessory.spec.ts index 60ebffdb4..c46b66e07 100644 --- a/src/lib/Accessory.spec.ts +++ b/src/lib/Accessory.spec.ts @@ -1,4 +1,3 @@ -import './gen'; import { Accessory, Categories, diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index c41e44668..258a8c61d 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -1,48 +1,76 @@ import { MDNSServerOptions } from "@homebridge/ciao"; +import assert from "assert"; import crypto from 'crypto'; import createDebug from 'debug'; -import assert from "assert"; - -import * as uuid from './util/uuid'; -import { clone } from './util/clone'; -import { SerializedService, Service, ServiceConfigurationChange, ServiceEventTypes, ServiceId } from './Service'; -import { Access, Characteristic, CharacteristicEventTypes, CharacteristicSetCallback, Perms } from './Characteristic'; -import { Advertiser, AdvertiserEvent } from './Advertiser'; -import { CharacteristicsWriteRequest, Codes, HAPServer, HAPServerEventTypes, Status, PairIdentity } from './HAPServer'; -import { AccessoryInfo, PairingInformation, PermissionTypes } from './model/AccessoryInfo'; -import { IdentifierCache } from './model/IdentifierCache'; +import { EventEmitter } from "events"; +import net from "net"; import { - CharacteristicChange, - CharacteristicData, - CharacteristicValue, MacAddress, - NodeCallback, + AccessoryJsonObject, + CharacteristicId, + CharacteristicReadData, + CharacteristicsReadRequest, + CharacteristicsReadResponse, + CharacteristicsWriteRequest, + CharacteristicsWriteResponse, + CharacteristicWrite, + CharacteristicWriteData, + PartialCharacteristicReadData, + PartialCharacteristicWriteData, + ResourceRequest, + ResourceRequestType +} from "../internal-types"; +import { + CharacteristicValue, + HAPPincode, + InterfaceName, + IPAddress, + MacAddress, Nullable, - PairingsCallback, - SessionIdentifier, - ToHAPOptions, VoidCallback, WithUUID, + NodeCallback, } from '../types'; +import { Advertiser, AdvertiserEvent } from './Advertiser'; // noinspection JSDeprecatedSymbols import { LegacyCameraSource, LegacyCameraSourceAdapter, StreamController } from './camera'; -import { EventEmitter } from './EventEmitter'; -import { Session } from "./util/eventedhttp"; -import { generateSetupCode } from './util/setupcode'; +import { Access, Characteristic, CharacteristicEventTypes, CharacteristicSetCallback, Perms } from './Characteristic'; import { CameraController, CameraControllerOptions, Controller, ControllerConstructor, - ControllerServiceMap, ControllerType, + ControllerServiceMap, + ControllerType, isSerializableController, } from "./controller"; import { - CameraEventRecordingManagement, - CameraOperatingMode, - CameraRTPStreamManagement, -} from "./gen/HomeKit"; + AccessoriesCallback, + AddPairingCallback, + HAPHTTPCode, + HAPServer, + HAPServerEventTypes, + HAPStatus, + IdentifyCallback, + ListPairingsCallback, + PairCallback, + ReadCharacteristicsCallback, + RemovePairingCallback, + ResourceRequestCallback, + TLVErrorCode, + WriteCharacteristicsCallback, + PairIdentity +} from './HAPServer'; +import { AccessoryInfo, PermissionTypes } from './model/AccessoryInfo'; import { ControllerStorage } from "./model/ControllerStorage"; +import { IdentifierCache } from './model/IdentifierCache'; +import { SerializedService, Service, ServiceCharacteristicChange, ServiceEventTypes, ServiceId } from './Service'; +import { clone } from './util/clone'; +import { EventName, HAPConnection, HAPUsername } from "./util/eventedhttp"; +import * as uuid from "./util/uuid"; +import { toShortForm } from "./util/uuid"; import { generateSetupId, generateSetupUri } from './util/setupid'; +import { generateSetupCode } from './util/setupcode'; +import Timeout = NodeJS.Timeout; import { Identity } from 'fast-srp-hap'; const debug = createDebug('HAP-NodeJS:Accessory'); @@ -115,62 +143,105 @@ export interface ControllerContext { serviceMap: ControllerServiceMap, } -export type SetupCode = Identity & {setupuri: string | null}; - -export const enum AccessoryEventTypes { - IDENTIFY = "identify", - LISTENING = "listening", - PAIR_SETUP_STARTED = 'pair-setup-started', - PAIR_SETUP_FINISHED = 'pair-setup-finished', - SERVICE_CONFIGURATION_CHANGE = "service-configurationChange", - SERVICE_CHARACTERISTIC_CHANGE = "service-characteristic-change", - PAIRED = "paired", - UNPAIRED = "unpaired", -} - -type Events = { - [AccessoryEventTypes.IDENTIFY]: (paired: boolean, cb: VoidCallback) => void; - [AccessoryEventTypes.LISTENING]: (port: number) => void; - [AccessoryEventTypes.PAIR_SETUP_STARTED]: (setupcode: SetupCode, session: Session) => void; - [AccessoryEventTypes.PAIR_SETUP_FINISHED]: (err: Nullable, clientUsername: string, session: Session) => void; - [AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE]: VoidCallback; - [AccessoryEventTypes.SERVICE_CHARACTERISTIC_CHANGE]: (change: ServiceCharacteristicChange) => void; - [AccessoryEventTypes.PAIRED]: () => void; - [AccessoryEventTypes.UNPAIRED]: () => void; +export const enum CharacteristicWarningType { + SLOW_WRITE = "slow-write", + TIMEOUT_WRITE = "timeout-write", + SLOW_READ = "slow-read", + TIMEOUT_READ = "timeout-read", + WARN_MESSAGE = "warn-message", + ERROR_MESSAGE = "error-message", } /** - * @deprecated Use AccessoryEventTypes instead + * @deprecated */ -export type EventAccessory = "identify" | "listening" | "service-configurationChange" | "service-characteristic-change"; - export type CharacteristicEvents = Record; export interface PublishInfo { username: MacAddress; - pincode?: string - | ((callback: NodeCallback, session: Session) => void) + pincode?: HAPPincode + | ((callback: NodeCallback, connection: HAPConnection) => void) | {salt: Buffer; verifier: Buffer}; + /** + * Specify the category for the HomeKit accessory. + * The category is used only in the mdns advertisement and specifies the devices type + * for the HomeKit controller. + * Currently this only affects the icon shown in the pairing screen. + * For the Television and Smart Speaker service it also affects the icon shown in + * the Home app when paired. + */ category?: Categories; setupID?: string; + /** + * Defines the host where the HAP server will be bound to. + * When undefined the HAP server will bind to all available interfaces + * (see https://nodejs.org/api/net.html#net_server_listen_port_host_backlog_callback). + * + * This property accepts a mixture of IPAddresses and network interface names. + * Depending on the mixture of supplied addresses/names hap-nodejs will bind differently. + * + * It is advised to not just bind to a specific address, but specifying the interface name + * in oder to bind on all address records (and ip version) available. + * + * HAP-NodeJS (or the underlying ciao library) will not report about misspelled interface names, + * as it could be that the interface is currently just down and will come up later. + * + * Here are a few examples: + * - bind: "::" + * Pretty much identical to not specifying anything, as most systems (with ipv6 support) + * will default to the unspecified ipv6 address (with dual stack support). + * + * - bind: "0.0.0.0" + * Binding TCP socket to the unspecified ipv4 address. + * The mdns advertisement will exclude any ipv6 address records. + * + * - bind: ["en0", "lo0"] + * The mdns advertising will advertise all records of the en0 and loopback interface (if available) and + * will also react to address changes on those interfaces. + * In order for the HAP server to accept all those address records (which may contain ipv6 records) + * it will bind on the unspecified ipv6 address "::" (assuming dual stack is supported). + * + * - bind: ["en0", "lo0", "0.0.0.0"] + * Same as above, only that the HAP server will bind on the unspecified ipv4 address "0.0.0.0". + * The mdns advertisement will not advertise any ipv6 records. + * + * - bind: "169.254.104.90" + * This will bind the HAP server to the address 0.0.0.0. + * The mdns advertisement will only advertise the A record 169.254.104.90. + * If the given network interface of that address encounters an ip address change (to a different address), + * the mdns advertisement will result in not advertising a address at all. + * So it is advised to specify a interface name instead of a specific address. + * This is identical with ipv6 addresses. + * + * - bind: ["169.254.104.90", "192.168.1.4"] + * As the HAP TCP socket can only bind to a single address, when specifying multiple ip addresses + * the HAP server will bind to the unspecified ip address (0.0.0.0 if only ipv4 addresses are supplied, + * :: if a mixture or only ipv6 addresses are supplied). + * The mdns advertisement will only advertise the specified ip addresses. + * If the given network interface of that address encounters an ip address change (to different addresses), + * the mdns advertisement will result in not advertising a address at all. + * So it is advised to specify a interface name instead of a specific address. + * + */ + bind?: (InterfaceName | IPAddress) | (InterfaceName | IPAddress)[]; + /** + * Defines the port where the HAP server will be bound to. + * When undefined port 0 will be used resulting in a random port. + */ port?: number; + /** + * Used to define custom MDNS options. Is not used anymore. + * @deprecated + */ mdns?: MDNSServerOptions; } -export type ServiceCharacteristicChange = CharacteristicChange & { - accessory: Accessory; +export type AccessoryCharacteristicChange = ServiceCharacteristicChange & { service: Service; }; -export const enum ResourceTypes { - IMAGE = 'image', -} - -export type Resource = { - 'aid'?: number; - 'image-height': number; - 'image-width': number; - 'resource-type': ResourceTypes; +export interface ServiceConfigurationChange { + service: Service; } const enum WriteRequestState { @@ -179,14 +250,57 @@ const enum WriteRequestState { TIMED_WRITE_REJECTED } -type IdentifyCallback = VoidCallback; -type PairCallback = VoidCallback; -type AddPairingCallback = PairingsCallback; -type RemovePairingCallback = PairingsCallback; -type ListPairingsCallback = PairingsCallback; -type HandleAccessoriesCallback = NodeCallback<{ accessories: any[] }>; -type HandleGetCharacteristicsCallback = NodeCallback; -type HandleSetCharacteristicsCallback = NodeCallback; +export type SetupCode = Identity & {setupcode: string | null; setupuri: string | null}; + +// noinspection JSUnusedGlobalSymbols +/** + * @deprecated Use AccessoryEventTypes instead + */ +export type EventAccessory = "identify" | "listening" | "service-configurationChange" | "service-characteristic-change"; + +export const enum AccessoryEventTypes { + IDENTIFY = "identify", + LISTENING = "listening", + PAIR_SETUP_STARTED = 'pair-setup-started', + PAIR_SETUP_FINISHED = 'pair-setup-finished', + SERVICE_CONFIGURATION_CHANGE = "service-configurationChange", + SERVICE_CHARACTERISTIC_CHANGE = "service-characteristic-change", + PAIRED = "paired", + UNPAIRED = "unpaired", + + CHARACTERISTIC_WARNING = "characteristic-warning-v2", +} + +export declare interface Accessory { + on(event: "identify", listener: (paired: boolean, callback: VoidCallback) => void): this; + on(event: "listening", listener: (port: number, address: string) => void): this; + + on(event: "service-configurationChange", listener: (change: ServiceConfigurationChange) => void): this; + on(event: "service-characteristic-change", listener: (change: AccessoryCharacteristicChange) => void): this; + + on(event: "pair-setup-started", listener: (setupcode: SetupCode, connection: HAPConnection) => void): this; + on(event: "pair-setup-finished", listener: (err: Error | null, clientUsername: string | null, connection: HAPConnection) => void): this; + + on(event: "paired", listener: () => void): this; + on(event: "unpaired", listener: () => void): this; + + on(event: "characteristic-warning-v2", listener: (characteristic: Characteristic, type: CharacteristicWarningType, message: string) => void): this; + + + emit(event: "identify", paired: boolean, callback: VoidCallback): boolean; + emit(event: "listening", port: number, address: string): boolean; + + emit(event: "service-configurationChange", change: ServiceConfigurationChange): boolean; + emit(event: "service-characteristic-change", change: AccessoryCharacteristicChange): boolean; + + emit(event: "pair-setup-started", setupcode: SetupCode, connection: HAPConnection): boolean; + emit(event: "pair-setup-finished", err: Error | null, clientUsername: string | null, connection: HAPConnection): boolean; + + emit(event: "paired"): boolean; + emit(event: "unpaired"): boolean; + + emit(event: "characteristic-warning-v2", characteristic: Characteristic, type: CharacteristicWarningType, message: string): boolean; +} /** * Accessory is a virtual HomeKit device. It can publish an associated HAP server for iOS devices to communicate @@ -208,7 +322,7 @@ type HandleSetCharacteristicsCallback = NodeCallback; * @event 'service-characteristic-change' => function({service, characteristic, oldValue, newValue, context}) { } * Emitted after a change in the value of one of the provided Service's Characteristics. */ -export class Accessory extends EventEmitter { +export class Accessory extends EventEmitter { /** * @deprecated Please use the Categories const enum above. Scheduled to be removed in 2021-06. @@ -240,6 +354,8 @@ export class Accessory extends EventEmitter { _server?: HAPServer; _setupURI?: string; + private configurationChangeDebounceTimeout?: Timeout; + constructor(public displayName: string, public UUID: string) { super(); assert(displayName, "Accessories must be created with a non-empty displayName."); @@ -257,12 +373,12 @@ export class Accessory extends EventEmitter { .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { if (value) { const paired = true; - this._identificationRequest(paired, callback); + this.identificationRequest(paired, callback); } }); } - _identificationRequest = (paired: boolean, callback: CharacteristicSetCallback) => { + private identificationRequest(paired: boolean, callback: IdentifyCallback) { debug("[%s] Identification request", this.displayName); if (this.listeners(AccessoryEventTypes.IDENTIFY).length > 0) { @@ -275,7 +391,7 @@ export class Accessory extends EventEmitter { } } - addService = (serviceParam: Service | typeof Service, ...constructorArgs: any[]) => { + public addService(serviceParam: Service | typeof Service, ...constructorArgs: any[]): Service { // service might be a constructor like `Service.AccessoryInformation` instead of an instance // of Service. Coerce if necessary. const service: Service = typeof serviceParam === 'function' @@ -283,8 +399,8 @@ export class Accessory extends EventEmitter { : serviceParam; // check for UUID+subtype conflict - for (var index in this.services) { - var existing = this.services[index]; + for (let index in this.services) { + const existing = this.services[index]; if (existing.UUID === service.UUID) { // OK we have two Services with the same UUID. Check that each defines a `subtype` property and that each is unique. if (!service.subtype) @@ -310,40 +426,12 @@ export class Accessory extends EventEmitter { } if (!this.bridged) { - this._updateConfiguration(); + this.enqueueConfigurationUpdate(); } else { - this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({accessory:this, service:service})); + this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, { service: service }); } - service.on(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, (change: ServiceConfigurationChange) => { - if (!service.isPrimaryService && service === this.primaryService) { - // service changed form primary to non primary service - this.primaryService = undefined; - } else if (service.isPrimaryService && service !== this.primaryService) { - // service changed from non primary to primary service - if (this.primaryService !== undefined) { - this.primaryService.isPrimaryService = false; - } - - this.primaryService = service; - } - - if (!this.bridged) { - this._updateConfiguration(); - } else { - this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({accessory:this, service:service})); - } - }); - - // listen for changes in characteristics and bubble them up - service.on(ServiceEventTypes.CHARACTERISTIC_CHANGE, (change: CharacteristicChange) => { - this.emit(AccessoryEventTypes.SERVICE_CHARACTERISTIC_CHANGE, clone(change, {service:service as Service})); - - // if we're not bridged, when we'll want to process this event through our HAPServer - if (!this.bridged) - this._handleCharacteristicChange(clone(change, {accessory:this, service:service as Service})); - - }); + this.setupServiceEventHandlers(service); return service; } @@ -351,11 +439,11 @@ export class Accessory extends EventEmitter { /** * @deprecated use {@link Service.setPrimaryService} directly */ - setPrimaryService = (service: Service) => { + public setPrimaryService(service: Service): void { service.setPrimaryService(); - }; + } - removeService = (service: Service) => { + public removeService(service: Service): void { const index = this.services.indexOf(service); if (index >= 0) { @@ -364,29 +452,37 @@ export class Accessory extends EventEmitter { if (this.primaryService === service) { // check if we are removing out primary service this.primaryService = undefined; } + this.removeLinkedService(service); // remove it from linked service entries on the local accessory if (!this.bridged) { - this._updateConfiguration(); + this.enqueueConfigurationUpdate(); } else { - this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({accessory:this, service:service})); + this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, { service: service }); } service.removeAllListeners(); } } - getService = >(name: string | T) => { - for (var index in this.services) { - var service = this.services[index]; + private removeLinkedService(removed: Service) { + for (const service of this.services) { + service.removeLinkedService(removed); + } + } - if (typeof name === 'string' && (service.displayName === name || service.name === name || service.subtype === name)) + public getService>(name: string | T): Service | undefined { + for (const service of this.services) { + if (typeof name === 'string' && (service.displayName === name || service.name === name || service.subtype === name)) { return service; - else if (typeof name === 'function' && ((service instanceof name) || (name.UUID === service.UUID))) + } else if (typeof name === 'function' && ((service instanceof name) || (name.UUID === service.UUID))) { return service; + } } + + return undefined; } - getServiceById>(uuid: string | T, subType: string): Service | undefined { + public getServiceById>(uuid: string | T, subType: string): Service | undefined { for (const index in this.services) { const service = this.services[index]; @@ -406,11 +502,14 @@ export class Accessory extends EventEmitter { * * @returns the primary accessory */ - getPrimaryAccessory = (): Accessory => { + public getPrimaryAccessory = (): Accessory => { return this.bridged? this.bridge!: this; } - updateReachability = (reachable: boolean) => { + /** + * @deprecated Not supported anymore + */ + public updateReachability(reachable: boolean): void { if (!this.bridged) throw new Error("Cannot update reachability on non-bridged accessory!"); this.reachable = reachable; @@ -418,30 +517,25 @@ export class Accessory extends EventEmitter { debug('Reachability update is no longer being supported.'); } - addBridgedAccessory = (accessory: Accessory, deferUpdate: boolean = false) => { - if (accessory._isBridge) + public addBridgedAccessory(accessory: Accessory, deferUpdate: boolean = false): Accessory { + if (accessory._isBridge) { throw new Error("Cannot Bridge another Bridge!"); + } // check for UUID conflict - for (var index in this.bridgedAccessories) { - var existing = this.bridgedAccessories[index]; - if (existing.UUID === accessory.UUID) + for (const existing of this.bridgedAccessories) { + if (existing.UUID === accessory.UUID) { throw new Error("Cannot add a bridged Accessory with the same UUID as another bridged Accessory: " + existing.UUID); + } } - // A bridge too far... if (this.bridgedAccessories.length >= MAX_ACCESSORIES) { throw new Error("Cannot Bridge more than " + MAX_ACCESSORIES + " Accessories"); } // listen for changes in ANY characteristics of ANY services on this Accessory - accessory.on(AccessoryEventTypes.SERVICE_CHARACTERISTIC_CHANGE, (change: ServiceCharacteristicChange) => { - this._handleCharacteristicChange(clone(change, {accessory:accessory})); - }); - - accessory.on(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, () => { - this._updateConfiguration(); - }); + accessory.on(AccessoryEventTypes.SERVICE_CHARACTERISTIC_CHANGE, change => this.handleCharacteristicChangeEvent(accessory, change.service, change)); + accessory.on(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, this.enqueueConfigurationUpdate.bind(this)); accessory.bridged = true; accessory.bridge = this; @@ -451,29 +545,29 @@ export class Accessory extends EventEmitter { this.controllerStorage.linkAccessory(accessory); // init controllers of bridged accessory if(!deferUpdate) { - this._updateConfiguration(); + this.enqueueConfigurationUpdate(); } return accessory; } - addBridgedAccessories = (accessories: Accessory[]) => { - for (var index in accessories) { - var accessory = accessories[index]; + public addBridgedAccessories(accessories: Accessory[]): void { + for (let index in accessories) { + const accessory = accessories[index]; this.addBridgedAccessory(accessory, true); } - this._updateConfiguration(); + this.enqueueConfigurationUpdate(); } - removeBridgedAccessory = (accessory: Accessory, deferUpdate: boolean) => { + public removeBridgedAccessory(accessory: Accessory, deferUpdate: boolean): void { if (accessory._isBridge) throw new Error("Cannot Bridge another Bridge!"); - var foundMatchAccessory = false; + let foundMatchAccessory = false; // check for UUID conflict - for (var index in this.bridgedAccessories) { - var existing = this.bridgedAccessories[index]; + for (let index in this.bridgedAccessories) { + const existing = this.bridgedAccessories[index]; if (existing.UUID === accessory.UUID) { foundMatchAccessory = true; this.bridgedAccessories.splice(Number.parseInt(index), 1); @@ -484,53 +578,60 @@ export class Accessory extends EventEmitter { if (!foundMatchAccessory) throw new Error("Cannot find the bridged Accessory to remove."); - accessory.removeAllListeners(); + accessory.removeAllListeners(); // TODO remove all listeners from services and characteristics as well if(!deferUpdate) { - this._updateConfiguration(); + this.enqueueConfigurationUpdate(); } } - removeBridgedAccessories = (accessories: Accessory[]) => { - for (var index in accessories) { - var accessory = accessories[index]; + public removeBridgedAccessories(accessories: Accessory[]): void { + for (let index in accessories) { + const accessory = accessories[index]; this.removeBridgedAccessory(accessory, true); } - this._updateConfiguration(); + this.enqueueConfigurationUpdate(); } - removeAllBridgedAccessories = () => { - for (var i = this.bridgedAccessories.length - 1; i >= 0; i --) { + public removeAllBridgedAccessories(): void { + for (let i = this.bridgedAccessories.length - 1; i >= 0; i --) { this.removeBridgedAccessory(this.bridgedAccessories[i], true); } - this._updateConfiguration(); + this.enqueueConfigurationUpdate(); } - getCharacteristicByIID = (iid: number) => { - for (var index in this.services) { - var service = this.services[index]; - var characteristic = service.getCharacteristicByIID(iid); - if (characteristic) return characteristic; + private getCharacteristicByIID(iid: number): Characteristic | undefined { + for (let index in this.services) { + const service = this.services[index]; + const characteristic = service.getCharacteristicByIID(iid); + + if (characteristic) { + return characteristic; + } } } - getBridgedAccessoryByAID = (aid: number) => { - for (var index in this.bridgedAccessories) { - var accessory = this.bridgedAccessories[index]; - if (accessory.aid === aid) return accessory; + protected getAccessoryByAID(aid: number): Accessory | undefined { + if (aid === 1) { + return this; } - } - findCharacteristic = (aid: number, iid: number) => { + for (const accessory of this.bridgedAccessories) { + if (accessory.aid === aid) { + return accessory; + } + } - // if aid === 1, the accessory is us (because we are the server), otherwise find it among our bridged - // accessories (if any) - var accessory = (aid === 1) ? this : this.getBridgedAccessoryByAID(aid); + return undefined; + } + protected findCharacteristic(aid: number, iid: number): Characteristic | undefined { + const accessory = this.getAccessoryByAID(aid); return accessory && accessory.getCharacteristicByIID(iid); } + // noinspection JSDeprecatedSymbols /** * Method is used to configure an old style CameraSource. * The CameraSource API was fully replaced by the new Controller API used by {@link CameraController}. @@ -553,7 +654,7 @@ export class Accessory extends EventEmitter { * @param cameraSource {LegacyCameraSource} * @deprecated please refer to the new {@see CameraController} API and {@link configureController} */ - configureCameraSource(cameraSource: LegacyCameraSource): CameraController { + public configureCameraSource(cameraSource: LegacyCameraSource): CameraController { if (cameraSource.streamControllers.length === 0) { throw new Error("Malformed legacy CameraSource. Did not expose any StreamControllers!"); } @@ -570,8 +671,8 @@ export class Accessory extends EventEmitter { // we try here to be as good as possibly of keeping current behaviour cameraSource.services.forEach(service => { - if (service.UUID === CameraRTPStreamManagement.UUID || service.UUID === CameraOperatingMode.UUID - || service.UUID === CameraEventRecordingManagement.UUID) { + if (service.UUID === Service.CameraRTPStreamManagement.UUID || service.UUID === Service.CameraOperatingMode.UUID + || service.UUID === Service.CameraRecordingManagement.UUID) { return; // ignore those services, as they get replaced by the RTPStreamManagement } @@ -581,6 +682,7 @@ export class Accessory extends EventEmitter { }); // replace stream controllers; basically only to still support the "forceStop" call + // noinspection JSDeprecatedSymbols cameraSource.streamControllers = cameraController.streamManagements as StreamController[]; return cameraController; // return the reference for the controller (maybe this could be useful?) @@ -600,7 +702,7 @@ export class Accessory extends EventEmitter { * * @param controllerConstructor {Controller | ControllerConstructor} */ - configureController(controllerConstructor: Controller | ControllerConstructor) { + public configureController(controllerConstructor: Controller | ControllerConstructor) { // TODO add support to remove controllers const controller = typeof controllerConstructor === "function" ? new controllerConstructor() // any custom constructor arguments should be passed before using .bind(...) : controllerConstructor; @@ -651,16 +753,6 @@ export class Accessory extends EventEmitter { this.controllerStorage.trackController(controller); } - if (controller.handleFactoryReset) { // if the controller implements handleFactoryReset, setup event handlers for this controller - this.getPrimaryAccessory().on(AccessoryEventTypes.UNPAIRED, () => { - controller.handleFactoryReset!(); - - if (isSerializableController(controller)) { // we force a purge here - this.controllerStorage.purgeControllerData(controller); - } - }); - } - this.controllers[controller.controllerType] = context; if (controller instanceof CameraController) { // save CameraController for Snapshot handling @@ -668,6 +760,19 @@ export class Accessory extends EventEmitter { } } + private handleAccessoryUnpairedForControllers(): void { + for (const context of Object.values(this.controllers)) { + const controller = context.controller; + if (controller.handleFactoryReset) { // if the controller implements handleFactoryReset, setup event handlers for this controller + controller.handleFactoryReset(); + } + + if (isSerializableController(controller)) { + this.controllerStorage.purgeControllerData(controller); + } + } + } + private handleUpdatedControllerServiceMap(originalServiceMap: ControllerServiceMap, updatedServiceMap: ControllerServiceMap) { updatedServiceMap = clone(updatedServiceMap); // clone it so we can alter it @@ -719,13 +824,11 @@ export class Accessory extends EventEmitter { } }; - const manufacturer = service.getCharacteristic(Characteristic.Manufacturer).value; const model = service.getCharacteristic(Characteristic.Model).value; const serialNumber = service.getCharacteristic(Characteristic.SerialNumber).value; const firmwareRevision = service.getCharacteristic(Characteristic.FirmwareRevision).value; const name = service.getCharacteristic(Characteristic.Name).value; - checkValue("Manufacturer", manufacturer); checkValue("Model", model); checkValue("SerialNumber", serialNumber); checkValue("FirmwareRevision", firmwareRevision); @@ -746,7 +849,7 @@ export class Accessory extends EventEmitter { * Assigns aid/iid to ourselves, any Accessories we are bridging, and all associated Services+Characteristics. Uses * the provided identifierCache to keep IDs stable. */ - _assignIDs = (identifierCache: IdentifierCache) => { + _assignIDs(identifierCache: IdentifierCache): void { // if we are responsible for our own identifierCache, start the expiration process // also check weather we want to have an expiration process @@ -764,8 +867,7 @@ export class Accessory extends EventEmitter { this.aid = 1; } - for (var index in this.services) { - var service = this.services[index]; + for (const service of this.services) { if (this._isBridge) { service._assignIDs(identifierCache, this.UUID, 2000000000); } else { @@ -774,8 +876,8 @@ export class Accessory extends EventEmitter { } // now assign IDs for any Accessories we are bridging - for (var index in this.bridgedAccessories) { - var accessory = this.bridgedAccessories[index]; + for (let index in this.bridgedAccessories) { + const accessory = this.bridgedAccessories[index]; accessory._assignIDs(identifierCache); } @@ -804,51 +906,72 @@ export class Accessory extends EventEmitter { * when you have disabled auto purge so you can do it manually */ purgeUnusedIDs = () => { - //Cache the state of the purge mechanisam and set it to true - var oldValue = this.shouldPurgeUnusedIDs; + //Cache the state of the purge mechanism and set it to true + const oldValue = this.shouldPurgeUnusedIDs; this.shouldPurgeUnusedIDs = true; //Reassign all ids this._assignIDs(this._identifierCache!); - //Revert back the purge mechanisam state + //Revert back the purge mechanism state this.shouldPurgeUnusedIDs = oldValue; } /** - * Returns a JSON representation of this Accessory suitable for delivering to HAP clients. + * Returns a JSON representation of this accessory suitable for delivering to HAP clients. */ - toHAP = (opt?: ToHAPOptions) => { + private async toHAP(connection: HAPConnection): Promise { + assert(this.aid, "aid cannot be undefined for accessory '" + this.displayName + "'"); + assert(this.services.length, "accessory '" + this.displayName + "' does not have any services!"); - var servicesHAP = []; + const accessory: AccessoryJsonObject = { + aid: this.aid!, + services: await Promise.all(this.services.map(service => service.toHAP(connection))), + }; + + const accessories: AccessoryJsonObject[] = [accessory]; + + if (!this.bridged) { + accessories.push(... await Promise.all( + this.bridgedAccessories + .map(accessory => accessory.toHAP(connection).then(value => value[0])) + )); + } - for (var index in this.services) { - var service = this.services[index]; - servicesHAP.push(service.toHAP(opt)); + return accessories; + } + + /** + * Returns a JSON representation of this accessory without characteristic values. + */ + private internalHAPRepresentation(assignIds: boolean = true): AccessoryJsonObject[] { + if (assignIds) { + this._assignIDs(this._identifierCache!); // make sure our aid/iid's are all assigned } + assert(this.aid, "aid cannot be undefined for accessory '" + this.displayName + "'"); + assert(this.services.length, "accessory '" + this.displayName + "' does not have any services!"); - var accessoriesHAP = [{ - aid: this.aid, - services: servicesHAP - }]; + const accessory: AccessoryJsonObject = { + aid: this.aid!, + services: this.services.map(service => service.internalHAPRepresentation()), + }; - // now add any Accessories we are bridging - for (var index in this.bridgedAccessories) { - var accessory = this.bridgedAccessories[index]; - var bridgedAccessoryHAP = accessory.toHAP(opt); + const accessories: AccessoryJsonObject[] = [accessory]; - // bridgedAccessoryHAP is an array of accessories with one item - extract it - // and add it to our own array - accessoriesHAP.push(bridgedAccessoryHAP[0]) + if (!this.bridged) { + for (const accessory of this.bridgedAccessories) { + accessories.push(accessory.internalHAPRepresentation(false)[0]); + } } - return accessoriesHAP; + return accessories; } /** * Publishes this Accessory on the local network for iOS clients to communicate with. * * @param {Object} info - Required info for publishing. + * @param allowInsecureRequest - Will allow unencrypted and unauthenticated access to the http server * @param {string} info.username - The "username" (formatted as a MAC address - like "CC:22:3D:E3:CE:F6") of * this Accessory. Must be globally unique from all Accessories on your local network. * @param {string|function} info.pincode - The 8-digit pincode for clients to use when pairing this Accessory. Must @@ -859,8 +982,12 @@ export class Accessory extends EventEmitter { * that for instance an appropriate icon can be drawn for the user while adding a * new Accessory. */ - publish = (info: PublishInfo, allowInsecureRequest?: boolean) => { - // TODO maybe directly enqueue the method call on nextTick (could solve most out of order constructions) + public publish(info: PublishInfo, allowInsecureRequest?: boolean): void { + // noinspection JSDeprecatedSymbols + if (info.mdns) { + console.log("DEPRECATED user supplied a custom 'mdns' option. This option is deprecated and ignored. " + + "Please move to the new 'bind' option."); + } let service = this.getService(Service.ProtocolInformation); if (!service) { @@ -915,7 +1042,7 @@ export class Accessory extends EventEmitter { //If it's bridge and there are not accessories already assigned to the bridge //probably purge is not needed since it's going to delete all the ids - //of accessories that might be added later. Usefull when dynamically adding + //of accessories that might be added later. Useful when dynamically adding //accessories. if (this._isBridge && this.bridgedAccessories.length == 0) { this.disableUnusedIDPurge(); @@ -930,23 +1057,19 @@ export class Accessory extends EventEmitter { // get our accessory information in HAP format and determine if our configuration (that is, our // Accessories/Services/Characteristics) has changed since the last time we were published. make // sure to omit actual values since these are not part of the "configuration". - var config = this.toHAP({omitValues:true}); - - // now convert it into a hash code and check it against the last one we made, if we have one - var shasum = crypto.createHash('sha1'); - shasum.update(JSON.stringify(config)); - var configHash = shasum.digest('hex'); - - if (configHash !== this._accessoryInfo.configHash) { - - // our configuration has changed! we'll need to bump our config version number - this._accessoryInfo.updateConfigHash(configHash); - } + const config = this.internalHAPRepresentation(false); // TODO ensure this stuff is ordered + this._accessoryInfo.checkForCurrentConfigurationNumberIncrement(config, true); this.validateAccessory(true); // create our Advertiser which broadcasts our presence over mdns - this._advertiser = new Advertiser(this._accessoryInfo, info.mdns); + const parsed = Accessory.parseBindOption(info); + this._advertiser = new Advertiser(this._accessoryInfo, { + interface: parsed.advertiserAddress + }, { + restrictedAddresses: parsed.serviceRestrictedAddress, + disabledIpv6: parsed.serviceDisableIpv6, + }); this._advertiser.on(AdvertiserEvent.UPDATED_NAME, name => { this.displayName = name; if (this._accessoryInfo) { @@ -962,19 +1085,19 @@ export class Accessory extends EventEmitter { // create our HAP server which handles all communication between iOS devices and us this._server = new HAPServer(this._accessoryInfo); this._server.allowInsecureRequest = !!allowInsecureRequest; - this._server.on(HAPServerEventTypes.LISTENING, this._onListening.bind(this)); - this._server.on(HAPServerEventTypes.IDENTIFY, this._handleIdentify.bind(this)); + this._server.on(HAPServerEventTypes.LISTENING, this.onListening.bind(this)); + this._server.on(HAPServerEventTypes.IDENTIFY, this.identificationRequest.bind(this, false)); this._server.on(HAPServerEventTypes.PAIR_SETUP_STARTED, this._handlePairSetupStarted.bind(this)); this._server.on(HAPServerEventTypes.PAIR_SETUP_FINISHED, this._handlePairSetupFinished.bind(this)); - this._server.on(HAPServerEventTypes.PAIR, this._handlePair.bind(this)); - this._server.on(HAPServerEventTypes.ADD_PAIRING, this._handleAddPairing.bind(this)); - this._server.on(HAPServerEventTypes.REMOVE_PAIRING, this._handleRemovePairing.bind(this)); - this._server.on(HAPServerEventTypes.LIST_PAIRINGS, this._handleListPairings.bind(this)); - this._server.on(HAPServerEventTypes.ACCESSORIES, this._handleAccessories.bind(this)); - this._server.on(HAPServerEventTypes.GET_CHARACTERISTICS, this._handleGetCharacteristics.bind(this)); - this._server.on(HAPServerEventTypes.SET_CHARACTERISTICS, this._handleSetCharacteristics.bind(this)); - this._server.on(HAPServerEventTypes.SESSION_CLOSE, this._handleSessionClose.bind(this)); - this._server.on(HAPServerEventTypes.REQUEST_RESOURCE, this._handleResource.bind(this)); + this._server.on(HAPServerEventTypes.PAIR, this.handleInitialPairSetupFinished.bind(this)); + this._server.on(HAPServerEventTypes.ADD_PAIRING, this.handleAddPairing.bind(this)); + this._server.on(HAPServerEventTypes.REMOVE_PAIRING, this.handleRemovePairing.bind(this)); + this._server.on(HAPServerEventTypes.LIST_PAIRINGS, this.handleListPairings.bind(this)); + this._server.on(HAPServerEventTypes.ACCESSORIES, this.handleAccessories.bind(this)); + this._server.on(HAPServerEventTypes.GET_CHARACTERISTICS, this.handleGetCharacteristics.bind(this)); + this._server.on(HAPServerEventTypes.SET_CHARACTERISTICS, this.handleSetCharacteristics.bind(this)); + this._server.on(HAPServerEventTypes.CONNECTION_CLOSED, this.handleHAPConnectionClosed.bind(this)); + this._server.on(HAPServerEventTypes.REQUEST_RESOURCE, this.handleResource.bind(this)); if (typeof info.pincode === 'function') { this._server.on(HAPServerEventTypes.GENERATE_SETUP_CODE, info.pincode); @@ -982,7 +1105,7 @@ export class Accessory extends EventEmitter { this._server.on(HAPServerEventTypes.GENERATE_SETUP_CODE, generateSetupCode); } - this._server.listen(info.port || 0); + this._server.listen(info.port, parsed.serverAddress); } /** @@ -990,7 +1113,7 @@ export class Accessory extends EventEmitter { * Accessory object will no longer valid after invoking this method * Trying to invoke publish() on the object will result undefined behavior */ - destroy = () => { + public destroy(): void { this.unpublish(); if (this._accessoryInfo) { @@ -1000,75 +1123,72 @@ export class Accessory extends EventEmitter { this._identifierCache = undefined; this.controllerStorage = new ControllerStorage(this); } + this.removeAllListeners(); } - unpublish = () => { + public unpublish(): void { if (this._server) { this._server.stop(); this._server = undefined; } if (this._advertiser) { + // noinspection JSIgnoredPromiseFromCall this._advertiser.shutdown(); this._advertiser = undefined; } } - _updateConfiguration = () => { - if (this._advertiser && this._advertiser.isServiceCreated()) { - // get our accessory information in HAP format and determine if our configuration (that is, our - // Accessories/Services/Characteristics) has changed since the last time we were published. make - // sure to omit actual values since these are not part of the "configuration". - var config = this.toHAP({omitValues:true}); - - // now convert it into a hash code and check it against the last one we made, if we have one - var shasum = crypto.createHash('sha1'); - shasum.update(JSON.stringify(config)); - var configHash = shasum.digest('hex'); + private enqueueConfigurationUpdate(): void { + if (this.configurationChangeDebounceTimeout) { + return; // already enqueued + } - if (this._accessoryInfo && configHash !== this._accessoryInfo.configHash) { + this.configurationChangeDebounceTimeout = setTimeout(() => { + this.configurationChangeDebounceTimeout = undefined; - // our configuration has changed! we'll need to bump our config version number - this._accessoryInfo.updateConfigHash(configHash); + if (this._advertiser && this._advertiser) { + // get our accessory information in HAP format and determine if our configuration (that is, our + // Accessories/Services/Characteristics) has changed since the last time we were published. make + // sure to omit actual values since these are not part of the "configuration". + const config = this.internalHAPRepresentation(); // TODO ensure this stuff is ordered + if (this._accessoryInfo?.checkForCurrentConfigurationNumberIncrement(config)) { + this._advertiser.updateAdvertisement(); + } } - - // update our advertisement so HomeKit on iOS can pickup new accessory - this._advertiser.updateAdvertisement(); - } + }, 1000); + this.configurationChangeDebounceTimeout.unref(); + // 1d is fine, HomeKit is built that with configuration updates no iid or aid conflicts occur. + // Thus the only thing happening when the txt update arrives late is already removed accessories/services + // not responding or new accessories/services not yet shown } - _onListening = (port: number) => { + private onListening(port: number, hostname: string): void { assert(this._advertiser, "Advertiser wasn't created at onListening!"); // the HAP server is listening, so we can now start advertising our presence. - this._advertiser!.initAdvertiser(port); + this._advertiser!.initPort(port); + // noinspection JSIgnoredPromiseFromCall this._advertiser!.startAdvertising(); - this.emit(AccessoryEventTypes.LISTENING, port); - } - -// Called when an unpaired client wishes for us to identify ourself - _handleIdentify = (callback: IdentifyCallback) => { - this._identificationRequest(false, callback); + this.emit(AccessoryEventTypes.LISTENING, port, hostname); } /** Called when starting the pair setup process after a setup code has been generated */ - _handlePairSetupStarted = (i: PairIdentity, session: Session) => { + private _handlePairSetupStarted(i: PairIdentity, connection: HAPConnection) { if (this.listenerCount(AccessoryEventTypes.PAIR_SETUP_STARTED)) { if (!this._setupID) this._setupID = generateSetupId(); const setupuri = i.setupcode ? generateSetupUri(i.setupcode, this._setupID, this.category) : null; - this.emit(AccessoryEventTypes.PAIR_SETUP_STARTED, {...i, setupuri}, session); + this.emit(AccessoryEventTypes.PAIR_SETUP_STARTED, {...i, setupuri}, connection); } else if (!this._accessoryInfo!.pincode) { // If we're using random setup codes and there's nothing listening for the setup code print it to the console - console.log('[%s] Received pair request from %s', this.displayName, session._connection._clientSocket.remoteAddress, i.setupcode); + console.log('[%s] Received pair request from %s', this.displayName, connection.remoteAddress, i.setupcode); } } /** Called when the pair setup process has finished with the error or paired client username */ - _handlePairSetupFinished = (err: Nullable, clientUsername: Nullable, session: Session) => { - this.emit(AccessoryEventTypes.PAIR_SETUP_FINISHED, err, clientUsername, session); + private _handlePairSetupFinished(err: Nullable, clientUsername: Nullable, connection: HAPConnection) { + this.emit(AccessoryEventTypes.PAIR_SETUP_FINISHED, err, clientUsername, connection); } -// Called when HAPServer has completed the pairing process with a client - _handlePair = (username: string, publicKey: Buffer, callback: PairCallback) => { - + private handleInitialPairSetupFinished(username: string, publicKey: Buffer, callback: PairCallback): void { debug("[%s] Paired with client %s", this.displayName, username); this._accessoryInfo && this._accessoryInfo.addPairedClient(username, publicKey, PermissionTypes.ADMIN); @@ -1082,22 +1202,21 @@ export class Accessory extends EventEmitter { this.emit(AccessoryEventTypes.PAIRED); } -// called when a controller adds an additional pairing - _handleAddPairing = (controller: Session, username: string, publicKey: Buffer, permission: PermissionTypes, callback: AddPairingCallback) => { + private handleAddPairing(connection: HAPConnection, username: string, publicKey: Buffer, permission: PermissionTypes, callback: AddPairingCallback): void { if (!this._accessoryInfo) { - callback(Codes.UNAVAILABLE); + callback(TLVErrorCode.UNAVAILABLE); return; } - if (!this._accessoryInfo.hasAdminPermissions(controller.username!)) { - callback(Codes.AUTHENTICATION); + if (!this._accessoryInfo.hasAdminPermissions(connection.username!)) { + callback(TLVErrorCode.AUTHENTICATION); return; } const existingKey = this._accessoryInfo.getClientPublicKey(username); if (existingKey) { if (existingKey.toString() !== publicKey.toString()) { - callback(Codes.UNKNOWN); + callback(TLVErrorCode.UNKNOWN); return; } @@ -1111,18 +1230,18 @@ export class Accessory extends EventEmitter { callback(0); }; - _handleRemovePairing = (controller: Session, username: string, callback: RemovePairingCallback) => { + private handleRemovePairing(connection: HAPConnection, username: HAPUsername, callback: RemovePairingCallback): void { if (!this._accessoryInfo) { - callback(Codes.UNAVAILABLE); + callback(TLVErrorCode.UNAVAILABLE); return; } - if (!this._accessoryInfo.hasAdminPermissions(controller.username!)) { - callback(Codes.AUTHENTICATION); + if (!this._accessoryInfo.hasAdminPermissions(connection.username!)) { + callback(TLVErrorCode.AUTHENTICATION); return; } - this._accessoryInfo.removePairedClient(controller, username); + this._accessoryInfo.removePairedClient(connection, username); this._accessoryInfo.save(); callback(0); // first of all ensure the pairing is removed before we advertise availability again @@ -1130,168 +1249,195 @@ export class Accessory extends EventEmitter { if (!this._accessoryInfo.paired()) { this._advertiser && this._advertiser.updateAdvertisement(); this.emit(AccessoryEventTypes.UNPAIRED); + + this.handleAccessoryUnpairedForControllers(); + for (const accessory of this.bridgedAccessories) { + accessory.handleAccessoryUnpairedForControllers(); + } } }; - _handleListPairings = (controller: Session, callback: ListPairingsCallback) => { + private handleListPairings(connection: HAPConnection, callback: ListPairingsCallback): void { if (!this._accessoryInfo) { - callback(Codes.UNAVAILABLE); + callback(TLVErrorCode.UNAVAILABLE); return; } - if (!this._accessoryInfo.hasAdminPermissions(controller.username!)) { - callback(Codes.AUTHENTICATION); + if (!this._accessoryInfo.hasAdminPermissions(connection.username!)) { + callback(TLVErrorCode.AUTHENTICATION); return; } callback(0, this._accessoryInfo.listPairings()); }; -// Called when an iOS client wishes to know all about our accessory via JSON payload - _handleAccessories = (callback: HandleAccessoriesCallback) => { + private handleAccessories(connection: HAPConnection, callback: AccessoriesCallback): void { + this._assignIDs(this._identifierCache!); // make sure our aid/iid's are all assigned - // make sure our aid/iid's are all assigned - this._assignIDs(this._identifierCache!); - - // build out our JSON payload and call the callback - callback(null, { - accessories: this.toHAP() // array of Accessory HAP + this.toHAP(connection).then(value => { + callback(undefined, { + accessories: value, + }); + }, reason => { + console.error("[" + this.displayName + "] /accessories request error with: " + reason.stack); + callback({ httpCode: HAPHTTPCode.INTERNAL_SERVER_ERROR, status: HAPStatus.SERVICE_COMMUNICATION_FAILURE }); }); } -// Called when an iOS client wishes to query the state of one or more characteristics, like "door open?", "light on?", etc. - _handleGetCharacteristics = (data: CharacteristicData[], events: CharacteristicEvents, callback: HandleGetCharacteristicsCallback, remote: boolean, session: Session) => { + private handleGetCharacteristics(connection: HAPConnection, request: CharacteristicsReadRequest, callback: ReadCharacteristicsCallback): void { + const characteristics: CharacteristicReadData[] = []; + const response: CharacteristicsReadResponse = { characteristics: characteristics }; - // build up our array of responses to the characteristics requested asynchronously - var characteristics: CharacteristicData[] = []; - var statusKey = remote ? 's' : 'status'; - var valueKey = remote ? 'v' : 'value'; + const missingCharacteristics: Set = new Set(request.ids.map(id => id.aid + "." + id.iid)); + if (missingCharacteristics.size !== request.ids.length) { + // if those sizes differ, we have duplicates and can't properly handle that + callback({ httpCode: HAPHTTPCode.UNPROCESSABLE_ENTITY, status: HAPStatus.INVALID_VALUE_IN_REQUEST }); + return; + } - data.forEach((characteristicData) => { - var aid = characteristicData.aid; - var iid = characteristicData.iid; + let timeout: Timeout | undefined = setTimeout(() => { + for (const id of missingCharacteristics) { + const split = id.split("."); + const aid = parseInt(split[0]); + const iid = parseInt(split[1]); - var includeEvent = characteristicData.e; + const accessory = this.getAccessoryByAID(aid)!; + const characteristic = accessory.getCharacteristicByIID(iid)!; + characteristic.characteristicWarning("The read handler for the characteristic '" + characteristic.displayName + + "' on the accessory '" + accessory.displayName + "' was slow to respond!", CharacteristicWarningType.SLOW_READ); + } - var characteristic = this.findCharacteristic(characteristicData.aid, characteristicData.iid); + // after a total of 10s we do not longer wait for a request to appear and just return status code timeout + timeout = setTimeout(() => { + timeout = undefined; - if (!characteristic) { - debug('[%s] Could not find a Characteristic with aid of %s and iid of %s', this.displayName, characteristicData.aid, characteristicData.iid); - var response: any = { - aid: aid, - iid: iid - }; - response[statusKey] = Status.SERVICE_COMMUNICATION_FAILURE; // generic error status - characteristics.push(response); + for (const id of missingCharacteristics) { + const split = id.split("."); + const aid = parseInt(split[0]); + const iid = parseInt(split[1]); - // have we collected all responses yet? - if (characteristics.length === data.length) - callback(null, characteristics); + const accessory = this.getAccessoryByAID(aid)!; + const characteristic = accessory.getCharacteristicByIID(iid)!; + characteristic.characteristicWarning("The read handler for the characteristic '" + characteristic.displayName + "' on the accessory '" + accessory.displayName + + "' didn't respond at all!. Please check that you properly call the callback!", CharacteristicWarningType.TIMEOUT_READ); - return; - } - - if (!characteristic.props.perms.includes(Perms.PAIRED_READ)) { // check if we are allowed to read from this characteristic - debug('[%s] Tried reading from Characteristic which does not allow reading (iid of %s and aid of %s)', this.displayName, characteristicData.aid, characteristicData.iid); - const response: any = { - aid: aid, - iid: iid + characteristics.push({ + aid: aid, + iid: iid, + status: HAPStatus.OPERATION_TIMED_OUT, + }); + } + missingCharacteristics.clear(); + + callback(undefined, response); + }, 7000); + timeout.unref(); + }, 3000); + timeout.unref(); + + for (const id of request.ids) { + const name = id.aid + "." + id.iid; + this.handleCharacteristicRead(connection, id, request).then(value => { + return { + aid: id.aid, + iid: id.iid, + ...value, }; - response[statusKey] = Status.WRITE_ONLY_CHARACTERISTIC; - characteristics.push(response); + }, reason => { // this error block is only called if hap-nodejs itself messed up + console.error(`[${this.displayName}] Read request for characteristic ${name} encountered an error: ${reason.stack}`) - if (characteristics.length === data.length) { - callback(null, characteristics); + return { + aid: id.aid, + iid: id.iid, + status: HAPStatus.SERVICE_COMMUNICATION_FAILURE, + }; + }).then(value => { + if (!timeout) { + return; // if timeout is undefined, response was already sent out } - return; - } - if (characteristic.props.adminOnlyAccess && characteristic.props.adminOnlyAccess.includes(Access.READ)) { - let verifiable = true; - if (!session || !session.username || !this._accessoryInfo) { - verifiable = false; - debug('[%s] Could not verify admin permissions for Characteristic which requires admin permissions for reading (iid of %s and aid of %s)', this.displayName, characteristicData.aid, characteristicData.iid) - } + missingCharacteristics.delete(name); + characteristics.push(value); - if (!verifiable || !this._accessoryInfo!.hasAdminPermissions(session.username!)) { - const response: any = { - aid: aid, - iid: iid - }; - response[statusKey] = Status.INSUFFICIENT_PRIVILEGES; - characteristics.push(response); - - if (characteristics.length === data.length) - callback(null, characteristics); - return; + if (missingCharacteristics.size === 0) { + if (timeout) { + clearTimeout(timeout); + timeout = undefined; + } + callback(undefined, response); } - } + }); + } + } - // Found the Characteristic! Get the value! - debug('[%s] Getting value for Characteristic "%s"', this.displayName, characteristic.displayName); - - // we want to remember "who" made this request, so that we don't send them an event notification - // about any changes that occurred as a result of the request. For instance, if after querying - // the current value of a characteristic, the value turns out to be different than the previously - // cached Characteristic value, an internal 'change' event will be emitted which will cause us to - // notify all connected clients about that new value. But this client is about to get the new value - // anyway, so we don't want to notify it twice. - var context = events; - - // set the value and wait for success - characteristic.getValue((err, value) => { - if (err) { - debug('[%s] Error getting value for Characteristic "%s": %s', this.displayName, characteristic!.displayName, err.message); - var response: any = { - aid: aid, - iid: iid - }; - response[statusKey] = hapStatus(err); - characteristics.push(response); - } else { - debug('[%s] Got Characteristic "%s" value: %s', this.displayName, characteristic!.displayName, value); - - var response: any = { - aid: aid, - iid: iid - }; - response[valueKey] = value; + private async handleCharacteristicRead(connection: HAPConnection, id: CharacteristicId, request: CharacteristicsReadRequest): Promise { + const characteristic = this.findCharacteristic(id.aid, id.iid); - if (includeEvent) { - var eventName = aid + '.' + iid; - response['e'] = (events[eventName] === true); - } + if (!characteristic) { + debug('[%s] Could not find a Characteristic with aid of %s and iid of %s', this.displayName, id.aid, id.iid); + return { status: HAPStatus.INVALID_VALUE_IN_REQUEST }; + } - // compose the response and add it to the list - characteristics.push(response); - } + if (!characteristic.props.perms.includes(Perms.PAIRED_READ)) { // check if read is allowed for this characteristic + debug('[%s] Tried reading from characteristic which does not allow reading (aid of %s and iid of %s)', this.displayName, id.aid, id.iid); + return { status: HAPStatus.WRITE_ONLY_CHARACTERISTIC }; + } - // have we collected all responses yet? - if (characteristics.length === data.length) - callback(null, characteristics); + if (characteristic.props.adminOnlyAccess && characteristic.props.adminOnlyAccess.includes(Access.READ)) { + let verifiable = true; + if (!connection.username || !this._accessoryInfo) { + verifiable = false; + debug('[%s] Could not verify admin permissions for Characteristic which requires admin permissions for reading (aid of %s and iid of %s)', this.displayName, id.aid, id.iid) + } - }, context, session? session.sessionID: undefined); + if (!verifiable || !this._accessoryInfo!.hasAdminPermissions(connection.username!)) { + return { status: HAPStatus.INSUFFICIENT_PRIVILEGES }; + } + } - }); - } + return characteristic.handleGetRequest(connection).then(value => { + debug('[%s] Got Characteristic "%s" value: %s', this.displayName, characteristic!.displayName, value); -// Called when an iOS client wishes to change the state of this accessory - like opening a door, or turning on a light. -// Or, to subscribe to change events for a particular Characteristic. - _handleSetCharacteristics = (writeRequest: CharacteristicsWriteRequest, events: CharacteristicEvents, callback: HandleSetCharacteristicsCallback, remote: boolean, session: Session) => { - const data = writeRequest.characteristics; + const data: PartialCharacteristicReadData = { + value: value == undefined? null: value, + }; - // data is an array of characteristics and values like this: - // [ { aid: 1, iid: 8, value: true, ev: true } ] + if (request.includeMeta) { + data.format = characteristic.props.format; + data.unit = characteristic.props.unit; + data.minValue = characteristic.props.minValue; + data.maxValue = characteristic.props.maxValue; + data.minStep = characteristic.props.minStep; + data.maxLen = characteristic.props.maxLen || characteristic.props.maxDataLen; + } + if (request.includePerms) { + data.perms = characteristic.props.perms; + } + if (request.includeType) { + data.type = toShortForm(this.UUID); + } + if (request.includeEvent) { + data.ev = connection.hasEventNotifications(id.aid, id.iid); + } - debug("[%s] Processing characteristic set: %s", this.displayName, JSON.stringify(data)); + return data; + }, (reason: HAPStatus) => { + // @ts-expect-error + debug('[%s] Error getting value for characteristic "%s": %s', this.displayName, characteristic.displayName, HAPStatus[reason]); + return { status: reason }; + }); + } + + private handleSetCharacteristics(connection: HAPConnection, writeRequest: CharacteristicsWriteRequest, callback: WriteCharacteristicsCallback): void { + debug("[%s] Processing characteristic set: %s", this.displayName, JSON.stringify(writeRequest)); let writeState: WriteRequestState = WriteRequestState.REGULAR_REQUEST; if (writeRequest.pid !== undefined) { // check for timed writes - if (session.timedWritePid === writeRequest.pid) { + if (connection.timedWritePid === writeRequest.pid) { writeState = WriteRequestState.TIMED_WRITE_AUTHENTICATED; - clearTimeout(session.timedWriteTimeout!); - session.timedWritePid = undefined; - session.timedWriteTimeout = undefined; + clearTimeout(connection.timedWriteTimeout!); + connection.timedWritePid = undefined; + connection.timedWriteTimeout = undefined; debug("[%s] Timed write request got acknowledged for pid %d", this.displayName, writeRequest.pid); } else { @@ -1300,318 +1446,304 @@ export class Accessory extends EventEmitter { } } - // build up our array of responses to the characteristics requested asynchronously - var characteristics: CharacteristicData[] = []; + const characteristics: CharacteristicWriteData[] = []; + const response: CharacteristicsWriteResponse = { characteristics: characteristics }; - data.forEach((characteristicData) => { - var aid = characteristicData.aid; - var iid = characteristicData.iid; - var value = remote ? characteristicData.v : characteristicData.value; - var ev = remote ? characteristicData.e : characteristicData.ev; - var includeValue = characteristicData.r || false; + const missingCharacteristics: Set = new Set( + writeRequest.characteristics + .map(characteristic => characteristic.aid + "." + characteristic.iid) + ); + if (missingCharacteristics.size !== writeRequest.characteristics.length) { + // if those sizes differ, we have duplicates and can't properly handle that + callback({ httpCode: HAPHTTPCode.UNPROCESSABLE_ENTITY, status: HAPStatus.INVALID_VALUE_IN_REQUEST }); + return; + } - var statusKey = remote ? 's' : 'status'; + let timeout: Timeout | undefined = setTimeout(() => { + for (const id of missingCharacteristics) { + const split = id.split("."); + const aid = parseInt(split[0]); + const iid = parseInt(split[1]); - var characteristic = this.findCharacteristic(aid, iid); + const accessory = this.getAccessoryByAID(aid)!; + const characteristic = accessory.getCharacteristicByIID(iid)!; + characteristic.characteristicWarning("The write handler for the characteristic '" + characteristic.displayName + + "' on the accessory '" + accessory.displayName + "' was slow to respond!", CharacteristicWarningType.SLOW_WRITE); + } - if (!characteristic) { - debug('[%s] Could not find a Characteristic with iid of %s and aid of %s', this.displayName, characteristicData.aid, characteristicData.iid); - var response: any = { - aid: aid, - iid: iid - }; - response[statusKey] = Status.SERVICE_COMMUNICATION_FAILURE; // generic error status - characteristics.push(response); + // after a total of 10s we do not longer wait for a request to appear and just return status code timeout + timeout = setTimeout(() => { + timeout = undefined; - // have we collected all responses yet? - if (characteristics.length === data.length) - callback(null, characteristics); + for (const id of missingCharacteristics) { + const split = id.split("."); + const aid = parseInt(split[0]); + const iid = parseInt(split[1]); - return; - } + const accessory = this.getAccessoryByAID(aid)!; + const characteristic = accessory.getCharacteristicByIID(iid)!; + characteristic.characteristicWarning("The write handler for the characteristic '" + characteristic.displayName + "' on the accessory '" + accessory.displayName + + "' didn't respond at all!. Please check that you properly call the callback!", CharacteristicWarningType.TIMEOUT_WRITE); - if (writeState === WriteRequestState.TIMED_WRITE_REJECTED) { - const response: any = { - aid: aid, - iid: iid + characteristics.push({ + aid: aid, + iid: iid, + status: HAPStatus.OPERATION_TIMED_OUT, + }); + } + missingCharacteristics.clear(); + + callback(undefined, response); + }, 7000); + timeout.unref(); + }, 3000); + timeout.unref(); + + for (const data of writeRequest.characteristics) { + const name = data.aid + "." + data.iid; + this.handleCharacteristicWrite(connection, data, writeState).then(value => { + return { + aid: data.aid, + iid: data.iid, + ...value, }; - response[statusKey] = Status.INVALID_VALUE_IN_REQUEST; - characteristics.push(response); + }, reason => { // this error block is only called if hap-nodejs itself messed up + console.error(`[${this.displayName}] Write request for characteristic ${name} encountered an error: ${reason.stack}`) - if (characteristics.length === data.length) - callback(null, characteristics); - return; - } + return { + aid: data.aid, + iid: data.iid, + status: HAPStatus.SERVICE_COMMUNICATION_FAILURE, + }; + }).then(value => { + if (!timeout) { + return; // if timeout is undefined, response was already sent out + } - // we want to remember "who" initiated this change, so that we don't send them an event notification - // about the change they just made. We do this by leveraging the arbitrary "context" object supported - // by Characteristic and passed on to the corresponding 'change' events bubbled up from Characteristic - // through Service and Accessory. We'll assign it to the events object since it essentially represents - // the connection requesting the change. - var context = events; - - // if "ev" is present, that means we need to register or unregister this client for change events for - // this characteristic. - if (typeof ev !== 'undefined') { - if (!characteristic.props.perms.includes(Perms.NOTIFY)) { // check if notify is allowed for this characteristic - debug('[%s] Tried enabling notifications for Characteristic which does not allow notify (iid of %s and aid of %s)', this.displayName, characteristicData.aid, characteristicData.iid); - const response: any = { - aid: aid, - iid: iid - }; - response[statusKey] = Status.NOTIFICATION_NOT_SUPPORTED; - characteristics.push(response); + missingCharacteristics.delete(name); + characteristics.push(value); - if (characteristics.length === data.length) { - callback(null, characteristics); + if (missingCharacteristics.size === 0) { // if everything returned send the response + if (timeout) { + clearTimeout(timeout); + timeout = undefined; } - return; + callback(undefined, response); } + }) + } + } - if (characteristic.props.adminOnlyAccess && characteristic.props.adminOnlyAccess.includes(Access.NOTIFY)) { - let verifiable = true; - if (!session || !session.username || !this._accessoryInfo) { - verifiable = false; - debug('[%s] Could not verify admin permissions for Characteristic which requires admin permissions for notify (iid of %s and aid of %s)', this.displayName, characteristicData.aid, characteristicData.iid) - } + private async handleCharacteristicWrite(connection: HAPConnection, data: CharacteristicWrite, writeState: WriteRequestState): Promise { + const characteristic = this.findCharacteristic(data.aid, data.iid); + let evResponse: boolean | undefined = undefined; - if (!verifiable || !this._accessoryInfo!.hasAdminPermissions(session.username!)) { - const response: any = { - aid: aid, - iid: iid - }; - response[statusKey] = Status.INSUFFICIENT_PRIVILEGES; - characteristics.push(response); - - if (characteristics.length === data.length) - callback(null, characteristics); - return; - } - } + if (!characteristic) { + debug('[%s] Could not find a Characteristic with aid of %s and iid of %s', this.displayName, data.aid, data.iid); + return { status: HAPStatus.INVALID_VALUE_IN_REQUEST }; + } - debug('[%s] %s Characteristic "%s" for events', this.displayName, ev ? "Registering" : "Unregistering", characteristic.displayName); + if (writeState === WriteRequestState.TIMED_WRITE_REJECTED) { + return { status: HAPStatus.INVALID_VALUE_IN_REQUEST }; + } - // store event registrations in the supplied "events" dict which is associated with the connection making - // the request. - var eventName = aid + '.' + iid; + if (data.ev != undefined) { // register/unregister event notifications + if (!characteristic.props.perms.includes(Perms.NOTIFY)) { // check if notify is allowed for this characteristic + debug('[%s] Tried enabling notifications for Characteristic which does not allow notify (aid of %s and iid of %s)', this.displayName, data.aid, data.iid); + return { status: HAPStatus.NOTIFICATION_NOT_SUPPORTED }; + } - if (ev === true && events[eventName] != true) { - events[eventName] = true; // value is arbitrary, just needs to be non-falsey - characteristic.subscribe(); + if (characteristic.props.adminOnlyAccess && characteristic.props.adminOnlyAccess.includes(Access.NOTIFY)) { + let verifiable = true; + if (!connection.username || !this._accessoryInfo) { + verifiable = false; + debug('[%s] Could not verify admin permissions for Characteristic which requires admin permissions for notify (aid of %s and iid of %s)', this.displayName, data.aid, data.iid) } - if (ev === false && events[eventName] != undefined) { - characteristic.unsubscribe(); - delete events[eventName]; // unsubscribe by deleting name from dict + if (!verifiable || !this._accessoryInfo!.hasAdminPermissions(connection.username!)) { + return { status: HAPStatus.INSUFFICIENT_PRIVILEGES }; } } - // Found the characteristic - set the value if there is one - if (typeof value !== 'undefined') { - if (!characteristic.props.perms.includes(Perms.PAIRED_WRITE)) { // check if write is allowed for this characteristic - debug('[%s] Tried writing to Characteristic which does not allow writing (iid of %s and aid of %s)', this.displayName, characteristicData.aid, characteristicData.iid); - const response: any = { - aid: aid, - iid: iid - }; - response[statusKey] = Status.READ_ONLY_CHARACTERISTIC; - characteristics.push(response); + if (data.ev && !connection.hasEventNotifications(data.aid, data.iid)) { + connection.enableEventNotifications(data.aid, data.iid); + characteristic.subscribe(); + evResponse = true; + debug('[%s] Registered Characteristic "%s" on "%s" for events', connection.remoteAddress, characteristic.displayName, this.displayName); + } - if (characteristics.length === data.length) { - callback(null, characteristics); - } - return; - } + if (!data.ev && connection.hasEventNotifications(data.aid, data.iid)) { + characteristic.unsubscribe(); + connection.disableEventNotifications(data.aid, data.iid); + evResponse = false; + debug('[%s] Unregistered Characteristic "%s" on "%s" for events', connection.remoteAddress, characteristic.displayName, this.displayName); + } + // response is returned below in the else block + } - if (characteristic.props.adminOnlyAccess && characteristic.props.adminOnlyAccess.includes(Access.WRITE)) { - let verifiable = true; - if (!session || !session.username || !this._accessoryInfo) { - verifiable = false; - debug('[%s] Could not verify admin permissions for Characteristic which requires admin permissions for write (iid of %s and aid of %s)', this.displayName, characteristicData.aid, characteristicData.iid) - } + if (data.value != undefined) { + if (!characteristic.props.perms.includes(Perms.PAIRED_WRITE)) { // check if write is allowed for this characteristic + debug('[%s] Tried writing to Characteristic which does not allow writing (aid of %s and iid of %s)', this.displayName, data.aid, data.iid); + return { status: HAPStatus.READ_ONLY_CHARACTERISTIC }; + } - if (!verifiable || !this._accessoryInfo!.hasAdminPermissions(session.username!)) { - const response: any = { - aid: aid, - iid: iid - }; - response[statusKey] = Status.INSUFFICIENT_PRIVILEGES; - characteristics.push(response); - - if (characteristics.length === data.length) - callback(null, characteristics); - return; - } + if (characteristic.props.adminOnlyAccess && characteristic.props.adminOnlyAccess.includes(Access.WRITE)) { + let verifiable = true; + if (!connection.username || !this._accessoryInfo) { + verifiable = false; + debug('[%s] Could not verify admin permissions for Characteristic which requires admin permissions for write (aid of %s and iid of %s)', this.displayName, data.aid, data.iid) } - if (characteristic.props.perms.includes(Perms.TIMED_WRITE) && writeState !== WriteRequestState.TIMED_WRITE_AUTHENTICATED) { - debug('[%s] Tried writing to a timed write only Characteristic without properly preparing (iid of %s and aid of %s)', this.displayName, characteristicData.aid, characteristicData.iid); - const response: any = { - aid: aid, - iid: iid - }; - response[statusKey] = Status.INVALID_VALUE_IN_REQUEST; - characteristics.push(response); - - if (characteristics.length === data.length) - callback(null, characteristics); - return; + if (!verifiable || !this._accessoryInfo!.hasAdminPermissions(connection.username!)) { + return { status: HAPStatus.INSUFFICIENT_PRIVILEGES }; } + } - debug('[%s] Setting Characteristic "%s" to value %s', this.displayName, characteristic.displayName, value); - - // set the value and wait for success - characteristic.setValue(value, (err) => { - - if (err) { - debug('[%s] Error setting Characteristic "%s" to value %s: ', this.displayName, characteristic!.displayName, value, err.message); - - var response: any = { - aid: aid, - iid: iid - }; - response[statusKey] = hapStatus(err); - characteristics.push(response); - } else { - var response: any = { - aid: aid, - iid: iid - }; - response[statusKey] = 0; + if (characteristic.props.perms.includes(Perms.ADDITIONAL_AUTHORIZATION) && characteristic.additionalAuthorizationHandler) { + // if the characteristic "supports additional authorization" but doesn't define a handler for the check + // we conclude that the characteristic doesn't want to check the authData (currently) and just allows access for everybody - if (includeValue) - response['value'] = characteristic!.value; + let allowWrite; + try { + allowWrite = characteristic.additionalAuthorizationHandler(data.authData); + } catch (error) { + console.log("[" + this.displayName + "] Additional authorization handler has thrown an error when checking authData: " + error.stack); + allowWrite = false; + } - characteristics.push(response); - } + if (!allowWrite) { + return { status: HAPStatus.INSUFFICIENT_AUTHORIZATION }; + } + } - // have we collected all responses yet? - if (characteristics.length === data.length) - callback(null, characteristics); + if (characteristic.props.perms.includes(Perms.TIMED_WRITE) && writeState !== WriteRequestState.TIMED_WRITE_AUTHENTICATED) { + debug('[%s] Tried writing to a timed write only Characteristic without properly preparing (iid of %s and aid of %s)', this.displayName, data.aid, data.iid); + return { status: HAPStatus.INVALID_VALUE_IN_REQUEST }; + } - }, context, session? session.sessionID: undefined); + return characteristic.handleSetRequest(data.value, connection).then(value => { + debug('[%s] Setting Characteristic "%s" to value %s', this.displayName, characteristic.displayName, data.value); + return { + value: data.r && value? value: undefined, // if write response is requests and value is provided, return that - } else { - // no value to set, so we're done (success) - var response: any = { - aid: aid, - iid: iid + ev: evResponse, }; - response[statusKey] = 0; - characteristics.push(response); - - // have we collected all responses yet? - if (characteristics.length === data.length) - callback(null, characteristics); - } + }, (status: HAPStatus) => { + // @ts-expect-error + debug('[%s] Error setting Characteristic "%s" to value %s: ', this.displayName, characteristic.displayName, data.value, HAPStatus[status]); - }); + return { status: status }; + }); + } else { + return { ev: evResponse }; + } } - _handleResource(data: Resource, callback: NodeCallback): void { - if (data["resource-type"] === ResourceTypes.IMAGE) { + private handleResource(data: ResourceRequest, callback: ResourceRequestCallback): void { + if (data["resource-type"] === ResourceRequestType.IMAGE) { const aid = data.aid; // aid is optionally supplied by HomeKit (for example when camera is bridged, multiple cams, etc) + let accessory: Accessory | undefined = undefined; let controller: CameraController | undefined = undefined; if (aid) { - if (this.aid === aid && this.activeCameraController) { // bridge is probably not a camera but it is possible in theory - controller = this.activeCameraController; - } else { - const accessory = this.getBridgedAccessoryByAID(aid); - if (accessory && accessory.activeCameraController) { - controller = accessory.activeCameraController; - } + accessory = this.getAccessoryByAID(aid); + if (accessory && accessory.activeCameraController) { + controller = accessory.activeCameraController; } } else if (this.activeCameraController) { // aid was not supplied, check if this accessory is a camera + accessory = this; controller = this.activeCameraController; } if (!controller) { - callback(new Error("resource not found")); + debug("[%s] received snapshot request though no camera controller was associated!"); + callback({ httpCode: HAPHTTPCode.NOT_FOUND, status: HAPStatus.RESOURCE_DOES_NOT_EXIST }); return; } - controller.handleSnapshotRequest(data["image-height"], data["image-width"], callback); + controller.handleSnapshotRequest(data["image-height"], data["image-width"], accessory?.displayName).then(buffer => { + callback(undefined, buffer); + }, (status: HAPStatus) => { + callback({ httpCode: HAPHTTPCode.OK, status: status }); + }); return; } - callback(new Error('unsupported image type: ' + data["resource-type"])); + debug("[%s] received request for unsupported image type: " + data["resource-type"], this._accessoryInfo?.username); + callback({ httpCode: HAPHTTPCode.NOT_FOUND, status: HAPStatus.RESOURCE_DOES_NOT_EXIST}); } - _handleSessionClose = (sessionID: SessionIdentifier, events: CharacteristicEvents) => { + private handleHAPConnectionClosed(connection: HAPConnection): void { if (this.activeCameraController) { - this.activeCameraController.handleCloseConnection(sessionID); + this.activeCameraController.handleCloseConnection(connection.sessionID); } - this._unsubscribeEvents(events); - } - - _unsubscribeEvents = (events: CharacteristicEvents) => { - for (var key in events) { - if (key.indexOf('.') !== -1) { - try { - var id = key.split('.'); - var aid = Number.parseInt(id[0]); - var iid = Number.parseInt(id[1]); + for (const event of connection.getRegisteredEvents()) { + const ids = event.split("."); + const aid = parseInt(ids[0]); + const iid = parseInt(ids[1]); - var characteristic = this.findCharacteristic(aid, iid); - if (characteristic) { - characteristic.unsubscribe(); - } - } catch (e) { - } + const characteristic = this.findCharacteristic(aid, iid); + if (characteristic) { + characteristic.unsubscribe(); } } + connection.clearRegisteredEvents(); } -// Called internally above when a change was detected in one of our hosted Characteristics somewhere in our hierarchy. - _handleCharacteristicChange = (change: ServiceCharacteristicChange) => { - if (!this._server) - return; // we're not running a HAPServer, so there's no one to notify about this event - - var data = { - characteristics: [{ - aid: change.accessory.aid, - iid: change.characteristic.iid, - value: change.newValue - }] - }; - - // name for this event that corresponds to what we stored when the client signed up (in handleSetCharacteristics) - var eventName = change.accessory.aid + '.' + change.characteristic.iid; + private handleServiceConfigurationChangeEvent(service: Service): void { + if (!service.isPrimaryService && service === this.primaryService) { + // service changed form primary to non primary service + this.primaryService = undefined; + } else if (service.isPrimaryService && service !== this.primaryService) { + // service changed from non primary to primary service + if (this.primaryService !== undefined) { + this.primaryService.isPrimaryService = false; + } - // pull the events object associated with the original connection (if any) that initiated the change request, - // which we assigned in handleGetCharacteristics/handleSetCharacteristics. - var excludeEvents = change.context; + this.primaryService = service; + } - // pass it along to notifyClients() so that it can omit the connection where events === excludeEvents. - this._server.notifyClients(eventName, data, excludeEvents); + if (this.bridged) { + this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, { service: service }); + } else { + this.enqueueConfigurationUpdate(); + } } - _setupService = (service: Service) => { - service.on(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, () => { - if (!this.bridged) { - this._updateConfiguration(); - } else { - this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({accessory:this, service })); + private handleCharacteristicChangeEvent(accessory: Accessory, service: Service, change: ServiceCharacteristicChange): void { + if (this.bridged) { // forward this to our main accessory + this.emit(AccessoryEventTypes.SERVICE_CHARACTERISTIC_CHANGE, { ...change, service: service }); + } else { + if (!this._server) { + return; // we're not running a HAPServer, so there's no one to notify about this event } - }); - // listen for changes in characteristics and bubble them up - service.on(ServiceEventTypes.CHARACTERISTIC_CHANGE, (change: any) => { - this.emit(AccessoryEventTypes.SERVICE_CHARACTERISTIC_CHANGE, clone(change, {service })); + if (accessory.aid == undefined || change.characteristic.iid == undefined) { + debug("[%s] Muting event notification for %s as ids aren't yet assigned!", accessory.displayName, change.characteristic.displayName); + return; + } - // if we're not bridged, when we'll want to process this event through our HAPServer - if (!this.bridged) - this._handleCharacteristicChange(clone(change, {accessory:this, service })); + const uuid = change.characteristic.UUID; + const immediateDelivery = uuid === Characteristic.ButtonEvent.UUID || uuid === Characteristic.ProgrammableSwitchEvent.UUID + || uuid === Characteristic.MotionDetected.UUID || uuid === Characteristic.ContactSensorState.UUID; + this._server.sendEventNotifications(accessory.aid, change.characteristic.iid, change.newValue, change.originator, immediateDelivery); + } + } + + private setupServiceEventHandlers(service: Service): void { + service.on(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, this.handleServiceConfigurationChangeEvent.bind(this, service)); + service.on(ServiceEventTypes.CHARACTERISTIC_CHANGE, this.handleCharacteristicChangeEvent.bind(this, this, service)); + service.on(ServiceEventTypes.CHARACTERISTIC_WARNING, (characteristic, type, message) => { + this.emit(AccessoryEventTypes.CHARACTERISTIC_WARNING, characteristic, type, message); }); } - _sideloadServices = (targetServices: Service[]) => { - for (var index in targetServices) { - var target = targetServices[index]; - this._setupService(target); + private _sideloadServices(targetServices: Service[]): void { + for (let index in targetServices) { + const target = targetServices[index]; + this.setupServiceEventHandlers(target); } this.services = targetServices.slice(); @@ -1622,8 +1754,8 @@ export class Accessory extends EventEmitter { .getCharacteristic(Characteristic.Identify)! .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { if (value) { - var paired = true; - this._identificationRequest(paired, callback); + const paired = true; + this.identificationRequest(paired, callback); } }); } @@ -1662,7 +1794,7 @@ export class Accessory extends EventEmitter { const controllers: SerializedControllerContext[] = []; // save controllers - Object.entries(accessory.controllers).forEach(([key, context]: [string, ControllerContext]) => { + Object.values(accessory.controllers).forEach((context: ControllerContext) => { controllers.push({ type: context.controller.controllerType, services: Accessory.serializeServiceMap(context.serviceMap), @@ -1765,20 +1897,65 @@ export class Accessory extends EventEmitter { return controllerServiceMap; } -} + private static parseBindOption(info: PublishInfo): { advertiserAddress?: string[], serviceRestrictedAddress?: string[], serviceDisableIpv6?: boolean, serverAddress?: string } { + let advertiserAddress: string[] | undefined = undefined; + let disableIpv6 = false; + let serverAddress: string | undefined = undefined; + + if (info.bind) { + const entries: Set = new Set(Array.isArray(info.bind)? info.bind: [info.bind]); -const numberPattern = /^-?\d+$/; + if (entries.has("::")) { + entries.delete("::"); + if (entries.size) { + advertiserAddress = Array.from(entries); + } + } else if (entries.has("0.0.0.0")) { + disableIpv6 = true; + serverAddress = "0.0.0.0"; + + entries.delete("0.0.0.0"); + if (entries.size) { + advertiserAddress = Array.from(entries); + } + } else { + advertiserAddress = Array.from(entries); -function hapStatus(err: Error) { - let errorValue = Status.SERVICE_COMMUNICATION_FAILURE; + if (entries.size === 1) { + const entry = entries.values().next().value; // grab the first one - if (numberPattern.test(err.message)) { - const value = parseInt(err.message); + const version = net.isIP(entry); // check if ip address was specified or a interface name + if (version) { + serverAddress = version === 4? "0.0.0.0": "::"; // we currently bind to unspecified addresses so config-ui always has a connection via loopback + } else { + serverAddress = "::"; // the interface could have both ipv4 and ipv6 addresses + } + } else if (entries.size > 1) { + let bindUnspecifiedIpv6 = false; // we bind on "::" if there are interface names, or we detect ipv6 addresses + + for (const entry of entries) { + const version = net.isIP(entry); + if (version === 0 || version === 6) { + bindUnspecifiedIpv6 = true; + break; + } + } - if (value >= Status.INSUFFICIENT_PRIVILEGES && value <= Status.INSUFFICIENT_AUTHORIZATION) { - errorValue = value; + if (bindUnspecifiedIpv6) { + serverAddress = "::"; + } else { + serverAddress = "0.0.0.0"; + } + } + } } + + return { + advertiserAddress: advertiserAddress, + serviceRestrictedAddress: advertiserAddress, + serviceDisableIpv6: disableIpv6, + serverAddress: serverAddress, + }; } - return errorValue; } diff --git a/src/lib/AccessoryLoader.ts b/src/lib/AccessoryLoader.ts index 3942cb1b4..5a436583a 100644 --- a/src/lib/AccessoryLoader.ts +++ b/src/lib/AccessoryLoader.ts @@ -1,17 +1,16 @@ +import createDebug from 'debug'; import fs from 'fs'; import path from 'path'; - -import createDebug from 'debug'; - +import { CharacteristicValue, Nullable } from '../types'; import { Accessory } from './Accessory'; -import { Service } from './Service'; import { Characteristic, - CharacteristicEventTypes, CharacteristicGetCallback, + CharacteristicEventTypes, + CharacteristicGetCallback, CharacteristicSetCallback } from './Characteristic'; +import { Service } from './Service'; import * as uuid from './util/uuid'; -import { CharacteristicValue, NodeCallback, Nullable } from '../types'; const debug = createDebug('HAP-NodeJS:AccessoryLoader'); @@ -23,7 +22,7 @@ const debug = createDebug('HAP-NodeJS:AccessoryLoader'); export function loadDirectory(dir: string): Accessory[] { // exported accessory objects loaded from this dir - var accessories: Accessory[] = []; + let accessories: unknown[] = []; fs.readdirSync(dir).forEach((file) => { const suffix = file.split('_').pop(); @@ -31,7 +30,7 @@ export function loadDirectory(dir: string): Accessory[] { // "Accessories" are modules that export a single accessory. if (suffix === 'accessory.js' || suffix === 'accessory.ts') { debug('Parsing accessory: %s', file); - var loadedAccessory = require(path.join(dir, file)).accessory; + const loadedAccessory = require(path.join(dir, file)).accessory; accessories.push(loadedAccessory); } // "Accessory Factories" are modules that export an array of accessories. @@ -39,7 +38,7 @@ export function loadDirectory(dir: string): Accessory[] { debug('Parsing accessory factory: %s', file); // should return an array of objects { accessory: accessory-json } - var loadedAccessories = require(path.join(dir, file)); + const loadedAccessories = require(path.join(dir, file)); accessories = accessories.concat(loadedAccessories); } }); @@ -53,7 +52,7 @@ export function loadDirectory(dir: string): Accessory[] { } else { return (accessory instanceof Accessory) ? accessory : parseAccessoryJSON(accessory); } - }).filter((accessory: Accessory | false) => { return accessory ? true : false; }) as Accessory[]; + }).filter((accessory: Accessory | false) => { return !!accessory; }) as Accessory[]; } /** @@ -64,14 +63,14 @@ export function loadDirectory(dir: string): Accessory[] { export function parseAccessoryJSON(json: any) { // parse services first so we can extract the accessory name - var services: Service[] = []; + const services: Service[] = []; json.services.forEach(function(serviceJSON: any) { - var service = parseServiceJSON(serviceJSON); + const service = parseServiceJSON(serviceJSON); services.push(service); }); - var displayName = json.displayName; + let displayName = json.displayName; services.forEach(function(service) { if (service.UUID === '0000003E-0000-1000-8000-0026BB765291') { // Service.AccessoryInformation.UUID @@ -83,7 +82,7 @@ export function parseAccessoryJSON(json: any) { } }); - var accessory = new Accessory(displayName, uuid.generate(displayName)); + const accessory = new Accessory(displayName, uuid.generate(displayName)); // create custom properties for "username" and "pincode" for Core.js to find later (if using Core.js) // @ts-ignore @@ -103,17 +102,17 @@ export function parseAccessoryJSON(json: any) { } export function parseServiceJSON(json: any) { - var serviceUUID = json.sType; + const serviceUUID = json.sType; // build characteristics first so we can extract the Name (if present) - var characteristics: Characteristic[] = []; + const characteristics: Characteristic[] = []; json.characteristics.forEach((characteristicJSON: any) => { - var characteristic = parseCharacteristicJSON(characteristicJSON); + const characteristic = parseCharacteristicJSON(characteristicJSON); characteristics.push(characteristic); }); - var displayName: Nullable = null; + let displayName: Nullable = null; // extract the "Name" characteristic to use for 'type' discrimination if necessary characteristics.forEach(function(characteristic) { @@ -122,7 +121,7 @@ export function parseServiceJSON(json: any) { }); // Use UUID for "displayName" if necessary, as the JSON structures don't have a value for this - var service = new Service(displayName || serviceUUID, serviceUUID, `${displayName}`); + const service = new Service(displayName || serviceUUID, serviceUUID, `${displayName}`); characteristics.forEach(function(characteristic) { if (characteristic.UUID != '00000023-0000-1000-8000-0026BB765291') // Characteristic.Name.UUID, already present in all Services @@ -133,13 +132,9 @@ export function parseServiceJSON(json: any) { } export function parseCharacteristicJSON(json: any) { - var characteristicUUID = json.cType; + const characteristicUUID = json.cType; - var characteristic = new Characteristic(json.manfDescription || characteristicUUID, characteristicUUID); - - // copy simple properties - characteristic.value = json.initialValue; - characteristic.setProps({ + const characteristic = new Characteristic(json.manfDescription || characteristicUUID, characteristicUUID, { format: json.format, // example: "int" minValue: json.designedMinValue, maxValue: json.designedMaxValue, @@ -148,21 +143,16 @@ export function parseCharacteristicJSON(json: any) { perms: json.perms // example: ["pw","pr","ev"] }); - // monkey-patch this characteristic to add the legacy method `updateValue` which used to exist, - // and that accessory modules had access to via the `onRegister` function. This was the old mechanism - // for communicating state changes about accessories that happened "outside" HomeKit. - // @ts-ignore - characteristic.updateValue = function(value, peer) { - characteristic.setValue(value); - }; + // copy simple properties + characteristic.value = json.initialValue; // monkey-patch legacy "locals" property which used to exist. // @ts-ignore characteristic.locals = json.locals; - var updateFunc = json.onUpdate; // optional function(value) - var readFunc = json.onRead; // optional function(callback(value)) - var registerFunc = json.onRegister; // optional function + const updateFunc = json.onUpdate; // optional function(value) + const readFunc = json.onRead; // optional function(callback(value)) + const registerFunc = json.onRegister; // optional function if (updateFunc) { characteristic.on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { diff --git a/src/lib/Advertiser.spec.ts b/src/lib/Advertiser.spec.ts index c1ad02628..56626d240 100644 --- a/src/lib/Advertiser.spec.ts +++ b/src/lib/Advertiser.spec.ts @@ -1,5 +1,4 @@ import { Advertiser, PairingFeatureFlag, StatusFlag } from './Advertiser'; -import { AccessoryInfo } from './model/AccessoryInfo'; describe(Advertiser, () => { describe("ff and sf", () => { diff --git a/src/lib/Advertiser.ts b/src/lib/Advertiser.ts index 07c210217..9915cdd4f 100644 --- a/src/lib/Advertiser.ts +++ b/src/lib/Advertiser.ts @@ -6,7 +6,7 @@ import ciao, { ServiceTxt, ServiceType } from "@homebridge/ciao"; -import assert from "assert"; +import { ServiceOptions } from "@homebridge/ciao/lib/CiaoService"; import { EventEmitter } from "events"; import { AccessoryInfo } from './model/AccessoryInfo'; import { generateSetupHash } from "./util/setupid"; @@ -54,53 +54,47 @@ export class Advertiser extends EventEmitter { static protocolVersionService: string = "1.1.0"; private readonly accessoryInfo: AccessoryInfo; - private readonly responder: Responder; private readonly setupHash: string; - private advertisedService?: CiaoService; + private readonly responder: Responder; + private readonly advertisedService: CiaoService; - constructor(accessoryInfo: AccessoryInfo, options?: MDNSServerOptions) { + constructor(accessoryInfo: AccessoryInfo, responderOptions?: MDNSServerOptions, serviceOptions?: Partial) { super(); this.accessoryInfo = accessoryInfo; - this.responder = ciao.getResponder(options); - this.setupHash = generateSetupHash(this.accessoryInfo.username, this.accessoryInfo.setupID).toString('base64') - } - - public initAdvertiser(port: number): void { - assert(!this.advertisedService, "Service was already created!"); + this.setupHash = generateSetupHash(this.accessoryInfo.username, this.accessoryInfo.setupID).toString('base64'); + this.responder = ciao.getResponder(responderOptions); this.advertisedService = this.responder.createService({ name: this.accessoryInfo.displayName, type: ServiceType.HAP, - port: port, txt: this.createTxt(), // host will default now to .local, spaces replaced with dashes + ...serviceOptions, }); this.advertisedService.on(ServiceEvent.NAME_CHANGED, this.emit.bind(this, AdvertiserEvent.UPDATED_NAME)); } - public startAdvertising(): Promise { - assert(this.advertisedService, "Cannot create advertisement when the service wasn't created yet!"); - return this.advertisedService!.advertise(); + public initPort(port: number): void { + this.advertisedService.updatePort(port); } - public isServiceCreated(): boolean { - return !!this.advertisedService; + public startAdvertising(): Promise { + return this.advertisedService!.advertise(); } public updateAdvertisement(): void { - assert(this.advertisedService, "Cannot update advertisement when service wasn't yet advertised!"); this.advertisedService!.updateTxt(this.createTxt()); } - public stopAdvertising(): Promise { - assert(this.advertisedService, "Cannot stop advertisement when service wasn't yet advertised!"); - return this.advertisedService!.end(); + public destroyAdvertising(): Promise { + return this.advertisedService!.destroy(); } public async shutdown(): Promise { - await this.stopAdvertising(); // would also be done by the shutdown method below + await this.destroyAdvertising(); // would also be done by the shutdown method below await this.responder.shutdown(); + this.removeAllListeners(); } private createTxt(): ServiceTxt { diff --git a/src/lib/Characteristic.spec.ts b/src/lib/Characteristic.spec.ts index 4960a26db..9475c31ac 100644 --- a/src/lib/Characteristic.spec.ts +++ b/src/lib/Characteristic.spec.ts @@ -1,22 +1,22 @@ -import './gen'; import { Access, Characteristic, CharacteristicEventTypes, CharacteristicProps, Formats, + HAPStatus, Perms, SerializedCharacteristic, Units, uuid } from '..'; -const createCharacteristic = (type: Formats) => { - return new Characteristic('Test', uuid.generate('Foo'), { format: type, perms: [] }); -}; +function createCharacteristic(type: Formats, customUUID?: string): Characteristic { + return new Characteristic('Test', customUUID || uuid.generate('Foo'), { format: type, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE] }); +} -const createCharacteristicWithProps = (props: CharacteristicProps) => { - return new Characteristic('Test', uuid.generate('Foo'), props); +const createCharacteristicWithProps = (props: CharacteristicProps, customUUID?: string) => { + return new Characteristic('Test', customUUID || uuid.generate('Foo'), props); }; describe('Characteristic', () => { @@ -25,7 +25,7 @@ describe('Characteristic', () => { it('should overwrite existing properties', () => { const characteristic = createCharacteristic(Formats.BOOL); - const NEW_PROPS = {format: Formats.STRING, perms: []}; + const NEW_PROPS = {format: Formats.STRING, perms: [Perms.NOTIFY]}; characteristic.setProps(NEW_PROPS); expect(characteristic.props).toEqual(NEW_PROPS); @@ -58,6 +58,7 @@ describe('Characteristic', () => { characteristic.subscribe(); expect(subscribeSpy).toHaveBeenCalledTimes(1); + // @ts-expect-error expect(characteristic.subscriptions).toEqual(1); }); @@ -70,6 +71,7 @@ describe('Characteristic', () => { characteristic.subscribe(); expect(subscribeSpy).toHaveBeenCalledTimes(1); + // @ts-expect-error expect(characteristic.subscriptions).toEqual(3); }); }); @@ -86,6 +88,7 @@ describe('Characteristic', () => { expect(subscribeSpy).toHaveBeenCalledTimes(1); expect(unsubscribeSpy).toHaveBeenCalledTimes(1); + // @ts-expect-error expect(characteristic.subscriptions).toEqual(0); }); @@ -104,37 +107,96 @@ describe('Characteristic', () => { expect(subscribeSpy).toHaveBeenCalledTimes(1); expect(unsubscribeSpy).toHaveBeenCalledTimes(1); + // @ts-expect-error expect(characteristic.subscriptions).toEqual(0); }); }); - describe('#getValue()', () => { - it('should handle special event only characteristics', () => { - const characteristic = createCharacteristic(Formats.BOOL); - characteristic.eventOnlyCharacteristic = true; + describe('#handleGetRequest()', () => { + it('should handle special event only characteristics', (callback) => { + const characteristic = createCharacteristic(Formats.BOOL, Characteristic.ProgrammableSwitchEvent.UUID); - characteristic.getValue((error, value) => { - expect(error).toEqual(null); - expect(value).toEqual(null); + characteristic.handleGetRequest().then(() => { + expect(characteristic.status).toEqual(HAPStatus.SUCCESS); + expect(characteristic.value).toEqual(null); + callback(); }); }); - it('should return cached values if no listeners are registered', () => { + it('should return cached values if no listeners are registered', (callback) => { const characteristic = createCharacteristic(Formats.BOOL); - characteristic.getValue((status, value) => { - expect(status).toEqual(null); - expect(value).toEqual(null); + characteristic.handleGetRequest().then(() => { + expect(characteristic.status).toEqual(HAPStatus.SUCCESS); + expect(characteristic.value).toEqual(null); + callback(); }); }); }); - describe('#validateValue()', () => { + describe("#roundNumericValue()", () => { + it("should not round valid value", () => { + const characteristic = createCharacteristic(Formats.INT); + characteristic.setProps({ + minStep: 1, + }) + // @ts-expect-error + const result = characteristic.roundNumericValue(4); + expect(result).toBe(4); + }); + + it("should round invalid value", () => { + const characteristic = createCharacteristic(Formats.INT); + characteristic.setProps({ + minStep: 0.15, + minValue: 6, + }) + // @ts-expect-error + const result = characteristic.roundNumericValue(6.1500001); + expect(result).toBe(6.15); + }); + + it("should round up invalid value", () => { + const characteristic = createCharacteristic(Formats.INT); + characteristic.setProps({ + minStep: 0.1, + minValue: 2, + }) + // @ts-expect-error + const result = characteristic.roundNumericValue(2.1542); + expect(result).toBe(2.2); + }); + + it("should round up invalid huge value", () => { + const characteristic = createCharacteristic(Formats.INT); + characteristic.setProps({ + minStep: 90, + }) + // @ts-expect-error + const result = characteristic.roundNumericValue(240); + expect(result).toBe(270); + }); + + it("should round invalid huge value", () => { + const characteristic = createCharacteristic(Formats.INT); + characteristic.setProps({ + maxValue: 38, + minValue: 10, + minStep: 0.1, + }) + // @ts-expect-error + const result = characteristic.roundNumericValue(36.135795); + expect(result).toBe(36.1); + }); + }); + + describe('#validateUserInput()', () => { it('should validate an integer property', () => { const VALUE = 1024; const characteristic = createCharacteristic(Formats.INT); - expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + // @ts-expect-error + expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE); }); it('should validate a float property', () => { @@ -142,97 +204,91 @@ describe('Characteristic', () => { const characteristic = createCharacteristicWithProps({ format: Formats.FLOAT, minStep: 0.001, - perms: [], + perms: [Perms.NOTIFY], }); - expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + // @ts-expect-error + expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE); }); - it('should cut off decimal places correctly', function () { + it('should round decimal places correctly', function () { const VALUE = 1.5642; const characteristic = createCharacteristicWithProps({ format: Formats.FLOAT, minStep: 0.1, - perms: [], + perms: [Perms.NOTIFY], }); - expect(characteristic.validateValue(VALUE)).toEqual(1.5); + // @ts-expect-error + expect(characteristic.validateUserInput(VALUE)).toEqual(1.6); }); it('should validate a UINT8 property', () => { const VALUE = 10; const characteristic = createCharacteristic(Formats.UINT8); - expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + // @ts-expect-error + expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE); }); it('should validate a UINT16 property', () => { const VALUE = 10; const characteristic = createCharacteristic(Formats.UINT16); - expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + // @ts-expect-error + expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE); }); it('should validate a UINT32 property', () => { const VALUE = 10; const characteristic = createCharacteristic(Formats.UINT32); - expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + // @ts-expect-error + expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE); }); it('should validate a UINT64 property', () => { const VALUE = 10; const characteristic = createCharacteristic(Formats.UINT64); - expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + // @ts-expect-error + expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE); }); it('should validate a boolean property', () => { const VALUE = true; const characteristic = createCharacteristic(Formats.BOOL); - expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + // @ts-expect-error + expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE); }); it('should validate a string property', () => { const VALUE = 'Test'; const characteristic = createCharacteristic(Formats.STRING); - expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + // @ts-expect-error + expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE); }); it('should validate a data property', () => { - const VALUE = {}; + const VALUE = Buffer.from("Hello my good friend. Have a nice day!", "ascii").toString("base64"); const characteristic = createCharacteristic(Formats.DATA); - expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + // @ts-expect-error + expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE); }); it('should validate a TLV8 property', () => { const VALUE = ''; const characteristic = createCharacteristic(Formats.TLV8); - expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + // @ts-expect-error + expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE); }); it('should validate a dictionary property', () => { const VALUE = {}; const characteristic = createCharacteristic(Formats.DICTIONARY); - expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + // @ts-expect-error + expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE); }); it('should validate an array property', () => { const VALUE = ['asd']; const characteristic = createCharacteristic(Formats.ARRAY); - expect(characteristic.validateValue(VALUE)).toEqual(VALUE); - }); - }); - - describe('#setValue()', () => { - it(`should set error values as the characteristic's status property`, () => { - const VALUE = new Error(); - const characteristic = createCharacteristic(Formats.DATA); - characteristic.setValue(VALUE); - expect(characteristic.status).toEqual(VALUE); - }); - }); - - describe('#updateValue()', () => { - it(`should set error values as the characteristic's status property`, () => { - const VALUE = new Error(); - const characteristic = createCharacteristic(Formats.DATA); - characteristic.setValue(VALUE); - expect(characteristic.status).toEqual(VALUE); + // @ts-expect-error + expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE); }); }); @@ -240,31 +296,37 @@ describe('Characteristic', () => { it('should get the correct default value for a boolean property', () => { const characteristic = createCharacteristic(Formats.BOOL); + // @ts-expect-error expect(characteristic.getDefaultValue()).toEqual(false); }); it('should get the correct default value for a string property', () => { const characteristic = createCharacteristic(Formats.STRING); + // @ts-expect-error expect(characteristic.getDefaultValue()).toEqual(''); }); it('should get the correct default value for a data property', () => { const characteristic = createCharacteristic(Formats.DATA); + // @ts-expect-error expect(characteristic.getDefaultValue()).toEqual(null); }); it('should get the correct default value for a TLV8 property', () => { const characteristic = createCharacteristic(Formats.TLV8); + // @ts-expect-error expect(characteristic.getDefaultValue()).toEqual(null); }); it('should get the correct default value for a dictionary property', () => { const characteristic = createCharacteristic(Formats.DICTIONARY); + // @ts-expect-error expect(characteristic.getDefaultValue()).toEqual({}); }); it('should get the correct default value for an array property', () => { const characteristic = createCharacteristic(Formats.ARRAY); + // @ts-expect-error expect(characteristic.getDefaultValue()).toEqual([]); }); @@ -276,18 +338,17 @@ describe('Characteristic', () => { describe(`@${CharacteristicEventTypes.GET}`, () => { - it('should call any listeners for the event', () => { + it('should call any listeners for the event', (callback) => { const characteristic = createCharacteristic(Formats.STRING); const listenerCallback = jest.fn(); - const getValueCallback = jest.fn(); - characteristic.getValue(getValueCallback); - characteristic.on(CharacteristicEventTypes.GET, listenerCallback); - characteristic.getValue(getValueCallback); - - expect(listenerCallback).toHaveBeenCalledTimes(1); - expect(getValueCallback).toHaveBeenCalledTimes(1); + characteristic.handleGetRequest().then(() => { + characteristic.on(CharacteristicEventTypes.GET, listenerCallback); + characteristic.handleGetRequest(); + expect(listenerCallback).toHaveBeenCalledTimes(1); + callback(); + }) }); }); @@ -298,33 +359,36 @@ describe('Characteristic', () => { const VALUE = 'NewValue'; const listenerCallback = jest.fn(); - const setValueCallback = jest.fn(); - characteristic.setValue(VALUE, setValueCallback) + characteristic.handleSetRequest(VALUE); characteristic.on(CharacteristicEventTypes.SET, listenerCallback); - characteristic.setValue(VALUE, setValueCallback); + characteristic.handleSetRequest(VALUE); expect(listenerCallback).toHaveBeenCalledTimes(1); - expect(setValueCallback).toHaveBeenCalledTimes(1); }); }); describe(`@${CharacteristicEventTypes.CHANGE}`, () => { - it('should call any listeners for the event when the characteristic is event-only, and the value is set', () => { - const characteristic = createCharacteristic(Formats.STRING); - characteristic.eventOnlyCharacteristic = true; + it('should call listeners for the event when the characteristic is event-only, and the value is set', (callback) => { + const characteristic = createCharacteristic(Formats.STRING, Characteristic.ProgrammableSwitchEvent.UUID); const VALUE = 'NewValue'; const listenerCallback = jest.fn(); const setValueCallback = jest.fn(); - characteristic.setValue(VALUE, setValueCallback) - characteristic.on(CharacteristicEventTypes.CHANGE, listenerCallback); - characteristic.setValue(VALUE, setValueCallback) + characteristic.setValue(VALUE, () => { + setValueCallback(); - expect(listenerCallback).toHaveBeenCalledTimes(1); - expect(setValueCallback).toHaveBeenCalledTimes(2); + characteristic.on(CharacteristicEventTypes.CHANGE, listenerCallback); + characteristic.setValue(VALUE, () => { + setValueCallback(); + + expect(listenerCallback).toHaveBeenCalledTimes(1); + expect(setValueCallback).toHaveBeenCalledTimes(2); + callback(); + }); + }) }); it('should call any listeners for the event when the characteristic is event-only, and the value is updated', () => { @@ -336,6 +400,7 @@ describe('Characteristic', () => { const updateValueCallback = jest.fn(); characteristic.on(CharacteristicEventTypes.CHANGE, listenerCallback); + // noinspection JSDeprecatedSymbols characteristic.updateValue(VALUE, updateValueCallback) expect(listenerCallback).toHaveBeenCalledTimes(1); @@ -387,7 +452,7 @@ describe('Characteristic', () => { it('should serialize characteristic', () => { const props: CharacteristicProps = { format: Formats.STRING, - perms: [Perms.TIMED_WRITE, Perms.READ], + perms: [Perms.TIMED_WRITE, Perms.PAIRED_READ], unit: Units.LUX, maxValue: 1234, minValue: 123, @@ -395,9 +460,8 @@ describe('Characteristic', () => { adminOnlyAccess: [Access.WRITE], }; - const characteristic = createCharacteristicWithProps(props); + const characteristic = createCharacteristicWithProps(props, Characteristic.ProgrammableSwitchEvent.UUID); characteristic.value = "TestValue"; - characteristic.eventOnlyCharacteristic = true; const json = Characteristic.serialize(characteristic); expect(json).toEqual({ @@ -408,12 +472,27 @@ describe('Characteristic', () => { eventOnlyCharacteristic: true, }) }); + + it("should serialize characteristic with proper constructor name", () => { + const characteristic = new Characteristic.Name(); + characteristic.updateValue("New Name!"); + + const json = Characteristic.serialize(characteristic); + expect(json).toEqual({ + displayName: 'Name', + UUID: '00000023-0000-1000-8000-0026BB765291', + eventOnlyCharacteristic: false, + constructorName: 'Name', + value: 'New Name!', + props: { format: 'string', perms: [ 'pr' ], maxLen: 64 } + }); + }); }); describe('#deserialize', () => { it('should deserialize legacy json from homebridge', () => { const json = JSON.parse('{"displayName": "On", "UUID": "00000025-0000-1000-8000-0026BB765291", ' + - '"props": {"format": "bool", "unit": null, "minValue": null, "maxValue": null, "minStep": null, "perms": ["pr", "pw", "ev"]}, ' + + '"props": {"format": "bool", "unit": "seconds", "minValue": 4, "maxValue": 6, "minStep": 0.1, "perms": ["pr", "pw", "ev"]}, ' + '"value": false, "eventOnlyCharacteristic": false}'); const characteristic = Characteristic.deserialize(json); @@ -421,7 +500,6 @@ describe('Characteristic', () => { expect(characteristic.UUID).toEqual(json.UUID); expect(characteristic.props).toEqual(json.props); expect(characteristic.value).toEqual(json.value); - expect(characteristic.eventOnlyCharacteristic).toEqual(json.eventOnlyCharacteristic); }); it('should deserialize complete json', () => { @@ -430,7 +508,7 @@ describe('Characteristic', () => { UUID: "00000001-0000-1000-8000-0026BB765291", props: { format: Formats.STRING, - perms: [Perms.TIMED_WRITE, Perms.READ], + perms: [Perms.TIMED_WRITE, Perms.PAIRED_READ], unit: Units.LUX, maxValue: 1234, minValue: 123, @@ -438,7 +516,7 @@ describe('Characteristic', () => { adminOnlyAccess: [Access.NOTIFY, Access.READ], }, value: "testValue", - eventOnlyCharacteristic: true, + eventOnlyCharacteristic: false, }; const characteristic = Characteristic.deserialize(json); @@ -447,7 +525,21 @@ describe('Characteristic', () => { expect(characteristic.UUID).toEqual(json.UUID); expect(characteristic.props).toEqual(json.props); expect(characteristic.value).toEqual(json.value); - expect(characteristic.eventOnlyCharacteristic).toEqual(json.eventOnlyCharacteristic); + }); + + it("should deserialize from json with constructor name", () => { + const json: SerializedCharacteristic = { + displayName: 'Name', + UUID: '00000023-0000-1000-8000-0026BB765291', + eventOnlyCharacteristic: false, + constructorName: 'Name', + value: 'New Name!', + props: { format: 'string', perms: [ Perms.PAIRED_READ ], maxLen: 64 } + }; + + const characteristic = Characteristic.deserialize(json); + + expect(characteristic instanceof Characteristic.Name).toBeTruthy(); }); }); }); diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts index ece0bc6ae..4905b3de5 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -1,46 +1,314 @@ -import Decimal from 'decimal.js'; - -import { once } from './util/once'; -import { clone } from "./util/clone"; -import { IdentifierCache } from './model/IdentifierCache'; +import assert from "assert"; +import createDebug from "debug"; +import { EventEmitter } from "events"; +import { CharacteristicJsonObject } from "../internal-types"; +import { CharacteristicValue, Nullable, VoidCallback, } from '../types'; +import { CharacteristicWarningType } from "./Accessory"; import { - CharacteristicChange, - CharacteristicValue, - HapCharacteristic, - SessionIdentifier, - Nullable, - ToHAPOptions, - VoidCallback, -} from '../types'; -import { EventEmitter } from './EventEmitter'; -import * as HomeKitTypes from './gen'; + AccessControlLevel, + AccessoryFlags, + AccessoryIdentifier, + Active, + ActiveIdentifier, + ActivityInterval, + AdministratorOnlyAccess, + AirParticulateDensity, + AirParticulateSize, + AirQuality, + AppMatchingIdentifier, + AudioFeedback, + BatteryLevel, + Brightness, + ButtonEvent, + CameraOperatingModeIndicator, + CarbonDioxideDetected, + CarbonDioxideLevel, + CarbonDioxidePeakLevel, + CarbonMonoxideDetected, + CarbonMonoxideLevel, + CarbonMonoxidePeakLevel, + CCAEnergyDetectThreshold, + CCASignalDetectThreshold, + CharacteristicValueActiveTransitionCount, + CharacteristicValueTransitionControl, + ChargingState, + ClosedCaptions, + ColorTemperature, + ConfiguredName, + ContactSensorState, + CoolingThresholdTemperature, + CurrentAirPurifierState, + CurrentAmbientLightLevel, + CurrentDoorState, + CurrentFanState, + CurrentHeaterCoolerState, + CurrentHeatingCoolingState, + CurrentHorizontalTiltAngle, + CurrentHumidifierDehumidifierState, + CurrentMediaState, + CurrentPosition, + CurrentRelativeHumidity, + CurrentSlatState, + CurrentTemperature, + CurrentTiltAngle, + CurrentTransport, + CurrentVerticalTiltAngle, + CurrentVisibilityState, + DataStreamHAPTransport, + DataStreamHAPTransportInterrupt, + DiagonalFieldOfView, + DigitalZoom, + DisplayOrder, + EventRetransmissionMaximum, + EventSnapshotsActive, + EventTransmissionCounters, + FilterChangeIndication, + FilterLifeLevel, + FirmwareRevision, + HardwareRevision, + HeartBeat, + HeatingThresholdTemperature, + HoldPosition, + HomeKitCameraActive, + Hue, + Identifier, + Identify, + ImageMirroring, + ImageRotation, + InputDeviceType, + InputSourceType, + InUse, + IsConfigured, + LeakDetected, + ListPairings, + LockControlPoint, + LockCurrentState, + LockLastKnownAction, + LockManagementAutoSecurityTimeout, + LockPhysicalControls, + LockTargetState, + Logs, + MACRetransmissionMaximum, + MACTransmissionCounters, + ManagedNetworkEnable, + ManuallyDisabled, + Manufacturer, + MaximumTransmitPower, + Model, + MotionDetected, + Mute, + Name, + NetworkAccessViolationControl, + NetworkClientProfileControl, + NetworkClientStatusControl, + NightVision, + NitrogenDioxideDensity, + ObstructionDetected, + OccupancyDetected, + On, + OperatingStateResponse, + OpticalZoom, + OutletInUse, + OzoneDensity, + PairingFeatures, + PairSetup, + PairVerify, + PasswordSetting, + PeriodicSnapshotsActive, + PictureMode, + Ping, + PM10Density, + PM2_5Density, + PositionState, + PowerModeSelection, + ProductData, + ProgrammableSwitchEvent, + ProgrammableSwitchOutputState, + ProgramMode, + ReceivedSignalStrengthIndication, + ReceiverSensitivity, + RecordingAudioActive, + RelativeHumidityDehumidifierThreshold, + RelativeHumidityHumidifierThreshold, + RelayControlPoint, + RelayEnabled, + RelayState, + RemainingDuration, + RemoteKey, + ResetFilterIndication, + RotationDirection, + RotationSpeed, + RouterStatus, + Saturation, + SecuritySystemAlarmType, + SecuritySystemCurrentState, + SecuritySystemTargetState, + SelectedAudioStreamConfiguration, + SelectedCameraRecordingConfiguration, + SelectedRTPStreamConfiguration, + SerialNumber, + ServiceLabelIndex, + ServiceLabelNamespace, + SetDuration, + SetupDataStreamTransport, + SetupEndpoints, + SetupTransferTransport, + SignalToNoiseRatio, + SiriInputType, + SlatType, + SleepDiscoveryMode, + SleepInterval, + SmokeDetected, + SoftwareRevision, + StatusActive, + StatusFault, + StatusJammed, + StatusLowBattery, + StatusTampered, + StreamingStatus, + SulphurDioxideDensity, + SupportedAudioRecordingConfiguration, + SupportedAudioStreamConfiguration, + SupportedCameraRecordingConfiguration, + SupportedCharacteristicValueTransitionConfiguration, + SupportedDataStreamTransportConfiguration, + SupportedDiagnosticsSnapshot, + SupportedRouterConfiguration, + SupportedRTPConfiguration, + SupportedTransferTransportConfiguration, + SupportedVideoRecordingConfiguration, + SupportedVideoStreamConfiguration, + SwingMode, + TargetAirPurifierState, + TargetControlList, + TargetControlSupportedConfiguration, + TargetDoorState, + TargetFanState, + TargetHeaterCoolerState, + TargetHeatingCoolingState, + TargetHorizontalTiltAngle, + TargetHumidifierDehumidifierState, + TargetMediaState, + TargetPosition, + TargetRelativeHumidity, + TargetTemperature, + TargetTiltAngle, + TargetVerticalTiltAngle, + TargetVisibilityState, + TemperatureDisplayUnits, + ThirdPartyCameraActive, + ThreadControlPoint, + ThreadNodeCapabilities, + ThreadOpenThreadVersion, + ThreadStatus, + TransmitPower, + TunnelConnectionTimeout, + TunneledAccessoryAdvertising, + TunneledAccessoryConnected, + TunneledAccessoryStateNumber, + ValveType, + Version, + VideoAnalysisActive, + VOCDensity, + Volume, + VolumeControlType, + VolumeSelector, + WakeConfiguration, + WANConfigurationList, + WANStatusList, + WaterLevel, + WiFiCapabilities, + WiFiConfigurationControl, + WiFiSatelliteStatus, +} from "./definitions"; +import { HAPStatus } from "./HAPServer"; +import { IdentifierCache } from './model/IdentifierCache'; +import { Service } from "./Service"; +import { clone } from "./util/clone"; +import { HAPConnection } from "./util/eventedhttp"; +import { HapStatusError } from './util/hapStatusError'; +import { once } from './util/once'; import { toShortForm } from './util/uuid'; +const debug = createDebug("HAP-NodeJS:Characteristic"); + export const enum Formats { BOOL = 'bool', - INT = 'int', + /** + * Signed 32-bit integer + */ + INT = 'int', // signed 32-bit int + /** + * Signed 64-bit floating point + */ FLOAT = 'float', + /** + * String encoded in utf8 + */ STRING = 'string', + /** + * Unsigned 8-bit integer. + */ UINT8 = 'uint8', + /** + * Unsigned 16-bit integer. + */ UINT16 = 'uint16', + /** + * Unsigned 32-bit integer. + */ UINT32 = 'uint32', + /** + * Unsigned 64-bit integer. + */ UINT64 = 'uint64', + /** + * Data is base64 encoded string. + */ DATA = 'data', + /** + * Base64 encoded tlv8 string. + */ TLV8 = 'tlv8', - ARRAY = 'array', //Not in HAP Spec - DICTIONARY = 'dict', //Not in HAP Spec + /** + * @deprecated Not contained in the HAP spec + */ + ARRAY = 'array', + /** + * @deprecated Not contained in the HAP spec + */ + DICTIONARY = 'dict', +} + +function isNumericFormat(format: Formats | string): boolean { + switch (format) { + case Formats.INT: + case Formats.FLOAT: + case Formats.UINT8: + case Formats.UINT16: + case Formats.UINT32: + case Formats.UINT64: + return true; + default: + return false; + } } export const enum Units { - CELSIUS = 'celsius', // celsius is the only temperature unit, conversion to fahrenheit is done on the iOS device + /** + * Celsius is the only temperature unit in the HomeKit Accessory Protocol. + * Unit conversion is always done on the client side e.g. on the iPhone in the Home App depending on + * the configured unit on the device itself. + */ + CELSIUS = 'celsius', PERCENTAGE = 'percentage', ARC_DEGREE = 'arcdegrees', LUX = 'lux', SECONDS = 'seconds', } -// Known HomeKit permission types export const enum Perms { + // noinspection JSUnusedGlobalSymbols /** * @deprecated replaced by {@link PAIRED_READ}. Kept for backwards compatibility. */ @@ -51,7 +319,7 @@ export const enum Perms { WRITE = 'pw', PAIRED_READ = 'pr', PAIRED_WRITE = 'pw', - NOTIFY = 'ev', // both terms are used in hap spec + NOTIFY = 'ev', EVENTS = 'ev', ADDITIONAL_AUTHORIZATION = 'aa', TIMED_WRITE = 'tw', @@ -60,18 +328,29 @@ export const enum Perms { } export interface CharacteristicProps { - format: Formats; - unit?: Units; + format: Formats | string; perms: Perms[]; - ev?: boolean; + unit?: Units | string; description?: string; minValue?: number; maxValue?: number; minStep?: number; + /** + * Maximum number of characters when format is {@link Formats.STRING}. + * Default is 64 characters. Maximum allowed is 256 characters. + */ maxLen?: number; + /** + * Maximum number of characters when format is {@link Formats.DATA}. + * Default is 2097152 characters. + */ maxDataLen?: number; validValues?: number[]; - validValueRanges?: [number, number]; + /** + * Two element array where the first value specifies the lowest valid value and + * the second element specifies the highest valid value. + */ + validValueRanges?: [min: number, max: number]; adminOnlyAccess?: Access[]; } @@ -81,376 +360,503 @@ export const enum Access { NOTIFY = 0x02 } +export type CharacteristicChange = { + originator?: HAPConnection, + newValue: Nullable; + oldValue: Nullable; + context?: any; +}; + export interface SerializedCharacteristic { displayName: string, UUID: string, - props: CharacteristicProps, - value: Nullable, eventOnlyCharacteristic: boolean, + constructorName?: string, + + value: Nullable, + props: CharacteristicProps, } export const enum CharacteristicEventTypes { + /** + * This event is thrown when a HomeKit controller wants to read the current value of the characteristic. + * The event handler should call the supplied callback as fast as possible. + * + * HAP-NodeJS will complain about slow running get handlers after 3 seconds and terminate the request after 10 seconds. + */ GET = "get", + /** + * This event is thrown when a HomeKit controller wants to write a new value to the characteristic. + * The event handler should call the supplied callback as fast as possible. + * + * HAP-NodeJS will complain about slow running set handlers after 3 seconds and terminate the request after 10 seconds. + */ SET = "set", + /** + * Emitted after a new value is set for the characteristic. + * The new value can be set via a request by a HomeKit controller or via an API call. + */ + CHANGE = "change", + /** + * @internal + */ SUBSCRIBE = "subscribe", + /** + * @internal + */ UNSUBSCRIBE = "unsubscribe", - CHANGE = "change", + /** + * @internal + */ + CHARACTERISTIC_WARNING = "characteristic-warning", } -export type CharacteristicGetCallback> = (error?: Error | null , value?: T) => void -export type CharacteristicSetCallback = (error?: Error | null, value?: CharacteristicValue) => void +export type CharacteristicGetCallback = (status?: HAPStatus | null | Error, value?: Nullable) => void; +export type CharacteristicSetCallback = (error?: HAPStatus | null | Error, writeResponse?: Nullable) => void; +export type CharacteristicGetHandler = () => Promise> | Nullable; +export type CharacteristicSetHandler = (value: CharacteristicValue) => Promise | void> | Nullable | void; + +export type AdditionalAuthorizationHandler = (additionalAuthorizationData: string | undefined) => boolean; + +export declare interface Characteristic { + + on(event: "get", listener: (callback: CharacteristicGetCallback, context: any, connection?: HAPConnection) => void): this; + on(event: "set", listener: (value: CharacteristicValue, callback: CharacteristicSetCallback, context: any, connection?: HAPConnection) => void): this + on(event: "change", listener: (change: CharacteristicChange) => void): this; + /** + * @internal + */ + on(event: "subscribe", listener: VoidCallback): this; + /** + * @internal + */ + on(event: "unsubscribe", listener: VoidCallback): this; + /** + * @internal + */ + on(event: "characteristic-warning", listener: (type: CharacteristicWarningType, message: string) => void): this; + + /** + * @internal + */ + emit(event: "get", callback: CharacteristicGetCallback, context: any, connection?: HAPConnection): boolean; + /** + * @internal + */ + emit(event: "set", value: CharacteristicValue, callback: CharacteristicSetCallback, context: any, connection?: HAPConnection): boolean; + /** + * @internal + */ + emit(event: "change", change: CharacteristicChange): boolean; + /** + * @internal + */ + emit(event: "subscribe"): boolean; + /** + * @internal + */ + emit(event: "unsubscribe"): boolean; + /** + * @internal + */ + emit(event: "characteristic-warning", type: CharacteristicWarningType, message: string): boolean; -type Events = { - ["change"]: (change: CharacteristicChange) => void; - ["get"]: (cb: CharacteristicGetCallback, context?: any, connectionID?: SessionIdentifier) => void; - ["set"]: (value: CharacteristicValue, cb: CharacteristicSetCallback, context?: any, connectionID?: SessionIdentifier) => void; - ["subscribe"]: VoidCallback; - ["unsubscribe"]: VoidCallback; } /** * Characteristic represents a particular typed variable that can be assigned to a Service. For instance, a * "Hue" Characteristic might store a 'float' value of type 'arcdegrees'. You could add the Hue Characteristic - * to a Service in order to store that value. A particular Characteristic is distinguished from others by its + * to a {@link Service} in order to store that value. A particular Characteristic is distinguished from others by its * UUID. HomeKit provides a set of known Characteristic UUIDs defined in HomeKit.ts along with a * corresponding concrete subclass. * * You can also define custom Characteristics by providing your own UUID. Custom Characteristics can be added * to any native or custom Services, but Siri will likely not be able to work with these. - * - * Note that you can get the "value" of a Characteristic by accessing the "value" property directly, but this - * is really a "cached value". If you want to fetch the latest value, which may involve doing some work, then - * call getValue(). - * - * @event 'get' => function(callback(err, newValue), context) { } - * Emitted when someone calls getValue() on this Characteristic and desires the latest non-cached - * value. If there are any listeners to this event, one of them MUST call the callback in order - * for the value to ever be delivered. The `context` object is whatever was passed in by the initiator - * of this event (for instance whomever called `getValue`). - * - * @event 'set' => function(newValue, callback(err), context) { } - * Emitted when someone calls setValue() on this Characteristic with a desired new value. If there - * are any listeners to this event, one of them MUST call the callback in order for this.value to - * actually be set. The `context` object is whatever was passed in by the initiator of this change - * (for instance, whomever called `setValue`). - * - * @event 'change' => function({ oldValue, newValue, context }) { } - * Emitted after a change in our value has occurred. The new value will also be immediately accessible - * in this.value. The event object contains the new value as well as the context object originally - * passed in by the initiator of this change (if known). */ -export class Characteristic extends EventEmitter { +export class Characteristic extends EventEmitter { /** * @deprecated Please use the Formats const enum above. Scheduled to be removed in 2021-06. */ - // @ts-ignore + // @ts-expect-error static Formats = Formats; /** * @deprecated Please use the Units const enum above. Scheduled to be removed in 2021-06. */ - // @ts-ignore + // @ts-expect-error static Units = Units; /** * @deprecated Please use the Perms const enum above. Scheduled to be removed in 2021-06. */ - // @ts-ignore + // @ts-expect-error static Perms = Perms; - static AccessControlLevel: typeof HomeKitTypes.Generated.AccessControlLevel; - static AccessoryFlags: typeof HomeKitTypes.Generated.AccessoryFlags; - static AccessoryIdentifier: typeof HomeKitTypes.Bridged.AccessoryIdentifier; - static Active: typeof HomeKitTypes.Generated.Active; - static ActiveIdentifier: typeof HomeKitTypes.TV.ActiveIdentifier; - static AdministratorOnlyAccess: typeof HomeKitTypes.Generated.AdministratorOnlyAccess; - static AirParticulateDensity: typeof HomeKitTypes.Generated.AirParticulateDensity; - static AirParticulateSize: typeof HomeKitTypes.Generated.AirParticulateSize; - static AirQuality: typeof HomeKitTypes.Generated.AirQuality; - static AppMatchingIdentifier: typeof HomeKitTypes.Bridged.AppMatchingIdentifier; - static AudioFeedback: typeof HomeKitTypes.Generated.AudioFeedback; - static BatteryLevel: typeof HomeKitTypes.Generated.BatteryLevel; - static Brightness: typeof HomeKitTypes.Generated.Brightness; - static ButtonEvent: typeof HomeKitTypes.Remote.ButtonEvent; - static CarbonDioxideDetected: typeof HomeKitTypes.Generated.CarbonDioxideDetected; - static CarbonDioxideLevel: typeof HomeKitTypes.Generated.CarbonDioxideLevel; - static CarbonDioxidePeakLevel: typeof HomeKitTypes.Generated.CarbonDioxidePeakLevel; - static CarbonMonoxideDetected: typeof HomeKitTypes.Generated.CarbonMonoxideDetected; - static CarbonMonoxideLevel: typeof HomeKitTypes.Generated.CarbonMonoxideLevel; - static CarbonMonoxidePeakLevel: typeof HomeKitTypes.Generated.CarbonMonoxidePeakLevel; - static Category: typeof HomeKitTypes.Bridged.Category; - static ChargingState: typeof HomeKitTypes.Generated.ChargingState; - static ClosedCaptions: typeof HomeKitTypes.TV.ClosedCaptions; - static ColorTemperature: typeof HomeKitTypes.Generated.ColorTemperature; - static ConfigureBridgedAccessory: typeof HomeKitTypes.Bridged.ConfigureBridgedAccessory; - static ConfigureBridgedAccessoryStatus: typeof HomeKitTypes.Bridged.ConfigureBridgedAccessoryStatus; - static ConfiguredName: typeof HomeKitTypes.TV.ConfiguredName; - static ContactSensorState: typeof HomeKitTypes.Generated.ContactSensorState; - static CoolingThresholdTemperature: typeof HomeKitTypes.Generated.CoolingThresholdTemperature; - static CurrentAirPurifierState: typeof HomeKitTypes.Generated.CurrentAirPurifierState; - static CurrentAmbientLightLevel: typeof HomeKitTypes.Generated.CurrentAmbientLightLevel; - static CurrentDoorState: typeof HomeKitTypes.Generated.CurrentDoorState; - static CurrentFanState: typeof HomeKitTypes.Generated.CurrentFanState; - static CurrentHeaterCoolerState: typeof HomeKitTypes.Generated.CurrentHeaterCoolerState; - static CurrentHeatingCoolingState: typeof HomeKitTypes.Generated.CurrentHeatingCoolingState; - static CurrentHorizontalTiltAngle: typeof HomeKitTypes.Generated.CurrentHorizontalTiltAngle; - static CurrentHumidifierDehumidifierState: typeof HomeKitTypes.Generated.CurrentHumidifierDehumidifierState; - static CurrentMediaState: typeof HomeKitTypes.TV.CurrentMediaState; - static CurrentPosition: typeof HomeKitTypes.Generated.CurrentPosition; - static CurrentRelativeHumidity: typeof HomeKitTypes.Generated.CurrentRelativeHumidity; - static CurrentSlatState: typeof HomeKitTypes.Generated.CurrentSlatState; - static CurrentTemperature: typeof HomeKitTypes.Generated.CurrentTemperature; - static CurrentTiltAngle: typeof HomeKitTypes.Generated.CurrentTiltAngle; - static CurrentTime: typeof HomeKitTypes.Bridged.CurrentTime; - static CurrentVerticalTiltAngle: typeof HomeKitTypes.Generated.CurrentVerticalTiltAngle; - static CurrentVisibilityState: typeof HomeKitTypes.TV.CurrentVisibilityState; - static DayoftheWeek: typeof HomeKitTypes.Bridged.DayoftheWeek; - static DigitalZoom: typeof HomeKitTypes.Generated.DigitalZoom; - static DiscoverBridgedAccessories: typeof HomeKitTypes.Bridged.DiscoverBridgedAccessories; - static DiscoveredBridgedAccessories: typeof HomeKitTypes.Bridged.DiscoveredBridgedAccessories; - static DisplayOrder: typeof HomeKitTypes.TV.DisplayOrder; - static FilterChangeIndication: typeof HomeKitTypes.Generated.FilterChangeIndication; - static FilterLifeLevel: typeof HomeKitTypes.Generated.FilterLifeLevel; - static FirmwareRevision: typeof HomeKitTypes.Generated.FirmwareRevision; - static HardwareRevision: typeof HomeKitTypes.Generated.HardwareRevision; - static HeatingThresholdTemperature: typeof HomeKitTypes.Generated.HeatingThresholdTemperature; - static HoldPosition: typeof HomeKitTypes.Generated.HoldPosition; - static Hue: typeof HomeKitTypes.Generated.Hue; - static Identifier: typeof HomeKitTypes.TV.Identifier; - static Identify: typeof HomeKitTypes.Generated.Identify; - static ImageMirroring: typeof HomeKitTypes.Generated.ImageMirroring; - static ImageRotation: typeof HomeKitTypes.Generated.ImageRotation; - static InUse: typeof HomeKitTypes.Generated.InUse; - static InputDeviceType: typeof HomeKitTypes.TV.InputDeviceType; - static InputSourceType: typeof HomeKitTypes.TV.InputSourceType; - static IsConfigured: typeof HomeKitTypes.Generated.IsConfigured; - /** - * @deprecated Removed in iOS 11. Use ServiceLabelIndex instead. - */ - static LabelIndex: typeof HomeKitTypes.Generated.ServiceLabelIndex; - /** - * @deprecated Removed in iOS 11. Use ServiceLabelNamespace instead. - */ - static LabelNamespace: typeof HomeKitTypes.Generated.ServiceLabelNamespace; - static LeakDetected: typeof HomeKitTypes.Generated.LeakDetected; - static LinkQuality: typeof HomeKitTypes.Bridged.LinkQuality; - static LockControlPoint: typeof HomeKitTypes.Generated.LockControlPoint; - static LockCurrentState: typeof HomeKitTypes.Generated.LockCurrentState; - static LockLastKnownAction: typeof HomeKitTypes.Generated.LockLastKnownAction; - static LockManagementAutoSecurityTimeout: typeof HomeKitTypes.Generated.LockManagementAutoSecurityTimeout; - static LockPhysicalControls: typeof HomeKitTypes.Generated.LockPhysicalControls; - static LockTargetState: typeof HomeKitTypes.Generated.LockTargetState; - static Logs: typeof HomeKitTypes.Generated.Logs; - static Manufacturer: typeof HomeKitTypes.Generated.Manufacturer; - static Model: typeof HomeKitTypes.Generated.Model; - static MotionDetected: typeof HomeKitTypes.Generated.MotionDetected; - static Mute: typeof HomeKitTypes.Generated.Mute; - static Name: typeof HomeKitTypes.Generated.Name; - static NightVision: typeof HomeKitTypes.Generated.NightVision; - static NitrogenDioxideDensity: typeof HomeKitTypes.Generated.NitrogenDioxideDensity; - static ObstructionDetected: typeof HomeKitTypes.Generated.ObstructionDetected; - static OccupancyDetected: typeof HomeKitTypes.Generated.OccupancyDetected; - static On: typeof HomeKitTypes.Generated.On; - static OpticalZoom: typeof HomeKitTypes.Generated.OpticalZoom; - static OutletInUse: typeof HomeKitTypes.Generated.OutletInUse; - static OzoneDensity: typeof HomeKitTypes.Generated.OzoneDensity; - static PM10Density: typeof HomeKitTypes.Generated.PM10Density; - static PM2_5Density: typeof HomeKitTypes.Generated.PM2_5Density; - static PairSetup: typeof HomeKitTypes.Generated.PairSetup; - static PairVerify: typeof HomeKitTypes.Generated.PairVerify; - static PairingFeatures: typeof HomeKitTypes.Generated.PairingFeatures; - static PairingPairings: typeof HomeKitTypes.Generated.PairingPairings; - static PasswordSetting: typeof HomeKitTypes.Generated.PasswordSetting; - static PictureMode: typeof HomeKitTypes.TV.PictureMode; - static PositionState: typeof HomeKitTypes.Generated.PositionState; - static PowerModeSelection: typeof HomeKitTypes.TV.PowerModeSelection; - static ProgramMode: typeof HomeKitTypes.Generated.ProgramMode; - static ProgrammableSwitchEvent: typeof HomeKitTypes.Generated.ProgrammableSwitchEvent; - static ProductData: typeof HomeKitTypes.Generated.ProductData; - static ProgrammableSwitchOutputState: typeof HomeKitTypes.Bridged.ProgrammableSwitchOutputState; - static Reachable: typeof HomeKitTypes.Bridged.Reachable; - static RelativeHumidityDehumidifierThreshold: typeof HomeKitTypes.Generated.RelativeHumidityDehumidifierThreshold; - static RelativeHumidityHumidifierThreshold: typeof HomeKitTypes.Generated.RelativeHumidityHumidifierThreshold; - static RelayControlPoint: typeof HomeKitTypes.Bridged.RelayControlPoint; - static RelayEnabled: typeof HomeKitTypes.Bridged.RelayEnabled; - static RelayState: typeof HomeKitTypes.Bridged.RelayState; - static RemainingDuration: typeof HomeKitTypes.Generated.RemainingDuration; - static RemoteKey: typeof HomeKitTypes.TV.RemoteKey; - static ResetFilterIndication: typeof HomeKitTypes.Generated.ResetFilterIndication; - static RotationDirection: typeof HomeKitTypes.Generated.RotationDirection; - static RotationSpeed: typeof HomeKitTypes.Generated.RotationSpeed; - static Saturation: typeof HomeKitTypes.Generated.Saturation; - static SecuritySystemAlarmType: typeof HomeKitTypes.Generated.SecuritySystemAlarmType; - static SecuritySystemCurrentState: typeof HomeKitTypes.Generated.SecuritySystemCurrentState; - static SecuritySystemTargetState: typeof HomeKitTypes.Generated.SecuritySystemTargetState; - static SelectedAudioStreamConfiguration: typeof HomeKitTypes.Remote.SelectedAudioStreamConfiguration; - /** - * @deprecated Removed in iOS 11. Use SelectedRTPStreamConfiguration instead. - */ - static SelectedStreamConfiguration: typeof HomeKitTypes.Generated.SelectedRTPStreamConfiguration; - static SelectedRTPStreamConfiguration: typeof HomeKitTypes.Generated.SelectedRTPStreamConfiguration; - static SerialNumber: typeof HomeKitTypes.Generated.SerialNumber; - static ServiceLabelIndex: typeof HomeKitTypes.Generated.ServiceLabelIndex; - static ServiceLabelNamespace: typeof HomeKitTypes.Generated.ServiceLabelNamespace; - static SetDuration: typeof HomeKitTypes.Generated.SetDuration; - static SetupDataStreamTransport: typeof HomeKitTypes.DataStream.SetupDataStreamTransport; - static SetupEndpoints: typeof HomeKitTypes.Generated.SetupEndpoints; - static SiriInputType: typeof HomeKitTypes.Remote.SiriInputType; - static SlatType: typeof HomeKitTypes.Generated.SlatType; - static SleepDiscoveryMode: typeof HomeKitTypes.TV.SleepDiscoveryMode; - static SmokeDetected: typeof HomeKitTypes.Generated.SmokeDetected; - static SoftwareRevision: typeof HomeKitTypes.Bridged.SoftwareRevision; - static StatusActive: typeof HomeKitTypes.Generated.StatusActive; - static StatusFault: typeof HomeKitTypes.Generated.StatusFault; - static StatusJammed: typeof HomeKitTypes.Generated.StatusJammed; - static StatusLowBattery: typeof HomeKitTypes.Generated.StatusLowBattery; - static StatusTampered: typeof HomeKitTypes.Generated.StatusTampered; - static StreamingStatus: typeof HomeKitTypes.Generated.StreamingStatus; - static SulphurDioxideDensity: typeof HomeKitTypes.Generated.SulphurDioxideDensity; - static SupportedAudioStreamConfiguration: typeof HomeKitTypes.Generated.SupportedAudioStreamConfiguration; - static SupportedDataStreamTransportConfiguration: typeof HomeKitTypes.DataStream.SupportedDataStreamTransportConfiguration; - static SupportedRTPConfiguration: typeof HomeKitTypes.Generated.SupportedRTPConfiguration; - static SupportedVideoStreamConfiguration: typeof HomeKitTypes.Generated.SupportedVideoStreamConfiguration; - static SwingMode: typeof HomeKitTypes.Generated.SwingMode; - static TargetAirPurifierState: typeof HomeKitTypes.Generated.TargetAirPurifierState; - static TargetAirQuality: typeof HomeKitTypes.Generated.TargetAirQuality; - static TargetControlList: typeof HomeKitTypes.Remote.TargetControlList; - static TargetControlSupportedConfiguration: typeof HomeKitTypes.Remote.TargetControlSupportedConfiguration; - static TargetDoorState: typeof HomeKitTypes.Generated.TargetDoorState; - static TargetFanState: typeof HomeKitTypes.Generated.TargetFanState; - static TargetHeaterCoolerState: typeof HomeKitTypes.Generated.TargetHeaterCoolerState; - static TargetHeatingCoolingState: typeof HomeKitTypes.Generated.TargetHeatingCoolingState; - static TargetHorizontalTiltAngle: typeof HomeKitTypes.Generated.TargetHorizontalTiltAngle; - static TargetHumidifierDehumidifierState: typeof HomeKitTypes.Generated.TargetHumidifierDehumidifierState; - static TargetMediaState: typeof HomeKitTypes.TV.TargetMediaState; - static TargetPosition: typeof HomeKitTypes.Generated.TargetPosition; - static TargetRelativeHumidity: typeof HomeKitTypes.Generated.TargetRelativeHumidity; - static TargetSlatState: typeof HomeKitTypes.Generated.TargetSlatState; - static TargetTemperature: typeof HomeKitTypes.Generated.TargetTemperature; - static TargetTiltAngle: typeof HomeKitTypes.Generated.TargetTiltAngle; - static TargetVerticalTiltAngle: typeof HomeKitTypes.Generated.TargetVerticalTiltAngle; - static TargetVisibilityState: typeof HomeKitTypes.TV.TargetVisibilityState; - static TemperatureDisplayUnits: typeof HomeKitTypes.Generated.TemperatureDisplayUnits; - static TimeUpdate: typeof HomeKitTypes.Bridged.TimeUpdate; - static TunnelConnectionTimeout: typeof HomeKitTypes.Bridged.TunnelConnectionTimeout; - static TunneledAccessoryAdvertising: typeof HomeKitTypes.Bridged.TunneledAccessoryAdvertising; - static TunneledAccessoryConnected: typeof HomeKitTypes.Bridged.TunneledAccessoryConnected; - static TunneledAccessoryStateNumber: typeof HomeKitTypes.Bridged.TunneledAccessoryStateNumber; - static VOCDensity: typeof HomeKitTypes.Generated.VOCDensity; - static ValveType: typeof HomeKitTypes.Generated.ValveType; - static Version: typeof HomeKitTypes.Generated.Version; - static Volume: typeof HomeKitTypes.Generated.Volume; - static VolumeControlType: typeof HomeKitTypes.TV.VolumeControlType; - static VolumeSelector: typeof HomeKitTypes.TV.VolumeSelector; - static WaterLevel: typeof HomeKitTypes.Generated.WaterLevel; - static ManuallyDisabled: typeof HomeKitTypes.Generated.ManuallyDisabled; - static ThirdPartyCameraActive: typeof HomeKitTypes.Generated.ThirdPartyCameraActive; - static PeriodicSnapshotsActive: typeof HomeKitTypes.Generated.PeriodicSnapshotsActive; - static EventSnapshotsActive: typeof HomeKitTypes.Generated.EventSnapshotsActive; - static HomeKitCameraActive: typeof HomeKitTypes.Generated.HomeKitCameraActive; - static RecordingAudioActive: typeof HomeKitTypes.Generated.RecordingAudioActive; - static SupportedCameraRecordingConfiguration: typeof HomeKitTypes.Generated.SupportedCameraRecordingConfiguration; - static SupportedVideoRecordingConfiguration: typeof HomeKitTypes.Generated.SupportedVideoRecordingConfiguration; - static SupportedAudioRecordingConfiguration: typeof HomeKitTypes.Generated.SupportedAudioRecordingConfiguration; - static SelectedCameraRecordingConfiguration: typeof HomeKitTypes.Generated.SelectedCameraRecordingConfiguration; - static CameraOperatingModeIndicator: typeof HomeKitTypes.Generated.CameraOperatingModeIndicator; - /** - * @deprecated Removed in iOS 13.4 - */ - static DiagonalFieldOfView: typeof HomeKitTypes.Generated.DiagonalFieldOfView; - static NetworkClientProfileControl: typeof HomeKitTypes.Generated.NetworkClientProfileControl; - static NetworkClientStatusControl: typeof HomeKitTypes.Generated.NetworkClientStatusControl; - static RouterStatus: typeof HomeKitTypes.Generated.RouterStatus; - static SupportedRouterConfiguration: typeof HomeKitTypes.Generated.SupportedRouterConfiguration; - static WANConfigurationList: typeof HomeKitTypes.Generated.WANConfigurationList; - static WANStatusList: typeof HomeKitTypes.Generated.WANStatusList; - static ManagedNetworkEnable: typeof HomeKitTypes.Generated.ManagedNetworkEnable; - static NetworkAccessViolationControl: typeof HomeKitTypes.Generated.NetworkAccessViolationControl; - static WiFiSatelliteStatus: typeof HomeKitTypes.Generated.WiFiSatelliteStatus; - static WakeConfiguration: typeof HomeKitTypes.Generated.WakeConfiguration; - static SupportedTransferTransportConfiguration: typeof HomeKitTypes.Generated.SupportedTransferTransportConfiguration; - static SetupTransferTransport: typeof HomeKitTypes.Generated.SetupTransferTransport; - - - static ActivityInterval: typeof HomeKitTypes.Generated.ActivityInterval; - static CCAEnergyDetectThreshold: typeof HomeKitTypes.Generated.CCAEnergyDetectThreshold; - static CCASignalDetectThreshold: typeof HomeKitTypes.Generated.CCASignalDetectThreshold; - static CharacteristicValueTransitionControl: typeof HomeKitTypes.Generated.CharacteristicValueTransitionControl; - static SupportedCharacteristicValueTransitionConfiguration: typeof HomeKitTypes.Generated.SupportedCharacteristicValueTransitionConfiguration; - static CurrentTransport: typeof HomeKitTypes.Generated.CurrentTransport; - static DataStreamHAPTransport: typeof HomeKitTypes.Generated.DataStreamHAPTransport; - static DataStreamHAPTransportInterrupt: typeof HomeKitTypes.Generated.DataStreamHAPTransportInterrupt; - static EventRetransmissionMaximum: typeof HomeKitTypes.Generated.EventRetransmissionMaximum; - static EventTransmissionCounters: typeof HomeKitTypes.Generated.EventTransmissionCounters; - static HeartBeat: typeof HomeKitTypes.Generated.HeartBeat; - static MACRetransmissionMaximum: typeof HomeKitTypes.Generated.MACRetransmissionMaximum; - static MACTransmissionCounters: typeof HomeKitTypes.Generated.MACTransmissionCounters; - static OperatingStateResponse: typeof HomeKitTypes.Generated.OperatingStateResponse; - static Ping: typeof HomeKitTypes.Generated.Ping; - static ReceiverSensitivity: typeof HomeKitTypes.Generated.ReceiverSensitivity; - static ReceivedSignalStrengthIndication: typeof HomeKitTypes.Generated.ReceivedSignalStrengthIndication; - static SleepInterval: typeof HomeKitTypes.Generated.SleepInterval; - static SignalToNoiseRatio: typeof HomeKitTypes.Generated.SignalToNoiseRatio; - static SupportedDiagnosticsSnapshot: typeof HomeKitTypes.Generated.SupportedDiagnosticsSnapshot; - static TransmitPower: typeof HomeKitTypes.Generated.TransmitPower; - static TransmitPowerMaximum: typeof HomeKitTypes.Generated.TransmitPowerMaximum; - static VideoAnalysisActive: typeof HomeKitTypes.Generated.VideoAnalysisActive; - static WiFiCapabilities: typeof HomeKitTypes.Generated.WiFiCapabilities; - static WiFiConfigurationControl: typeof HomeKitTypes.Generated.WiFiConfigurationControl; + // Pattern below is for automatic detection of the section of defined characteristics. Used by the generator + // -=-=-=-=-=-=-=-=-=-=-=-=-=-=- + public static AccessControlLevel: typeof AccessControlLevel; + public static AccessoryFlags: typeof AccessoryFlags; + public static AccessoryIdentifier: typeof AccessoryIdentifier; + public static Active: typeof Active; + public static ActiveIdentifier: typeof ActiveIdentifier; + public static ActivityInterval: typeof ActivityInterval; + public static AdministratorOnlyAccess: typeof AdministratorOnlyAccess; + public static AirParticulateDensity: typeof AirParticulateDensity; + public static AirParticulateSize: typeof AirParticulateSize; + public static AirQuality: typeof AirQuality; + public static AppMatchingIdentifier: typeof AppMatchingIdentifier; + public static AudioFeedback: typeof AudioFeedback; + public static BatteryLevel: typeof BatteryLevel; + public static Brightness: typeof Brightness; + public static ButtonEvent: typeof ButtonEvent; + public static CameraOperatingModeIndicator: typeof CameraOperatingModeIndicator; + public static CarbonDioxideDetected: typeof CarbonDioxideDetected; + public static CarbonDioxideLevel: typeof CarbonDioxideLevel; + public static CarbonDioxidePeakLevel: typeof CarbonDioxidePeakLevel; + public static CarbonMonoxideDetected: typeof CarbonMonoxideDetected; + public static CarbonMonoxideLevel: typeof CarbonMonoxideLevel; + public static CarbonMonoxidePeakLevel: typeof CarbonMonoxidePeakLevel; + public static CCAEnergyDetectThreshold: typeof CCAEnergyDetectThreshold; + public static CCASignalDetectThreshold: typeof CCASignalDetectThreshold; + public static CharacteristicValueActiveTransitionCount: typeof CharacteristicValueActiveTransitionCount; + public static CharacteristicValueTransitionControl: typeof CharacteristicValueTransitionControl; + public static ChargingState: typeof ChargingState; + public static ClosedCaptions: typeof ClosedCaptions; + public static ColorTemperature: typeof ColorTemperature; + public static ConfiguredName: typeof ConfiguredName; + public static ContactSensorState: typeof ContactSensorState; + public static CoolingThresholdTemperature: typeof CoolingThresholdTemperature; + public static CurrentAirPurifierState: typeof CurrentAirPurifierState; + public static CurrentAmbientLightLevel: typeof CurrentAmbientLightLevel; + public static CurrentDoorState: typeof CurrentDoorState; + public static CurrentFanState: typeof CurrentFanState; + public static CurrentHeaterCoolerState: typeof CurrentHeaterCoolerState; + public static CurrentHeatingCoolingState: typeof CurrentHeatingCoolingState; + public static CurrentHorizontalTiltAngle: typeof CurrentHorizontalTiltAngle; + public static CurrentHumidifierDehumidifierState: typeof CurrentHumidifierDehumidifierState; + public static CurrentMediaState: typeof CurrentMediaState; + public static CurrentPosition: typeof CurrentPosition; + public static CurrentRelativeHumidity: typeof CurrentRelativeHumidity; + public static CurrentSlatState: typeof CurrentSlatState; + public static CurrentTemperature: typeof CurrentTemperature; + public static CurrentTiltAngle: typeof CurrentTiltAngle; + public static CurrentTransport: typeof CurrentTransport; + public static CurrentVerticalTiltAngle: typeof CurrentVerticalTiltAngle; + public static CurrentVisibilityState: typeof CurrentVisibilityState; + public static DataStreamHAPTransport: typeof DataStreamHAPTransport; + public static DataStreamHAPTransportInterrupt: typeof DataStreamHAPTransportInterrupt; + public static DiagonalFieldOfView: typeof DiagonalFieldOfView; + public static DigitalZoom: typeof DigitalZoom; + public static DisplayOrder: typeof DisplayOrder; + public static EventRetransmissionMaximum: typeof EventRetransmissionMaximum; + public static EventSnapshotsActive: typeof EventSnapshotsActive; + public static EventTransmissionCounters: typeof EventTransmissionCounters; + public static FilterChangeIndication: typeof FilterChangeIndication; + public static FilterLifeLevel: typeof FilterLifeLevel; + public static FirmwareRevision: typeof FirmwareRevision; + public static HardwareRevision: typeof HardwareRevision; + public static HeartBeat: typeof HeartBeat; + public static HeatingThresholdTemperature: typeof HeatingThresholdTemperature; + public static HoldPosition: typeof HoldPosition; + public static HomeKitCameraActive: typeof HomeKitCameraActive; + public static Hue: typeof Hue; + public static Identifier: typeof Identifier; + public static Identify: typeof Identify; + public static ImageMirroring: typeof ImageMirroring; + public static ImageRotation: typeof ImageRotation; + public static InputDeviceType: typeof InputDeviceType; + public static InputSourceType: typeof InputSourceType; + public static InUse: typeof InUse; + public static IsConfigured: typeof IsConfigured; + public static LeakDetected: typeof LeakDetected; + public static ListPairings: typeof ListPairings; + public static LockControlPoint: typeof LockControlPoint; + public static LockCurrentState: typeof LockCurrentState; + public static LockLastKnownAction: typeof LockLastKnownAction; + public static LockManagementAutoSecurityTimeout: typeof LockManagementAutoSecurityTimeout; + public static LockPhysicalControls: typeof LockPhysicalControls; + public static LockTargetState: typeof LockTargetState; + public static Logs: typeof Logs; + public static MACRetransmissionMaximum: typeof MACRetransmissionMaximum; + public static MACTransmissionCounters: typeof MACTransmissionCounters; + public static ManagedNetworkEnable: typeof ManagedNetworkEnable; + public static ManuallyDisabled: typeof ManuallyDisabled; + public static Manufacturer: typeof Manufacturer; + public static MaximumTransmitPower: typeof MaximumTransmitPower; + public static Model: typeof Model; + public static MotionDetected: typeof MotionDetected; + public static Mute: typeof Mute; + public static Name: typeof Name; + public static NetworkAccessViolationControl: typeof NetworkAccessViolationControl; + public static NetworkClientProfileControl: typeof NetworkClientProfileControl; + public static NetworkClientStatusControl: typeof NetworkClientStatusControl; + public static NightVision: typeof NightVision; + public static NitrogenDioxideDensity: typeof NitrogenDioxideDensity; + public static ObstructionDetected: typeof ObstructionDetected; + public static OccupancyDetected: typeof OccupancyDetected; + public static On: typeof On; + public static OperatingStateResponse: typeof OperatingStateResponse; + public static OpticalZoom: typeof OpticalZoom; + public static OutletInUse: typeof OutletInUse; + public static OzoneDensity: typeof OzoneDensity; + public static PairingFeatures: typeof PairingFeatures; + /** + * @deprecated Please use {@link Characteristic.ListPairings}. + */ + public static PairingPairings: typeof ListPairings; + public static PairSetup: typeof PairSetup; + public static PairVerify: typeof PairVerify; + public static PasswordSetting: typeof PasswordSetting; + public static PeriodicSnapshotsActive: typeof PeriodicSnapshotsActive; + public static PictureMode: typeof PictureMode; + public static Ping: typeof Ping; + public static PM10Density: typeof PM10Density; + public static PM2_5Density: typeof PM2_5Density; + public static PositionState: typeof PositionState; + public static PowerModeSelection: typeof PowerModeSelection; + public static ProductData: typeof ProductData; + public static ProgrammableSwitchEvent: typeof ProgrammableSwitchEvent; + public static ProgrammableSwitchOutputState: typeof ProgrammableSwitchOutputState; + public static ProgramMode: typeof ProgramMode; + public static ReceivedSignalStrengthIndication: typeof ReceivedSignalStrengthIndication; + public static ReceiverSensitivity: typeof ReceiverSensitivity; + public static RecordingAudioActive: typeof RecordingAudioActive; + public static RelativeHumidityDehumidifierThreshold: typeof RelativeHumidityDehumidifierThreshold; + public static RelativeHumidityHumidifierThreshold: typeof RelativeHumidityHumidifierThreshold; + public static RelayControlPoint: typeof RelayControlPoint; + public static RelayEnabled: typeof RelayEnabled; + public static RelayState: typeof RelayState; + public static RemainingDuration: typeof RemainingDuration; + public static RemoteKey: typeof RemoteKey; + public static ResetFilterIndication: typeof ResetFilterIndication; + public static RotationDirection: typeof RotationDirection; + public static RotationSpeed: typeof RotationSpeed; + public static RouterStatus: typeof RouterStatus; + public static Saturation: typeof Saturation; + public static SecuritySystemAlarmType: typeof SecuritySystemAlarmType; + public static SecuritySystemCurrentState: typeof SecuritySystemCurrentState; + public static SecuritySystemTargetState: typeof SecuritySystemTargetState; + public static SelectedAudioStreamConfiguration: typeof SelectedAudioStreamConfiguration; + public static SelectedCameraRecordingConfiguration: typeof SelectedCameraRecordingConfiguration; + public static SelectedRTPStreamConfiguration: typeof SelectedRTPStreamConfiguration; + public static SerialNumber: typeof SerialNumber; + public static ServiceLabelIndex: typeof ServiceLabelIndex; + public static ServiceLabelNamespace: typeof ServiceLabelNamespace; + public static SetDuration: typeof SetDuration; + public static SetupDataStreamTransport: typeof SetupDataStreamTransport; + public static SetupEndpoints: typeof SetupEndpoints; + public static SetupTransferTransport: typeof SetupTransferTransport; + public static SignalToNoiseRatio: typeof SignalToNoiseRatio; + public static SiriInputType: typeof SiriInputType; + public static SlatType: typeof SlatType; + public static SleepDiscoveryMode: typeof SleepDiscoveryMode; + public static SleepInterval: typeof SleepInterval; + public static SmokeDetected: typeof SmokeDetected; + public static SoftwareRevision: typeof SoftwareRevision; + public static StatusActive: typeof StatusActive; + public static StatusFault: typeof StatusFault; + public static StatusJammed: typeof StatusJammed; + public static StatusLowBattery: typeof StatusLowBattery; + public static StatusTampered: typeof StatusTampered; + public static StreamingStatus: typeof StreamingStatus; + public static SulphurDioxideDensity: typeof SulphurDioxideDensity; + public static SupportedAudioRecordingConfiguration: typeof SupportedAudioRecordingConfiguration; + public static SupportedAudioStreamConfiguration: typeof SupportedAudioStreamConfiguration; + public static SupportedCameraRecordingConfiguration: typeof SupportedCameraRecordingConfiguration; + public static SupportedCharacteristicValueTransitionConfiguration: typeof SupportedCharacteristicValueTransitionConfiguration; + public static SupportedDataStreamTransportConfiguration: typeof SupportedDataStreamTransportConfiguration; + public static SupportedDiagnosticsSnapshot: typeof SupportedDiagnosticsSnapshot; + public static SupportedRouterConfiguration: typeof SupportedRouterConfiguration; + public static SupportedRTPConfiguration: typeof SupportedRTPConfiguration; + public static SupportedTransferTransportConfiguration: typeof SupportedTransferTransportConfiguration; + public static SupportedVideoRecordingConfiguration: typeof SupportedVideoRecordingConfiguration; + public static SupportedVideoStreamConfiguration: typeof SupportedVideoStreamConfiguration; + public static SwingMode: typeof SwingMode; + public static TargetAirPurifierState: typeof TargetAirPurifierState; + public static TargetControlList: typeof TargetControlList; + public static TargetControlSupportedConfiguration: typeof TargetControlSupportedConfiguration; + public static TargetDoorState: typeof TargetDoorState; + public static TargetFanState: typeof TargetFanState; + public static TargetHeaterCoolerState: typeof TargetHeaterCoolerState; + public static TargetHeatingCoolingState: typeof TargetHeatingCoolingState; + public static TargetHorizontalTiltAngle: typeof TargetHorizontalTiltAngle; + public static TargetHumidifierDehumidifierState: typeof TargetHumidifierDehumidifierState; + public static TargetMediaState: typeof TargetMediaState; + public static TargetPosition: typeof TargetPosition; + public static TargetRelativeHumidity: typeof TargetRelativeHumidity; + public static TargetTemperature: typeof TargetTemperature; + public static TargetTiltAngle: typeof TargetTiltAngle; + public static TargetVerticalTiltAngle: typeof TargetVerticalTiltAngle; + public static TargetVisibilityState: typeof TargetVisibilityState; + public static TemperatureDisplayUnits: typeof TemperatureDisplayUnits; + public static ThirdPartyCameraActive: typeof ThirdPartyCameraActive; + public static ThreadControlPoint: typeof ThreadControlPoint; + public static ThreadNodeCapabilities: typeof ThreadNodeCapabilities; + public static ThreadOpenThreadVersion: typeof ThreadOpenThreadVersion; + public static ThreadStatus: typeof ThreadStatus; + public static TransmitPower: typeof TransmitPower; + public static TunnelConnectionTimeout: typeof TunnelConnectionTimeout; + public static TunneledAccessoryAdvertising: typeof TunneledAccessoryAdvertising; + public static TunneledAccessoryConnected: typeof TunneledAccessoryConnected; + public static TunneledAccessoryStateNumber: typeof TunneledAccessoryStateNumber; + public static ValveType: typeof ValveType; + public static Version: typeof Version; + public static VideoAnalysisActive: typeof VideoAnalysisActive; + public static VOCDensity: typeof VOCDensity; + public static Volume: typeof Volume; + public static VolumeControlType: typeof VolumeControlType; + public static VolumeSelector: typeof VolumeSelector; + public static WakeConfiguration: typeof WakeConfiguration; + public static WANConfigurationList: typeof WANConfigurationList; + public static WANStatusList: typeof WANStatusList; + public static WaterLevel: typeof WaterLevel; + public static WiFiCapabilities: typeof WiFiCapabilities; + public static WiFiConfigurationControl: typeof WiFiConfigurationControl; + public static WiFiSatelliteStatus: typeof WiFiSatelliteStatus; + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-= // NOTICE: when adding/changing properties, remember to possibly adjust the serialize/deserialize functions + public displayName: string; + public UUID: string; iid: Nullable = null; value: Nullable = null; - status: Nullable = null; - eventOnlyCharacteristic: boolean = false; + status: HAPStatus = HAPStatus.SUCCESS; props: CharacteristicProps; - subscriptions: number = 0; - 'valid-values': number[]; - 'valid-values-range': [number, number]; + /** + * The {@link onGet} handler + */ + private getHandler?: CharacteristicGetHandler; - constructor(public displayName: string, public UUID: string, props?: CharacteristicProps) { + /** + * The {@link onSet} handler + */ + private setHandler?: CharacteristicSetHandler; + + private subscriptions: number = 0; + /** + * @internal + */ + additionalAuthorizationHandler?: AdditionalAuthorizationHandler; + + public constructor(displayName: string, UUID: string, props: CharacteristicProps) { super(); - // @ts-ignore - this.props = props || { - format: null, - unit: null, - minValue: null, - maxValue: null, - minStep: null, - perms: [] + this.displayName = displayName; + this.UUID = UUID; + this.props = { // some weird defaults (with legacy constructor props was optional) + format: Formats.INT, + perms: [Perms.NOTIFY], }; - this.setProps({}); // ensure sanity checks are called + this.setProps(props || {}); // ensure sanity checks are called + } + + /** + * Accepts a function that will be called to retrieve the current value of a Characteristic. + * The function must return a valid Characteristic value for the Characteristic type. + * May optionally return a promise. + * + * @example + * ```ts + * Characteristic.onGet(async () => { + * return true; + * }); + * ``` + * @param handler + */ + public onGet(handler: CharacteristicGetHandler) { + if (typeof handler !== 'function' || handler.length !== 0) { + this.characteristicWarning(`.onGet handler must be a function with exactly zero input arguments.`); + return this; + } + this.getHandler = handler; + return this; + } + + /** + * Accepts a function that will be called when setting the value of a Characteristic. + * If the characteristic supports {@link Perms.WRITE_RESPONSE} and the request requests a write response value, + * the returned value will be used. + * May optionally return a promise. + * + * @example + * ```ts + * Characteristic.onSet(async (value: CharacteristicValue) => { + * console.log(value); + * }); + * ``` + * @param handler + */ + public onSet(handler: CharacteristicSetHandler) { + if (typeof handler !== 'function' || handler.length !== 1) { + this.characteristicWarning(`.onSet handler must be a function with exactly one input argument.`); + return this; + } + this.setHandler = handler; + return this; } /** - * Copies the given properties to our props member variable, - * and returns 'this' for chaining. + * Updates the properties of this characteristic. + * Properties passed via the parameter will be set. Any parameter set to null will be deleted. + * See {@link CharacteristicProps}. * - * @param 'props' { - * format: , - * unit: , - * perms: array of [Characteristic.Perms] like [Characteristic.Perms.READ, Characteristic.Perms.WRITE] - * ev: , (Optional) - * description: , (Optional) - * minValue: , (Optional) - * maxValue: , (Optional) - * minStep: , (Optional) - * maxLen: , (Optional default: 64) - * maxDataLen: , (Optional default: 2097152) - * valid-values: , (Optional) - * valid-values-range: (Optional) - * } - */ - setProps = (props: Partial) => { - for (var key in (props || {})) - if (Object.prototype.hasOwnProperty.call(props, key)) { - // @ts-ignore - this.props[key] = props[key]; + * @param props - Partial properties object with the desired updates. + */ + public setProps(props: Partial): Characteristic { + assert(props, "props cannot be undefined when setting props"); + // TODO calling setProps after publish doesn't lead to a increment in the current configuration number + + // for every value "null" can be used to reset props, except for required props + if (props.format) { + this.props.format = props.format; + } + if (props.perms) { + assert(props.perms.length > 0, "characteristic prop perms cannot be empty array"); + this.props.perms = props.perms; + } + if (props.unit !== undefined) { + this.props.unit = props.unit != null? props.unit: undefined; + } + if (props.description !== undefined) { + this.props.description = props.description != null? props.description: undefined; + } + if (props.minValue !== undefined) { + this.props.minValue = props.minValue != null? props.minValue: undefined; + } + if (props.maxValue !== undefined) { + this.props.maxValue = props.maxValue != null? props.maxValue: undefined; + } + if (props.minStep !== undefined) { + this.props.minStep = props.minStep != null? props.minStep: undefined; + } + if (props.maxLen !== undefined) { + if (props.maxLen != null) { + if (props.maxLen > 256) { + this.characteristicWarning("setProps: string maxLen cannot be bigger than 256!"); + props.maxLen = 256; + } + this.props.maxLen = props.maxLen; + } else { + this.props.maxLen = undefined; } + } + if (props.maxDataLen !== undefined) { + this.props.maxDataLen = props.maxDataLen != null? props.maxDataLen: undefined; + } + if (props.validValues !== undefined) { + assert(props.validValues.length, "characteristic prop validValues cannot be empty array"); + this.props.validValues = props.validValues != null? props.validValues: undefined; + } + if (props.validValueRanges !== undefined) { + assert(props.validValueRanges.length === 2, "characteristic prop validValueRanges must have a length of 2"); + this.props.validValueRanges = props.validValueRanges != null? props.validValueRanges: undefined; + } + if (props.adminOnlyAccess !== undefined) { + this.props.adminOnlyAccess = props.adminOnlyAccess != null? props.adminOnlyAccess: undefined; + } if (this.props.minValue != null && this.props.maxValue != null) { // the eqeq instead of eqeqeq is important here - if (this.props.minValue > this.props.maxValue) { // preventing DOS attack, see https://github.com/homebridge/HAP-NodeJS/issues/690 + if (this.props.minValue > this.props.maxValue) { // see https://github.com/homebridge/HAP-NodeJS/issues/690 this.props.minValue = undefined; this.props.maxValue = undefined; throw new Error("Error setting CharacteristicsProps for '" + this.displayName + "': 'minValue' cannot be greater or equal the 'maxValue'!"); @@ -460,15 +866,364 @@ export class Characteristic extends EventEmitter { return this; } - subscribe = () => { + // noinspection JSUnusedGlobalSymbols + /** + * This method can be used to setup additional authorization for a characteristic. + * For one it adds the {@link Perms.ADDITIONAL_AUTHORIZATION} permission to the characteristic + * (if it wasn't already) to signal support for additional authorization to HomeKit. + * Additionally an {@link AdditionalAuthorizationHandler} is setup up which is called + * before a write request is performed. + * + * Additional Authorization Data can be added to SET request via a custom iOS App. + * Before hap-nodejs executes a write request it will call the {@link AdditionalAuthorizationHandler} + * with 'authData' supplied in the write request. The 'authData' is a base64 encoded string + * (or undefined if no authData was supplied). + * The {@link AdditionalAuthorizationHandler} must then return true or false to indicate if the write request + * is authorized and should be accepted. + * + * @param handler - Handler called to check additional authorization data. + */ + public setupAdditionalAuthorization(handler: AdditionalAuthorizationHandler): void { + if (!this.props.perms.includes(Perms.ADDITIONAL_AUTHORIZATION)) { + this.props.perms.push(Perms.ADDITIONAL_AUTHORIZATION); + } + this.additionalAuthorizationHandler = handler; + } + + /** + * Updates the current value of the characteristic. + * + * @param callback + * @param context + * @internal use to return the current value on HAP requests + * + * @deprecated + */ + getValue(callback?: CharacteristicGetCallback, context?: any): void { + this.handleGetRequest(undefined, context).then(value => { + if (callback) { + callback(null, value); + } + }, reason => { + if (callback) { + callback(reason); + } + }); + } + + /** + * This updates the value by calling the {@link CharacteristicEventTypes.SET} event handler associated with the characteristic. + * This acts the same way as when a HomeKit controller sends a /characteristics request to update the characteristic. + * A event notification will be sent to all connected HomeKit controllers which are registered + * to receive event notifications for this characteristic. + * + * This method behaves like a {@link updateValue} call with the addition that the own {@link CharacteristicEventTypes.SET} + * event handler is called. + * + * @param value - The new value. + */ + setValue(value: CharacteristicValue): Characteristic + /** + * This updates the value by calling the {@link CharacteristicEventTypes.SET} event handler associated with the characteristic. + * This acts the same way as when a HomeKit controller sends a /characteristics request to update the characteristic. + * A event notification will be sent to all connected HomeKit controllers which are registered + * to receive event notifications for this characteristic. + * + * This method behaves like a {@link updateValue} call with the addition that the own {@link CharacteristicEventTypes.SET} + * event handler is called. + * + * @param value - The new value. + * @param callback - Deprecated parameter there to provide backwards compatibility. Called once the + * {@link CharacteristicEventTypes.SET} event handler returns. + * @param context - Deprecated parameter there to provide backwards compatibility. Passed to the + * {@link CharacteristicEventTypes.SET} event handler. + * @deprecated Parameters callback and context are deprecated. + */ + setValue(value: CharacteristicValue, callback?: CharacteristicSetCallback, context?: any): Characteristic + setValue(value: CharacteristicValue, callback?: CharacteristicSetCallback, context?: any): Characteristic { + value = this.validateUserInput(value)!; + this.handleSetRequest(value, undefined, context).then(value => { + if (callback) { + if (value) { // possible write response + callback(null, value); + } else { + callback(null); + } + } + }, reason => { + if (callback) { + callback(reason); + } + }); + + return this; + } + + /** + * This updates the value of the characteristic. A event notification will be sent to all connected + * HomeKit controllers which are registered to receive event notifications for this characteristic. + * + * @param value - The new value. + */ + updateValue(value: Nullable): Characteristic; + /** + * This updates the value of the characteristic. A event notification will be sent to all connected + * HomeKit controllers which are registered to receive event notifications for this characteristic. + * + * @param value - The new value. + * @param callback - Deprecated parameter there to provide backwards compatibility. Callback is called instantly. + * @param context - Deprecated parameter there to provide backwards compatibility. + * @deprecated Parameters callback and context are deprecated. + */ + updateValue(value: Nullable, callback?: () => void, context?: any): Characteristic; + updateValue(value: Nullable, callback?: () => void, context?: any): Characteristic { + this.status = HAPStatus.SUCCESS; + + value = this.validateUserInput(value); + const oldValue = this.value; + this.value = value; + + if (callback) { + callback(); + } + + if (oldValue !== value || this.UUID === Characteristic.ProgrammableSwitchEvent.UUID) { + this.emit(CharacteristicEventTypes.CHANGE, { originator: undefined, oldValue: oldValue, newValue: value, context: context }); + } + + return this; // for chaining + } + + /** + * Called when a HAP requests wants to know the current value of the characteristic. + * + * @param connection - The HAP connection from which the request originated from. + * @param context - Deprecated parameter. There for backwards compatibility. + * @internal Used by the Accessory to load the characteristic value + */ + async handleGetRequest(connection?: HAPConnection, context?: any): Promise> { + if (!this.props.perms.includes(Perms.PAIRED_READ)) { // check if we are allowed to read from this characteristic + throw HAPStatus.WRITE_ONLY_CHARACTERISTIC; + } + + if (this.UUID === Characteristic.ProgrammableSwitchEvent.UUID) { + // special workaround for event only programmable switch event, which must always return null + return null; + } + + if (this.getHandler) { + if (this.listeners(CharacteristicEventTypes.GET).length > 0) { + this.characteristicWarning(`Ignoring on('get') handler as onGet handler was defined instead.`); + } + + try { + let value = await this.getHandler(); + this.status = HAPStatus.SUCCESS; + + try { + value = this.validateUserInput(value); + } catch (error) { + this.characteristicWarning(`An illegal value was supplied by the read handler for characteristic: ${error.message}`); + this.status = HAPStatus.SERVICE_COMMUNICATION_FAILURE; + return Promise.reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE) + } + + const oldValue = this.value; + this.value = value; + + if (oldValue !== value) { // emit a change event if necessary + this.emit(CharacteristicEventTypes.CHANGE, { originator: connection, oldValue: oldValue, newValue: value, context: context }); + } + } catch (error) { + if (typeof error === "number") { + this.status = error; + } else if (error instanceof HapStatusError) { + this.status = error.hapStatus; + } else { + this.characteristicWarning(`Unhandled error thrown inside read handler for characteristic: ${error.stack}`); + this.status = HAPStatus.SERVICE_COMMUNICATION_FAILURE; + } + throw this.status; + } + } + + if (this.listeners(CharacteristicEventTypes.GET).length === 0) { + if (this.status) { + throw this.status; + } else { + return this.value; + } + } + + return new Promise((resolve, reject) => { + try { + this.emit(CharacteristicEventTypes.GET, once((status?: Error | HAPStatus | null, value?: Nullable) => { + if (status) { + if (typeof status === "number") { + this.status = status; + } else if (status instanceof HapStatusError) { + this.status = status.hapStatus; + } else { + this.status = extractHAPStatusFromError(status); + debug("[%s] Received error from get handler %s", this.displayName, status.stack); + } + reject(this.status); + return; + } + + this.status = HAPStatus.SUCCESS; + + value = this.validateUserInput(value); + const oldValue = this.value; + this.value = value; + + resolve(value); + + if (oldValue !== value) { // emit a change event if necessary + this.emit(CharacteristicEventTypes.CHANGE, { originator: connection, oldValue: oldValue, newValue: value, context: context }); + } + }), context, connection); + } catch (error) { + this.characteristicWarning(`Unhandled error thrown inside read handler for characteristic: ${error.stack}`); + this.status = HAPStatus.SERVICE_COMMUNICATION_FAILURE; + reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE); + } + }); + } + + /** + * Called when a HAP requests update the current value of the characteristic. + * + * @param value - The updated value + * @param connection - The connection from which the request originated from + * @param context - Deprecated parameter. There for backwards compatibility. + * @returns Promise resolve to void in normal operation. When characteristic supports write response, the + * HAP request requests write response and the set handler returns a write response value, the respective + * write response value is resolved. + * @internal + */ + async handleSetRequest(value: CharacteristicValue, connection?: HAPConnection, context?: any): Promise { + this.status = HAPStatus.SUCCESS; + + if (connection !== undefined && !this.validClientSuppliedValue(value)) { + // if connection is undefined, the set "request" comes from the setValue method. + // for setValue a value of "null" is allowed and checked via validateUserInput. + return Promise.reject(HAPStatus.INVALID_VALUE_IN_REQUEST); + } + + if (this.props.format === Formats.BOOL && typeof value === "number") { + // we already validate in validClientSuppliedValue that a number value can only be 0 or 1. + value = value === 1; + } else if (isNumericFormat(this.props.format)) { + value = this.roundNumericValue(value as number); + } + + const oldValue = this.value; + + if (this.setHandler) { + if (this.listeners(CharacteristicEventTypes.SET).length > 0) { + this.characteristicWarning(`Ignoring on('set') handler as onSet handler was defined instead.`); + } + + try { + const writeResponse = await this.setHandler(value); + this.status = HAPStatus.SUCCESS; + + if (writeResponse != null && this.props.perms.includes(Perms.WRITE_RESPONSE)) { + this.value = writeResponse; + } else { + if (writeResponse != null) { + this.characteristicWarning(`SET handler returned write response value, though the characteristic doesn't support write response!`); + } + this.value = value; + + if (oldValue !== value || this.UUID === Characteristic.ProgrammableSwitchEvent.UUID) { + this.emit(CharacteristicEventTypes.CHANGE, { originator: connection, oldValue: oldValue, newValue: value, context: context }); + } + } + + return this.value; + } catch (error) { + if (typeof error === "number") { + this.status = error; + } else if (error instanceof HapStatusError) { + this.status = error.hapStatus; + } else { + this.characteristicWarning(`Unhandled error thrown inside write handler for characteristic: ${error.stack}`); + this.status = HAPStatus.SERVICE_COMMUNICATION_FAILURE; + } + throw this.status; + } + } + + if (this.listeners(CharacteristicEventTypes.SET).length === 0) { + this.value = value; + if (oldValue !== value || this.UUID === Characteristic.ProgrammableSwitchEvent.UUID) { + this.emit(CharacteristicEventTypes.CHANGE, { originator: connection, oldValue: oldValue, newValue: value, context: context }); + } + return Promise.resolve(); + } else { + return new Promise((resolve, reject) => { + try { + this.emit(CharacteristicEventTypes.SET, value, once((status?: Error | HAPStatus | null, writeResponse?: Nullable) => { + if (status) { + if (typeof status === "number") { + this.status = status; + } else if (status instanceof HapStatusError) { + this.status = status.hapStatus; + } else { + this.status = extractHAPStatusFromError(status); + debug("[%s] Received error from set handler %s", this.displayName, status.stack); + } + reject(this.status); + return; + } + + this.status = HAPStatus.SUCCESS; + + if (writeResponse != null && this.props.perms.includes(Perms.WRITE_RESPONSE)) { + // support write response simply by letting the implementor pass the response as second argument to the callback + this.value = writeResponse; + resolve(writeResponse); + } else { + if (writeResponse != null) { + this.characteristicWarning(`SET handler returned write response value, though the characteristic doesn't support write response!`); + } + this.value = value; + resolve(); + + if (oldValue !== value || this.UUID === Characteristic.ProgrammableSwitchEvent.UUID) { + this.emit(CharacteristicEventTypes.CHANGE, { originator: connection, oldValue: oldValue, newValue: value, context: context }); + } + } + }), context, connection); + } catch (error) { + this.characteristicWarning(`Unhandled error thrown inside write handler for characteristic: ${error.stack}`); + this.status = HAPStatus.SERVICE_COMMUNICATION_FAILURE; + reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE); + } + }); + } + } + + /** + * Called once a HomeKit controller subscribes to events of this characteristics. + * @internal + */ + subscribe(): void { if (this.subscriptions === 0) { this.emit(CharacteristicEventTypes.SUBSCRIBE); } this.subscriptions++; } - unsubscribe = () => { - var wasOne = this.subscriptions === 1; + /** + * Called once a HomeKit controller unsubscribe to events of this characteristics or a HomeKit controller + * which was subscribed to this characteristic disconnects. + * @internal + */ + unsubscribe(): void { + const wasOne = this.subscriptions === 1; this.subscriptions--; this.subscriptions = Math.max(this.subscriptions, 0); if (wasOne) { @@ -476,366 +1231,520 @@ export class Characteristic extends EventEmitter { } } - getValue = (callback?: CharacteristicGetCallback, context?: any, connectionID?: SessionIdentifier) => { - // Handle special event only characteristics. - if (this.eventOnlyCharacteristic === true) { - if (callback) { - callback(null, null); - } - return; - } - if (this.listeners(CharacteristicEventTypes.GET).length > 0) { - // allow a listener to handle the fetching of this value, and wait for completion - this.emit(CharacteristicEventTypes.GET, once((err: Error, newValue: Nullable) => { - this.status = err; - if (err) { - // pass the error along to our callback - if (callback) - callback(err); - } else { - newValue = this.validateValue(newValue); //validateValue returns a value that has be cooerced into a valid value. - if (newValue === undefined || newValue === null) - newValue = this.getDefaultValue(); - // getting the value was a success; we can pass it along and also update our cached value - var oldValue = this.value; - this.value = newValue; - if (callback) - callback(null, newValue); - // emit a change event if necessary - if (oldValue !== newValue) - this.emit(CharacteristicEventTypes.CHANGE, {oldValue: oldValue, newValue: newValue, context: context}); - } - }), context, connectionID); - } else { - // no one is listening to the 'get' event, so just return the cached value - if (callback) - callback(this.status, this.value); + protected getDefaultValue(): Nullable { + // noinspection JSDeprecatedSymbols + switch (this.props.format) { + case Formats.BOOL: + return false; + case Formats.STRING: + switch (this.UUID) { + case Characteristic.Manufacturer.UUID: + return "Default-Manufacturer"; + case Characteristic.Model.UUID: + return "Default-Model"; + case Characteristic.SerialNumber.UUID: + return "Default-SerialNumber"; + case Characteristic.FirmwareRevision.UUID: + return "0.0.0"; + default: + return ""; + } + case Formats.DATA: + return null; // who knows! + case Formats.TLV8: + return null; // who knows! + case Formats.DICTIONARY: + return {}; + case Formats.ARRAY: + return []; + default: + return this.props.minValue ?? 0; + } + } + + private roundNumericValue(value: number): number { + if (!this.props.minStep) { + return Math.round(value); // round to 0 decimal places } + const base = this.props.minValue ?? 0; + const times = ((value - base) / this.props.minStep); // this value could become very large, so this doesn't really support little minStep values + return Math.round(times) * this.props.minStep + base; } - validateValue = (newValue: Nullable): Nullable => { - let isNumericType = false; - let minValue_resolved: number | undefined = 0; - let maxValue_resolved: number | undefined = 0; - let minStep_resolved = undefined; - let stepDecimals = 0; + /** + * Checks if the value received from the HAP request is valid. + * If returned false the received value is not valid and {@link HAPStatus.INVALID_VALUE_IN_REQUEST} + * must be returned. + * @param value - Value supplied by the HomeKit controller + */ + private validClientSuppliedValue(value?: Nullable): boolean { + if (value == undefined) { + return false; + } + + let numericMin: number | undefined = undefined; + let numericMax: number | undefined = undefined; + switch (this.props.format) { - case Formats.INT: - minStep_resolved = 1; - minValue_resolved = -2147483648; - maxValue_resolved = 2147483647; - isNumericType = true; + case Formats.BOOL: + if (!(typeof value === "boolean" || value == 0 || value == 1)) { + return false; + } + break; + case Formats.INT: // 32-bit signed int + if (typeof value === "boolean") { + value = value? 1: 0; + } if (typeof value !== "number") { + return false; + } + + numericMin = maxWithUndefined(this.props.minValue, -2147483648); + numericMax = minWithUndefined(this.props.maxValue, 2147483647); break; case Formats.FLOAT: - minStep_resolved = undefined; - minValue_resolved = undefined; - maxValue_resolved = undefined; - isNumericType = true; + if (typeof value === "boolean") { + value = value? 1: 0; + } if (typeof value !== "number") { + return false; + } + + if (this.props.minValue != null) { + numericMin = this.props.minValue; + } + if (this.props.maxValue != null) { + numericMax = this.props.maxValue; + } break; case Formats.UINT8: - minStep_resolved = 1; - minValue_resolved = 0; - maxValue_resolved = 255; - isNumericType = true; + if (typeof value === "boolean") { + value = value? 1: 0; + } if (typeof value !== "number") { + return false; + } + + numericMin = maxWithUndefined(this.props.minValue, 0); + numericMax = minWithUndefined(this.props.maxValue, 255); break; case Formats.UINT16: - minStep_resolved = 1; - minValue_resolved = 0; - maxValue_resolved = 65535; - isNumericType = true; + if (typeof value === "boolean") { + value = value? 1: 0; + } if (typeof value !== "number") { + return false; + } + + numericMin = maxWithUndefined(this.props.minValue, 0); + numericMax = minWithUndefined(this.props.maxValue, 65535); break; case Formats.UINT32: - minStep_resolved = 1; - minValue_resolved = 0; - maxValue_resolved = 4294967295; - isNumericType = true; + if (typeof value === "boolean") { + value = value? 1: 0; + } if (typeof value !== "number") { + return false; + } + + numericMin = maxWithUndefined(this.props.minValue, 0); + numericMax = minWithUndefined(this.props.maxValue, 4294967295); break; case Formats.UINT64: - minStep_resolved = 1; - minValue_resolved = 0; - maxValue_resolved = 18446744073709551615; - isNumericType = true; - break; - //All of the following datatypes return from this switch. - case Formats.BOOL: - // @ts-ignore - return (newValue == true); //We don't need to make sure this returns true or false + if (typeof value === "boolean") { + value = value? 1: 0; + } if (typeof value !== "number") { + return false; + } + + numericMin = maxWithUndefined(this.props.minValue, 0); + numericMax = minWithUndefined(this.props.maxValue, 18446744073709551615); // don't get fooled, javascript uses 18446744073709552000 here break; - case Formats.STRING: - let myString = newValue as string || ''; //If null or undefined or anything odd, make it a blank string - myString = String(myString); - var maxLength = this.props.maxLen; - if (maxLength === undefined) - maxLength = 64; //Default Max Length is 64. - if (myString.length > maxLength) - myString = myString.substring(0, maxLength); //Truncate strings that are too long - return myString; //We don't need to do any validation after having truncated the string + case Formats.STRING: { + if (typeof value !== "string") { + return false; + } + + const maxLength = this.props.maxLen != null? this.props.maxLen: 64; // default is 64; max is 256 which is set in setProps + if (value.length > maxLength) { + return false; + } break; - case Formats.DATA: - var maxLength = this.props.maxDataLen; - if (maxLength === undefined) - maxLength = 2097152; //Default Max Length is 2097152. - //if (newValue.length>maxLength) //I don't know the best way to handle this since it's unknown binary data. - //I suspect that it will crash HomeKit for this bridge if the length is too long. - return newValue; + } + case Formats.DATA: { + if (typeof value !== "string") { + return false; + } + // we don't validate base64 here + + const maxLength = this.props.maxDataLen != null? this.props.maxDataLen: 0x200000; // default is 0x200000 + if (value.length > maxLength) { + return false; + } break; + } case Formats.TLV8: - //Should we parse this to make sure the tlv8 is valid? + if (typeof value !== "string") { + return false; + } break; - default: //Datatype out of HAP Spec encountered. We'll assume the developer knows what they're doing. - return newValue; - }; + } - if (isNumericType) { - if (newValue === false) { - return 0; - } - if (newValue === true) { - return 1; - } - if (isNaN(Number.parseInt(newValue as string, 10))) { - return this.value!; - } //This is not a number so we'll just pass out the last value. - if ((this.props.maxValue && !isNaN(this.props.maxValue)) && (this.props.maxValue !== null)) - maxValue_resolved = this.props.maxValue; - if ((this.props.minValue && !isNaN(this.props.minValue)) && (this.props.minValue !== null)) - minValue_resolved = this.props.minValue; - if ((this.props.minStep && !isNaN(this.props.minStep)) && (this.props.minStep !== null)) - minStep_resolved = this.props.minStep; - if (newValue! < minValue_resolved!) - newValue = minValue_resolved!; //Fails Minimum Value Test - if (newValue! > maxValue_resolved!) - newValue = maxValue_resolved!; //Fails Maximum Value Test - if (minStep_resolved !== undefined) { - //Determine how many decimals we need to display - if (Math.floor(minStep_resolved) === minStep_resolved) - stepDecimals = 0; - else - stepDecimals = minStep_resolved.toString().split(".")[1].length || 0; - //Use Decimal to detemine the lowest value within the step. - try { - var decimalVal = new Decimal(parseFloat(newValue as string)); - var decimalDiff = decimalVal.mod(minStep_resolved); - decimalVal = decimalVal.minus(decimalDiff); - if (stepDecimals === 0) { - newValue = parseInt(decimalVal.toFixed(0)); - } else { - newValue = parseFloat(decimalVal.toFixed(stepDecimals)); //Convert it to a fixed decimal - } - } catch (e) { - return this.value!; //If we had an error, return the current value. - } + if (typeof value === "number") { + if (numericMin != null && value < numericMin) { + return false; } - if (this['valid-values'] !== undefined) - if (!this['valid-values'].includes(newValue as number)) - return this.value!; //Fails Valid Values Test - if (this['valid-values-range'] !== undefined) { //This is another way Apple has to handle min/max - if (newValue! < this['valid-values-range'][0]) - newValue = this['valid-values-range'][0]; - if (newValue! > this['valid-values-range'][1]) - newValue = this['valid-values-range'][1]; + if (numericMax != null && value > numericMax) { + return false; + } + + if (this.props.validValues && !this.props.validValues.includes(value)) { + return false; + } + if (this.props.validValueRanges && this.props.validValueRanges.length === 2) { + if (value < this.props.validValueRanges[0]) { + return false; + } else if (value > this.props.validValueRanges[1]) { + return false; + } } } - return newValue; + + return true; } - setValue = (newValue: Nullable, callback?: CharacteristicSetCallback, context?: any, connectionID?: SessionIdentifier): Characteristic => { - if (newValue instanceof Error) { - this.status = newValue; - } else { - this.status = null; - } - newValue = this.validateValue(newValue as Nullable); //validateValue returns a value that has be cooerced into a valid value. - var oldValue = this.value; - if (this.listeners(CharacteristicEventTypes.SET).length > 0) { - // allow a listener to handle the setting of this value, and wait for completion - this.emit(CharacteristicEventTypes.SET, newValue, once((err: Error, writeResponse?: CharacteristicValue) => { - this.status = err; - if (err) { - // pass the error along to our callback - if (callback) - callback(err); - } else { - if (writeResponse !== undefined && this.props.perms.includes(Perms.WRITE_RESPONSE)) - newValue = writeResponse; // support write response simply by letting the implementor pass the response as second argument to the callback - - if (newValue === undefined || newValue === null) - newValue = this.getDefaultValue() as CharacteristicValue; - // setting the value was a success; so we can cache it now - this.value = newValue as CharacteristicValue; - if (callback) - callback(); - if (this.eventOnlyCharacteristic === true || oldValue !== newValue) - this.emit(CharacteristicEventTypes.CHANGE, {oldValue: oldValue, newValue: newValue, context: context}); - } - }), context, connectionID); - } else { - if (newValue === undefined || newValue === null) - newValue = this.getDefaultValue() as CharacteristicValue; - // no one is listening to the 'set' event, so just assign the value blindly - this.value = newValue as string | number; - if (callback) - callback(); - if (this.eventOnlyCharacteristic === true || oldValue !== newValue) - this.emit(CharacteristicEventTypes.CHANGE, {oldValue: oldValue, newValue: newValue, context: context}); + /** + * Checks if the value received from the API call is valid. + * It adjust the value where it makes sense, prints a warning where values may be rejected with an error + * in the future and throws an error which can't be converted to a valid value. + * + * @param value - The value received from the API call + */ + private validateUserInput(value?: Nullable): Nullable { + if (value === undefined) { + this.characteristicWarning(`characteristic was supplied illegal value: undefined! This might throw errors in the future!`); + return this.value; // don't change the value + } else if (value === null) { + if (this.UUID === Characteristic.Model.UUID || this.UUID === Characteristic.SerialNumber.UUID) { // mirrors the statement in case: Formats.STRING + this.characteristicWarning(new Error(`characteristic must have a non null value otherwise HomeKit will reject this accessory. Ignoring new value.`).stack + "", CharacteristicWarningType.ERROR_MESSAGE); + return this.value; // don't change the value + } + + return null; // null is allowed } - return this; // for chaining - } - updateValue = (newValue: Nullable, callback?: () => void, context?: any): Characteristic => { - if (newValue instanceof Error) { - this.status = newValue; - } else { - this.status = null; - } - newValue = this.validateValue(newValue as Nullable); //validateValue returns a value that has be cooerced into a valid value. - if (newValue === undefined || newValue === null) - newValue = this.getDefaultValue() as CharacteristicValue; - // no one is listening to the 'set' event, so just assign the value blindly - var oldValue = this.value; - this.value = newValue; - if (callback) - callback(); - if (this.eventOnlyCharacteristic === true || oldValue !== newValue) - this.emit(CharacteristicEventTypes.CHANGE, {oldValue: oldValue, newValue: newValue, context: context}); - return this; // for chaining - } - getDefaultValue = (): Nullable => { + let numericMin: number | undefined = undefined; + let numericMax: number | undefined = undefined; + switch (this.props.format) { - case Formats.BOOL: - return false; - case Formats.STRING: - return ""; + case Formats.BOOL: { + const type = typeof value; + if (type === "boolean") { + return value; + } else if (type === "number") { + return value === 1; + } else if (type === "string") { + return value === "1"; + } else { + throw new Error("characteristic value expected boolean and received " + type); + } + } + case Formats.INT: { + if (typeof value === "boolean") { + value = value? 1: 0; + } if (typeof value === "string") { + this.characteristicWarning(`characteristic was supplied illegal value: string instead of number. Supplying illegal values will throw errors in the future!`); + value = parseInt(value, 10); + } else if (typeof value !== "number") { + throw new Error("characteristic value expected number and received " + typeof value); + } + + numericMin = maxWithUndefined(this.props.minValue, -2147483648); + numericMax = minWithUndefined(this.props.maxValue, 2147483647); + break; + } + case Formats.FLOAT: { + if (typeof value === "boolean") { + value = value? 1: 0; + } if (typeof value === "string") { + this.characteristicWarning(`characteristic was supplied illegal value: string instead of float. Supplying illegal values will throw errors in the future!`); + value = parseFloat(value); + } else if (typeof value !== "number") { + throw new Error("characteristic value expected float and received " + typeof value); + } + + if (this.props.minValue != null) { + numericMin = this.props.minValue; + } + if (this.props.maxValue != null) { + numericMax = this.props.maxValue; + } + break; + } + case Formats.UINT8: { + if (typeof value === "boolean") { + value = value? 1: 0; + } if (typeof value === "string") { + this.characteristicWarning(`characteristic was supplied illegal value: string instead of number. Supplying illegal values will throw errors in the future!`); + value = parseInt(value, 10); + } else if (typeof value !== "number") { + throw new Error("characteristic value expected number and received " + typeof value); + } + + numericMin = maxWithUndefined(this.props.minValue, 0); + numericMax = minWithUndefined(this.props.maxValue, 255); + break; + } + case Formats.UINT16: { + if (typeof value === "boolean") { + value = value? 1: 0; + } if (typeof value === "string") { + this.characteristicWarning(`characteristic was supplied illegal value: string instead of number. Supplying illegal values will throw errors in the future!`); + value = parseInt(value, 10); + } else if (typeof value !== "number") { + throw new Error("characteristic value expected number and received " + typeof value); + } + + numericMin = maxWithUndefined(this.props.minValue, 0); + numericMax = minWithUndefined(this.props.maxValue, 65535); + break; + } + case Formats.UINT32: { + if (typeof value === "boolean") { + value = value? 1: 0; + } if (typeof value === "string") { + this.characteristicWarning(`characteristic was supplied illegal value: string instead of number. Supplying illegal values will throw errors in the future!`); + value = parseInt(value, 10); + } else if (typeof value !== "number") { + throw new Error("characteristic value expected number and received " + typeof value); + } + + numericMin = maxWithUndefined(this.props.minValue, 0); + numericMax = minWithUndefined(this.props.maxValue, 4294967295); + break; + } + case Formats.UINT64: { + if (typeof value === "boolean") { + value = value? 1: 0; + } if (typeof value === "string") { + this.characteristicWarning(`characteristic was supplied illegal value: string instead of number. Supplying illegal values will throw errors in the future!`); + value = parseInt(value, 10); + } else if (typeof value !== "number") { + throw new Error("characteristic value expected number and received " + typeof value); + } + + numericMin = maxWithUndefined(this.props.minValue, 0); + numericMax = minWithUndefined(this.props.maxValue, 18446744073709551615); // don't get fooled, javascript uses 18446744073709552000 here + break; + } + case Formats.STRING: { + if (typeof value === "number") { + this.characteristicWarning(`characteristic was supplied illegal value: number instead of string. Supplying illegal values will throw errors in the future!`); + value = String(value); + } else if (typeof value !== "string") { + throw new Error("characteristic value expected string and received " + (typeof value)); + } + + if (value.length <= 1 && (this.UUID === Characteristic.Model.UUID || this.UUID === Characteristic.SerialNumber.UUID)) { // mirrors the case value = null at the beginning + this.characteristicWarning(new Error(`[${this.displayName}] characteristic must have a length of more than 1 character otherwise HomeKit will reject this accessory. Ignoring new value.`).stack + "", CharacteristicWarningType.ERROR_MESSAGE); + return this.value; // just return the current value + } + + const maxLength = this.props.maxLen != null? this.props.maxLen: 64; // default is 64 (max is 256 which is set in setProps) + if (value.length > maxLength) { + this.characteristicWarning(`characteristic was supplied illegal value: string '${value}' exceeded max length of ${maxLength}.`); + value = value.substring(0, maxLength); + } + + return value; + } case Formats.DATA: - return null; // who knows! + if (typeof value !== "string") { + throw new Error("characteristic with data format must have string value"); + } + + if (this.props.maxDataLen != null && value.length > this.props.maxDataLen) { + // can't cut it as we would basically yet binary rubbish afterwards + throw new Error("characteristic with data format exceeds specified maxDataLen!"); + } + return value; case Formats.TLV8: - return null; // who knows! - case Formats.DICTIONARY: - return {}; - case Formats.ARRAY: - return []; - default: - return this.props.minValue || 0; + return value; // we trust that this is valid tlv8 + } + + if (typeof value === "number") { + if (numericMin != null && value < numericMin) { + this.characteristicWarning(`characteristic was supplied illegal value: number ${value} exceeded minimum of ${numericMin}.`); + value = numericMin; + } + if (numericMax != null && value > numericMax) { + this.characteristicWarning(`characteristic was supplied illegal value: number ${value} exceeded maximum of ${numericMax}.`); + value = numericMax; + } + + if (this.props.validValues && !this.props.validValues.includes(value)) { + throw new Error(`characteristic value ${value} is not contained in valid values array!`); + } + + if (this.props.validValueRanges && this.props.validValueRanges.length === 2) { + if (value < this.props.validValueRanges[0]) { + this.characteristicWarning(`characteristic was supplied illegal value: number ${value} not contained in valid value range of ${this.props.validValueRanges}. Supplying illegal values will throw errors in the future!`); + value = this.props.validValueRanges[0]; + } else if (value > this.props.validValueRanges[1]) { + this.characteristicWarning(`characteristic was supplied illegal value: number ${value} not contained in valid value range of ${this.props.validValueRanges}. Supplying illegal values will throw errors in the future!`); + value = this.props.validValueRanges[1]; + } + } + + value = this.roundNumericValue(value); } + + return value; } - _assignID = (identifierCache: IdentifierCache, accessoryName: string, serviceUUID: string, serviceSubtype?: string) => { + /** + * @internal used to assign iid to characteristic + */ + _assignID(identifierCache: IdentifierCache, accessoryName: string, serviceUUID: string, serviceSubtype?: string): void { // generate our IID based on our UUID this.iid = identifierCache.getIID(accessoryName, serviceUUID, serviceSubtype, this.UUID); } /** - * Returns a JSON representation of this Accessory suitable for delivering to HAP clients. + * @internal */ - toHAP = (opt?: ToHAPOptions) => { - // ensure our value fits within our constraints if present - var value = this.value; - - if (this.props.minValue != null && value! < this.props.minValue) - value = this.props.minValue; - if (this.props.maxValue != null && value! > this.props.maxValue) - value = this.props.maxValue; - if (this.props.format != null) { - if (this.props.format === Formats.INT) - value = parseInt(value as string); - else if (this.props.format === Formats.UINT8) - value = parseInt(value as string); - else if (this.props.format === Formats.UINT16) - value = parseInt(value as string); - else if (this.props.format === Formats.UINT32) - value = parseInt(value as string); - else if (this.props.format === Formats.UINT64) - value = parseInt(value as string); - else if (this.props.format === Formats.FLOAT) { - value = parseFloat(value as string); - if (this.props.minStep != null) { - var pow = Math.pow(10, decimalPlaces(this.props.minStep)); - value = Math.round(value * pow) / pow; - } + characteristicWarning(message: string, type = CharacteristicWarningType.WARN_MESSAGE): void { + const emitted = this.emit(CharacteristicEventTypes.CHARACTERISTIC_WARNING, type, message); + if (!emitted) { + if (type === CharacteristicWarningType.ERROR_MESSAGE || type === CharacteristicWarningType.TIMEOUT_READ || CharacteristicWarningType.TIMEOUT_WRITE) { + console.error(`[${this.displayName}] ${message}`); + } else { + console.warn(`[${this.displayName}] ${message}`); } } - if (this.eventOnlyCharacteristic === true) { - // @ts-ignore - value = null; + } + + /** + * Returns a JSON representation of this characteristic suitable for delivering to HAP clients. + * @internal used to generate response to /accessories query + */ + async toHAP(connection: HAPConnection): Promise { + const object = this.internalHAPRepresentation(); + + if (!this.props.perms.includes(Perms.PAIRED_READ)) { + object.value = undefined; + } else if (this.UUID === Characteristic.ProgrammableSwitchEvent.UUID) { + // special workaround for event only programmable switch event, which must always return null + object.value = null; + } else { // query the current value + object.value = await this.handleGetRequest(connection).catch(() => { + debug('[%s] Error getting value for characteristic on /accessories request', this.displayName); + return this.value; // use cached value + }); } - const hap: Partial = { + return object; + } + + /** + * Returns a JSON representation of this characteristic without the value. + * @internal used to generate the config hash + */ + internalHAPRepresentation(): CharacteristicJsonObject { + assert(this.iid,"iid cannot be undefined for characteristic '" + this.displayName + "'"); + return { + type: toShortForm(this.UUID), iid: this.iid!, - type: toShortForm(this.UUID, HomeKitTypes.BASE_UUID), + value: null, perms: this.props.perms, + description: this.props.description || this.displayName, format: this.props.format, - value: value, - description: this.displayName, - // These properties used to be sent but do not seem to be used: - // - // events: false, - // bonjour: false - }; - if (this.props.validValues != null && this.props.validValues.length > 0) { - hap['valid-values'] = this.props.validValues; - } - if (this.props.validValueRanges != null && this.props.validValueRanges.length > 0 && !(this.props.validValueRanges.length & 1)) { - hap['valid-values-range'] = this.props.validValueRanges; - } - // extra properties - if (this.props.unit != null) - hap.unit = this.props.unit; - if (this.props.maxValue != null) - hap.maxValue = this.props.maxValue; - if (this.props.minValue != null) - hap.minValue = this.props.minValue; - if (this.props.minStep != null) - hap.minStep = this.props.minStep; - // add maxLen if string length is > 64 bytes and trim to max 256 bytes - if (this.props.format === Formats.STRING) { - var str = Buffer.from(value as string, 'utf8'), len = str.byteLength; - if (len > 256) { // 256 bytes is the max allowed length - hap.value = str.toString('utf8', 0, 256); - hap.maxLen = 256; - } else if (len > 64) { // values below can be ommited - hap.maxLen = len; - } - } - // if we're not readable, omit the "value" property - otherwise iOS will complain about non-compliance - if (this.props.perms.indexOf(Perms.READ) == -1) - delete hap.value; - // delete the "value" property anyway if we were asked to - if (opt && opt.omitValues) - delete hap.value; - return hap as HapCharacteristic; + unit: this.props.unit, + minValue: this.props.minValue, + maxValue: this.props.maxValue, + minStep: this.props.minStep, + maxLen: this.props.maxLen, + maxDataLen: this.props.maxDataLen, + "valid-values": this.props.validValues, + "valid-values-range": this.props.validValueRanges, + } } - static serialize = (characteristic: Characteristic): SerializedCharacteristic => { + /** + * Serialize characteristic into json string. + * + * @param characteristic - Characteristic object. + * @internal used to store characteristic on disk + */ + static serialize(characteristic: Characteristic): SerializedCharacteristic { + let constructorName: string | undefined; + if (characteristic.constructor.name !== "Characteristic") { + constructorName = characteristic.constructor.name; + } + return { displayName: characteristic.displayName, UUID: characteristic.UUID, - props: clone({}, characteristic.props), + eventOnlyCharacteristic: characteristic.UUID === Characteristic.ProgrammableSwitchEvent.UUID, // support downgrades for now + constructorName: constructorName, value: characteristic.value, - eventOnlyCharacteristic: characteristic.eventOnlyCharacteristic, + props: clone({}, characteristic.props), } }; - static deserialize = (json: SerializedCharacteristic): Characteristic => { - const characteristic = new Characteristic(json.displayName, json.UUID, json.props); + /** + * Deserialize characteristic from json string. + * + * @param json - Json string representing a characteristic. + * @internal used to recreate characteristic from disk + */ + static deserialize(json: SerializedCharacteristic): Characteristic { + let characteristic: Characteristic; + + if (json.constructorName && json.constructorName.charAt(0).toUpperCase() === json.constructorName.charAt(0) + && Characteristic[json.constructorName as keyof (typeof Characteristic)]) { // MUST start with uppercase character and must exist on Characteristic object + const constructor = Characteristic[json.constructorName as keyof (typeof Characteristic)] as { new(): Characteristic }; + characteristic = new constructor(); + characteristic.displayName = json.displayName; + characteristic.setProps(json.props); + } else { + characteristic = new Characteristic(json.displayName, json.UUID, json.props); + } characteristic.value = json.value; - characteristic.eventOnlyCharacteristic = json.eventOnlyCharacteristic; return characteristic; }; } -// Mike Samuel -// http://stackoverflow.com/questions/10454518/javascript-how-to-retrieve-the-number-of-decimals-of-a-string-number -function decimalPlaces(num: number) { - var match = (''+num).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/); - if (!match) { return 0; } - return Math.max( - 0, - // Number of digits right of decimal point. - (match[1] ? match[1].length : 0) - // Adjust for scientific notation. - - (match[2] ? +match[2] : 0)); +const numberPattern = /^-?\d+$/; +function extractHAPStatusFromError(error: Error) { + let errorValue = HAPStatus.SERVICE_COMMUNICATION_FAILURE; + + if (numberPattern.test(error.message)) { + const value = parseInt(error.message); + + if (value >= HAPStatus.INSUFFICIENT_PRIVILEGES && value <= HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE) { + errorValue = value; + } + } + + return errorValue; +} + +function maxWithUndefined(a?: number, b?: number): number | undefined { + if (a === undefined) { + return b; + } else if (b === undefined) { + return a; + } else { + return Math.max(a, b); + } +} + +function minWithUndefined(a?: number, b?: number): number | undefined { + if (a === undefined) { + return b; + } else if (b === undefined) { + return a; + } else { + return Math.min(a, b); + } } diff --git a/src/lib/EventEmitter.ts b/src/lib/EventEmitter.ts deleted file mode 100644 index c1a550a38..000000000 --- a/src/lib/EventEmitter.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { EventEmitter as BaseEventEmitter } from "events"; -import { Callback } from '../types'; - -export type EventKey = string | symbol; -export type Event = T & EventKey; -export type EventMap = { [name: string]: Callback }; - -export class EventEmitter = Event> extends BaseEventEmitter { - addListener(event: K, listener: T[K]): this { - return super.addListener(event, listener); - }; - - on(event: K, listener: T[K]): this { - return super.on(event, listener); - } - - once(event: K, listener: T[K]): this { - return super.once(event, listener); - } - - removeListener(event: K, listener: T[K]): this { - return super.removeListener(event, listener); - } - - removeAllListeners(event?: K): this { - return super.removeAllListeners(event); - } - - setMaxListeners(n: number): this { - return super.setMaxListeners(n); - } - - getMaxListeners(): number { - return super.getMaxListeners(); - } - - listeners(event: K): T[K] [] { - return super.listeners(event) as T[K][]; - } - - emit(event: K, ...args: any[]): boolean { - return super.emit(event, ...args); - } - - listenerCount(type: string): number { - return super.listenerCount(type); - } -} diff --git a/src/lib/HAPServer.ts b/src/lib/HAPServer.ts index c39809f3d..23f7873a0 100644 --- a/src/lib/HAPServer.ts +++ b/src/lib/HAPServer.ts @@ -1,23 +1,38 @@ import crypto from 'crypto'; - import createDebug from 'debug'; +import { EventEmitter } from "events"; +import { SRP, SrpServer, Identity } from "fast-srp-hap"; +import { IncomingMessage, ServerResponse } from "http"; import tweetnacl from 'tweetnacl'; -import url from 'url'; - +import { URL } from 'url'; +import { + AccessoriesResponse, + CharacteristicId, + CharacteristicsReadRequest, + CharacteristicsReadResponse, + CharacteristicsWriteRequest, + CharacteristicsWriteResponse, + consideredTrue, + PrepareWriteRequest, + ResourceRequest +} from "../internal-types"; +import { CharacteristicValue, Nullable, VoidCallback, NodeCallback } from '../types'; +import { AccessoryInfo, PairingInformation, PermissionTypes } from "./model/AccessoryInfo"; +import { + EventedHTTPServer, + EventedHTTPServerEvent, + HAPConnection, + HAPEncryption, + HAPUsername +} from './util/eventedhttp'; import * as hapCrypto from './util/hapCrypto'; -import * as tlv from './util/tlv'; -import { EventedHTTPServer, EventedHTTPServerEvents, Session } from './util/eventedhttp'; import { once } from './util/once'; -import { IncomingMessage, ServerResponse } from "http"; -import { Accessory, CharacteristicEvents, Resource } from './Accessory'; -import { CharacteristicData, NodeCallback, PairingsCallback, SessionIdentifier, VoidCallback, Nullable } from '../types'; -import { EventEmitter } from './EventEmitter'; -import { PairingInformation, PermissionTypes, AccessoryInfo } from "./model/AccessoryInfo"; -import { SRP, SrpServer, Identity } from "fast-srp-hap"; +import * as tlv from './util/tlv'; const debug = createDebug('HAP-NodeJS:HAPServer'); const enum TLVValues { + // noinspection JSUnusedGlobalSymbols REQUEST_TYPE = 0x00, METHOD = 0x00, // (match the terminology of the spec sheet but keep backwards compatibility with entry above) USERNAME = 0x01, @@ -40,7 +55,8 @@ const enum TLVValues { SEPARATOR = 0x0FF // Zero-length TLV that separates different TLVs in a list. } -const enum Methods { +const enum PairMethods { + // noinspection JSUnusedGlobalSymbols PAIR_SETUP = 0x00, PAIR_SETUP_WITH_AUTH = 0x01, PAIR_VERIFY = 0x02, @@ -49,7 +65,10 @@ const enum Methods { LIST_PAIRINGS = 0x05 } -const enum States { +/** + * Pairing states (pair-setup or pair-verify). Encoded in {@link TLVValues.SEQUENCE_NUM}. + */ +const enum PairingStates { M1 = 0x01, M2 = 0x02, M3 = 0x03, @@ -58,7 +77,11 @@ const enum States { M6 = 0x06 } -export const enum Codes { +/** + * TLV error codes for the {@link TLVValues.ERROR_CODE} field. + */ +export const enum TLVErrorCode { + // noinspection JSUnusedGlobalSymbols UNKNOWN = 0x01, INVALID_REQUEST = 0x02, AUTHENTICATION = 0x02, // setup code or signature verification failed @@ -74,77 +97,179 @@ export enum PairingFlags { SPLIT = 0x01000000 } -export const enum Status { +export const enum HAPStatus { SUCCESS = 0, INSUFFICIENT_PRIVILEGES = -70401, SERVICE_COMMUNICATION_FAILURE = -70402, RESOURCE_BUSY = -70403, - READ_ONLY_CHARACTERISTIC = -70404, - WRITE_ONLY_CHARACTERISTIC = -70405, + READ_ONLY_CHARACTERISTIC = -70404, // cannot write to read only + WRITE_ONLY_CHARACTERISTIC = -70405, // cannot read from write only NOTIFICATION_NOT_SUPPORTED = -70406, OUT_OF_RESOURCE = -70407, OPERATION_TIMED_OUT = -70408, RESOURCE_DOES_NOT_EXIST = -70409, INVALID_VALUE_IN_REQUEST = -70410, - INSUFFICIENT_AUTHORIZATION = -70411 + INSUFFICIENT_AUTHORIZATION = -70411, + NOT_ALLOWED_IN_CURRENT_STATE = -70412, + + // when adding new status codes, remember to change upper bound in extractHAPStatusFromError } -export type CharacteristicsWriteRequest = { - characteristics: CharacteristicData[], - pid?: number +// noinspection JSUnusedGlobalSymbols +/** + * @deprecated please use {@link TLVErrorCode} as naming is more precise + */ +// @ts-expect-error (as we use const enums with --preserveConstEnums) +export const Codes = TLVErrorCode; +// noinspection JSUnusedGlobalSymbols +/** + * @deprecated please use {@link HAPStatus} as naming is more precise + */ +// @ts-expect-error (as we use const enums with --preserveConstEnums) +export const Status = HAPStatus; + +/** + * Those status codes are the one listed as appropriate for the HAP spec! + * + * When the response is a client error 4xx or server error 5xx, the response + * must include a status {@link HAPStatus} property. + * + * When the response is a MULTI_STATUS EVERY entry in the characteristics property MUST include a status property (even success). + */ +export const enum HAPHTTPCode { + // noinspection JSUnusedGlobalSymbols + OK = 200, + NO_CONTENT = 204, + MULTI_STATUS = 207, + + // client error + BAD_REQUEST = 400, // e.g. malformed request + NOT_FOUND = 404, + UNPROCESSABLE_ENTITY = 422, // for well-formed requests tha contain invalid http parameters (semantics are wrong and not syntax) + + // server error + INTERNAL_SERVER_ERROR = 500, + SERVICE_UNAVAILABLE = 503, // e.g. max connections reached } -export type PrepareWriteRequest = { - ttl: number, - pid: number +/** + * When in a request is made to the pairing endpoints, and mime type is 'application/pairing+tlv8' + * one should use the below status codes. + */ +export const enum HAPPairingHTTPCode { + // noinspection JSUnusedGlobalSymbols + OK = 200, + + BAD_REQUEST = 400, // e.g. bad tlv, state errors, etc + METHOD_NOT_ALLOWED = 405, + TOO_MANY_REQUESTS = 429, // e.g. attempt to pair while already pairing + CONNECTION_AUTHORIZATION_REQUIRED = 470, // didn't do pair-verify step + + INTERNAL_SERVER_ERROR = 500, } export type PairIdentity = Identity & {setupcode: string | null}; +type HAPRequestHandler = (connection: HAPConnection, url: URL, request: IncomingMessage, data: Buffer, response: ServerResponse) => void; + +export type IdentifyCallback = VoidCallback; + +export type HAPHttpError = { httpCode: HAPHTTPCode, status: HAPStatus}; + +export type PairingsCallback = (error: TLVErrorCode | 0, data?: T) => void; +export type AddPairingCallback = PairingsCallback; +export type RemovePairingCallback = PairingsCallback; +export type ListPairingsCallback = PairingsCallback; +export type PairCallback = VoidCallback; +export type AccessoriesCallback = (error: HAPHttpError | undefined, result?: AccessoriesResponse) => void; +export type ReadCharacteristicsCallback = (error: HAPHttpError | undefined, response?: CharacteristicsReadResponse) => void; +export type WriteCharacteristicsCallback = (error: HAPHttpError | undefined, response?: CharacteristicsWriteResponse) => void; +export type ResourceRequestCallback = (error: HAPHttpError | undefined, resource?: Buffer) => void; + export const enum HAPServerEventTypes { - IDENTIFY = "identify", + /** + * Emitted when the server is fully set up and ready to receive connections. + */ LISTENING = "listening", + /** + * Emitted when a client wishes for this server to identify itself before pairing. You must call the + * callback to respond to the client with success. + */ + IDENTIFY = "identify", + ADD_PAIRING = "add-pairing", + REMOVE_PAIRING = "remove-pairing", + LIST_PAIRINGS = "list-pairings", GENERATE_SETUP_CODE = 'generate-setup-code', PAIR_SETUP_STARTED = 'pair-setup-started', PAIR_SETUP_FINISHED = 'pair-setup-finished', - PAIR = 'pair', - ADD_PAIRING = 'add-pairing', - REMOVE_PAIRING = 'remove_pairing', - LIST_PAIRINGS = 'list-pairings', - ACCESSORIES = 'accessories', - GET_CHARACTERISTICS = 'get-characteristics', - SET_CHARACTERISTICS = 'set-characteristics', - SESSION_CLOSE = "session-close", - REQUEST_RESOURCE = 'request-resource' + /** + * This event is emitted when a client completes the "pairing" process and exchanges encryption keys. + * Note that this does not mean the "Add Accessory" process in iOS has completed. + * You must call the callback to complete the process. + */ + PAIR = "pair", + /** + * This event is emitted when a client requests the complete representation of Accessory data for + * this Accessory (for instance, what services, characteristics, etc. are supported) and any bridged + * Accessories in the case of a Bridge Accessory. The listener must call the provided callback function + * when the accessory data is ready. We will automatically JSON.stringify the data. + */ + ACCESSORIES = "accessories", + /** + * This event is emitted when a client wishes to retrieve the current value of one or more characteristics. + * The listener must call the provided callback function when the values are ready. iOS clients can typically + * wait up to 10 seconds for this call to return. We will automatically JSON.stringify the data (which must + * be an array) and wrap it in an object with a top-level "characteristics" property. + */ + GET_CHARACTERISTICS = "get-characteristics", + /** + * This event is emitted when a client wishes to set the current value of one or more characteristics and/or + * subscribe to one or more events. The 'events' param is an initially-empty object, associated with the current + * connection, on which you may store event registration keys for later processing. The listener must call + * the provided callback when the request has been processed. + */ + SET_CHARACTERISTICS = "set-characteristics", + REQUEST_RESOURCE = "request-resource", + CONNECTION_CLOSED = "connection-closed", } -export type Events = { - [HAPServerEventTypes.IDENTIFY]: (cb: VoidCallback) => void; - [HAPServerEventTypes.LISTENING]: (port: number) => void; - [HAPServerEventTypes.GENERATE_SETUP_CODE]: (callback: NodeCallback, session: Session) => void; - [HAPServerEventTypes.PAIR_SETUP_STARTED]: (pairIdentity: PairIdentity, session: Session) => void; - [HAPServerEventTypes.PAIR_SETUP_FINISHED]: (err: Nullable, clientUsername: Nullable, session: Session) => void; - [HAPServerEventTypes.PAIR]: (clientUsername: string, clientLTPK: Buffer, cb: VoidCallback) => void; - [HAPServerEventTypes.ADD_PAIRING]: (controller: Session, username: string, publicKey: Buffer, permission: number, callback: PairingsCallback) => void; - [HAPServerEventTypes.REMOVE_PAIRING]: (controller: Session, username: string, callback: PairingsCallback) => void; - [HAPServerEventTypes.LIST_PAIRINGS]: (controller: Session, callback: PairingsCallback) => void; - [HAPServerEventTypes.ACCESSORIES]: (cb: NodeCallback) => void; - [HAPServerEventTypes.GET_CHARACTERISTICS]: ( - data: CharacteristicData[], - events: CharacteristicEvents, - cb: NodeCallback, - remote: boolean, - session: Session, - ) => void; - [HAPServerEventTypes.SET_CHARACTERISTICS]: ( - writeRequest: CharacteristicsWriteRequest, - events: CharacteristicEvents, - cb: NodeCallback, - remote: boolean, - session: Session, - ) => void; - [HAPServerEventTypes.SESSION_CLOSE]: (sessionID: SessionIdentifier, events: CharacteristicEvents) => void; - [HAPServerEventTypes.REQUEST_RESOURCE]: (data: Resource, cb: NodeCallback) => void; +export declare interface HAPServer { + on(event: "listening", listener: (port: number, address: string) => void): this; + on(event: "identify", listener: (callback: IdentifyCallback) => void): this; + + on(event: "add-pairing", listener: (connection: HAPConnection, username: HAPUsername, publicKey: Buffer, permission: PermissionTypes, callback: AddPairingCallback) => void): this; + on(event: "remove-pairing", listener: (connection: HAPConnection, username: HAPUsername, callback: RemovePairingCallback) => void): this; + on(event: "list-pairings", listener: (connection: HAPConnection, callback: ListPairingsCallback) => void): this; + on(event: "generate-setup-code", listener: (callback: NodeCallback, connection: HAPConnection) => void): this; + on(event: "pair-setup-started", listener: (pairIdentity: PairIdentity, connection: HAPConnection) => void): this; + on(event: "pair-setup-finished", listener: (err: Error | null, clientUsername: string | null, connection: HAPConnection) => void): this; + on(event: "pair", listener: (username: HAPUsername, clientLTPK: Buffer, callback: PairCallback) => void): this; + + on(event: "accessories", listener: (connection: HAPConnection, callback: AccessoriesCallback) => void): this; + on(event: "get-characteristics", listener: (connection: HAPConnection, request: CharacteristicsReadRequest, callback: ReadCharacteristicsCallback) => void): this; + on(event: "set-characteristics", listener: (connection: HAPConnection, request: CharacteristicsWriteRequest, callback: WriteCharacteristicsCallback) => void): this; + on(event: "request-resource", listener: (resource: ResourceRequest, callback: ResourceRequestCallback) => void): this; + + on(event: "connection-closed", listener: (connection: HAPConnection) => void): this; + + + emit(event: "listening", port: number, address: string): boolean; + emit(event: "identify", callback : IdentifyCallback): boolean; + + emit(event: "add-pairing", connection: HAPConnection, username: HAPUsername, publicKey: Buffer, permission: PermissionTypes, callback: AddPairingCallback): boolean; + emit(event: "remove-pairing", connection: HAPConnection, username: HAPUsername, callback: RemovePairingCallback): boolean; + emit(event: "list-pairings", connection: HAPConnection, callback: ListPairingsCallback): boolean; + emit(event: "generate-setup-code", callback: NodeCallback, connection: HAPConnection): boolean; + emit(event: "pair-setup-started", pairIdentity: PairIdentity, connection: HAPConnection): boolean; + emit(event: "pair-setup-finished", err: Error | null, clientUsername: string | null, connection: HAPConnection): boolean; + emit(event: "pair", username: HAPUsername, clientLTPK: Buffer, callback: PairCallback): boolean; + + emit(event: "accessories", connection: HAPConnection, callback : AccessoriesCallback): boolean; + emit(event: "get-characteristics", connection: HAPConnection, request: CharacteristicsReadRequest, callback: ReadCharacteristicsCallback): boolean; + emit(event: "set-characteristics", connection: HAPConnection, request: CharacteristicsWriteRequest, callback: WriteCharacteristicsCallback): boolean; + emit(event: "request-resource", resource: ResourceRequest, callback: ResourceRequestCallback): boolean; + + emit(event: "connection-closed", connection: HAPConnection): boolean; } /** @@ -165,268 +290,202 @@ export type Events = { * typically sent to inform the iOS device of a characteristic change for the accessory (like "Door was Unlocked"). * * See eventedhttp.js for more detail on the implementation of this protocol. - * - * @event 'listening' => function() { } - * Emitted when the server is fully set up and ready to receive connections. - * - * @event 'identify' => function(callback(err)) { } - * Emitted when a client wishes for this server to identify itself before pairing. You must call the - * callback to respond to the client with success. - * - * @event 'generate-setup-code' => function(callback(err, pincode), session) {} - * Emitted when a client starts the pairing process. You must call the callback with the setup code to use and - * display the code to the user. You don't have to listen to this event if you provide a static setup code. - * - * @event 'pair' => function(username, publicKey, callback(err)) { } - * This event is emitted when a client completes the "pairing" process and exchanges encryption keys. - * Note that this does not mean the "Add Accessory" process in iOS has completed. You must call the - * callback to complete the process. - * - * @event 'verify' => function() { } - * This event is emitted after a client successfully completes the "verify" process, thereby authenticating - * itself to an Accessory as a known-paired client. - * - * @event 'unpair' => function(username, callback(err)) { } - * This event is emitted when a client has requested us to "remove their pairing info", or basically to unpair. - * You must call the callback to complete the process. - * - * @event 'accessories' => function(callback(err, accessories)) { } - * This event is emitted when a client requests the complete representation of Accessory data for - * this Accessory (for instance, what services, characteristics, etc. are supported) and any bridged - * Accessories in the case of a Bridge Accessory. The listener must call the provided callback function - * when the accessory data is ready. We will automatically JSON.stringify the data. - * - * @event 'get-characteristics' => function(data, events, callback(err, characteristics), remote, connectionID) { } - * This event is emitted when a client wishes to retrieve the current value of one or more characteristics. - * The listener must call the provided callback function when the values are ready. iOS clients can typically - * wait up to 10 seconds for this call to return. We will automatically JSON.stringify the data (which must - * be an array) and wrap it in an object with a top-level "characteristics" property. - * - * @event 'set-characteristics' => function(data, events, callback(err), remote, connectionID) { } - * This event is emitted when a client wishes to set the current value of one or more characteristics and/or - * subscribe to one or more events. The 'events' param is an initially-empty object, associated with the current - * connection, on which you may store event registration keys for later processing. The listener must call - * the provided callback when the request has been processed. */ -export class HAPServer extends EventEmitter { - - static handlers = { - '/identify': '_handleIdentify', - '/pair-setup': '_handlePair', - '/pair-verify': '_handlePairVerify', - '/pairings': '_handlePairings', - '/accessories': '_handleAccessories', - '/characteristics': '_handleCharacteristics', - '/prepare': '_prepareWrite', - '/resource': '_handleResource' - } as const; - - _httpServer: EventedHTTPServer; +export class HAPServer extends EventEmitter { + + private accessoryInfo: AccessoryInfo; + private httpServer: EventedHTTPServer; private unsuccessfulPairAttempts: number = 0; // after 100 unsuccessful attempts the server won't accept any further attempts. Will currently be reset on a reboot /** Session currently trying to pair with the server */ - _pairing: Session | null = null; + _pairing: HAPConnection | null = null; _setupCodeIdentity: PairIdentity | null = null; allowInsecureRequest: boolean; - _keepAliveTimerID: NodeJS.Timeout; - constructor(public accessoryInfo: AccessoryInfo) { + constructor(accessoryInfo: AccessoryInfo) { super(); this.accessoryInfo = accessoryInfo; this.allowInsecureRequest = false; // internal server that does all the actual communication - this._httpServer = new EventedHTTPServer(); - this._httpServer.on(EventedHTTPServerEvents.LISTENING, this._onListening); - this._httpServer.on(EventedHTTPServerEvents.REQUEST, this._onRequest); - this._httpServer.on(EventedHTTPServerEvents.ENCRYPT, this._onEncrypt); - this._httpServer.on(EventedHTTPServerEvents.DECRYPT, this._onDecrypt); - this._httpServer.on(EventedHTTPServerEvents.SESSION_CLOSE, this._onSessionClose); - - // so iOS is very reluctant to actually disconnect HAP connections (as in, sending a FIN packet). - // For instance, if you turn off wifi on your phone, it will not close the connection, instead - // it will leave it open and hope that it's still valid when it returns to the network. And Node, - // by itself, does not ever "discover" that the connection has been closed behind it, until a - // potentially very long system-level socket timeout (like, days). To work around this, we have - // invented a manual "keepalive" mechanism where we send "empty" events perodicially, such that - // when Node attempts to write to the socket, it discovers that it's been disconnected after - // an additional one-minute timeout (this timeout appears to be hardcoded). - this._keepAliveTimerID = setInterval(this._onKeepAliveTimerTick, 1000 * 60 * 10); // send keepalive every 10 minutes - } - - listen = (port: number) => { - this._httpServer.listen(port); + this.httpServer = new EventedHTTPServer(); + this.httpServer.on(EventedHTTPServerEvent.LISTENING, this.onListening.bind(this)); + this.httpServer.on(EventedHTTPServerEvent.REQUEST, this.handleRequestOnHAPConnection.bind(this)); + this.httpServer.on(EventedHTTPServerEvent.CONNECTION_CLOSED, this.handleConnectionClosed.bind(this)); } - stop = () => { - this._httpServer.stop(); - clearInterval(this._keepAliveTimerID); + public listen(port: number = 0, host?: string): void { + this.httpServer.listen(port, host); } - _onKeepAliveTimerTick = () => { - // send out a "keepalive" event which all connections automatically sign up for once pairVerify is - // completed. The event contains no actual data, so iOS devices will simply ignore it. - this.notifyClients('keepalive', {characteristics: []}); + public stop(): void { + this.httpServer.stop(); + this.removeAllListeners(); } /** - * Notifies connected clients who have subscribed to a particular event. + * Send a even notification for given characteristic and changed value to all connected clients. + * If {@param originator} is specified, the given {@link HAPConnection} will be excluded from the broadcast. * - * @param event {string} - the name of the event (only clients who have subscribed to this name will be notified) - * @param data {object} - the object containing the event data; will be JSON.stringify'd automatically + * @param aid - The accessory id of the updated characteristic. + * @param iid - The instance id of the updated characteristic. + * @param value - The newly set value of the characteristic. + * @param originator - If specified, the connection will not get a event message. + * @param immediateDelivery - The HAP spec requires some characteristics to be delivery immediately. + * Namely for the {@link ButtonEvent} and the {@link ProgrammableSwitchEvent} characteristics. */ - notifyClients = (event: string, data: any, excludeEvents?: Record) => { - // encode notification data as JSON, set content-type, and hand it off to the server. - this._httpServer.sendEvent(event, JSON.stringify(data), "application/hap+json", excludeEvents); + public sendEventNotifications(aid: number, iid: number, value: Nullable, originator?: HAPConnection, immediateDelivery?: boolean): void { + try { + this.httpServer.broadcastEvent(aid, iid, value, originator, immediateDelivery); + } catch (error) { + console.warn("[" + this.accessoryInfo.username + "] Error when sending event notifications: " + error.message); + } } - _onListening = (port: number) => { - this.emit(HAPServerEventTypes.LISTENING, port); + private onListening(port: number, hostname: string): void { + this.emit(HAPServerEventTypes.LISTENING, port, hostname); } // Called when an HTTP request was detected. - _onRequest = (request: IncomingMessage, response: ServerResponse, session: Session, events: any) => { + private handleRequestOnHAPConnection(connection: HAPConnection, request: IncomingMessage, response: ServerResponse): void { debug("[%s] HAP Request: %s %s", this.accessoryInfo.username, request.method, request.url); - // collect request data, if any - var requestData = Buffer.alloc(0); - request.on('data', (data) => { - requestData = Buffer.concat([requestData, data]); - }); + const buffers: Buffer[] = []; + request.on('data', data => buffers.push(data)); + request.on('end', () => { - // parse request.url (which can contain querystring, etc.) into components, then extract just the path - var pathname = url.parse(request.url!).pathname!; - // all request data received; now process this request - for (var path in HAPServer.handlers) - if (new RegExp('^' + path + '/?$').test(pathname)) { // match exact string and allow trailing slash - const handler = HAPServer.handlers[path as keyof typeof HAPServer.handlers]; - this[handler](request, response, session, events, requestData); - return; + const url = new URL(request.url!, "http://hap-nodejs.local"); // parse the url (query strings etc) + + const handler = this.getHandler(url); // TODO check that content-type is supported by the handler? + + if (!handler) { + debug("[%s] WARNING: Handler for %s not implemented", this.accessoryInfo.username, request.url); + response.writeHead(HAPHTTPCode.NOT_FOUND, {'Content-Type': 'application/hap+json'}); + response.end(JSON.stringify({ status: HAPStatus.RESOURCE_DOES_NOT_EXIST })); + } else { + const data = Buffer.concat(buffers); + try { + handler(connection, url, request, data, response); + } catch (error) { + debug("[%s] Error executing route handler: %s", this.accessoryInfo.username, error.stack); + response.writeHead(HAPHTTPCode.INTERNAL_SERVER_ERROR, {'Content-Type': 'application/hap+json'}); + response.end(JSON.stringify({ status: HAPStatus.RESOURCE_BUSY })); // resource busy try again, does somehow fit? } - // nobody handled this? reply 404 - debug("[%s] WARNING: Handler for %s not implemented", this.accessoryInfo.username, request.url); - response.writeHead(404, "Not found", {'Content-Type': 'text/html'}); - response.end(); + } }); } - _onEncrypt = (data: Buffer, encrypted: { data: Buffer; }, session: Session) => { - // instance of HAPEncryption (created in handlePairVerifyStepOne) - var enc = session.encryption; - // if accessoryToControllerKey is not empty, then encryption is enabled for this connection. However, we'll - // need to be careful to ensure that we don't encrypt the last few bytes of the response from handlePairVerifyStepTwo. - // Since all communication calls are asynchronous, we could easily receive this 'encrypt' event for those bytes. - // So we want to make sure that we aren't encrypting data until we have *received* some encrypted data from the - // client first. - if (enc && enc.accessoryToControllerKey.length > 0 && enc.controllerToAccessoryCount.value > 0) { - encrypted.data = hapCrypto.layerEncrypt(data, enc.accessoryToControllerCount, enc.accessoryToControllerKey); - } + private handleConnectionClosed(connection: HAPConnection): void { + this.emit(HAPServerEventTypes.CONNECTION_CLOSED, connection); } - _onDecrypt = (data: Buffer, decrypted: { data: number | Buffer; error: Error | null }, session: Session) => { - // possibly an instance of HAPEncryption (created in handlePairVerifyStepOne) - var enc = session.encryption; - // if controllerToAccessoryKey is not empty, then encryption is enabled for this connection. - if (enc && enc.controllerToAccessoryKey.length > 0) { - try { - decrypted.data = hapCrypto.layerDecrypt(data, enc.controllerToAccessoryCount, enc.controllerToAccessoryKey, enc.extraInfo); - } catch (error) { - decrypted.error = error; - } + private getHandler(url: URL): HAPRequestHandler | undefined { + switch (url.pathname.toLowerCase()) { + case "/identify": + return this.handleIdentifyRequest.bind(this); + case "/pair-setup": + return this.handlePairSetup.bind(this); + case "/pair-verify": + return this.handlePairVerify.bind(this); + case "/pairings": + return this.handlePairings.bind(this); + case "/accessories": + return this.handleAccessories.bind(this); + case "/characteristics": + return this.handleCharacteristics.bind(this); + case "/prepare": + return this.handlePrepareWrite.bind(this); + case "/resource": + return this.handleResource.bind(this); + default: + return undefined; } } - _onSessionClose = (sessionID: SessionIdentifier, events: any) => { - this.emit(HAPServerEventTypes.SESSION_CLOSE, sessionID, events); - } - /** - * Unpaired Accessory identification. + * UNPAIRED Accessory identification. */ - _handleIdentify = (request: IncomingMessage, response: ServerResponse, session: Session, events: any, requestData: any) => { - // /identify only works if the accesory is not paired + private handleIdentifyRequest(connection: HAPConnection, url: URL, request: IncomingMessage, data: Buffer, response: ServerResponse): void { + // POST body is empty if (!this.allowInsecureRequest && this.accessoryInfo.paired()) { - response.writeHead(400, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({status: Status.INSUFFICIENT_PRIVILEGES})); + response.writeHead(HAPHTTPCode.BAD_REQUEST, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify({ status: HAPStatus.INSUFFICIENT_PRIVILEGES })); return; } + this.emit(HAPServerEventTypes.IDENTIFY, once((err: Error) => { if (!err) { debug("[%s] Identification success", this.accessoryInfo.username); - response.writeHead(204); + response.writeHead(HAPHTTPCode.NO_CONTENT); response.end(); } else { debug("[%s] Identification error: %s", this.accessoryInfo.username, err.message); - response.writeHead(500); - response.end(); + response.writeHead(HAPHTTPCode.INTERNAL_SERVER_ERROR, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify({ status: HAPStatus.RESOURCE_BUSY })); } })); } - /** - * iOS <-> Accessory pairing process. - */ - async _handlePair(request: IncomingMessage, response: ServerResponse, session: Session, events: any, requestData: Buffer) { + private async handlePairSetup(connection: HAPConnection, url: URL, request: IncomingMessage, data: Buffer, response: ServerResponse) { // Can only be directly paired with one iOS device if (!this.allowInsecureRequest && this.accessoryInfo.paired()) { - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.STATE, States.M2, TLVValues.ERROR_CODE, Codes.UNAVAILABLE)); + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(TLVValues.STATE, PairingStates.M2, TLVValues.ERROR_CODE, TLVErrorCode.UNAVAILABLE)); return; } - if (this.unsuccessfulPairAttempts > 100) { - debug("[%s] Pair request from %s was rejected as there has been 100 unsuccessful pair attempts", this.accessoryInfo.username, session._connection.remoteAddress); + if (this._pairing && this._pairing !== connection && this._pairing.connected) { response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.STATE, States.M2, TLVValues.ERROR_CODE, Codes.MAX_TRIES)); + response.end(tlv.encode(TLVValues.STATE, PairingStates.M2, TLVValues.ERROR_CODE, Codes.BUSY)); return; } + this._pairing = connection; - if (this._pairing && this._pairing !== session && this._pairing._connection.connected) { - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.STATE, States.M2, TLVValues.ERROR_CODE, Codes.BUSY)); + if (this.unsuccessfulPairAttempts > 100) { + debug("[%s] Reached maximum amount of unsuccessful pair attempts!", this.accessoryInfo.username); + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(TLVValues.STATE, PairingStates.M2, TLVValues.ERROR_CODE, TLVErrorCode.MAX_TRIES)); return; } - this._pairing = session; - var objects = tlv.decode(requestData); - var sequence = objects[TLVValues.SEQUENCE_NUM][0]; // value is single byte with sequence number + const tlvData = tlv.decode(data); + const sequence = tlvData[TLVValues.SEQUENCE_NUM][0]; // value is single byte with sequence number try { - if (sequence == States.M1) - await this._handlePairStepOne(request, response, session, objects); - else if (sequence == States.M3 && session._pairSetupState === States.M2) - this._handlePairStepTwo(request, response, session, objects); - else if (sequence == States.M5 && session._pairSetupState === States.M4) - this._handlePairStepThree(request, response, session, objects); - else throw new Error('Invalid state/sequence number'); + if (sequence == PairingStates.M1) { + await this.handlePairSetupM1(connection, request, response, tlvData); + } else if (sequence == PairingStates.M3 && connection._pairSetupState === PairingStates.M2) { + this.handlePairSetupM3(connection, request, response, tlvData); + } else if (sequence == PairingStates.M5 && connection._pairSetupState === PairingStates.M4) { + this.handlePairSetupM5(connection, request, response, tlvData); + } else throw new Error('Invalid state/sequence number'); } catch (error) { debug("[%s] Error occurred during pairing: %s", this.accessoryInfo.username, error.message); - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); + response.writeHead(HAPPairingHTTPCode.BAD_REQUEST, {"Content-Type": "application/pairing+tlv8"}); response.end(tlv.encode(TLVValues.STATE, sequence + 1, TLVValues.ERROR_CODE, Codes.UNKNOWN)); this._pairing = null; } } - // M1 + M2 - async _handlePairStepOne(request: IncomingMessage, response: ServerResponse, session: Session, objects: Record) { + private async handlePairSetupM1(connection: HAPConnection, request: IncomingMessage, response: ServerResponse, tlvData: Record) { debug("[%s] Pair step 1/5", this.accessoryInfo.username); - if (objects[TLVValues.METHOD][0] !== Methods.PAIR_SETUP) { + if (tlvData[TLVValues.METHOD][0] !== PairMethods.PAIR_SETUP) { response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.STATE, States.M2, TLVValues.ERROR_CODE, Codes.UNAVAILABLE)); + response.end(tlv.encode(TLVValues.STATE, PairingStates.M2, TLVValues.ERROR_CODE, TLVErrorCode.UNAVAILABLE)); return; } - const flags = objects[TLVValues.FLAGS]; + const flags = tlvData[TLVValues.FLAGS]; const decodedFlags = flags && flags.length === 4 ? tlv.readUInt32(flags) : null; const transient = !!decodedFlags && !!(decodedFlags & PairingFlags.TRANSIENT); const split = !!decodedFlags && !!(decodedFlags & PairingFlags.SPLIT); - session._pairSetupFlags = { + connection._pairSetupFlags = { raw: flags, flags: decodedFlags, transient, split, }; if ((!decodedFlags || (transient && split)) && this.listenerCount(HAPServerEventTypes.GENERATE_SETUP_CODE)) { try { - const setupcode = await this._getSetupCodeForPairing(session); + const setupcode = await this._getSetupCodeForPairing(connection); const salt = crypto.randomBytes(16); const verifier = SRP.computeVerifier(SRP.params.hap, salt, Buffer.from('Pair-Setup'), Buffer.from(setupcode)); @@ -436,7 +495,7 @@ export class HAPServer extends EventEmitter { } catch (err) { debug("[%s] Error occurred when generating setup code: %s", this.accessoryInfo.username, err.message); response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.STATE, States.M2, TLVValues.ERROR_CODE, Codes.UNAVAILABLE)); + response.end(tlv.encode(TLVValues.STATE, PairingStates.M2, TLVValues.ERROR_CODE, Codes.UNAVAILABLE)); this._pairing = null; return; } @@ -459,7 +518,7 @@ export class HAPServer extends EventEmitter { } response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.SEQUENCE_NUM, States.M2, TLVValues.ERROR_CODE, Codes.AUTHENTICATION)); + response.end(tlv.encode(TLVValues.SEQUENCE_NUM, PairingStates.M2, TLVValues.ERROR_CODE, Codes.AUTHENTICATION)); this._pairing = null; return; } @@ -471,17 +530,17 @@ export class HAPServer extends EventEmitter { const srpServer = new SrpServer(SRP.params.hap, this._setupCodeIdentity, key); const srpB = srpServer.computeB(); // attach it to the current TCP session - session.srpServer = srpServer; + connection.srpServer = srpServer; - const responseTLV = tlv.encode(TLVValues.SEQUENCE_NUM, States.M2, TLVValues.SALT, salt, TLVValues.PUBLIC_KEY, srpB); + const responseTLV = tlv.encode(TLVValues.SEQUENCE_NUM, PairingStates.M2, TLVValues.SALT, salt, TLVValues.PUBLIC_KEY, srpB); response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); response.end(flags ? Buffer.concat([responseTLV, tlv.encode(TLVValues.FLAGS, flags)]) : responseTLV); - session._pairSetupState = States.M2; - this.emit(HAPServerEventTypes.PAIR_SETUP_STARTED, this._setupCodeIdentity, session); + connection._pairSetupState = PairingStates.M2; + this.emit(HAPServerEventTypes.PAIR_SETUP_STARTED, this._setupCodeIdentity, connection); } - private _getSetupCodeForPairing(session: Session) { + private _getSetupCodeForPairing(connection: HAPConnection) { if (!this.listenerCount(HAPServerEventTypes.GENERATE_SETUP_CODE)) { throw new Error('No setup code handler'); } @@ -493,18 +552,16 @@ export class HAPServer extends EventEmitter { } else { resolve(setupCode); } - }), session); + }), connection); }); } - // M3 + M4 - _handlePairStepTwo = (request: IncomingMessage, response: ServerResponse, session: Session, objects: Record) => { + private handlePairSetupM3(connection: HAPConnection, request: IncomingMessage, response: ServerResponse, tlvData: Record): void { debug("[%s] Pair step 2/5", this.accessoryInfo.username); - var A = objects[TLVValues.PUBLIC_KEY]; // "A is a public key that exists only for a single login session." - var M1 = objects[TLVValues.PASSWORD_PROOF]; // "M1 is the proof that you actually know your own password." - + const A = tlvData[TLVValues.PUBLIC_KEY]; // "A is a public key that exists only for a single login session." + const M1 = tlvData[TLVValues.PASSWORD_PROOF]; // "M1 is the proof that you actually know your own password." // pull the SRP server we created in stepOne out of the current session - var srpServer = session.srpServer!; + const srpServer = connection.srpServer!; srpServer.setA(A); try { @@ -513,102 +570,98 @@ export class HAPServer extends EventEmitter { // most likely the client supplied an incorrect pincode. this.unsuccessfulPairAttempts++; debug("[%s] Error while checking pincode: %s", this.accessoryInfo.username, err.message); - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.SEQUENCE_NUM, States.M4, TLVValues.ERROR_CODE, Codes.AUTHENTICATION)); - this._handlePairFinished([States.M4, Codes.AUTHENTICATION], null, session); + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(TLVValues.SEQUENCE_NUM, PairingStates.M4, TLVValues.ERROR_CODE, TLVErrorCode.AUTHENTICATION)); + this._handlePairFinished([PairingStates.M4, TLVErrorCode.AUTHENTICATION], null, connection); return; } // "M2 is the proof that the server actually knows your password." - var M2 = srpServer.computeM2(); - - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.SEQUENCE_NUM, States.M4, TLVValues.PASSWORD_PROOF, M2)); - session._pairSetupState = States.M4; + const M2 = srpServer.computeM2(); + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(TLVValues.SEQUENCE_NUM, PairingStates.M4, TLVValues.PASSWORD_PROOF, M2)); + connection._pairSetupState = PairingStates.M4; - if (session._pairSetupFlags!.transient) { - this._handlePairFinished(null, null, session); + if (connection._pairSetupFlags!.transient) { + this._handlePairFinished(null, null, connection); // For transient pair setup we should enable session encryption now - const enc = session.encryption = new HAPEncryption(); - enc.sharedSec = srpServer.computeK(); + const sharedSec = srpServer.computeK(); + const enc = connection.encryption = new HAPEncryption(Buffer.alloc(0), Buffer.alloc(0), Buffer.alloc(0), sharedSec, Buffer.alloc(0)); const encSalt = Buffer.from("SplitSetupSalt"); const infoRead = Buffer.from("ControllerEncrypt-Control"); const infoWrite = Buffer.from("AccessoryEncrypt-Control"); - enc.controllerToAccessoryKey = hapCrypto.HKDF("sha512", encSalt, enc.sharedSec, infoRead, 32); - enc.accessoryToControllerKey = hapCrypto.HKDF("sha512", encSalt, enc.sharedSec, infoWrite, 32); + enc.controllerToAccessoryKey = hapCrypto.HKDF("sha512", encSalt, sharedSec, infoRead, 32); + enc.accessoryToControllerKey = hapCrypto.HKDF("sha512", encSalt, sharedSec, infoWrite, 32); // TODO: how should the session be authenticated? We don't have any identification here. - session.authenticated = true; } } - // M5-1 - _handlePairStepThree = (request: IncomingMessage, response: ServerResponse, session: Session, objects: Record) => { + private handlePairSetupM5(connection: HAPConnection, request: IncomingMessage, response: ServerResponse, tlvData: Record): void { debug("[%s] Pair step 3/5", this.accessoryInfo.username); // pull the SRP server we created in stepOne out of the current session - var srpServer = session.srpServer!; - var encryptedData = objects[TLVValues.ENCRYPTED_DATA]; - var messageData = Buffer.alloc(encryptedData.length - 16); - var authTagData = Buffer.alloc(16); + const srpServer = connection.srpServer!; + const encryptedData = tlvData[TLVValues.ENCRYPTED_DATA]; + const messageData = Buffer.alloc(encryptedData.length - 16); + const authTagData = Buffer.alloc(16); encryptedData.copy(messageData, 0, 0, encryptedData.length - 16); encryptedData.copy(authTagData, 0, encryptedData.length - 16, encryptedData.length); - var S_private = srpServer.computeK(); - var encSalt = Buffer.from("Pair-Setup-Encrypt-Salt"); - var encInfo = Buffer.from("Pair-Setup-Encrypt-Info"); - var outputKey = hapCrypto.HKDF("sha512", encSalt, S_private, encInfo, 32); + const S_private = srpServer.computeK(); + const encSalt = Buffer.from("Pair-Setup-Encrypt-Salt"); + const encInfo = Buffer.from("Pair-Setup-Encrypt-Info"); + const outputKey = hapCrypto.HKDF("sha512", encSalt, S_private, encInfo, 32); let plaintext; try { plaintext = hapCrypto.chacha20_poly1305_decryptAndVerify(outputKey, Buffer.from("PS-Msg05"), null, messageData, authTagData); } catch (error) { debug("[%s] Error while decrypting and verifying M5 subTlv: %s", this.accessoryInfo.username); - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.SEQUENCE_NUM, States.M6, TLVValues.ERROR_CODE, Codes.AUTHENTICATION)); - this._handlePairFinished([States.M6, Codes.AUTHENTICATION], null, session); + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(TLVValues.SEQUENCE_NUM, PairingStates.M6, TLVValues.ERROR_CODE, TLVErrorCode.AUTHENTICATION)); + this._handlePairFinished([PairingStates.M6, TLVErrorCode.AUTHENTICATION], null, connection); return; } // decode the client payload and pass it on to the next step - var M5Packet = tlv.decode(plaintext); - var clientUsername = M5Packet[TLVValues.USERNAME]; - var clientLTPK = M5Packet[TLVValues.PUBLIC_KEY]; - var clientProof = M5Packet[TLVValues.PROOF]; - var hkdfEncKey = outputKey; - this._handlePairStepFour(request, response, session, clientUsername, clientLTPK, clientProof, hkdfEncKey); + const M5Packet = tlv.decode(plaintext); + const clientUsername = M5Packet[TLVValues.USERNAME]; + const clientLTPK = M5Packet[TLVValues.PUBLIC_KEY]; + const clientProof = M5Packet[TLVValues.PROOF]; + this.handlePairSetupM5_2(connection, request, response, clientUsername, clientLTPK, clientProof, outputKey); } // M5-2 - _handlePairStepFour = (request: IncomingMessage, response: ServerResponse, session: Session, clientUsername: Buffer, clientLTPK: Buffer, clientProof: Buffer, hkdfEncKey: Buffer) => { + private handlePairSetupM5_2(connection: HAPConnection, request: IncomingMessage, response: ServerResponse, clientUsername: Buffer, clientLTPK: Buffer, clientProof: Buffer, hkdfEncKey: Buffer): void { debug("[%s] Pair step 4/5", this.accessoryInfo.username); - var S_private = session.srpServer!.computeK(); - var controllerSalt = Buffer.from("Pair-Setup-Controller-Sign-Salt"); - var controllerInfo = Buffer.from("Pair-Setup-Controller-Sign-Info"); - var outputKey = hapCrypto.HKDF("sha512", controllerSalt, S_private, controllerInfo, 32); - var completeData = Buffer.concat([outputKey, clientUsername, clientLTPK]); + const S_private = connection.srpServer!.computeK(); + const controllerSalt = Buffer.from("Pair-Setup-Controller-Sign-Salt"); + const controllerInfo = Buffer.from("Pair-Setup-Controller-Sign-Info"); + const outputKey = hapCrypto.HKDF("sha512", controllerSalt, S_private, controllerInfo, 32); + const completeData = Buffer.concat([outputKey, clientUsername, clientLTPK]); if (!tweetnacl.sign.detached.verify(completeData, clientProof, clientLTPK)) { debug("[%s] Invalid signature", this.accessoryInfo.username); - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.SEQUENCE_NUM, States.M6, TLVValues.ERROR_CODE, Codes.AUTHENTICATION)); - this._handlePairFinished([States.M6, Codes.AUTHENTICATION], null, session); + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(TLVValues.SEQUENCE_NUM, PairingStates.M6, TLVValues.ERROR_CODE, TLVErrorCode.AUTHENTICATION)); + this._handlePairFinished([PairingStates.M6, TLVErrorCode.AUTHENTICATION], null, connection); return; } - this._handlePairStepFive(request, response, session, clientUsername, clientLTPK, hkdfEncKey); + this.handlePairSetupM5_3(connection, request, response, clientUsername, clientLTPK, hkdfEncKey); } // M5 - F + M6 - _handlePairStepFive = (request: IncomingMessage, response: ServerResponse, session: Session, clientUsername: Buffer, clientLTPK: Buffer, hkdfEncKey: Buffer) => { + private handlePairSetupM5_3(connection: HAPConnection, request: IncomingMessage, response: ServerResponse, clientUsername: Buffer, clientLTPK: Buffer, hkdfEncKey: Buffer): void { debug("[%s] Pair step 5/5", this.accessoryInfo.username); - var S_private = session.srpServer!.computeK(); - var accessorySalt = Buffer.from("Pair-Setup-Accessory-Sign-Salt"); - var accessoryInfo = Buffer.from("Pair-Setup-Accessory-Sign-Info"); - var outputKey = hapCrypto.HKDF("sha512", accessorySalt, S_private, accessoryInfo, 32); - var serverLTPK = this.accessoryInfo.signPk; - var usernameData = Buffer.from(this.accessoryInfo.username); - var material = Buffer.concat([outputKey, usernameData, serverLTPK]); - var privateKey = Buffer.from(this.accessoryInfo.signSk); - var serverProof = tweetnacl.sign.detached(material, privateKey); - var message = tlv.encode(TLVValues.USERNAME, usernameData, TLVValues.PUBLIC_KEY, serverLTPK, TLVValues.PROOF, serverProof); + const S_private = connection.srpServer!.computeK(); + const accessorySalt = Buffer.from("Pair-Setup-Accessory-Sign-Salt"); + const accessoryInfo = Buffer.from("Pair-Setup-Accessory-Sign-Info"); + const outputKey = hapCrypto.HKDF("sha512", accessorySalt, S_private, accessoryInfo, 32); + const serverLTPK = this.accessoryInfo.signPk; + const usernameData = Buffer.from(this.accessoryInfo.username); + const material = Buffer.concat([outputKey, usernameData, serverLTPK]); + const privateKey = Buffer.from(this.accessoryInfo.signSk); + const serverProof = tweetnacl.sign.detached(material, privateKey); + const message = tlv.encode(TLVValues.USERNAME, usernameData, TLVValues.PUBLIC_KEY, serverLTPK, TLVValues.PROOF, serverProof); const encrypted = hapCrypto.chacha20_poly1305_encryptAndSeal(hkdfEncKey, Buffer.from("PS-Msg06"), null, message); @@ -616,198 +669,187 @@ export class HAPServer extends EventEmitter { this.emit(HAPServerEventTypes.PAIR, clientUsername.toString(), clientLTPK, once((err?: Error) => { if (err) { debug("[%s] Error adding pairing info: %s", this.accessoryInfo.username, err.message); - response.writeHead(500, "Server Error"); - response.end(); - this._handlePairFinished(err, null, session); + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(TLVValues.SEQUENCE_NUM, PairingStates.M6, TLVValues.ERROR_CODE, TLVErrorCode.UNKNOWN)); + this._handlePairFinished(err, null, connection); return; } + // send final pairing response to client - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.SEQUENCE_NUM, 0x06, TLVValues.ENCRYPTED_DATA, Buffer.concat([encrypted.ciphertext, encrypted.authTag]))); - this._handlePairFinished(null, clientUsername.toString(), session); + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(TLVValues.SEQUENCE_NUM, PairingStates.M6, TLVValues.ENCRYPTED_DATA, Buffer.concat([encrypted.ciphertext, encrypted.authTag]))); + this._handlePairFinished(null, clientUsername.toString(), connection); })); } // It's possible for both err and clientUsername to be null (transient pair setup) - _handlePairFinished = (err: Nullable, clientUsername: Nullable, session: Session) => { + private _handlePairFinished(err: Error | [number, number] | null, clientUsername: string | null, connection: HAPConnection) { this._pairing = null; - session._pairSetupState = undefined; - session._pairSetupFlags = undefined; + connection._pairSetupState = undefined; + connection._pairSetupFlags = undefined; - this.emit(HAPServerEventTypes.PAIR_SETUP_FINISHED, err && err instanceof Array ? new Error(`${err[1]}`) : err, clientUsername, session); + this.emit(HAPServerEventTypes.PAIR_SETUP_FINISHED, err && err instanceof Array ? new Error(`${err[1]}`) : err, clientUsername, connection); } - /** - * iOS <-> Accessory pairing verification. - */ - _handlePairVerify = (request: IncomingMessage, response: ServerResponse, session: Session, events: any, requestData: Buffer) => { - var objects = tlv.decode(requestData); - var sequence = objects[TLVValues.SEQUENCE_NUM][0]; // value is single byte with sequence number - if (sequence == States.M1) - this._handlePairVerifyStepOne(request, response, session, objects); - else if (sequence == States.M3 && session._pairVerifyState === States.M2) - this._handlePairVerifyStepTwo(request, response, session, events, objects); + private handlePairVerify(connection: HAPConnection, url: URL, request: IncomingMessage, data: Buffer, response: ServerResponse): void { + const tlvData = tlv.decode(data); + const sequence = tlvData[TLVValues.SEQUENCE_NUM][0]; // value is single byte with sequence number + + if (sequence == PairingStates.M1) + this.handlePairVerifyM1(connection, request, response, tlvData); + else if (sequence == PairingStates.M3 && connection._pairVerifyState === PairingStates.M2) + this.handlePairVerifyM2(connection, request, response, tlvData); else { // Invalid state/sequence number - response.writeHead(400, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.STATE, sequence + 1, TLVValues.ERROR_CODE, Codes.UNKNOWN)); + response.writeHead(HAPPairingHTTPCode.BAD_REQUEST, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(TLVValues.STATE, sequence + 1, TLVValues.ERROR_CODE, TLVErrorCode.UNKNOWN)); return; } } - _handlePairVerifyStepOne = (request: IncomingMessage, response: ServerResponse, session: Session, objects: Record) => { + private handlePairVerifyM1(connection: HAPConnection, request: IncomingMessage, response: ServerResponse, tlvData: Record): void { debug("[%s] Pair verify step 1/2", this.accessoryInfo.username); - var clientPublicKey = objects[TLVValues.PUBLIC_KEY]; // Buffer + const clientPublicKey = tlvData[TLVValues.PUBLIC_KEY]; // Buffer // generate new encryption keys for this session - var keyPair = hapCrypto.generateCurve25519KeyPair(); - var secretKey = Buffer.from(keyPair.secretKey); - var publicKey = Buffer.from(keyPair.publicKey); - var sharedSec = Buffer.from(hapCrypto.generateCurve25519SharedSecKey(secretKey, clientPublicKey)); - var usernameData = Buffer.from(this.accessoryInfo.username); - var material = Buffer.concat([publicKey, usernameData, clientPublicKey]); - var privateKey = Buffer.from(this.accessoryInfo.signSk); - var serverProof = tweetnacl.sign.detached(material, privateKey); - var encSalt = Buffer.from("Pair-Verify-Encrypt-Salt"); - var encInfo = Buffer.from("Pair-Verify-Encrypt-Info"); - var outputKey = hapCrypto.HKDF("sha512", encSalt, sharedSec, encInfo, 32).slice(0, 32); - // store keys in a new instance of HAPEncryption - var enc = new HAPEncryption(); - enc.clientPublicKey = clientPublicKey; - enc.secretKey = secretKey; - enc.publicKey = publicKey; - enc.sharedSec = sharedSec; - enc.hkdfPairEncKey = outputKey; - // store this in the current TCP session - session.encryption = enc; + const keyPair = hapCrypto.generateCurve25519KeyPair(); + const secretKey = Buffer.from(keyPair.secretKey); + const publicKey = Buffer.from(keyPair.publicKey); + const sharedSec = Buffer.from(hapCrypto.generateCurve25519SharedSecKey(secretKey, clientPublicKey)); + const usernameData = Buffer.from(this.accessoryInfo.username); + const material = Buffer.concat([publicKey, usernameData, clientPublicKey]); + const privateKey = Buffer.from(this.accessoryInfo.signSk); + const serverProof = tweetnacl.sign.detached(material, privateKey); + const encSalt = Buffer.from("Pair-Verify-Encrypt-Salt"); + const encInfo = Buffer.from("Pair-Verify-Encrypt-Info"); + const outputKey = hapCrypto.HKDF("sha512", encSalt, sharedSec, encInfo, 32).slice(0, 32); + + connection.encryption = new HAPEncryption(clientPublicKey, secretKey, publicKey, sharedSec, outputKey); + // compose the response data in TLV format - var message = tlv.encode(TLVValues.USERNAME, usernameData, TLVValues.PROOF, serverProof); + const message = tlv.encode(TLVValues.USERNAME, usernameData, TLVValues.PROOF, serverProof); const encrypted = hapCrypto.chacha20_poly1305_encryptAndSeal(outputKey, Buffer.from("PV-Msg02"), null, message); - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.SEQUENCE_NUM, States.M2, TLVValues.ENCRYPTED_DATA, Buffer.concat([encrypted.ciphertext, encrypted.authTag]), TLVValues.PUBLIC_KEY, publicKey)); - session._pairVerifyState = States.M2; + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(TLVValues.SEQUENCE_NUM, PairingStates.M2, TLVValues.ENCRYPTED_DATA, Buffer.concat([encrypted.ciphertext, encrypted.authTag]), TLVValues.PUBLIC_KEY, publicKey)); + connection._pairVerifyState = PairingStates.M2; } - _handlePairVerifyStepTwo = (request: IncomingMessage, response: ServerResponse, session: Session, events: any, objects: Record) => { + private handlePairVerifyM2(connection: HAPConnection, request: IncomingMessage, response: ServerResponse, objects: Record): void { debug("[%s] Pair verify step 2/2", this.accessoryInfo.username); - var encryptedData = objects[TLVValues.ENCRYPTED_DATA]; - var messageData = Buffer.alloc(encryptedData.length - 16); - var authTagData = Buffer.alloc(16); + const encryptedData = objects[TLVValues.ENCRYPTED_DATA]; + const messageData = Buffer.alloc(encryptedData.length - 16); + const authTagData = Buffer.alloc(16); encryptedData.copy(messageData, 0, 0, encryptedData.length - 16); encryptedData.copy(authTagData, 0, encryptedData.length - 16, encryptedData.length); // instance of HAPEncryption (created in handlePairVerifyStepOne) - var enc = session.encryption!; + const enc = connection.encryption!; let plaintext; try { - plaintext = hapCrypto.chacha20_poly1305_decryptAndVerify(enc.hkdfPairEncKey, Buffer.from("PV-Msg03"), null, messageData, authTagData); + plaintext = hapCrypto.chacha20_poly1305_decryptAndVerify(enc.hkdfPairEncryptionKey, Buffer.from("PV-Msg03"), null, messageData, authTagData); } catch (error) { debug("[%s] M3: Failed to decrypt and/or verify", this.accessoryInfo.username); - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.STATE, States.M4, TLVValues.ERROR_CODE, Codes.AUTHENTICATION)); - session._pairVerifyState = undefined; + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(TLVValues.STATE, PairingStates.M4, TLVValues.ERROR_CODE, TLVErrorCode.AUTHENTICATION)); + connection._pairVerifyState = undefined; return; } - var decoded = tlv.decode(plaintext); - var clientUsername = decoded[TLVValues.USERNAME]; - var proof = decoded[TLVValues.PROOF]; - var material = Buffer.concat([enc.clientPublicKey, clientUsername, enc.publicKey]); + const decoded = tlv.decode(plaintext); + const clientUsername = decoded[TLVValues.USERNAME]; + const proof = decoded[TLVValues.PROOF]; + const material = Buffer.concat([enc.clientPublicKey, clientUsername, enc.publicKey]); // since we're paired, we should have the public key stored for this client - var clientPublicKey = this.accessoryInfo.getClientPublicKey(clientUsername.toString()); + const clientPublicKey = this.accessoryInfo.getClientPublicKey(clientUsername.toString()); // if we're not actually paired, then there's nothing to verify - this client thinks it's paired with us but we // disagree. Respond with invalid request (seems to match HomeKit Accessory Simulator behavior) if (!clientPublicKey) { debug("[%s] Client %s attempting to verify, but we are not paired; rejecting client", this.accessoryInfo.username, clientUsername); - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.STATE, States.M4, TLVValues.ERROR_CODE, Codes.AUTHENTICATION)); - session._pairVerifyState = undefined; + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(TLVValues.STATE, PairingStates.M4, TLVValues.ERROR_CODE, TLVErrorCode.AUTHENTICATION)); + connection._pairVerifyState = undefined; return; } if (!tweetnacl.sign.detached.verify(material, proof, clientPublicKey)) { debug("[%s] Client %s provided an invalid signature", this.accessoryInfo.username, clientUsername); - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.STATE, States.M4, TLVValues.ERROR_CODE, Codes.AUTHENTICATION)); - session._pairVerifyState = undefined; + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(TLVValues.STATE, PairingStates.M4, TLVValues.ERROR_CODE, TLVErrorCode.AUTHENTICATION)); + connection._pairVerifyState = undefined; return; } debug("[%s] Client %s verification complete", this.accessoryInfo.username, clientUsername); - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.SEQUENCE_NUM, 0x04)); - // now that the client has been verified, we must "upgrade" our pesudo-HTTP connection to include + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(TLVValues.SEQUENCE_NUM, PairingStates.M4)); + // now that the client has been verified, we must "upgrade" our pseudo-HTTP connection to include // TCP-level encryption. We'll do this by adding some more encryption vars to the session, and using them // in future calls to onEncrypt, onDecrypt. - var encSalt = Buffer.from("Control-Salt"); - var infoRead = Buffer.from("Control-Read-Encryption-Key"); - var infoWrite = Buffer.from("Control-Write-Encryption-Key"); - enc.accessoryToControllerKey = hapCrypto.HKDF("sha512", encSalt, enc.sharedSec, infoRead, 32); - enc.controllerToAccessoryKey = hapCrypto.HKDF("sha512", encSalt, enc.sharedSec, infoWrite, 32); + const encSalt = Buffer.from("Control-Salt"); + const infoRead = Buffer.from("Control-Read-Encryption-Key"); + const infoWrite = Buffer.from("Control-Write-Encryption-Key"); + enc.accessoryToControllerKey = hapCrypto.HKDF("sha512", encSalt, enc.sharedSecret, infoRead, 32); + enc.controllerToAccessoryKey = hapCrypto.HKDF("sha512", encSalt, enc.sharedSecret, infoWrite, 32); // Our connection is now completely setup. We now want to subscribe this connection to special - // "keepalive" events for detecting when connections are closed by the client. - events['keepalive'] = true; - session.establishSession(clientUsername.toString()); - session._pairVerifyState = undefined; + + connection.connectionAuthenticated(clientUsername.toString()); + connection._pairVerifyState = undefined; } - /** - * Pair add/remove/list - */ - _handlePairings = (request: IncomingMessage, response: ServerResponse, session: Session, events: any, requestData: Buffer) => { + private handlePairings(connection: HAPConnection, url: URL, request: IncomingMessage, data: Buffer, response: ServerResponse): void { // Only accept /pairing request if there is a secure session - if (!this.allowInsecureRequest && !session.authenticated) { - response.writeHead(470, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({status: Status.INSUFFICIENT_PRIVILEGES})); + if (!this.allowInsecureRequest && !connection.isAuthenticated()) { + response.writeHead(HAPPairingHTTPCode.CONNECTION_AUTHORIZATION_REQUIRED, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify({ status: HAPStatus.INSUFFICIENT_PRIVILEGES })); return; } - const objects = tlv.decode(requestData); + const objects = tlv.decode(data); const method = objects[TLVValues.METHOD][0]; // value is single byte with request type const state = objects[TLVValues.STATE][0]; - if (state !== States.M1) { + if (state !== PairingStates.M1) { return; } - if (method === Methods.ADD_PAIRING) { + if (method === PairMethods.ADD_PAIRING) { const identifier = objects[TLVValues.IDENTIFIER].toString(); const publicKey = objects[TLVValues.PUBLIC_KEY]; const permissions = objects[TLVValues.PERMISSIONS][0] as PermissionTypes; - this.emit(HAPServerEventTypes.ADD_PAIRING, session, identifier, publicKey, permissions, once((errorCode: number, data?: void) => { - if (errorCode > 0) { - debug("[%s] Pairings: failed ADD_PAIRING with code %d", this.accessoryInfo.username, errorCode); - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.STATE, States.M2, TLVValues.ERROR_CODE, errorCode)); + this.emit(HAPServerEventTypes.ADD_PAIRING, connection, identifier, publicKey, permissions, once((error: TLVErrorCode | 0) => { + if (error > 0) { + debug("[%s] Pairings: failed ADD_PAIRING with code %d", this.accessoryInfo.username, error); + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(TLVValues.STATE, PairingStates.M2, TLVValues.ERROR_CODE, error)); return; } - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.STATE, States.M2)); + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(TLVValues.STATE, PairingStates.M2)); debug("[%s] Pairings: successfully executed ADD_PAIRING", this.accessoryInfo.username); })); - } else if (method === Methods.REMOVE_PAIRING) { + } else if (method === PairMethods.REMOVE_PAIRING) { const identifier = objects[TLVValues.IDENTIFIER].toString(); - this.emit(HAPServerEventTypes.REMOVE_PAIRING, session, identifier, once((errorCode: number, data?: void) => { - if (errorCode > 0) { - debug("[%s] Pairings: failed REMOVE_PAIRING with code %d", this.accessoryInfo.username, errorCode); - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.STATE, States.M2, TLVValues.ERROR_CODE, errorCode)); + this.emit(HAPServerEventTypes.REMOVE_PAIRING, connection, identifier, once((error: TLVErrorCode | 0) => { + if (error > 0) { + debug("[%s] Pairings: failed REMOVE_PAIRING with code %d", this.accessoryInfo.username, error); + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(TLVValues.STATE, PairingStates.M2, TLVValues.ERROR_CODE, error)); return; } - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.STATE, States.M2)); + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(TLVValues.STATE, PairingStates.M2)); debug("[%s] Pairings: successfully executed REMOVE_PAIRING", this.accessoryInfo.username); })); - } else if (method === Methods.LIST_PAIRINGS) { - this.emit(HAPServerEventTypes.LIST_PAIRINGS, session, once((errorCode: number, data?: PairingInformation[]) => { - if (errorCode > 0) { - debug("[%s] Pairings: failed LIST_PAIRINGS with code %d", this.accessoryInfo.username, errorCode); - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.STATE, States.M2, TLVValues.ERROR_CODE, errorCode)); + } else if (method === PairMethods.LIST_PAIRINGS) { + this.emit(HAPServerEventTypes.LIST_PAIRINGS, connection, once((error: TLVErrorCode | 0, data?: PairingInformation[]) => { + if (error > 0) { + debug("[%s] Pairings: failed LIST_PAIRINGS with code %d", this.accessoryInfo.username, error); + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(TLVValues.STATE, PairingStates.M2, TLVValues.ERROR_CODE, error)); return; } @@ -824,262 +866,223 @@ export class HAPServer extends EventEmitter { ); }); - const list = tlv.encode(TLVValues.STATE, States.M2, ...tlvList); - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); + const list = tlv.encode(TLVValues.STATE, PairingStates.M2, ...tlvList); + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); response.end(list); debug("[%s] Pairings: successfully executed LIST_PAIRINGS", this.accessoryInfo.username); })); } } - /* - * Handlers for all after-pairing communication, or the bulk of HAP. - */ - - // Called when the client wishes to fetch all data regarding our published Accessories. - _handleAccessories = (request: IncomingMessage, response: ServerResponse, session: Session, events: any, requestData: any) => { - if (!this.allowInsecureRequest && !session.authenticated) { - response.writeHead(470, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({status: Status.INSUFFICIENT_PRIVILEGES})); + private handleAccessories(connection: HAPConnection, url: URL, request: IncomingMessage, data: Buffer, response: ServerResponse): void { + if (!this.allowInsecureRequest && !connection.isAuthenticated()) { + response.writeHead(HAPPairingHTTPCode.CONNECTION_AUTHORIZATION_REQUIRED, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify({status: HAPStatus.INSUFFICIENT_PRIVILEGES})); return; } // call out to listeners to retrieve the latest accessories JSON - this.emit(HAPServerEventTypes.ACCESSORIES, once((err: Error, accessories: Accessory[]) => { - if (err) { - debug("[%s] Error getting accessories: %s", this.accessoryInfo.username, err.message); - response.writeHead(500, "Server Error"); - response.end(); - return; + this.emit(HAPServerEventTypes.ACCESSORIES, connection, once((error: HAPHttpError | undefined, result: AccessoriesResponse) => { + if (error) { + response.writeHead(error.httpCode, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify({ status: error.status })); + } else { + response.writeHead(HAPHTTPCode.OK, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify(result)); } - response.writeHead(200, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify(accessories)); })); } - // Called when the client wishes to get or set particular characteristics - _handleCharacteristics = (request: IncomingMessage, response: ServerResponse, session: Session, events: any, requestData: { length: number; toString: () => string; }) => { - if (!this.allowInsecureRequest && !session.authenticated) { - response.writeHead(470, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({status: Status.INSUFFICIENT_PRIVILEGES})); + private handleCharacteristics(connection: HAPConnection, url: URL, request: IncomingMessage, data: Buffer, response: ServerResponse): void { + if (!this.allowInsecureRequest && !connection.isAuthenticated()) { + response.writeHead(HAPPairingHTTPCode.CONNECTION_AUTHORIZATION_REQUIRED, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify({status: HAPStatus.INSUFFICIENT_PRIVILEGES})); return; } - type Characteristics = Pick & { - status?: any; - }; + if (request.method === "GET") { + const searchParams = url.searchParams; - if (request.method == "GET") { - // Extract the query params from the URL which looks like: /characteristics?id=1.9,2.14,... - var parseQueryString = true; - var query = url.parse(request.url!, parseQueryString).query; // { id: '1.9,2.14' } - if (query == undefined || query.id == undefined) { - response.writeHead(500); - response.end(); + const idParam = searchParams.get("id"); + if (!idParam) { + response.writeHead(HAPHTTPCode.BAD_REQUEST, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify({ status: HAPStatus.INVALID_VALUE_IN_REQUEST })); return; } - var sets = (query.id as string).split(','); // ["1.9","2.14"] - var data: CharacteristicData[] = []; // [{aid:1,iid:9},{aid:2,iid:14}] - for (var i in sets) { - var ids = sets[i].split('.'); // ["1","9"] - var aid = parseInt(ids[0]); // accessory ID - var iid = parseInt(ids[1]); // instance ID (for characteristic) - data.push({aid: aid, iid: iid}); + + const ids: CharacteristicId[] = []; + for (const entry of idParam.split(",")) { // ["1.9","2.14"] + const split = entry.split(".") // ["1","9"] + ids.push({ + aid: parseInt(split[0]), // accessory Id + iid: parseInt(split[1]), // (characteristic) instance Id + }); } - this.emit(HAPServerEventTypes.GET_CHARACTERISTICS, data, events, once((err: Error, characteristics: CharacteristicData[]) => { - if (!characteristics && !err) - err = new Error("characteristics not supplied by the get-characteristics event callback"); - if (err) { - debug("[%s] Error getting characteristics: %s", this.accessoryInfo.username, err.stack); - // rewrite characteristics array to include error status for each characteristic requested - characteristics = []; - for (var i in data) { - characteristics.push({ - aid: data[i].aid, - iid: data[i].iid, - status: Status.SERVICE_COMMUNICATION_FAILURE - }); - } + + const readRequest: CharacteristicsReadRequest = { + ids: ids, + includeMeta: consideredTrue(searchParams.get("meta")), + includePerms: consideredTrue(searchParams.get("perms")), + includeType: consideredTrue(searchParams.get("type")), + includeEvent: consideredTrue(searchParams.get("ev")), + }; + + this.emit(HAPServerEventTypes.GET_CHARACTERISTICS, connection, readRequest, once((error: HAPHttpError | undefined, readResponse: CharacteristicsReadResponse) => { + if (error) { + response.writeHead(error.httpCode, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify({ status: error.status })); + return; } + const characteristics = readResponse.characteristics; + let errorOccurred = false; // determine if we send a 207 Multi-Status - for (let i = 0; i < characteristics.length; i++) { - const value = characteristics[i]; - if ((value.status !== undefined && value.status !== 0) - || (value.s !== undefined && value.s !== 0)) { + for (const data of characteristics) { + if (data.status) { errorOccurred = true; break; } } if (errorOccurred) { // on a 207 Multi-Status EVERY characteristic MUST include a status property - for (let i = 0; i < characteristics.length; i++) { - const value = characteristics[i]; - if (value.status === undefined) { // a status is undefined if the request was successful - value.status = 0; // a value of zero indicates success + for (const data of characteristics) { + if (!data.status) { // a status is undefined if the request was successful + data.status = HAPStatus.SUCCESS; // a value of zero indicates success } } } // 207 "multi-status" is returned when an error occurs reading a characteristic. otherwise 200 is returned - response.writeHead(errorOccurred? 207: 200, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({characteristics: characteristics})); - }), false, session); - } else if (request.method == "PUT") { - if (!session.authenticated) { + response.writeHead(errorOccurred? HAPHTTPCode.MULTI_STATUS: HAPHTTPCode.OK, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify({ characteristics: characteristics })); + })); + } else if (request.method === "PUT") { + if (!connection.isAuthenticated()) { if (!request.headers || (request.headers && request.headers["authorization"] !== this.accessoryInfo.pincode)) { - response.writeHead(470, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({status: Status.INSUFFICIENT_PRIVILEGES})); + response.writeHead(HAPPairingHTTPCode.CONNECTION_AUTHORIZATION_REQUIRED, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify({status: HAPStatus.INSUFFICIENT_PRIVILEGES})); return; } } - if (requestData.length == 0) { + if (data.length === 0) { response.writeHead(400, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({status: Status.INVALID_VALUE_IN_REQUEST})); + response.end(JSON.stringify({status: HAPStatus.INVALID_VALUE_IN_REQUEST})); return; } - // requestData is a JSON payload like { characteristics: [ { aid: 1, iid: 8, value: true, ev: true } ] } - var writeRequest = JSON.parse(requestData.toString()) as CharacteristicsWriteRequest; - var data = writeRequest.characteristics; // pull out characteristics array - // call out to listeners to retrieve the latest accessories JSON - this.emit(HAPServerEventTypes.SET_CHARACTERISTICS, writeRequest, events, once((err: Error, characteristics: CharacteristicData[]) => { - if (err) { - debug("[%s] Error setting characteristics: %s", this.accessoryInfo.username, err.message); - // rewrite characteristics array to include error status for each characteristic requested - characteristics = []; - for (var i in data) { - characteristics.push({ - aid: data[i].aid, - iid: data[i].iid, - // @ts-ignore - status: Status.SERVICE_COMMUNICATION_FAILURE - }); - } + + const writeRequest = JSON.parse(data.toString("utf8")) as CharacteristicsWriteRequest; + + this.emit(HAPServerEventTypes.SET_CHARACTERISTICS, connection, writeRequest, once((error: HAPHttpError | undefined, writeResponse: CharacteristicsWriteResponse) => { + if (error) { + response.writeHead(error.httpCode, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify({ status: error.status })); + return; } + const characteristics = writeResponse.characteristics; + let multiStatus = false; - for (let i = 0; i < characteristics.length; i++) { - const characteristic = characteristics[i]; - if ((characteristic.status !== undefined && characteristic.status !== 0) - || (characteristic.s !== undefined && characteristic.s !== 0) - || characteristic.value !== undefined) { // also send multiStatus on write response requests + for (const data of characteristics) { + if (data.status || data.value !== undefined) { + // also send multiStatus on write response requests multiStatus = true; break; } } if (multiStatus) { - for (let i = 0; i < characteristics.length; i++) { // on a 207 Multi-Status EVERY characteristic MUST include a status property - const value = characteristics[i]; - if (value.status === undefined) { // a status is undefined if the request was successful - value.status = 0; // a value of zero indicates success + for (const data of characteristics) { // on a 207 Multi-Status EVERY characteristic MUST include a status property + if (data.status === undefined) { + data.status = HAPStatus.SUCCESS; } } // 207 is "multi-status" since HomeKit may be setting multiple things and any one can fail independently - response.writeHead(207, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({characteristics: characteristics})); + response.writeHead(HAPHTTPCode.MULTI_STATUS, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify({ characteristics: characteristics })); } else { // if everything went fine send 204 no content response - response.writeHead(204); // 204 "No content" + response.writeHead(HAPHTTPCode.NO_CONTENT); response.end(); } - }), false, session); + })); + } else { + response.writeHead(HAPHTTPCode.BAD_REQUEST, {"Content-Type": "application/hap+json"}); // method not allowed + response.end(JSON.stringify({ status: HAPStatus.INVALID_VALUE_IN_REQUEST })); } } - // Called when controller requests a timed write - _prepareWrite = (request: IncomingMessage, response: ServerResponse, session: Session, events: any, requestData: { length: number; toString: () => string; }) => { - if (!this.allowInsecureRequest && !session.authenticated) { - response.writeHead(470, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({status: Status.INSUFFICIENT_PRIVILEGES})); + private handlePrepareWrite(connection: HAPConnection, url: URL, request: IncomingMessage, data: Buffer, response: ServerResponse): void { + if (!this.allowInsecureRequest && !connection.isAuthenticated()) { + response.writeHead(HAPPairingHTTPCode.CONNECTION_AUTHORIZATION_REQUIRED, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify({status: HAPStatus.INSUFFICIENT_PRIVILEGES})); return; } if (request.method == "PUT") { - if (requestData.length == 0) { - response.writeHead(400, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({status: Status.INVALID_VALUE_IN_REQUEST})); + if (data.length == 0) { + response.writeHead(HAPHTTPCode.BAD_REQUEST, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify({status: HAPStatus.INVALID_VALUE_IN_REQUEST})); return; } - const data = JSON.parse(requestData.toString()) as PrepareWriteRequest; + const prepareRequest = JSON.parse(data.toString()) as PrepareWriteRequest; - if (data.pid && data.ttl) { - debug("[%s] Received prepare write request with pid %d and ttl %d", this.accessoryInfo.username, data.pid, data.ttl); + if (prepareRequest.pid && prepareRequest.ttl) { + debug("[%s] Received prepare write request with pid %d and ttl %d", this.accessoryInfo.username, prepareRequest.pid, prepareRequest.ttl); - if (session.timedWriteTimeout) // clear any currently existing timeouts - clearTimeout(session.timedWriteTimeout); + if (connection.timedWriteTimeout) // clear any currently existing timeouts + clearTimeout(connection.timedWriteTimeout); - session.timedWritePid = data.pid; - session.timedWriteTimeout = setTimeout(() => { - debug("[%s] Timed write request timed out for pid %d", this.accessoryInfo.username, data.pid); - session.timedWritePid = undefined; - session.timedWriteTimeout = undefined; - }, data.ttl); + connection.timedWritePid = prepareRequest.pid; + connection.timedWriteTimeout = setTimeout(() => { + debug("[%s] Timed write request timed out for pid %d", this.accessoryInfo.username, prepareRequest.pid); + connection.timedWritePid = undefined; + connection.timedWriteTimeout = undefined; + }, prepareRequest.ttl); - response.writeHead(200, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({status: Status.SUCCESS})); + response.writeHead(HAPHTTPCode.OK, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify({status: HAPStatus.SUCCESS})); return; + } else { + response.writeHead(HAPHTTPCode.BAD_REQUEST, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify({ status: HAPStatus.INVALID_VALUE_IN_REQUEST })); } + } else { + response.writeHead(HAPHTTPCode.BAD_REQUEST, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify({ status: HAPStatus.INVALID_VALUE_IN_REQUEST })); } }; - // Called when controller request snapshot - _handleResource = (request: IncomingMessage, response: ServerResponse, session: Session, events: any, requestData: { length: number; toString: () => string; }) => { - if (!this.allowInsecureRequest && !session.authenticated) { - response.writeHead(470, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({status: Status.INSUFFICIENT_PRIVILEGES})); - return; - } - if (request.method == "POST") { - if (!session.authenticated) { - if (!request.headers || (request.headers && request.headers["authorization"] !== this.accessoryInfo.pincode)) { - response.writeHead(470, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({status: Status.INSUFFICIENT_PRIVILEGES})); - return; - } + private handleResource(connection: HAPConnection, url: URL, request: IncomingMessage, data: Buffer, response: ServerResponse): void { + if (!connection.isAuthenticated()) { + if (!(this.allowInsecureRequest && request.headers && request.headers.authorization === this.accessoryInfo.pincode)) { + response.writeHead(HAPPairingHTTPCode.CONNECTION_AUTHORIZATION_REQUIRED, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify({ status: HAPStatus.INSUFFICIENT_PRIVILEGES })); + return; } - if (requestData.length == 0) { - response.writeHead(400, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({status: Status.INVALID_VALUE_IN_REQUEST})); + } + if (request.method === "POST") { + if (data.length === 0) { + response.writeHead(HAPHTTPCode.BAD_REQUEST, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify({ status: HAPStatus.INVALID_VALUE_IN_REQUEST })); return; } - // requestData is a JSON payload - var data = JSON.parse(requestData.toString()); + + const resourceRequest = JSON.parse(data.toString()) as ResourceRequest; // call out to listeners to retrieve the resource, snapshot only right now - this.emit(HAPServerEventTypes.REQUEST_RESOURCE, data, once((err: Error, resource: any) => { - if (err) { - debug("[%s] Error getting snapshot: %s", this.accessoryInfo.username, err.message); - response.writeHead(404); - response.end(); + this.emit(HAPServerEventTypes.REQUEST_RESOURCE, resourceRequest, once((error: HAPHttpError | undefined, resource: Buffer) => { + if (error) { + response.writeHead(error.httpCode, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify({ status: error.status })); } else { - response.writeHead(200, {"Content-Type": "image/jpeg"}); + response.writeHead(HAPHTTPCode.OK, {"Content-Type": "image/jpeg"}); response.end(resource); } })); } else { - response.writeHead(405); - response.end(); + response.writeHead(HAPHTTPCode.BAD_REQUEST, {"Content-Type": "application/hap+json"}); // method not allowed + response.end(JSON.stringify({ status: HAPStatus.INVALID_VALUE_IN_REQUEST })); } } } - - -/** - * Simple struct to hold vars needed to support HAP encryption. - */ - -export class HAPEncryption { - // initialize member vars with null-object values - clientPublicKey = Buffer.alloc(0); - secretKey = Buffer.alloc(0); - publicKey = Buffer.alloc(0); - sharedSec = Buffer.alloc(0); - hkdfPairEncKey = Buffer.alloc(0); - accessoryToControllerCount = {value: 0}; - controllerToAccessoryCount = {value: 0}; - accessoryToControllerKey = Buffer.alloc(0); - controllerToAccessoryKey = Buffer.alloc(0); - extraInfo: Record = {}; -} diff --git a/src/lib/Service.spec.ts b/src/lib/Service.spec.ts index 0dc295d95..12acaa1fa 100644 --- a/src/lib/Service.spec.ts +++ b/src/lib/Service.spec.ts @@ -1,5 +1,4 @@ -import './gen'; -import { Characteristic, Service, ServiceEventTypes, uuid } from '..'; +import { Characteristic, SerializedService, Service, ServiceEventTypes, uuid } from '..'; const createService = () => { return new Service('Test', uuid.generate('Foo'), 'subtype'); @@ -95,6 +94,13 @@ describe('Service', () => { expect(service.optionalCharacteristics).toBeDefined(); expect(json.optionalCharacteristics!.length).toEqual(service.optionalCharacteristics.length); }); + + it("should serialize service with proper constructor name", () => { + const service = new Service.Speaker("Speaker Name"); + + const json = Service.serialize(service); + expect(json.constructorName).toBe("Speaker"); + }); }); describe('#deserialize', () => { @@ -161,5 +167,23 @@ describe('Service', () => { expect(service.optionalCharacteristics).toBeDefined(); expect(service.optionalCharacteristics!.length).toEqual(5); // as defined in the Lightbulb service }); + + it("should deserialize from json with constructor name", () => { + const json: SerializedService = JSON.parse('{"displayName":"Speaker Name","UUID":"00000113-0000-1000-8000-0026BB765291",' + + '"constructorName":"Speaker","hiddenService":false,"primaryService":false,"characteristics":' + + '[{"displayName":"Name","UUID":"00000023-0000-1000-8000-0026BB765291","eventOnlyCharacteristic":false,"constructorName":"Name",' + + '"value":"Speaker Name","props":{"format":"string","perms":["pr"],"maxLen":64}},{"displayName":"Mute",' + + '"UUID":"0000011A-0000-1000-8000-0026BB765291","eventOnlyCharacteristic":false,' + + '"constructorName":"Mute","value":false,"props":{"format":"bool","perms":["ev","pr","pw"]}}],' + + '"optionalCharacteristics":[{"displayName":"Active","UUID":"000000B0-0000-1000-8000-0026BB765291",' + + '"eventOnlyCharacteristic":false,"constructorName":"Active","value":0,"props":{"format":"uint8","perms":["ev","pr","pw"],' + + '"minValue":0,"maxValue":1,"minStep":1}},{"displayName":"Volume","UUID":"00000119-0000-1000-8000-0026BB765291",' + + '"eventOnlyCharacteristic":false,"constructorName":"Volume","value":0,"props":{"format":"uint8","perms":["ev","pr","pw"],' + + '"unit":"percentage","minValue":0,"maxValue":100,"minStep":1}}]}'); + + const service = Service.deserialize(json); + + expect(service instanceof Service.Speaker).toBeTruthy(); + }); }); }); diff --git a/src/lib/Service.ts b/src/lib/Service.ts index b59f3287c..e4523f4c7 100644 --- a/src/lib/Service.ts +++ b/src/lib/Service.ts @@ -1,17 +1,100 @@ -import { Characteristic, CharacteristicEventTypes, SerializedCharacteristic } from './Characteristic'; -import { clone } from './util/clone'; -import { EventEmitter } from './EventEmitter'; +import assert from "assert"; +import { EventEmitter } from "events"; +import { ServiceJsonObject } from "../internal-types"; +import { CharacteristicValue, Nullable, WithUUID } from '../types'; +import { CharacteristicWarningType } from "./Accessory"; +import { + Characteristic, + CharacteristicChange, + CharacteristicEventTypes, + SerializedCharacteristic +} from './Characteristic'; +import { + AccessControl, + AccessoryInformation, + AccessoryRuntimeInformation, + AirPurifier, + AirQualitySensor, + AudioStreamManagement, + Battery, + CameraControl, + CameraOperatingMode, + CameraRecordingManagement, + CameraRTPStreamManagement, + CarbonDioxideSensor, + CarbonMonoxideSensor, + CloudRelay, + ContactSensor, + DataStreamTransportManagement, + Diagnostics, + Door, + Doorbell, + Fan, + Fanv2, + Faucet, + FilterMaintenance, + GarageDoorOpener, + HeaterCooler, + HumidifierDehumidifier, + HumiditySensor, + InputSource, + IrrigationSystem, + LeakSensor, + Lightbulb, + LightSensor, + LockManagement, + LockMechanism, + Microphone, + MotionSensor, + OccupancySensor, + Outlet, + Pairing, + PowerManagement, + ProtocolInformation, + SecuritySystem, + ServiceLabel, + Siri, + Slats, + SmartSpeaker, + SmokeSensor, + Speaker, + StatefulProgrammableSwitch, + StatelessProgrammableSwitch, + Switch, + TargetControl, + TargetControlManagement, + Television, + TelevisionSpeaker, + TemperatureSensor, + Thermostat, + ThreadTransport, + TransferTransportManagement, + Tunnel, + Valve, + WiFiRouter, + WiFiSatellite, + WiFiTransport, + Window, + WindowCovering, +} from "./definitions"; import { IdentifierCache } from './model/IdentifierCache'; -import { CharacteristicChange, CharacteristicValue, HapService, Nullable, ToHAPOptions, WithUUID, } from '../types'; -import * as HomeKitTypes from './gen'; +import { HAPConnection } from "./util/eventedhttp"; import { toShortForm } from './util/uuid'; +import Timeout = NodeJS.Timeout; +/** + * HAP spec allows a maximum of 100 characteristics per service! + */ const MAX_CHARACTERISTICS = 100; +/** + * @internal + */ export interface SerializedService { displayName: string, UUID: string, subtype?: string, + constructorName?: string, hiddenService?: boolean, primaryService?: boolean, @@ -22,25 +105,30 @@ export interface SerializedService { export type ServiceId = string; // string with the format: UUID + (subtype | "") +export type ServiceCharacteristicChange = CharacteristicChange & { characteristic: Characteristic }; + +// noinspection JSUnusedGlobalSymbols +/** + * @deprecated Use ServiceEventTypes instead + */ +export type EventService = ServiceEventTypes.CHARACTERISTIC_CHANGE | ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE; + export const enum ServiceEventTypes { CHARACTERISTIC_CHANGE = "characteristic-change", SERVICE_CONFIGURATION_CHANGE = "service-configurationChange", + CHARACTERISTIC_WARNING = "characteristic-warning", } -export type ServiceConfigurationChange = { - service: Service; -}; +export declare interface Service { + on(event: "characteristic-change", listener: (change: ServiceCharacteristicChange) => void): this; + on(event: "service-configurationChange", listener: () => void): this; + on(event: "characteristic-warning", listener: (characteristic: Characteristic, type: CharacteristicWarningType, message: string) => void): this; -type Events = { - [ServiceEventTypes.CHARACTERISTIC_CHANGE]: (change: CharacteristicChange) => void; - [ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE]: (change: ServiceConfigurationChange) => void; + emit(event: "characteristic-change", change: ServiceCharacteristicChange): boolean; + emit(event: "service-configurationChange"): boolean; + emit(event: "characteristic-warning", characteristic: Characteristic, type: CharacteristicWarningType, message: string): boolean; } -/** - * @deprecated Use ServiceEventTypes instead - */ -export type EventService = ServiceEventTypes.CHARACTERISTIC_CHANGE | ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE; - /** * Service represents a set of grouped values necessary to provide a logical function. For instance, a * "Door Lock Mechanism" service might contain two values, one for the "desired lock state" and one for the @@ -60,109 +148,141 @@ export type EventService = ServiceEventTypes.CHARACTERISTIC_CHANGE | ServiceEven * You can also define custom Services by providing your own UUID for the type that you generate yourself. * Custom Services can contain an arbitrary set of Characteristics, but Siri will likely not be able to * work with these. - * - * @event 'characteristic-change' => function({characteristic, oldValue, newValue, context}) { } - * Emitted after a change in the value of one of our Characteristics has occurred. */ -export class Service extends EventEmitter { - - static AccessControl: typeof HomeKitTypes.Generated.AccessControl; - static AccessoryInformation: typeof HomeKitTypes.Generated.AccessoryInformation; - static AirPurifier: typeof HomeKitTypes.Generated.AirPurifier; - static AirQualitySensor: typeof HomeKitTypes.Generated.AirQualitySensor; - static AudioStreamManagement: typeof HomeKitTypes.Remote.AudioStreamManagement; - static BatteryService: typeof HomeKitTypes.Generated.BatteryService; - static BridgeConfiguration: typeof HomeKitTypes.Bridged.BridgeConfiguration; - static BridgingState: typeof HomeKitTypes.Bridged.BridgingState; - static CameraControl: typeof HomeKitTypes.Bridged.CameraControl; - static CameraRTPStreamManagement: typeof HomeKitTypes.Generated.CameraRTPStreamManagement; - static CarbonDioxideSensor: typeof HomeKitTypes.Generated.CarbonDioxideSensor; - static CarbonMonoxideSensor: typeof HomeKitTypes.Generated.CarbonMonoxideSensor; - static ContactSensor: typeof HomeKitTypes.Generated.ContactSensor; - static DataStreamTransportManagement: typeof HomeKitTypes.DataStream.DataStreamTransportManagement; - static Door: typeof HomeKitTypes.Generated.Door; - static Doorbell: typeof HomeKitTypes.Generated.Doorbell; - static Fan: typeof HomeKitTypes.Generated.Fan; - static Fanv2: typeof HomeKitTypes.Generated.Fanv2; - static Faucet: typeof HomeKitTypes.Generated.Faucet; - static FilterMaintenance: typeof HomeKitTypes.Generated.FilterMaintenance; - static GarageDoorOpener: typeof HomeKitTypes.Generated.GarageDoorOpener; - static HeaterCooler: typeof HomeKitTypes.Generated.HeaterCooler; - static HumidifierDehumidifier: typeof HomeKitTypes.Generated.HumidifierDehumidifier; - static HumiditySensor: typeof HomeKitTypes.Generated.HumiditySensor; - static InputSource: typeof HomeKitTypes.TV.InputSource; - static IrrigationSystem: typeof HomeKitTypes.Generated.IrrigationSystem; +export class Service extends EventEmitter { + // Service MUST NOT have any other static variables + + // Pattern below is for automatic detection of the section of defined services. Used by the generator + // -=-=-=-=-=-=-=-=-=-=-=-=-=-=- + public static AccessControl: typeof AccessControl; + public static AccessoryInformation: typeof AccessoryInformation; + public static AccessoryRuntimeInformation: typeof AccessoryRuntimeInformation; + public static AirPurifier: typeof AirPurifier; + public static AirQualitySensor: typeof AirQualitySensor; + public static AudioStreamManagement: typeof AudioStreamManagement; + public static Battery: typeof Battery; + /** + * @deprecated Please use {@link Service.Battery}. + */ + public static BatteryService: typeof Battery; + /** + * @deprecated This service has no usage anymore and will be ignored by iOS + */ + public static CameraControl: typeof CameraControl; + /** + * @deprecated Please use {@link Service.CameraRecordingManagement}. + */ + public static CameraEventRecordingManagement: typeof CameraRecordingManagement; + public static CameraOperatingMode: typeof CameraOperatingMode; + public static CameraRecordingManagement: typeof CameraRecordingManagement; + public static CameraRTPStreamManagement: typeof CameraRTPStreamManagement; + public static CarbonDioxideSensor: typeof CarbonDioxideSensor; + public static CarbonMonoxideSensor: typeof CarbonMonoxideSensor; + public static CloudRelay: typeof CloudRelay; + public static ContactSensor: typeof ContactSensor; + public static DataStreamTransportManagement: typeof DataStreamTransportManagement; + public static Diagnostics: typeof Diagnostics; + public static Door: typeof Door; + public static Doorbell: typeof Doorbell; + public static Fan: typeof Fan; + public static Fanv2: typeof Fanv2; + public static Faucet: typeof Faucet; + public static FilterMaintenance: typeof FilterMaintenance; + public static GarageDoorOpener: typeof GarageDoorOpener; + public static HeaterCooler: typeof HeaterCooler; + public static HumidifierDehumidifier: typeof HumidifierDehumidifier; + public static HumiditySensor: typeof HumiditySensor; + public static InputSource: typeof InputSource; + public static IrrigationSystem: typeof IrrigationSystem; + public static LeakSensor: typeof LeakSensor; + public static Lightbulb: typeof Lightbulb; + public static LightSensor: typeof LightSensor; + public static LockManagement: typeof LockManagement; + public static LockMechanism: typeof LockMechanism; + public static Microphone: typeof Microphone; + public static MotionSensor: typeof MotionSensor; + public static OccupancySensor: typeof OccupancySensor; + public static Outlet: typeof Outlet; + public static Pairing: typeof Pairing; + public static PowerManagement: typeof PowerManagement; + public static ProtocolInformation: typeof ProtocolInformation; /** - * @deprecated Removed in iOS 11. Use ServiceLabel instead. + * @deprecated Please use {@link Service.CloudRelay}. */ - static Label: typeof HomeKitTypes.Generated.ServiceLabel; - static LeakSensor: typeof HomeKitTypes.Generated.LeakSensor; - static LightSensor: typeof HomeKitTypes.Generated.LightSensor; - static Lightbulb: typeof HomeKitTypes.Generated.Lightbulb; - static LockManagement: typeof HomeKitTypes.Generated.LockManagement; - static LockMechanism: typeof HomeKitTypes.Generated.LockMechanism; - static Microphone: typeof HomeKitTypes.Generated.Microphone; - static MotionSensor: typeof HomeKitTypes.Generated.MotionSensor; - static OccupancySensor: typeof HomeKitTypes.Generated.OccupancySensor; - static Outlet: typeof HomeKitTypes.Generated.Outlet; - static Pairing: typeof HomeKitTypes.Bridged.Pairing; - static ProtocolInformation: typeof HomeKitTypes.Bridged.ProtocolInformation; - static Relay: typeof HomeKitTypes.Bridged.Relay; - static SecuritySystem: typeof HomeKitTypes.Generated.SecuritySystem; - static ServiceLabel: typeof HomeKitTypes.Generated.ServiceLabel; - static Siri: typeof HomeKitTypes.Remote.Siri; - static Slat: typeof HomeKitTypes.Generated.Slat; - static SmokeSensor: typeof HomeKitTypes.Generated.SmokeSensor; - static SmartSpeaker: typeof HomeKitTypes.Generated.SmartSpeaker; - static Speaker: typeof HomeKitTypes.Generated.Speaker; - static StatefulProgrammableSwitch: typeof HomeKitTypes.Bridged.StatefulProgrammableSwitch; - static StatelessProgrammableSwitch: typeof HomeKitTypes.Generated.StatelessProgrammableSwitch; - static Switch: typeof HomeKitTypes.Generated.Switch; - static TargetControl: typeof HomeKitTypes.Remote.TargetControl; - static TargetControlManagement: typeof HomeKitTypes.Remote.TargetControlManagement; - static Television: typeof HomeKitTypes.TV.Television; - static TelevisionSpeaker: typeof HomeKitTypes.TV.TelevisionSpeaker; - static TemperatureSensor: typeof HomeKitTypes.Generated.TemperatureSensor; - static Thermostat: typeof HomeKitTypes.Generated.Thermostat; - static TimeInformation: typeof HomeKitTypes.Bridged.TimeInformation; - static TunneledBTLEAccessoryService: typeof HomeKitTypes.Bridged.TunneledBTLEAccessoryService; - static Valve: typeof HomeKitTypes.Generated.Valve; - static Window: typeof HomeKitTypes.Generated.Window; - static WindowCovering: typeof HomeKitTypes.Generated.WindowCovering; - static CameraOperatingMode: typeof HomeKitTypes.Generated.CameraOperatingMode; - static CameraEventRecordingManagement: typeof HomeKitTypes.Generated.CameraEventRecordingManagement; - static WiFiRouter: typeof HomeKitTypes.Generated.WiFiRouter; - static WiFiSatellite: typeof HomeKitTypes.Generated.WiFiSatellite; - static PowerManagement: typeof HomeKitTypes.Generated.PowerManagement; - static TransferTransportManagement: typeof HomeKitTypes.Generated.TransferTransportManagement; - - static AccessoryRuntimeInformation: typeof HomeKitTypes.Generated.AccessoryRuntimeInformation; - static Diagnostics: typeof HomeKitTypes.Generated.Diagnostics; - static WiFiTransport: typeof HomeKitTypes.Generated.WiFiTransport; + public static Relay: typeof CloudRelay; + public static SecuritySystem: typeof SecuritySystem; + public static ServiceLabel: typeof ServiceLabel; + public static Siri: typeof Siri; + /** + * @deprecated Please use {@link Service.Slats}. + */ + public static Slat: typeof Slats; + public static Slats: typeof Slats; + public static SmartSpeaker: typeof SmartSpeaker; + public static SmokeSensor: typeof SmokeSensor; + public static Speaker: typeof Speaker; + public static StatefulProgrammableSwitch: typeof StatefulProgrammableSwitch; + public static StatelessProgrammableSwitch: typeof StatelessProgrammableSwitch; + public static Switch: typeof Switch; + public static TargetControl: typeof TargetControl; + public static TargetControlManagement: typeof TargetControlManagement; + public static Television: typeof Television; + public static TelevisionSpeaker: typeof TelevisionSpeaker; + public static TemperatureSensor: typeof TemperatureSensor; + public static Thermostat: typeof Thermostat; + public static ThreadTransport: typeof ThreadTransport; + public static TransferTransportManagement: typeof TransferTransportManagement; + public static Tunnel: typeof Tunnel; + /** + * @deprecated Please use {@link Service.Tunnel}. + */ + public static TunneledBTLEAccessoryService: typeof Tunnel; + public static Valve: typeof Valve; + public static WiFiRouter: typeof WiFiRouter; + public static WiFiSatellite: typeof WiFiSatellite; + public static WiFiTransport: typeof WiFiTransport; + public static Window: typeof Window; + public static WindowCovering: typeof WindowCovering; + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-= // NOTICE: when adding/changing properties, remember to possibly adjust the serialize/deserialize functions + public displayName: string; + public UUID: string; + subtype?: string; iid: Nullable = null; // assigned later by our containing Accessory name: Nullable = null; characteristics: Characteristic[] = []; optionalCharacteristics: Characteristic[] = []; + /** + * @internal + */ isHiddenService: boolean = false; + /** + * @internal + */ isPrimaryService: boolean = false; // do not write to this directly + /** + * @internal + */ linkedServices: Service[] = []; - constructor(public displayName: string = "", public UUID: string, public subtype?: string) { + public constructor(displayName: string = "", UUID: string, subtype?: string) { super(); - if (!UUID) throw new Error("Services must be created with a valid UUID."); + assert(UUID, "Services must be created with a valid UUID."); + this.displayName = displayName; + this.UUID = UUID; + this.subtype = subtype; // every service has an optional Characteristic.Name property - we'll set it to our displayName // if one was given // if you don't provide a display name, some HomeKit apps may choose to hide the device. if (displayName) { // create the characteristic if necessary - var nameCharacteristic = + const nameCharacteristic = this.getCharacteristic(Characteristic.Name) || this.addCharacteristic(Characteristic.Name); - nameCharacteristic.setValue(displayName); + nameCharacteristic.updateValue(displayName); } } @@ -173,19 +293,20 @@ export class Service extends EventEmitter { * * @returns the serviceId */ - getServiceId(): ServiceId { + public getServiceId(): ServiceId { return this.UUID + (this.subtype || ""); } - addCharacteristic = (characteristic: Characteristic | {new (...args: any[]): Characteristic}, ...constructorArgs: any[]) => { - // characteristic might be a constructor like `Characteristic.Brightness` instead of an instance - // of Characteristic. Coerce if necessary. - if (typeof characteristic === 'function') { - characteristic = new characteristic(...constructorArgs) as Characteristic; - } + public addCharacteristic(input: Characteristic): Characteristic + public addCharacteristic(input: { new (...args: any[]): Characteristic }, ...constructorArgs: any[]): Characteristic + public addCharacteristic(input: Characteristic | {new (...args: any[]): Characteristic}, ...constructorArgs: any[]): Characteristic { + // characteristic might be a constructor like `Characteristic.Brightness` instead of an instance of Characteristic. Coerce if necessary. + + let characteristic = typeof input === "function"? new input(...constructorArgs): input; + // check for UUID conflict - for (var index in this.characteristics) { - var existing = this.characteristics[index]; + for (let index in this.characteristics) { + const existing = this.characteristics[index]; if (existing.UUID === characteristic.UUID) { if (characteristic.UUID === '00000052-0000-1000-8000-0026BB765291') { //This is a special workaround for the Firmware Revision characteristic. @@ -199,15 +320,11 @@ export class Service extends EventEmitter { throw new Error("Cannot add more than " + MAX_CHARACTERISTICS + " characteristics to a single service!"); } - // listen for changes in characteristics and bubble them up - characteristic.on(CharacteristicEventTypes.CHANGE, (change: CharacteristicChange) => { - // make a new object with the relevant characteristic added, and bubble it up - this.emit(ServiceEventTypes.CHARACTERISTIC_CHANGE, clone(change, { characteristic: characteristic })); - }); + this.setupCharacteristicEventHandlers(characteristic); this.characteristics.push(characteristic); - this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({ service: this })); + this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE); return characteristic; } @@ -220,9 +337,9 @@ export class Service extends EventEmitter { * * @param isPrimary {boolean} - optional boolean (default true) if the service should be the primary service */ - setPrimaryService = (isPrimary: boolean = true) => { + public setPrimaryService(isPrimary: boolean = true): void { this.isPrimaryService = isPrimary; - this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({ service: this })); + this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE); }; /** @@ -230,31 +347,44 @@ export class Service extends EventEmitter { * * @param isHidden {boolean} - optional boolean (default true) if the service should be marked hidden */ - setHiddenService = (isHidden: boolean = true) => { + public setHiddenService(isHidden: boolean = true): void { this.isHiddenService = isHidden; - this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({ service: this })); + this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE); } -//Allows setting other services that link to this one. - addLinkedService = (newLinkedService: Service) => { + /** + * Adds a new link to the specified service. The service MUST be already added to + * the SAME accessory. + * + * @param service - The service this service should link to + */ + public addLinkedService(service: Service): void { //TODO: Add a check if the service is on the same accessory. - if (!this.linkedServices.includes(newLinkedService)) - this.linkedServices.push(newLinkedService); - this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({ service: this })); + if (!this.linkedServices.includes(service)) { + this.linkedServices.push(service); + } + this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE); } - removeLinkedService = (oldLinkedService: Service) => { + /** + * Removes a link to the specified service which was previously added with {@link addLinkedService} + * + * @param service - Previously linked service + */ + public removeLinkedService(service: Service): void { //TODO: Add a check if the service is on the same accessory. - if (this.linkedServices.includes(oldLinkedService)) - this.linkedServices.splice(this.linkedServices.indexOf(oldLinkedService), 1); - this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({ service: this })); + const index = this.linkedServices.indexOf(service); + if (index !== -1) { + this.linkedServices.splice(index, 1); + } + this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE); } - removeCharacteristic = (characteristic: Characteristic) => { - var targetCharacteristicIndex; + public removeCharacteristic(characteristic: Characteristic): void { + let targetCharacteristicIndex; - for (var index in this.characteristics) { - var existingCharacteristic = this.characteristics[index]; + for (let index in this.characteristics) { + const existingCharacteristic = this.characteristics[index]; if (existingCharacteristic === characteristic) { targetCharacteristicIndex = index; @@ -266,21 +396,21 @@ export class Service extends EventEmitter { this.characteristics.splice(Number.parseInt(targetCharacteristicIndex), 1); characteristic.removeAllListeners(); - this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({ service: this })); + this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE); } } // If a Characteristic constructor is passed a Characteristic object will always be returned - getCharacteristic(constructor: WithUUID<{new (): Characteristic}>): Characteristic + public getCharacteristic(constructor: WithUUID<{new (): Characteristic}>): Characteristic // Still support using a Characteristic constructor or a name so "passing though" a value still works // https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html#use-union-types - getCharacteristic(name: string | WithUUID<{new (): Characteristic}>): Characteristic | undefined - getCharacteristic(name: string | WithUUID<{new (): Characteristic}>) { + public getCharacteristic(name: string | WithUUID<{new (): Characteristic}>): Characteristic | undefined + public getCharacteristic(name: string | WithUUID<{new (): Characteristic}>) { // returns a characteristic object from the service // If Service.prototype.getCharacteristic(Characteristic.Type) does not find the characteristic, // but the type is in optionalCharacteristics, it adds the characteristic.type to the service and returns it. - var index, characteristic: Characteristic; + let index, characteristic: Characteristic; for (index in this.characteristics) { characteristic = this.characteristics[index] as Characteristic; if (typeof name === 'string' && characteristic.displayName === name) { @@ -296,17 +426,20 @@ export class Service extends EventEmitter { return this.addCharacteristic(name); } } + + const instance = this.addCharacteristic(name); // Not found in optional Characteristics. Adding anyway, but warning about it if it isn't the Name. if (name.UUID !== Characteristic.Name.UUID) { - console.warn("HAP Warning: Characteristic %s not in required or optional characteristics for service %s. Adding anyway.", name.UUID, this.UUID); + instance.characteristicWarning("Characteristic not in required or optional characteristic section for service " + this.constructor.name + ". Adding anyway."); } - return this.addCharacteristic(name); + + return instance; } } - testCharacteristic = >(name: string | T) => { + public testCharacteristic>(name: string | T): boolean { // checks for the existence of a characteristic object in the service - var index, characteristic; + let index, characteristic; for (index in this.characteristics) { characteristic = this.characteristics[index]; if (typeof name === 'string' && characteristic.displayName === name) { @@ -318,18 +451,18 @@ export class Service extends EventEmitter { return false; } - setCharacteristic = >(name: string | T, value: CharacteristicValue) => { + public setCharacteristic>(name: string | T, value: CharacteristicValue): Service { this.getCharacteristic(name)!.setValue(value); return this; // for chaining } // A function to only updating the remote value, but not firing the 'set' event. - updateCharacteristic = >(name: string | T, value: CharacteristicValue) => { + public updateCharacteristic>(name: string | T, value: CharacteristicValue): Service { this.getCharacteristic(name)!.updateValue(value); return this; } - addOptionalCharacteristic = (characteristic: Characteristic | {new (): Characteristic}) => { + public addOptionalCharacteristic(characteristic: Characteristic | {new (): Characteristic}): void { // characteristic might be a constructor like `Characteristic.Brightness` instead of an instance // of Characteristic. Coerce if necessary. if (typeof characteristic === 'function') { @@ -340,16 +473,18 @@ export class Service extends EventEmitter { this.optionalCharacteristics.push(characteristic); } + // noinspection JSUnusedGlobalSymbols /** * This method was created to copy all characteristics from another service to this. * It's only adopting is currently in homebridge to merge the AccessoryInformation service. So some things * my be explicitly tailored towards this use case. * * It will not remove characteristics which are present currently but not added on the other characteristic. - * It will not replace the characteristic if the value is falsey (except of '0' or 'false') + * It will not replace the characteristic if the value is falsy (except of '0' or 'false') * @param service + * @internal used by homebridge */ - replaceCharacteristicsFromService(service: Service) { + replaceCharacteristicsFromService(service: Service): void { if (this.UUID !== service.UUID) { throw new Error(`Incompatible services. Tried replacing characteristics of ${this.UUID} with characteristics from ${service.UUID}`); } @@ -363,7 +498,7 @@ export class Service extends EventEmitter { delete foreignCharacteristics[characteristic.UUID]; if (!foreignCharacteristic.value && foreignCharacteristic.value !== 0 && foreignCharacteristic.value !== false) { - return; // ignore falsey values expect if its the number zero or literally false + return; // ignore falsy values expect if its the number zero or literally false } characteristic.props = foreignCharacteristic.props; @@ -373,6 +508,7 @@ export class Service extends EventEmitter { if (getListeners.length) { // the callback can only be called once so we remove all old listeners characteristic.removeAllListeners(CharacteristicEventTypes.GET); + // @ts-expect-error getListeners.forEach(listener => characteristic.addListener(CharacteristicEventTypes.GET, listener)); } @@ -380,6 +516,7 @@ export class Service extends EventEmitter { if (setListeners.length) { // the callback can only be called once so we remove all old listeners characteristic.removeAllListeners(CharacteristicEventTypes.SET); + // @ts-expect-error setListeners.forEach(listener => characteristic.addListener(CharacteristicEventTypes.SET, listener)); } } @@ -389,15 +526,21 @@ export class Service extends EventEmitter { Object.values(foreignCharacteristics).forEach(characteristic => this.addCharacteristic(characteristic)); } - getCharacteristicByIID = (iid: number) => { - for (var index in this.characteristics) { - var characteristic = this.characteristics[index]; + /** + * @internal + */ + getCharacteristicByIID(iid: number): Characteristic | undefined { + for (let index in this.characteristics) { + const characteristic = this.characteristics[index]; if (characteristic.iid === iid) return characteristic; } } - _assignIDs = (identifierCache: IdentifierCache, accessoryName: string, baseIID: number = 0) => { + /** + * @internal + */ + _assignIDs(identifierCache: IdentifierCache, accessoryName: string, baseIID: number = 0): void { // the Accessory Information service must have a (reserved by IdentifierCache) ID of 1 if (this.UUID === '0000003E-0000-1000-8000-0026BB765291') { this.iid = 1; @@ -407,71 +550,147 @@ export class Service extends EventEmitter { } // assign IIDs to our Characteristics - for (var index in this.characteristics) { - var characteristic = this.characteristics[index]; + for (let index in this.characteristics) { + const characteristic = this.characteristics[index]; characteristic._assignID(identifierCache, accessoryName, this.UUID, this.subtype); } } /** - * Returns a JSON representation of this Accessory suitable for delivering to HAP clients. + * Returns a JSON representation of this service suitable for delivering to HAP clients. + * @internal used to generate response to /accessories query */ - toHAP = (opt?: ToHAPOptions) => { - var characteristicsHAP = []; + toHAP(connection: HAPConnection): Promise { + return new Promise(resolve => { + assert(this.iid, "iid cannot be undefined for service '" + this.displayName + "'"); + assert(this.characteristics.length, "service '" + this.displayName + "' does not have any characteristics!"); + + const service: ServiceJsonObject = { + type: toShortForm(this.UUID), + iid: this.iid!, + characteristics: [], + hidden: this.isHiddenService? true: undefined, + primary: this.isPrimaryService? true: undefined, + } - for (var index in this.characteristics) { - var characteristic = this.characteristics[index]; - characteristicsHAP.push(characteristic.toHAP(opt)); - } + if (this.linkedServices.length) { + service.linked = []; + for (const linked of this.linkedServices) { + assert(linked.iid, "iid of linked service '" + linked.displayName + "' is undefined on service '" + this.displayName + "'"); + service.linked.push(linked.iid!); + } + } - const hap: Partial = { - iid: this.iid!, - type: toShortForm(this.UUID, HomeKitTypes.BASE_UUID), - characteristics: characteristicsHAP - }; + const missingCharacteristics: Set = new Set(); + let timeout: Timeout | undefined = setTimeout(() => { + for (const characteristic of missingCharacteristics) { + characteristic.characteristicWarning(`The read handler for the characteristic '${characteristic.displayName}' was slow to respond!`, CharacteristicWarningType.SLOW_READ); + } - if (this.isPrimaryService) { - hap['primary'] = this.isPrimaryService; - } + timeout = setTimeout(() => { + timeout = undefined; + + for (const characteristic of missingCharacteristics) { + characteristic.characteristicWarning("The read handler for the characteristic '" + characteristic?.displayName + "' didn't respond at all!. " + + "Please check that you properly call the callback!", CharacteristicWarningType.TIMEOUT_READ); + service.characteristics.push(characteristic.internalHAPRepresentation()); // value is set to null + } + + missingCharacteristics.clear(); + resolve(service); + }, 7000); + }, 3000); + + for (const characteristic of this.characteristics) { + missingCharacteristics.add(characteristic); + characteristic.toHAP(connection).then(value => { + if (!timeout) { + return; // if timeout is undefined, response was already sent out + } + + missingCharacteristics.delete(characteristic); + service.characteristics.push(value); + + if (missingCharacteristics.size === 0) { + if (timeout) { + clearTimeout(timeout); + timeout = undefined; + } + resolve(service); + } + }); + } + }); + } + + /** + * Returns a JSON representation of this service without characteristic values. + * @internal used to generate the config hash + */ + internalHAPRepresentation(): ServiceJsonObject { + assert(this.iid, "iid cannot be undefined for service '" + this.displayName + "'"); + assert(this.characteristics.length, "service '" + this.displayName + "' does not have any characteristics!"); - if (this.isHiddenService) { - hap['hidden'] = this.isHiddenService; + const service: ServiceJsonObject = { + type: toShortForm(this.UUID), + iid: this.iid!, + characteristics: this.characteristics.map(characteristic => characteristic.internalHAPRepresentation()), + hidden: this.isHiddenService? true: undefined, + primary: this.isPrimaryService? true: undefined, } - if (this.linkedServices.length > 0) { - hap['linked'] = []; - for (var index in this.linkedServices) { - var otherService = this.linkedServices[index]; - hap['linked'].push(otherService.iid!); + if (this.linkedServices.length) { + service.linked = []; + for (const linked of this.linkedServices) { + assert(linked.iid, "iid of linked service '" + linked.displayName + "' is undefined on service '" + this.displayName + "'"); + service.linked.push(linked.iid!); } } - return hap as HapService; + return service; } - _setupCharacteristic = (characteristic: Characteristic) => { + /** + * @internal + */ + private setupCharacteristicEventHandlers(characteristic: Characteristic): void { // listen for changes in characteristics and bubble them up characteristic.on(CharacteristicEventTypes.CHANGE, (change: CharacteristicChange) => { - // make a new object with the relevant characteristic added, and bubble it up - this.emit(ServiceEventTypes.CHARACTERISTIC_CHANGE, clone(change, { characteristic: characteristic })); + this.emit(ServiceEventTypes.CHARACTERISTIC_CHANGE, { ...change, characteristic: characteristic }); + }); + + characteristic.on(CharacteristicEventTypes.CHARACTERISTIC_WARNING, (type, message) => { + this.emit(ServiceEventTypes.CHARACTERISTIC_WARNING, characteristic, type, message); }); } - _sideloadCharacteristics = (targetCharacteristics: Characteristic[]) => { - for (var index in targetCharacteristics) { - var target = targetCharacteristics[index]; - this._setupCharacteristic(target); + /** + * @internal + */ + private _sideloadCharacteristics(targetCharacteristics: Characteristic[]): void { + for (const target of targetCharacteristics) { + this.setupCharacteristicEventHandlers(target); } this.characteristics = targetCharacteristics.slice(); } - static serialize = (service: Service): SerializedService => { + /** + * @internal + */ + static serialize(service: Service): SerializedService { + let constructorName: string | undefined; + if (service.constructor.name !== "Service") { + constructorName = service.constructor.name; // TODO test + } + return { displayName: service.displayName, UUID: service.UUID, subtype: service.subtype, + constructorName: constructorName, + hiddenService: service.isHiddenService, primaryService: service.isPrimaryService, @@ -480,8 +699,20 @@ export class Service extends EventEmitter { }; }; - static deserialize = (json: SerializedService): Service => { - const service = new Service(json.displayName, json.UUID, json.subtype); + /** + * @internal + */ + static deserialize(json: SerializedService): Service { + let service: Service; + + // TODO test + if (json.constructorName && json.constructorName.charAt(0).toUpperCase() === json.constructorName.charAt(0) + && Service[json.constructorName as keyof (typeof Service)]) { // MUST start with uppercase character and must exist on Service object + const constructor = Service[json.constructorName as keyof (typeof Service)] as { new(displayName?: string, subtype?: string): Service }; + service = new constructor(json.displayName, json.subtype); + } else { + service = new Service(json.displayName, json.UUID, json.subtype); + } service.isHiddenService = !!json.hiddenService; service.isPrimaryService = !!json.primaryService; diff --git a/src/lib/camera/Camera.ts b/src/lib/camera/Camera.ts index 855e20559..476665741 100644 --- a/src/lib/camera/Camera.ts +++ b/src/lib/camera/Camera.ts @@ -1,4 +1,5 @@ import { Service } from '../Service'; +// noinspection JSDeprecatedSymbols import { CameraStreamingDelegate, PrepareStreamCallback, @@ -13,6 +14,7 @@ import { } from "../.."; import { NodeCallback, SessionIdentifier } from '../../types'; +// noinspection JSDeprecatedSymbols /** * @deprecated */ @@ -22,7 +24,9 @@ export type PreparedStreamRequestCallback = (response: PreparedStreamResponse) = */ export type PreparedStreamResponse = PrepareStreamResponse; +// noinspection JSDeprecatedSymbols,JSUnusedGlobalSymbols export type Camera = LegacyCameraSource; // provide backwards compatibility +// noinspection JSDeprecatedSymbols /** * Interface of and old style CameraSource. See {@see configureCameraSource} for more Information. * @@ -42,6 +46,7 @@ export interface LegacyCameraSource { } +// noinspection JSDeprecatedSymbols export class LegacyCameraSourceAdapter implements CameraStreamingDelegate { private readonly cameraSource: LegacyCameraSource; diff --git a/src/lib/camera/RTPStreamManagement.ts b/src/lib/camera/RTPStreamManagement.ts index 32c992139..060f874ed 100644 --- a/src/lib/camera/RTPStreamManagement.ts +++ b/src/lib/camera/RTPStreamManagement.ts @@ -1,19 +1,15 @@ import crypto from 'crypto'; import createDebug from 'debug'; import net from "net"; +// noinspection JSDeprecatedSymbols import { LegacyCameraSource, LegacyCameraSourceAdapter, once, uuid } from "../../index"; import { CharacteristicValue, Nullable, SessionIdentifier } from '../../types'; -import { - Characteristic, - CharacteristicEventTypes, - CharacteristicGetCallback, - CharacteristicSetCallback -} from '../Characteristic'; +import { Characteristic, CharacteristicEventTypes, CharacteristicSetCallback } from '../Characteristic'; import { CameraController, CameraStreamingDelegate } from "../controller"; -import { CameraRTPStreamManagement } from "../gen/HomeKit"; -import { Status } from "../HAPServer"; +import type { CameraRTPStreamManagement } from "../definitions"; +import { HAPStatus } from "../HAPServer"; import { Service } from '../Service'; -import { EventedHTTPServer, Session } from "../util/eventedhttp"; +import { HAPConnection, HAPConnectionEvent } from "../util/eventedhttp"; import * as tlv from '../util/tlv'; import RTPProxy from './RTPProxy'; @@ -482,7 +478,12 @@ export class RTPStreamManagement { readonly supportedVideoStreamConfiguration: string; readonly supportedAudioStreamConfiguration: string; + /** + * @deprecated + */ connectionID?: SessionIdentifier; + private activeConnection?: HAPConnection; + private activeConnectionClosedListener?: () => void; sessionIdentifier?: StreamSessionIdentifier = undefined; streamStatus: StreamingStatus = StreamingStatus.AVAILABLE; // use _updateStreamStatus to update this property private ipVersion?: "ipv4" | "ipv6"; // ip version for the current session @@ -529,12 +530,14 @@ export class RTPStreamManagement { return this.service; } - // Private - - handleCloseConnection(connectionID: SessionIdentifier) { - if (this.connectionID && this.connectionID === connectionID) { - this._handleStopStream(); - } + // noinspection JSUnusedGlobalSymbols,JSUnusedLocalSymbols + /** + * @deprecated + */ + handleCloseConnection(connectionID: SessionIdentifier): void { + // This method is only here for legacy compatibility. It used to be called by legacy style CameraSource + // implementations to signal that the associated HAP connection was closed. + // This is now handled automatically. Thus we don't need to do anything anymore. } handleFactoryReset() { @@ -559,17 +562,22 @@ export class RTPStreamManagement { this.service.setCharacteristic(Characteristic.SetupEndpoints, this.setupEndpointsResponse); // reset SetupEndpoints to default this.service.getCharacteristic(Characteristic.SelectedRTPStreamConfiguration)! - .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { + .on(CharacteristicEventTypes.GET, callback => { callback(null, this.selectedConfiguration); }) .on(CharacteristicEventTypes.SET, this._handleSelectedStreamConfigurationWrite.bind(this)); this.service.getCharacteristic(Characteristic.SetupEndpoints)! - .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { + .on(CharacteristicEventTypes.GET, callback => { callback(null, this.setupEndpointsResponse); }) - .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback, context?: any, connectionID?: SessionIdentifier) => { - this.handleSetupEndpoints(value, callback, connectionID!); + .on(CharacteristicEventTypes.SET, (value, callback, context, connection) => { + if (!connection) { + debug("Set event handler for SetupEndpoints cannot be called from plugin. Connection undefined!"); + callback(HAPStatus.INVALID_VALUE_IN_REQUEST); + return; + } + this.handleSetupEndpoints(value, callback, connection); }); } @@ -583,8 +591,15 @@ export class RTPStreamManagement { SetupEndpointsResponseTypes.STATUS, SetupEndpointsStatus.ERROR, ).toString("base64"); + if (this.activeConnectionClosedListener && this.activeConnection) { + this.activeConnection.removeListener(HAPConnectionEvent.CLOSED, this.activeConnectionClosedListener); + this.activeConnectionClosedListener = undefined; + } + this._updateStreamStatus(StreamingStatus.AVAILABLE); this.sessionIdentifier = undefined; + this.activeConnection = undefined; + // noinspection JSDeprecatedSymbols this.connectionID = undefined; this.ipVersion = undefined; @@ -608,7 +623,7 @@ export class RTPStreamManagement { if (sessionIdentifier !== this.sessionIdentifier) { debug(`Received unknown session Identifier with request to ${SessionControlCommand[requestType]}`); - callback(new Error(Status.INVALID_VALUE_IN_REQUEST + "")); + callback(HAPStatus.INVALID_VALUE_IN_REQUEST); return; } @@ -641,7 +656,7 @@ export class RTPStreamManagement { case SessionControlCommand.SUSPEND_SESSION: default: debug(`Unhandled request type ${SessionControlCommand[requestType]}`); - callback(new Error(Status.INVALID_VALUE_IN_REQUEST + "")); + callback(HAPStatus.INVALID_VALUE_IN_REQUEST); return; } } @@ -844,7 +859,7 @@ export class RTPStreamManagement { this.delegate.handleStreamRequest(request, error => callback? callback(error): undefined); } - private handleSetupEndpoints(value: CharacteristicValue, callback: CharacteristicSetCallback, connectionID: SessionIdentifier): void { + private handleSetupEndpoints(value: CharacteristicValue, callback: CharacteristicSetCallback, connection: HAPConnection): void { const data = Buffer.from(value as string, 'base64'); const objects = tlv.decode(data); @@ -859,12 +874,14 @@ export class RTPStreamManagement { return; } - this.connectionID = connectionID; + this.activeConnection = connection; + this.activeConnection.on(HAPConnectionEvent.CLOSED, (this.activeConnectionClosedListener = this._handleStopStream.bind(this))); + + // noinspection JSDeprecatedSymbols + this.connectionID = connection.sessionID; this.sessionIdentifier = sessionIdentifier; this._updateStreamStatus(StreamingStatus.IN_USE); - const session: Session = Session.getSession(connectionID); - // Address const targetAddressPayload = objects[SetupEndpointsTypes.CONTROLLER_ADDRESS]; const processedAddressInfo = tlv.decode(targetAddressPayload); @@ -925,7 +942,7 @@ export class RTPStreamManagement { const promises: Promise[] = []; if (this.requireProxy) { - prepareRequest.targetAddress = session.getLocalAddress(addressVersion === IPAddressVersion.IPV6? "ipv6": "ipv4"); // ip versions must be the same + prepareRequest.targetAddress = connection.getLocalAddress(addressVersion === IPAddressVersion.IPV6? "ipv6": "ipv4"); // ip versions must be the same this.videoProxy = new RTPProxy({ outgoingAddress: controllerAddress, @@ -966,13 +983,13 @@ export class RTPStreamManagement { this.handleSessionClosed(); callback(error); } else { - this.generateSetupEndpointResponse(session, sessionIdentifier, prepareRequest, response, callback); + this.generateSetupEndpointResponse(connection, sessionIdentifier, prepareRequest, response, callback); } })); }); } - private generateSetupEndpointResponse(session: Session, identifier: StreamSessionIdentifier, request: PrepareStreamRequest, response: PrepareStreamResponse, callback: CharacteristicSetCallback): void { + private generateSetupEndpointResponse(connection: HAPConnection, identifier: StreamSessionIdentifier, request: PrepareStreamRequest, response: PrepareStreamResponse, callback: CharacteristicSetCallback): void { let address: string; let addressVersion = request.addressVersion; @@ -1009,7 +1026,7 @@ export class RTPStreamManagement { addressVersion = net.isIPv4(response.addressOverride)? "ipv4": "ipv6"; address = response.addressOverride; } else { - address = session.getLocalAddress(addressVersion); + address = connection.getLocalAddress(addressVersion); } if (request.addressVersion !== addressVersion) { @@ -1043,7 +1060,7 @@ export class RTPStreamManagement { } else { const videoInfo = response.video as ProxiedSourceResponse; - address = session.getLocalAddress(request.addressVersion); + address = connection.getLocalAddress(request.addressVersion); videoCryptoSuite = SRTPCryptoSuites.NONE; diff --git a/src/lib/controller/CameraController.ts b/src/lib/controller/CameraController.ts index 6c57c48ef..5a07ee92a 100644 --- a/src/lib/controller/CameraController.ts +++ b/src/lib/controller/CameraController.ts @@ -1,82 +1,101 @@ -import {Controller, ControllerServiceMap, ControllerType, DefaultControllerType} from "./Controller"; +import crypto from 'crypto'; +import createDebug from "debug"; +import { EventEmitter } from "events"; import { - CameraStreamingOptions, - Characteristic, - CharacteristicEventTypes, - CharacteristicGetCallback, - CharacteristicSetCallback, - CharacteristicValue, - LegacyCameraSourceAdapter, - PrepareStreamResponse, - PrepareStreamRequest, - RTPStreamManagement, - Service, SnapshotRequest, - StreamingRequest + CameraStreamingOptions, + Characteristic, + CharacteristicEventTypes, + CharacteristicGetCallback, + CharacteristicSetCallback, + CharacteristicValue, + HAPStatus, + LegacyCameraSourceAdapter, + PrepareStreamRequest, + PrepareStreamResponse, + RTPStreamManagement, + Service, + SnapshotRequest, + StreamingRequest } from "../.."; -import {NodeCallback, SessionIdentifier} from "../../types"; -import {Doorbell, Microphone, Speaker} from "../gen/HomeKit"; -import {EventEmitter} from "../EventEmitter"; -import crypto from 'crypto'; +import { SessionIdentifier } from "../../types"; +import type { Doorbell, Microphone, Speaker } from "../definitions"; +import { Controller, ControllerServiceMap, ControllerType, DefaultControllerType } from "./Controller"; +import Timeout = NodeJS.Timeout; + +const debug = createDebug("HAP-NodeJS:Camera:Controller") export interface CameraControllerOptions { - /** - * Amount of parallel camera streams the accessory is capable of running. - * As of the official HAP specification non Secure Video cameras have a minimum required amount of 2 (but 1 is also fine). - * Secure Video cameras just expose 1 stream. - * - * Default value: 1 - */ - cameraStreamCount?: number, - - /** - * Delegate which handles the actual RTP/RTCP video/audio streaming and Snapshot requests. - */ - delegate: CameraStreamingDelegate, - - /** - * Options regarding video/audio streaming - */ - streamingOptions: CameraStreamingOptions, - /** - * Options regarding Recordings (Secure Video) - */ - // recordingOptions: CameraRecordingOptions, // soon + /** + * Amount of parallel camera streams the accessory is capable of running. + * As of the official HAP specification non Secure Video cameras have a minimum required amount of 2 (but 1 is also fine). + * Secure Video cameras just expose 1 stream. + * + * Default value: 1 + */ + cameraStreamCount?: number, + + /** + * Delegate which handles the actual RTP/RTCP video/audio streaming and Snapshot requests. + */ + delegate: CameraStreamingDelegate, + + /** + * Options regarding video/audio streaming + */ + streamingOptions: CameraStreamingOptions, + /** + * Options regarding Recordings (Secure Video) + */ + // recordingOptions: CameraRecordingOptions, // soon } -export type SnapshotRequestCallback = (error?: Error, buffer?: Buffer) => void; +export type SnapshotRequestCallback = (error?: Error | HAPStatus, buffer?: Buffer) => void; export type PrepareStreamCallback = (error?: Error, response?: PrepareStreamResponse) => void; export type StreamRequestCallback = (error?: Error) => void; export interface CameraStreamingDelegate { - handleSnapshotRequest(request: SnapshotRequest, callback: SnapshotRequestCallback): void; - - prepareStream(request: PrepareStreamRequest, callback: PrepareStreamCallback): void; - handleStreamRequest(request: StreamingRequest, callback: StreamRequestCallback): void; + /** + * This method is called when a HomeKit controller requests a snapshot image for the given camera. + * The handler must respect the desired image height and width given in the {@link SnapshotRequest}. + * The returned Buffer (via the callback) must be encoded in jpeg. + * + * HAP-NodeJS will complain about slow running handlers after 5 seconds and terminate the request after 15 seconds. + * + * @param request - Request containing image size. + * @param callback - Callback supplied with the resulting Buffer + */ + handleSnapshotRequest(request: SnapshotRequest, callback: SnapshotRequestCallback): void; + + prepareStream(request: PrepareStreamRequest, callback: PrepareStreamCallback): void; + handleStreamRequest(request: StreamingRequest, callback: StreamRequestCallback): void; } export interface CameraControllerServiceMap extends ControllerServiceMap { - // "streamManagement%d": CameraRTPStreamManagement, // format to map all stream management services; indexed by zero + // "streamManagement%d": CameraRTPStreamManagement, // format to map all stream management services; indexed by zero - microphone?: Microphone, - speaker?: Speaker, + microphone?: Microphone, + speaker?: Speaker, - // cameraOperatingMode: CameraOperatingMode, // soon - // cameraEventRecordingManagement: CameraEventRecordingManagement // soon + // cameraOperatingMode: CameraOperatingMode, // soon + // cameraEventRecordingManagement: CameraEventRecordingManagement // soon - // this ServiceMap is also used by the DoorbellController; there is no necessity to declare it, but i think its good practice to reserve the namespace - doorbell?: Doorbell; + // this ServiceMap is also used by the DoorbellController; there is no necessity to declare it, but i think its good practice to reserve the namespace + doorbell?: Doorbell; } export const enum CameraControllerEvents { - MICROPHONE_PROPERTIES_CHANGED = "microphone-change", - SPEAKER_PROPERTIES_CHANGED = "speaker-change", + MICROPHONE_PROPERTIES_CHANGED = "microphone-change", + SPEAKER_PROPERTIES_CHANGED = "speaker-change", } -export type CameraControllerEventMap = { - [CameraControllerEvents.MICROPHONE_PROPERTIES_CHANGED]: (muted: boolean, volume: number) => void; - [CameraControllerEvents.SPEAKER_PROPERTIES_CHANGED]: (muted: boolean, volume: number) => void; +export declare interface CameraController { + on(event: "microphone-change", listener: (muted: boolean, volume: number) => void): this; + on(event: "speaker-change", listener: (muted: boolean, volume: number) => void): this; + + emit(event: "microphone-change", muted: boolean, volume: number): boolean; + emit(event: "speaker-change", muted: boolean, volume: number): boolean; } /** @@ -90,276 +109,326 @@ export type CameraControllerEventMap = { * Emitted when the mute state or the volume changed. The Apple Home App typically does not set those values * except the mute state. When you unmute the device microphone it will reset the mute state if it was set previously. */ -export class CameraController extends EventEmitter implements Controller { - - private static readonly STREAM_MANAGEMENT = "streamManagement"; // key to index all RTPStreamManagement services - - readonly controllerType: ControllerType = DefaultControllerType.CAMERA; - - private readonly streamCount: number; - private readonly delegate: CameraStreamingDelegate; - private readonly streamingOptions: CameraStreamingOptions; - // private readonly recordingOptions: CameraRecordingOptions, // soon - private readonly legacyMode: boolean = false; - - streamManagements: RTPStreamManagement[] = []; - - private microphoneService?: Microphone; - private speakerService?: Speaker; - - private microphoneMuted: boolean = false; - private microphoneVolume: number = 100; - private speakerMuted: boolean = false; - private speakerVolume: number = 100; - - constructor(options: CameraControllerOptions, legacyMode: boolean = false) { - super(); - this.streamCount = Math.max(1, options.cameraStreamCount || 1); - this.delegate = options.delegate; - this.streamingOptions = options.streamingOptions; - - this.legacyMode = legacyMode; // legacy mode will prent from Microphone and Speaker services to get created to avoid collisions +export class CameraController extends EventEmitter implements Controller { + + private static readonly STREAM_MANAGEMENT = "streamManagement"; // key to index all RTPStreamManagement services + + readonly controllerType: ControllerType = DefaultControllerType.CAMERA; + + private readonly streamCount: number; + private readonly delegate: CameraStreamingDelegate; + private readonly streamingOptions: CameraStreamingOptions; + // private readonly recordingOptions: CameraRecordingOptions, // soon + private readonly legacyMode: boolean = false; + + streamManagements: RTPStreamManagement[] = []; + + private microphoneService?: Microphone; + private speakerService?: Speaker; + + private microphoneMuted: boolean = false; + private microphoneVolume: number = 100; + private speakerMuted: boolean = false; + private speakerVolume: number = 100; + + constructor(options: CameraControllerOptions, legacyMode: boolean = false) { + super(); + this.streamCount = Math.max(1, options.cameraStreamCount || 1); + this.delegate = options.delegate; + this.streamingOptions = options.streamingOptions; + + this.legacyMode = legacyMode; // legacy mode will prent from Microphone and Speaker services to get created to avoid collisions + } + + // ----------------------------------- STREAM API ------------------------------------ + + /** + * Call this method if you want to forcefully suspend an ongoing streaming session. + * This would be adequate if the the rtp server or media encoding encountered an unexpected error. + * + * @param sessionId {SessionIdentifier} - id of the current ongoing streaming session + */ + public forceStopStreamingSession(sessionId: SessionIdentifier) { + this.streamManagements.forEach(management => { + if (management.sessionIdentifier === sessionId) { + management.forceStop(); + } + }); + } + + public static generateSynchronisationSource() { + const ssrc = crypto.randomBytes(4); // range [-2.14748e+09 - 2.14748e+09] + ssrc[0] = 0; + return ssrc.readInt32BE(0); + } + + // ----------------------------- MICROPHONE/SPEAKER API ------------------------------ + + public setMicrophoneMuted(muted: boolean = true) { + if (!this.microphoneService) { + return; } - // ----------------------------------- STREAM API ------------------------------------ - - /** - * Call this method if you want to forcefully suspend an ongoing streaming session. - * This would be adequate if the the rtp server or media encoding encountered an unexpected error. - * - * @param sessionId {SessionIdentifier} - id of the current ongoing streaming session - */ - public forceStopStreamingSession(sessionId: SessionIdentifier) { - this.streamManagements.forEach(management => { - if (management.sessionIdentifier === sessionId) { - management.forceStop(); - } - }); - } + this.microphoneMuted = muted; + this.microphoneService.updateCharacteristic(Characteristic.Mute, muted); + } - public static generateSynchronisationSource() { - const ssrc = crypto.randomBytes(4); // range [-2.14748e+09 - 2.14748e+09] - ssrc[0] = 0; - return ssrc.readInt32BE(0); + public setMicrophoneVolume(volume: number) { + if (!this.microphoneService) { + return; } - // ----------------------------- MICROPHONE/SPEAKER API ------------------------------ - - public setMicrophoneMuted(muted: boolean = true) { - if (!this.microphoneService) { - return; - } + this.microphoneVolume = volume; + this.microphoneService.updateCharacteristic(Characteristic.Volume, volume); + } - this.microphoneMuted = muted; - this.microphoneService.updateCharacteristic(Characteristic.Mute, muted); + public setSpeakerMuted(muted: boolean = true) { + if (!this.speakerService) { + return; } - public setMicrophoneVolume(volume: number) { - if (!this.microphoneService) { - return; - } + this.speakerMuted = muted; + this.speakerService.updateCharacteristic(Characteristic.Mute, muted); + } - this.microphoneVolume = volume; - this.microphoneService.updateCharacteristic(Characteristic.Volume, volume); + public setSpeakerVolume(volume: number) { + if (!this.speakerService) { + return; } - public setSpeakerMuted(muted: boolean = true) { - if (!this.speakerService) { - return; - } + this.speakerVolume = volume; + this.speakerService.updateCharacteristic(Characteristic.Volume, volume); + } - this.speakerMuted = muted; - this.speakerService.updateCharacteristic(Characteristic.Mute, muted); - } + private emitMicrophoneChange() { + this.emit(CameraControllerEvents.MICROPHONE_PROPERTIES_CHANGED, this.microphoneMuted, this.microphoneVolume); + } - public setSpeakerVolume(volume: number) { - if (!this.speakerService) { - return; - } + private emitSpeakerChange() { + this.emit(CameraControllerEvents.SPEAKER_PROPERTIES_CHANGED, this.speakerMuted, this.speakerVolume); + } - this.speakerVolume = volume; - this.speakerService.updateCharacteristic(Characteristic.Volume, volume); - } + // ----------------------------------------------------------------------------------- - private emitMicrophoneChange() { - this.emit(CameraControllerEvents.MICROPHONE_PROPERTIES_CHANGED, this.microphoneMuted, this.microphoneVolume); + constructServices(): CameraControllerServiceMap { + for (let i = 0; i < this.streamCount; i++) { + this.streamManagements.push(new RTPStreamManagement(i, this.streamingOptions, this.delegate)); } - private emitSpeakerChange() { - this.emit(CameraControllerEvents.SPEAKER_PROPERTIES_CHANGED, this.speakerMuted, this.speakerVolume); - } - - // ----------------------------------------------------------------------------------- - - constructServices(): CameraControllerServiceMap { - for (let i = 0; i < this.streamCount; i++) { - this.streamManagements.push(new RTPStreamManagement(i, this.streamingOptions, this.delegate)); - } - - if (!this.legacyMode && this.streamingOptions.audio) { - // In theory the Microphone Service is a necessity. In practice its not. lol. So we just add it if the user wants to support audio - this.microphoneService = new Service.Microphone('', ''); - this.microphoneService.setCharacteristic(Characteristic.Volume, this.microphoneVolume); - - if (this.streamingOptions.audio.twoWayAudio) { - this.speakerService = new Service.Speaker('', ''); - this.speakerService.setCharacteristic(Characteristic.Volume, this.speakerVolume); - } - } - - const serviceMap: CameraControllerServiceMap = { - microphone: this.microphoneService, - speaker: this.speakerService, - }; + if (!this.legacyMode && this.streamingOptions.audio) { + // In theory the Microphone Service is a necessity. In practice its not. lol. So we just add it if the user wants to support audio + this.microphoneService = new Service.Microphone('', ''); + this.microphoneService.setCharacteristic(Characteristic.Volume, this.microphoneVolume); - this.streamManagements.forEach((management, index) => serviceMap[CameraController.STREAM_MANAGEMENT + index] = management.getService()); - - return serviceMap; + if (this.streamingOptions.audio.twoWayAudio) { + this.speakerService = new Service.Speaker('', ''); + this.speakerService.setCharacteristic(Characteristic.Volume, this.speakerVolume); + } } - initWithServices(serviceMap: CameraControllerServiceMap): void | CameraControllerServiceMap { - let modifiedServiceMap = false; + const serviceMap: CameraControllerServiceMap = { + microphone: this.microphoneService, + speaker: this.speakerService, + }; - for (let i = 0; true; i++) { - let streamManagementService = serviceMap[CameraController.STREAM_MANAGEMENT + i]; + this.streamManagements.forEach((management, index) => serviceMap[CameraController.STREAM_MANAGEMENT + index] = management.getService()); - if (i < this.streamCount) { - if (streamManagementService) { // normal init - this.streamManagements.push(new RTPStreamManagement(i, this.streamingOptions, this.delegate, streamManagementService)); - } else { // stream count got bigger, we need to create a new service - const management = new RTPStreamManagement(i, this.streamingOptions, this.delegate); + return serviceMap; + } - this.streamManagements.push(management); - serviceMap[CameraController.STREAM_MANAGEMENT + i] = management.getService(); + initWithServices(serviceMap: CameraControllerServiceMap): void | CameraControllerServiceMap { + let modifiedServiceMap = false; - modifiedServiceMap = true; - } - } else { - if (streamManagementService) { // stream count got reduced, we need to remove old service - delete serviceMap[CameraController.STREAM_MANAGEMENT + i]; - modifiedServiceMap = true; - } else { - break; // we finished counting and we got no saved service; we are finished - } - } - } + for (let i = 0; true; i++) { + let streamManagementService = serviceMap[CameraController.STREAM_MANAGEMENT + i]; - // MICROPHONE - if (!this.legacyMode && this.streamingOptions.audio) { // microphone should be present - if (serviceMap.microphone) { - this.microphoneService = serviceMap.microphone; - } else { - // microphone wasn't created yet => create a new one - this.microphoneService = new Service.Microphone('', ''); - this.microphoneService.setCharacteristic(Characteristic.Volume, this.microphoneVolume); + if (i < this.streamCount) { + if (streamManagementService) { // normal init + this.streamManagements.push(new RTPStreamManagement(i, this.streamingOptions, this.delegate, streamManagementService)); + } else { // stream count got bigger, we need to create a new service + const management = new RTPStreamManagement(i, this.streamingOptions, this.delegate); - serviceMap.microphone = this.microphoneService; - modifiedServiceMap = true; - } - } else if (serviceMap.microphone) { // microphone service supplied, though settings seemed to have changed - // we need to remove it - delete serviceMap.microphone; - modifiedServiceMap = true; - } - - // SPEAKER - if (!this.legacyMode && this.streamingOptions.audio?.twoWayAudio) { // speaker should be present - if (serviceMap.speaker) { - this.speakerService = serviceMap.speaker; - } else { - // speaker wasn't created yet => create a new one - this.speakerService = new Service.Speaker('', ''); - this.speakerService.setCharacteristic(Characteristic.Volume, this.speakerVolume); + this.streamManagements.push(management); + serviceMap[CameraController.STREAM_MANAGEMENT + i] = management.getService(); - serviceMap.speaker = this.speakerService; - modifiedServiceMap = true; - } - } else if (serviceMap.speaker) { // speaker service supplied, though settings seemed to have changed - // we need to remove it - delete serviceMap.speaker; - modifiedServiceMap = true; + modifiedServiceMap = true; } - - if (this.migrateFromDoorbell(serviceMap)) { - modifiedServiceMap = true; + } else { + if (streamManagementService) { // stream count got reduced, we need to remove old service + delete serviceMap[CameraController.STREAM_MANAGEMENT + i]; + modifiedServiceMap = true; + } else { + break; // we finished counting and we got no saved service; we are finished } + } + } - if (modifiedServiceMap) { // serviceMap must only be returned if anything actually changed - return serviceMap; - } + // MICROPHONE + if (!this.legacyMode && this.streamingOptions.audio) { // microphone should be present + if (serviceMap.microphone) { + this.microphoneService = serviceMap.microphone; + } else { + // microphone wasn't created yet => create a new one + this.microphoneService = new Service.Microphone('', ''); + this.microphoneService.setCharacteristic(Characteristic.Volume, this.microphoneVolume); + + serviceMap.microphone = this.microphoneService; + modifiedServiceMap = true; + } + } else if (serviceMap.microphone) { // microphone service supplied, though settings seemed to have changed + // we need to remove it + delete serviceMap.microphone; + modifiedServiceMap = true; } - // overwritten in DoorbellController (to avoid cyclic dependencies, i hate typescript for that) - protected migrateFromDoorbell(serviceMap: ControllerServiceMap): boolean { - if (serviceMap.doorbell) { // See NOTICE in DoorbellController - delete serviceMap.doorbell; - return true; - } + // SPEAKER + if (!this.legacyMode && this.streamingOptions.audio?.twoWayAudio) { // speaker should be present + if (serviceMap.speaker) { + this.speakerService = serviceMap.speaker; + } else { + // speaker wasn't created yet => create a new one + this.speakerService = new Service.Speaker('', ''); + this.speakerService.setCharacteristic(Characteristic.Volume, this.speakerVolume); + + serviceMap.speaker = this.speakerService; + modifiedServiceMap = true; + } + } else if (serviceMap.speaker) { // speaker service supplied, though settings seemed to have changed + // we need to remove it + delete serviceMap.speaker; + modifiedServiceMap = true; + } - return false; + if (this.migrateFromDoorbell(serviceMap)) { + modifiedServiceMap = true; } - configureServices(): void { - if (this.microphoneService) { - this.microphoneService.getCharacteristic(Characteristic.Mute)! - .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { - callback(undefined, this.microphoneMuted); - }) - .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { - this.microphoneMuted = value as boolean; - callback(); - this.emitMicrophoneChange(); - }); - this.microphoneService.getCharacteristic(Characteristic.Volume)! - .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { - callback(undefined, this.microphoneVolume); - }) - .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { - this.microphoneVolume = value as number; - callback(); - this.emitMicrophoneChange(); - }); - } + if (modifiedServiceMap) { // serviceMap must only be returned if anything actually changed + return serviceMap; + } + } - if (this.speakerService) { - this.speakerService.getCharacteristic(Characteristic.Mute)! - .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { - callback(undefined, this.speakerMuted); - }) - .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { - this.speakerMuted = value as boolean; - callback(); - this.emitSpeakerChange(); - }); - this.speakerService.getCharacteristic(Characteristic.Volume)! - .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { - callback(undefined, this.speakerVolume); - }) - .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { - this.speakerVolume = value as number; - callback(); - this.emitSpeakerChange(); - }); - } + // overwritten in DoorbellController (to avoid cyclic dependencies, i hate typescript for that) + protected migrateFromDoorbell(serviceMap: ControllerServiceMap): boolean { + if (serviceMap.doorbell) { // See NOTICE in DoorbellController + delete serviceMap.doorbell; + return true; } - handleFactoryReset(): void { - this.streamManagements.forEach(management => management.handleFactoryReset()); + return false; + } + + configureServices(): void { + if (this.microphoneService) { + this.microphoneService.getCharacteristic(Characteristic.Mute)! + .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { + callback(undefined, this.microphoneMuted); + }) + .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { + this.microphoneMuted = value as boolean; + callback(); + this.emitMicrophoneChange(); + }); + this.microphoneService.getCharacteristic(Characteristic.Volume)! + .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { + callback(undefined, this.microphoneVolume); + }) + .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { + this.microphoneVolume = value as number; + callback(); + this.emitMicrophoneChange(); + }); } - handleSnapshotRequest(height: number, width: number, callback: NodeCallback) { - this.delegate.handleSnapshotRequest({ - height: height, - width: width, - }, callback); + if (this.speakerService) { + this.speakerService.getCharacteristic(Characteristic.Mute)! + .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { + callback(undefined, this.speakerMuted); + }) + .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { + this.speakerMuted = value as boolean; + callback(); + this.emitSpeakerChange(); + }); + this.speakerService.getCharacteristic(Characteristic.Volume)! + .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { + callback(undefined, this.speakerVolume); + }) + .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { + this.speakerVolume = value as number; + callback(); + this.emitSpeakerChange(); + }); } + } + + handleFactoryReset(): void { + this.streamManagements.forEach(management => management.handleFactoryReset()); + } + + handleSnapshotRequest(height: number, width: number, accessoryName?: string): Promise { + return new Promise((resolve, reject) => { + let timeout: Timeout | undefined = setTimeout(() => { + console.warn(`[${accessoryName}] Image snapshot handler is slow to respond!`); + + timeout = setTimeout(() => { + timeout = undefined; + + console.warn(`[${accessoryName}] Image snapshot handler didn't respond at all!`); - handleCloseConnection(sessionID: SessionIdentifier): void { - this.streamManagements.forEach(management => management.handleCloseConnection(sessionID)); + reject(HAPStatus.OPERATION_TIMED_OUT); + }, 10000); + timeout.unref(); + }, 5000); + timeout.unref(); - if (this.delegate instanceof LegacyCameraSourceAdapter) { - this.delegate.forwardCloseConnection(sessionID); + try { + this.delegate.handleSnapshotRequest({ + height: height, + width: width, + }, (error, buffer) => { + if (!timeout) { + return; + } else { + clearTimeout(timeout); + timeout = undefined; + } + + if (error) { + if (typeof error === "number") { + reject(error); + } else { + debug("[%s] Error getting snapshot: %s", accessoryName, error.stack); + reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE); + } + return; + } + + if (!buffer || buffer.length === 0) { + console.warn(`[${accessoryName}] Snapshot request handler provided empty image buffer!`); + reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE); + } else { + resolve(buffer); + } + }); + } catch (error) { + if (!timeout) { + return; + } else { + clearTimeout(timeout); + timeout = undefined; } + + console.warn(`[${accessoryName}] Unhandled error thrown inside snapshot request handler: ${error.stack}`); + reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE); + } + }); + } + + handleCloseConnection(sessionID: SessionIdentifier): void { + if (this.delegate instanceof LegacyCameraSourceAdapter) { + this.delegate.forwardCloseConnection(sessionID); } + } } diff --git a/src/lib/controller/DoorbellController.ts b/src/lib/controller/DoorbellController.ts index f7a0beeff..cbe407733 100644 --- a/src/lib/controller/DoorbellController.ts +++ b/src/lib/controller/DoorbellController.ts @@ -1,10 +1,10 @@ -import {CameraController, CameraControllerOptions, CameraControllerServiceMap} from "./CameraController"; -import {Doorbell, ProgrammableSwitchEvent} from "../gen/HomeKit"; -import {Service} from "../Service"; -import {Characteristic, CharacteristicEventTypes, CharacteristicGetCallback} from "../Characteristic"; -import {ControllerServiceMap} from "./Controller"; +import { Characteristic, CharacteristicEventTypes, CharacteristicGetCallback } from "../Characteristic"; +import type { Doorbell } from "../definitions"; +import { Service } from "../Service"; +import { CameraController, CameraControllerOptions, CameraControllerServiceMap } from "./CameraController"; +import { ControllerServiceMap } from "./Controller"; -export class DoorbellController extends CameraController { +export class DoorbellController extends CameraController { // TODO optional name characteristic /* * NOTICE: We subclass from the CameraController here and deliberately do not introduce/set a @@ -22,7 +22,7 @@ export class DoorbellController extends CameraController { } public ringDoorbell() { - this.doorbellService!.updateCharacteristic(Characteristic.ProgrammableSwitchEvent, ProgrammableSwitchEvent.SINGLE_PRESS); + this.doorbellService!.updateCharacteristic(Characteristic.ProgrammableSwitchEvent, Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS); } constructServices(): CameraControllerServiceMap { diff --git a/src/lib/controller/RemoteController.ts b/src/lib/controller/RemoteController.ts index ac5ba68e8..206dcc9dc 100644 --- a/src/lib/controller/RemoteController.ts +++ b/src/lib/controller/RemoteController.ts @@ -1,17 +1,14 @@ -import * as tlv from '../util/tlv'; -import createDebug from 'debug'; import assert from 'assert'; - -import {Service} from "../Service"; +import createDebug from 'debug'; +import { EventEmitter } from "events"; +import { CharacteristicValue } from "../../types"; +import { Accessory } from "../Accessory"; import { Characteristic, CharacteristicEventTypes, CharacteristicGetCallback, CharacteristicSetCallback } from "../Characteristic"; -import {CharacteristicValue, SessionIdentifier} from "../../types"; -import {Status} from "../HAPServer"; -import {Accessory} from "../Accessory"; import { DataSendCloseReason, DataStreamConnection, @@ -27,11 +24,18 @@ import { RequestHandler, Topics } from "../datastream"; -import {EventEmitter} from "../EventEmitter"; -import {HAPSessionEvents, Session} from "../util/eventedhttp"; -import {ControllerServiceMap, DefaultControllerType, SerializableController, StateChangeDelegate} from "./Controller"; -import {AudioStreamManagement, Siri, TargetControl, TargetControlManagement} from "../gen/HomeKit-Remote"; -import {DataStreamTransportManagement} from "../gen/HomeKit-DataStream"; +import type { + AudioStreamManagement, + DataStreamTransportManagement, + Siri, + TargetControl, + TargetControlManagement +} from '../definitions'; +import { HAPStatus } from "../HAPServer"; +import { Service } from "../Service"; +import { HAPConnection, HAPConnectionEvent } from "../util/eventedhttp"; +import * as tlv from '../util/tlv'; +import { ControllerServiceMap, DefaultControllerType, SerializableController, StateChangeDelegate } from "./Controller"; import Timeout = NodeJS.Timeout; const debug = createDebug('HAP-NodeJS:Remote:Controller'); @@ -49,6 +53,7 @@ const enum SupportedButtonConfigurationTypes { } export const enum ButtonType { + // noinspection JSUnusedGlobalSymbols UNDEFINED = 0x00, MENU = 0x01, PLAY_PAUSE = 0x02, @@ -72,6 +77,7 @@ const enum TargetControlList { } enum Operation { + // noinspection JSUnusedGlobalSymbols UNDEFINED = 0x00, LIST = 0x01, ADD = 0x02, @@ -88,6 +94,7 @@ const enum TargetConfigurationTypes { } export const enum TargetCategory { + // noinspection JSUnusedGlobalSymbols UNDEFINED = 0x00, APPLE_TV = 0x18 } @@ -137,11 +144,6 @@ export type ButtonConfiguration = { } -export const enum SiriInputType { - PUSH_BUTTON_TRIGGERED_APPLE_TV = 0, -} - - const enum SelectedAudioInputStreamConfigurationTypes { SELECTED_AUDIO_INPUT_STREAM_CONFIGURATION = 0x01, } @@ -149,6 +151,7 @@ const enum SelectedAudioInputStreamConfigurationTypes { // ---------- const enum SupportedAudioStreamConfigurationTypes { + // noinspection JSUnusedGlobalSymbols AUDIO_CODEC_CONFIGURATION = 0x01, COMFORT_NOISE_SUPPORT = 0x02, } @@ -159,6 +162,7 @@ const enum AudioCodecConfigurationTypes { } export const enum AudioCodecTypes { // only really by HAP supported codecs are AAC-ELD and OPUS + // noinspection JSUnusedGlobalSymbols PCMU = 0x00, PCMA = 0x01, AAC_ELD = 0x02, @@ -264,6 +268,13 @@ export interface SiriAudioStreamProducerConstructor { } +export const enum TargetUpdates { + NAME, + CATEGORY, + UPDATED_BUTTONS, + REMOVED_BUTTONS, +} + export const enum RemoteControllerEvents { ACTIVE_CHANGE = "active-change", ACTIVE_IDENTIFIER_CHANGE = "active-identifier-change", @@ -274,21 +285,22 @@ export const enum RemoteControllerEvents { TARGETS_RESET = "targets-reset", } -export const enum TargetUpdates { - NAME, - CATEGORY, - UPDATED_BUTTONS, - REMOVED_BUTTONS, -} +export declare interface RemoteController { + on(event: "active-change", listener: (active: boolean) => void): this; + on(event: "active-identifier-change", listener: (activeIdentifier: number) => void): this; -export type RemoteControllerEventMap = { - [RemoteControllerEvents.ACTIVE_CHANGE]: (active: boolean) => void; - [RemoteControllerEvents.ACTIVE_IDENTIFIER_CHANGE]: (activeIdentifier: number) => void; + on(event: "target-add", listener: (targetConfiguration: TargetConfiguration) => void): this; + on(event: "target-update", listener: (targetConfiguration: TargetConfiguration, updates: TargetUpdates[]) => void): this; + on(event: "target-remove", listener: (targetIdentifier: number) => void): this; + on(event: "targets-reset", listener: () => void): this; - [RemoteControllerEvents.TARGET_ADDED]: (targetConfiguration: TargetConfiguration) => void; - [RemoteControllerEvents.TARGET_UPDATED]: (targetConfiguration: TargetConfiguration, updates: TargetUpdates[]) => void; - [RemoteControllerEvents.TARGET_REMOVED]: (targetIdentifier: number) => void; - [RemoteControllerEvents.TARGETS_RESET]: () => void; + emit(event: "active-change", active: boolean): boolean; + emit(event: "active-identifier-change", activeIdentifier: number): boolean; + + emit(event: "target-add", targetConfiguration: TargetConfiguration): boolean; + emit(event: "target-update", targetConfiguration: TargetConfiguration, updates: TargetUpdates[]): boolean; + emit(event: "target-remove", targetIdentifier: number): boolean; + emit(event: "targets-reset"): boolean; } export interface RemoteControllerServiceMap extends ControllerServiceMap { @@ -335,8 +347,7 @@ export interface SerializedControllerState { * With this event every configuration made should be reset. This event is also called * when the accessory gets unpaired. */ -export class RemoteController extends EventEmitter - implements SerializableController, DataStreamProtocolHandler { +export class RemoteController extends EventEmitter implements SerializableController, DataStreamProtocolHandler { readonly controllerType = DefaultControllerType.REMOTE; stateChangeDelegate?: StateChangeDelegate; @@ -360,8 +371,8 @@ export class RemoteController extends EventEmitter private lastButtonEvent: string = ""; activeIdentifier: number = 0; // id of 0 means no device selected - private activeSession?: Session; // session which marked this remote as active and listens for events and siri - private activeSessionDisconnectionListener?: () => void; + private activeConnection?: HAPConnection; // session which marked this remote as active and listens for events and siri + private activeConnectionDisconnectListener?: () => void; supportedAudioConfiguration: string; selectedAudioConfiguration: AudioCodecConfiguration; @@ -385,7 +396,7 @@ export class RemoteController extends EventEmitter * @param producerOptions - if supplied this argument will be supplied as third argument of the SiriAudioStreamProducer * constructor. This should be used to supply configurations to the stream producer. */ - constructor(audioProducerConstructor?: SiriAudioStreamProducerConstructor, producerOptions?: any) { + public constructor(audioProducerConstructor?: SiriAudioStreamProducerConstructor, producerOptions?: any) { super(); this.audioSupported = audioProducerConstructor !== undefined; this.audioProducerConstructor = audioProducerConstructor; @@ -395,7 +406,7 @@ export class RemoteController extends EventEmitter this.supportedConfiguration = this.buildTargetControlSupportedConfigurationTLV(configuration); const audioConfiguration: SupportedAudioStreamConfiguration = this.constructSupportedAudioConfiguration(); - this.supportedAudioConfiguration = this.buildSupportedAudioConfigurationTLV(audioConfiguration); + this.supportedAudioConfiguration = RemoteController.buildSupportedAudioConfigurationTLV(audioConfiguration); this.selectedAudioConfiguration = { // set the required defaults codecType: AudioCodecTypes.OPUS, @@ -406,7 +417,7 @@ export class RemoteController extends EventEmitter rtpTime: 20, } }; - this.selectedAudioConfigurationString = this.buildSelectedAudioConfigurationTLV({ + this.selectedAudioConfigurationString = RemoteController.buildSelectedAudioConfigurationTLV({ audioCodecConfiguration: this.selectedAudioConfiguration, }); } @@ -416,7 +427,7 @@ export class RemoteController extends EventEmitter * * @param activeIdentifier {number} - target identifier */ - setActiveIdentifier = (activeIdentifier: number) => { + public setActiveIdentifier(activeIdentifier: number): void { if (activeIdentifier === this.activeIdentifier) { return; } @@ -440,8 +451,8 @@ export class RemoteController extends EventEmitter /** * @returns if the current target is active, meaning the active device is listening for button events or audio sessions */ - isActive = () => { - return !!this.activeSession; + public isActive(): boolean { + return !!this.activeConnection; }; /** @@ -449,7 +460,7 @@ export class RemoteController extends EventEmitter * * @param targetIdentifier {number} */ - isConfigured = (targetIdentifier: number) => { + public isConfigured(targetIdentifier: number): boolean { return this.targetConfigurations[targetIdentifier] !== undefined; }; @@ -459,7 +470,7 @@ export class RemoteController extends EventEmitter * @param name {string} - the name of the device * @returns the targetIdentifier of the device or undefined if not existent */ - getTargetIdentifierByName = (name: string) => { + public getTargetIdentifierByName(name: string): number | undefined { for (const activeIdentifier in this.targetConfigurations) { const configuration = this.targetConfigurations[activeIdentifier]; if (configuration.targetName === name) { @@ -474,7 +485,7 @@ export class RemoteController extends EventEmitter * * @param button {ButtonType} - button to be pressed */ - pushButton = (button: ButtonType) => { + public pushButton(button: ButtonType): void { this.sendButtonEvent(button, ButtonState.DOWN); }; @@ -483,7 +494,7 @@ export class RemoteController extends EventEmitter * * @param button {ButtonType} - button which was released */ - releaseButton = (button: ButtonType) => { + public releaseButton(button: ButtonType): void { this.sendButtonEvent(button, ButtonState.UP); }; @@ -493,7 +504,7 @@ export class RemoteController extends EventEmitter * @param button {ButtonType} - button to be pressed and released * @param time {number} - time in milliseconds (defaults to 200ms) */ - pushAndReleaseButton = (button: ButtonType, time: number = 200) => { + public pushAndReleaseButton(button: ButtonType, time: number = 200): void { this.pushButton(button); setTimeout(() => this.releaseButton(button), time); }; @@ -504,14 +515,14 @@ export class RemoteController extends EventEmitter * @param accessory {Accessory} - the give accessory this remote should be added to * @deprecated - use {@link Accessory.configureController} instead */ - addServicesToAccessory = (accessory: Accessory) => { + addServicesToAccessory(accessory: Accessory): void { accessory.configureController(this); }; // ---------------------------------- CONFIGURATION ---------------------------------- // override methods if you would like to change anything (but should not be necessary most likely) - constructSupportedConfiguration = () => { + protected constructSupportedConfiguration(): SupportedConfiguration { const configuration: SupportedConfiguration = { maximumTargets: 10, // some random number. (ten should be okay?) ticksPerSecond: 1000, // we rely on unix timestamps @@ -540,7 +551,7 @@ export class RemoteController extends EventEmitter return configuration; }; - constructSupportedAudioConfiguration = (): SupportedAudioStreamConfiguration => { + protected constructSupportedAudioConfiguration(): SupportedAudioStreamConfiguration { // the following parameters are expected from HomeKit for a remote return { audioCodecConfiguration: { @@ -556,7 +567,7 @@ export class RemoteController extends EventEmitter // --------------------------------- TARGET CONTROL ---------------------------------- - private handleTargetControlWrite = (value: any, callback: CharacteristicSetCallback) => { + private handleTargetControlWrite(value: any, callback: CharacteristicSetCallback): void { const data = Buffer.from(value, 'base64'); const objects = tlv.decode(data); @@ -569,7 +580,7 @@ export class RemoteController extends EventEmitter debug("Received TargetControl write operation %s", Operation[operation]); - let handler: (targetConfiguration?: TargetConfiguration) => Status; + let handler: (targetConfiguration?: TargetConfiguration) => HAPStatus; switch (operation) { case Operation.ADD: handler = this.handleAddTarget; @@ -587,12 +598,12 @@ export class RemoteController extends EventEmitter handler = this.handleListTargets; break; default: - callback(new Error(Status.INVALID_VALUE_IN_REQUEST+""), undefined); + callback(HAPStatus.INVALID_VALUE_IN_REQUEST, undefined); return; } const status = handler(targetConfiguration); - if (status === Status.SUCCESS) { + if (status === HAPStatus.SUCCESS) { callback(undefined, this.targetConfigurationsString); // passing value for write response if (operation === Operation.ADD && this.activeIdentifier === 0) { @@ -603,9 +614,9 @@ export class RemoteController extends EventEmitter } }; - private handleAddTarget = (targetConfiguration?: TargetConfiguration): Status => { + private handleAddTarget(targetConfiguration?: TargetConfiguration): HAPStatus { if (!targetConfiguration) { - return Status.INVALID_VALUE_IN_REQUEST; + return HAPStatus.INVALID_VALUE_IN_REQUEST; } this.targetConfigurations[targetConfiguration.targetIdentifier] = targetConfiguration; @@ -615,12 +626,12 @@ export class RemoteController extends EventEmitter setTimeout(() => this.emit(RemoteControllerEvents.TARGET_ADDED, targetConfiguration), 0); this.updatedTargetConfiguration(); // set response - return Status.SUCCESS; + return HAPStatus.SUCCESS; }; - private handleUpdateTarget = (targetConfiguration?: TargetConfiguration): Status => { + private handleUpdateTarget(targetConfiguration?: TargetConfiguration): HAPStatus { if (!targetConfiguration) { - return Status.INVALID_VALUE_IN_REQUEST; + return HAPStatus.INVALID_VALUE_IN_REQUEST; } const updates: TargetUpdates[] = []; @@ -659,17 +670,17 @@ export class RemoteController extends EventEmitter setTimeout(() => this.emit(RemoteControllerEvents.TARGET_UPDATED, targetConfiguration, updates), 0); this.updatedTargetConfiguration(); // set response - return Status.SUCCESS; + return HAPStatus.SUCCESS; }; - private handleRemoveTarget = (targetConfiguration?: TargetConfiguration): Status => { + private handleRemoveTarget(targetConfiguration?: TargetConfiguration): HAPStatus { if (!targetConfiguration) { - return Status.INVALID_VALUE_IN_REQUEST; + return HAPStatus.INVALID_VALUE_IN_REQUEST; } const configuredTarget = this.targetConfigurations[targetConfiguration.targetIdentifier]; if (!configuredTarget) { - return Status.INVALID_VALUE_IN_REQUEST; + return HAPStatus.INVALID_VALUE_IN_REQUEST; } if (targetConfiguration.buttonConfiguration) { @@ -691,12 +702,12 @@ export class RemoteController extends EventEmitter } this.updatedTargetConfiguration(); // set response - return Status.SUCCESS; + return HAPStatus.SUCCESS; }; - private handleResetTargets = (targetConfiguration?: TargetConfiguration): Status => { + private handleResetTargets(targetConfiguration?: TargetConfiguration): HAPStatus { if (targetConfiguration) { - return Status.INVALID_VALUE_IN_REQUEST; + return HAPStatus.INVALID_VALUE_IN_REQUEST; } debug("Resetting all target configurations"); @@ -706,46 +717,36 @@ export class RemoteController extends EventEmitter setTimeout(() => this.emit(RemoteControllerEvents.TARGETS_RESET), 0); this.setActiveIdentifier(0); // resetting active identifier (also sets active to false) - return Status.SUCCESS; + return HAPStatus.SUCCESS; }; - private handleListTargets = (targetConfiguration?: TargetConfiguration): Status => { + private handleListTargets(targetConfiguration?: TargetConfiguration): HAPStatus { if (targetConfiguration) { - return Status.INVALID_VALUE_IN_REQUEST; + return HAPStatus.INVALID_VALUE_IN_REQUEST; } // this.targetConfigurationsString is updated after each change, so we basically don't need to do anything here debug("Returning " + Object.keys(this.targetConfigurations).length + " target configurations"); - return Status.SUCCESS; + return HAPStatus.SUCCESS; }; - private handleActiveWrite = (value: CharacteristicValue, callback: CharacteristicSetCallback, connectionID?: string) => { - if (!connectionID) { - callback(new Error(Status.INVALID_VALUE_IN_REQUEST + "")); - return; - } - const session: Session = Session.getSession(connectionID); - if (!session) { - callback(new Error(Status.INVALID_VALUE_IN_REQUEST + "")); - return; - } - + private handleActiveWrite(value: CharacteristicValue, callback: CharacteristicSetCallback, connection: HAPConnection): void { if (this.activeIdentifier === 0) { debug("Tried to change active state. There is no active target set though"); - callback(new Error(Status.INVALID_VALUE_IN_REQUEST + "")); + callback(HAPStatus.INVALID_VALUE_IN_REQUEST); return; } - if (this.activeSession) { - this.activeSession.removeListener(HAPSessionEvents.CLOSED, this.activeSessionDisconnectionListener!); - this.activeSession = undefined; - this.activeSessionDisconnectionListener = undefined; + if (this.activeConnection) { + this.activeConnection.removeListener(HAPConnectionEvent.CLOSED, this.activeConnectionDisconnectListener!); + this.activeConnection = undefined; + this.activeConnectionDisconnectListener = undefined; } - this.activeSession = value? session: undefined; - if (this.activeSession) { // register listener when hap session disconnects - this.activeSessionDisconnectionListener = this.handleActiveSessionDisconnected.bind(this, this.activeSession); - this.activeSession.on(HAPSessionEvents.CLOSED, this.activeSessionDisconnectionListener); + this.activeConnection = value? connection: undefined; + if (this.activeConnection) { // register listener when hap connection disconnects + this.activeConnectionDisconnectListener = this.handleActiveSessionDisconnected.bind(this, this.activeConnection); + this.activeConnection.on(HAPConnectionEvent.CLOSED, this.activeConnectionDisconnectListener); } const activeName = this.targetConfigurations[this.activeIdentifier].targetName; @@ -756,14 +757,14 @@ export class RemoteController extends EventEmitter this.emit(RemoteControllerEvents.ACTIVE_CHANGE, value as boolean); }; - private setInactive = () => { - if (this.activeSession === undefined) { + private setInactive(): void { + if (this.activeConnection === undefined) { return; } - this.activeSession.removeListener(HAPSessionEvents.CLOSED, this.activeSessionDisconnectionListener!); - this.activeSession = undefined; - this.activeSessionDisconnectionListener = undefined; + this.activeConnection.removeListener(HAPConnectionEvent.CLOSED, this.activeConnectionDisconnectListener!); + this.activeConnection = undefined; + this.activeConnectionDisconnectListener = undefined; this.targetControlService!.getCharacteristic(Characteristic.Active)!.updateValue(false); debug("Remote was set to INACTIVE"); @@ -771,8 +772,8 @@ export class RemoteController extends EventEmitter setTimeout(() => this.emit(RemoteControllerEvents.ACTIVE_CHANGE, false), 0); }; - private handleActiveSessionDisconnected = (session: Session) => { - if (session !== this.activeSession) { + private handleActiveSessionDisconnected(connection: HAPConnection): void { + if (connection !== this.activeConnection) { return; } @@ -780,7 +781,7 @@ export class RemoteController extends EventEmitter this.setInactive(); }; - private sendButtonEvent = (button: ButtonType, buttonState: ButtonState) => { + private sendButtonEvent(button: ButtonType, buttonState: ButtonState) { const buttonID = this.buttons[button]; if (buttonID === undefined || buttonID === 0) { throw new Error("Tried sending button event for unsupported button (" + button + ")"); @@ -827,7 +828,7 @@ export class RemoteController extends EventEmitter this.targetControlService!.getCharacteristic(Characteristic.ButtonEvent)!.updateValue(this.lastButtonEvent); }; - private parseTargetConfigurationTLV = (data: Buffer): TargetConfiguration => { + private parseTargetConfigurationTLV(data: Buffer): TargetConfiguration { const configTLV = tlv.decode(data); const identifier = tlv.readUInt32(configTLV[TargetConfigurationTypes.TARGET_IDENTIFIER]); @@ -871,7 +872,7 @@ export class RemoteController extends EventEmitter }; }; - private updatedTargetConfiguration = () => { + private updatedTargetConfiguration(): void { const bufferList = []; for (const key in this.targetConfigurations) { // noinspection JSUnfilteredForInLoop @@ -923,7 +924,7 @@ export class RemoteController extends EventEmitter this.stateChangeDelegate && this.stateChangeDelegate(); }; - private buildTargetControlSupportedConfigurationTLV = (configuration: SupportedConfiguration) => { + private buildTargetControlSupportedConfigurationTLV(configuration: SupportedConfiguration): string { const maximumTargets = tlv.encode( TargetControlCommands.MAXIMUM_TARGETS, configuration.maximumTargets ); @@ -953,7 +954,7 @@ export class RemoteController extends EventEmitter // --------------------------------- SIRI/DATA STREAM -------------------------------- - private handleTargetControlWhoAmI = (connection: DataStreamConnection, message: Record) => { + private handleTargetControlWhoAmI(connection: DataStreamConnection, message: Record): void { const targetIdentifier = message["identifier"]; this.dataStreamConnections[targetIdentifier] = connection; debug("Discovered HDS connection for targetIdentifier %s", targetIdentifier); @@ -961,7 +962,7 @@ export class RemoteController extends EventEmitter connection.addProtocolHandler(Protocols.DATA_SEND, this); }; - private handleSiriAudioStart = () => { + private handleSiriAudioStart(): void { if (!this.audioSupported) { throw new Error("Cannot start siri stream on remote where siri is not supported"); } @@ -997,7 +998,7 @@ export class RemoteController extends EventEmitter audioSession.start(); }; - private handleSiriAudioStop = () => { + private handleSiriAudioStop(): void { if (this.activeAudioSession) { if (!this.activeAudioSession.isClosing()) { this.activeAudioSession.stop(); @@ -1011,7 +1012,7 @@ export class RemoteController extends EventEmitter debug("handleSiriAudioStop called although no audio session was started"); }; - private handleDataSendAckEvent = (message: Record) => { // transfer was successful + private handleDataSendAckEvent(message: Record): void { // transfer was successful const streamId = message["streamId"]; const endOfStream = message["endOfStream"]; @@ -1024,7 +1025,7 @@ export class RemoteController extends EventEmitter } }; - private handleDataSendCloseEvent = (message: Record) => { // controller indicates he can't handle audio request currently + private handleDataSendCloseEvent(message: Record): void { // controller indicates he can't handle audio request currently const streamId = message["streamId"]; const reason = message["reason"] as DataSendCloseReason; @@ -1037,7 +1038,7 @@ export class RemoteController extends EventEmitter } }; - private handleSiriAudioSessionClosed = (session: SiriAudioSession) => { + private handleSiriAudioSessionClosed(session: SiriAudioSession): void { if (session === this.activeAudioSession) { this.activeAudioSession = this.nextAudioSession; this.nextAudioSession = undefined; @@ -1046,7 +1047,7 @@ export class RemoteController extends EventEmitter } }; - private handleDataStreamConnectionClosed = (connection: DataStreamConnection) => { + private handleDataStreamConnectionClosed(connection: DataStreamConnection): void { for (const targetIdentifier in this.dataStreamConnections) { const connection0 = this.dataStreamConnections[targetIdentifier]; if (connection === connection0) { @@ -1059,7 +1060,7 @@ export class RemoteController extends EventEmitter // ------------------------------- AUDIO CONFIGURATION ------------------------------- - private handleSelectedAudioConfigurationWrite = (value: any, callback: CharacteristicSetCallback) => { + private handleSelectedAudioConfigurationWrite(value: any, callback: CharacteristicSetCallback): void { const data = Buffer.from(value, 'base64'); const objects = tlv.decode(data); @@ -1083,15 +1084,15 @@ export class RemoteController extends EventEmitter rtpTime: 20 } }; - this.selectedAudioConfigurationString = this.buildSelectedAudioConfigurationTLV({ + this.selectedAudioConfigurationString = RemoteController.buildSelectedAudioConfigurationTLV({ audioCodecConfiguration: this.selectedAudioConfiguration, }); callback(); }; - private buildSupportedAudioConfigurationTLV = (configuration: SupportedAudioStreamConfiguration) => { - const codecConfigurationTLV = this.buildCodecConfigurationTLV(configuration.audioCodecConfiguration); + private static buildSupportedAudioConfigurationTLV(configuration: SupportedAudioStreamConfiguration): string { + const codecConfigurationTLV = RemoteController.buildCodecConfigurationTLV(configuration.audioCodecConfiguration); const supportedAudioStreamConfiguration = tlv.encode( SupportedAudioStreamConfigurationTypes.AUDIO_CODEC_CONFIGURATION, codecConfigurationTLV @@ -1099,8 +1100,8 @@ export class RemoteController extends EventEmitter return supportedAudioStreamConfiguration.toString('base64'); }; - private buildSelectedAudioConfigurationTLV = (configuration: SelectedAudioStreamConfiguration) => { - const codecConfigurationTLV = this.buildCodecConfigurationTLV(configuration.audioCodecConfiguration); + private static buildSelectedAudioConfigurationTLV(configuration: SelectedAudioStreamConfiguration): string { + const codecConfigurationTLV = RemoteController.buildCodecConfigurationTLV(configuration.audioCodecConfiguration); const supportedAudioStreamConfiguration = tlv.encode( SelectedAudioInputStreamConfigurationTypes.SELECTED_AUDIO_INPUT_STREAM_CONFIGURATION, codecConfigurationTLV, @@ -1108,7 +1109,7 @@ export class RemoteController extends EventEmitter return supportedAudioStreamConfiguration.toString('base64'); }; - private buildCodecConfigurationTLV = (codecConfiguration: AudioCodecConfiguration) => { + private static buildCodecConfigurationTLV(codecConfiguration: AudioCodecConfiguration): Buffer { const parameters = codecConfiguration.parameters; let parametersTLV = tlv.encode( @@ -1146,7 +1147,7 @@ export class RemoteController extends EventEmitter if (this.audioSupported) { this.siriService = new Service.Siri('', ''); - this.siriService.setCharacteristic(Characteristic.SiriInputType, SiriInputType.PUSH_BUTTON_TRIGGERED_APPLE_TV); + this.siriService.setCharacteristic(Characteristic.SiriInputType, Characteristic.SiriInputType.PUSH_BUTTON_TRIGGERED_APPLE_TV); this.audioStreamManagementService = new Service.AudioStreamManagement('', ''); this.audioStreamManagementService.setCharacteristic(Characteristic.SupportedAudioStreamConfiguration, this.supportedAudioConfiguration); @@ -1183,23 +1184,26 @@ export class RemoteController extends EventEmitter } this.targetControlManagementService.getCharacteristic(Characteristic.TargetControlList)! - .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { + .on(CharacteristicEventTypes.GET, callback => { callback(null, this.targetConfigurationsString); }) - .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { - this.handleTargetControlWrite(value, callback); - }); + .on(CharacteristicEventTypes.SET, this.handleTargetControlWrite.bind(this)); this.targetControlService.getCharacteristic(Characteristic.ActiveIdentifier)! - .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { + .on(CharacteristicEventTypes.GET, callback => { callback(undefined, this.activeIdentifier); }); this.targetControlService.getCharacteristic(Characteristic.Active)! - .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { + .on(CharacteristicEventTypes.GET, callback => { callback(undefined, this.isActive()); }) - .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback, context?: any, connectionID?: SessionIdentifier) => { - this.handleActiveWrite(value, callback, connectionID); + .on(CharacteristicEventTypes.SET, (value, callback, context, connection) => { + if (!connection) { + debug("Set event handler for Remote.Active cannot be called from plugin. Connection undefined!"); + callback(HAPStatus.INVALID_VALUE_IN_REQUEST); + return; + } + this.handleActiveWrite(value, callback, connection); }); this.targetControlService.getCharacteristic(Characteristic.ButtonEvent)! .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { @@ -1208,12 +1212,11 @@ export class RemoteController extends EventEmitter if (this.audioSupported) { this.audioStreamManagementService!.getCharacteristic(Characteristic.SelectedAudioStreamConfiguration)! - .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { + .on(CharacteristicEventTypes.GET, callback => { callback(null, this.selectedAudioConfigurationString); }) - .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { - this.handleSelectedAudioConfigurationWrite(value, callback); - }).getValue(); + .on(CharacteristicEventTypes.SET, this.handleSelectedAudioConfigurationWrite.bind(this)) + .updateValue(this.selectedAudioConfigurationString); this.dataStreamManagement! .onEventMessage(Protocols.TARGET_CONTROL, Topics.WHOAMI, this.handleTargetControlWhoAmI.bind(this)) @@ -1263,14 +1266,16 @@ export const enum SiriAudioSessionEvents { CLOSE = "close", } -export type SiriAudioSessionEventMap = { - [SiriAudioSessionEvents.CLOSE]: () => void; +export declare interface SiriAudioSession { + on(event: "close", listener: () => void): this; + + emit(event: "close"): boolean; } /** * Represents an ongoing audio transmission */ -export class SiriAudioSession extends EventEmitter { +export class SiriAudioSession extends EventEmitter { readonly connection: DataStreamConnection; private readonly selectedAudioConfiguration: AudioCodecConfiguration; @@ -1375,6 +1380,7 @@ export class SiriAudioSession extends EventEmitter { this.producerTimer = undefined; this.handleProducerError(DataSendCloseReason.CANCELLED); }, 3000); + this.producerTimer.unref(); } private stopAudioProducer() { @@ -1440,7 +1446,7 @@ export class SiriAudioSession extends EventEmitter { } }; - private handleProducerError = (error: DataSendCloseReason) => { // called from audio producer + private handleProducerError(error: DataSendCloseReason): void { // called from audio producer if (this.state >= SiriAudioSessionState.CLOSING) { return; } @@ -1451,7 +1457,7 @@ export class SiriAudioSession extends EventEmitter { } }; - handleDataSendAckEvent = (endOfStream: boolean) => { // transfer was successful + handleDataSendAckEvent(endOfStream: boolean): void { // transfer was successful assert.strictEqual(endOfStream, true); debug("Received acknowledgment for siri audio stream with streamId %s, closing it now", this.streamId); @@ -1459,7 +1465,7 @@ export class SiriAudioSession extends EventEmitter { this.sendDataSendCloseEvent(DataSendCloseReason.NORMAL); }; - handleDataSendCloseEvent = (reason: DataSendCloseReason) => { // controller indicates he can't handle audio request currently + handleDataSendCloseEvent(reason: DataSendCloseReason): void { // controller indicates he can't handle audio request currently debug("Received close event from controller with reason %s for stream with streamId %s", DataSendCloseReason[reason], this.streamId); if (this.state <= SiriAudioSessionState.SENDING) { this.stopAudioProducer(); @@ -1468,7 +1474,7 @@ export class SiriAudioSession extends EventEmitter { this.closed(); }; - private sendDataSendCloseEvent = (reason: DataSendCloseReason) => { + private sendDataSendCloseEvent(reason: DataSendCloseReason): void { assert(this.state >= SiriAudioSessionState.SENDING, "state was less than SENDING"); assert(this.state <= SiriAudioSessionState.CLOSING, "state was higher than CLOSING"); @@ -1480,7 +1486,7 @@ export class SiriAudioSession extends EventEmitter { this.closed(); }; - private handleDataStreamConnectionClosed = () => { + private handleDataStreamConnectionClosed(): void { debug("Closing audio session with streamId %d", this.streamId); if (this.state <= SiriAudioSessionState.SENDING) { @@ -1490,7 +1496,7 @@ export class SiriAudioSession extends EventEmitter { this.closed(); }; - private closed = () => { + private closed(): void { const lastState = this.state; this.state = SiriAudioSessionState.CLOSED; diff --git a/src/lib/datastream/DataStreamManagement.ts b/src/lib/datastream/DataStreamManagement.ts index f00b3a5c9..f2791cffe 100644 --- a/src/lib/datastream/DataStreamManagement.ts +++ b/src/lib/datastream/DataStreamManagement.ts @@ -1,17 +1,17 @@ -import * as tlv from '../util/tlv'; import createDebug from "debug"; -import {Service} from "../Service"; +import { Characteristic, CharacteristicEventTypes, CharacteristicSetCallback } from "../Characteristic"; +import type { DataStreamTransportManagement } from "../definitions"; +import { HAPStatus } from "../HAPServer"; +import { Service } from "../Service"; +import { HAPConnection } from "../util/eventedhttp"; +import * as tlv from '../util/tlv'; import { - Characteristic, - CharacteristicEventTypes, - CharacteristicGetCallback, - CharacteristicSetCallback -} from "../Characteristic"; -import {CharacteristicValue, SessionIdentifier} from "../../types"; -import {DataStreamTransportManagement} from "../gen/HomeKit-DataStream"; -import {DataStreamServer, DataStreamServerEventMap, GlobalEventHandler, GlobalRequestHandler} from "./DataStreamServer"; -import {Session} from "../util/eventedhttp"; -import {Event} from "../EventEmitter"; + DataStreamConnection, + DataStreamServer, + DataStreamServerEvents, + GlobalEventHandler, + GlobalRequestHandler +} from "./DataStreamServer"; const debug = createDebug('HAP-NodeJS:DataStream:Management'); @@ -61,7 +61,7 @@ export class DataStreamManagement { // one server per accessory is probably the best practice private readonly dataStreamServer: DataStreamServer = new DataStreamServer(); - private dataStreamTransportManagementService: DataStreamTransportManagement; + private readonly dataStreamTransportManagementService: DataStreamTransportManagement; readonly supportedDataStreamTransportConfiguration: string; lastSetupDataStreamTransportResponse: string = ""; // stripped. excludes ACCESSORY_KEY_SALT @@ -139,12 +139,13 @@ export class DataStreamManagement { * @param event - the event to register for * @param listener - the event handler */ - onServerEvent(event: Event, listener: DataStreamServerEventMap[Event]): this { + onServerEvent(event: DataStreamServerEvents, listener: (connection: DataStreamConnection) => void): this { + // @ts-expect-error this.dataStreamServer.on(event, listener); return this; } - private handleSetupDataStreamTransportWrite(value: any, callback: CharacteristicSetCallback, connectionID?: string) { + private handleSetupDataStreamTransportWrite(value: any, callback: CharacteristicSetCallback, connection: HAPConnection) { const data = Buffer.from(value, 'base64'); const objects = tlv.decode(data); @@ -155,23 +156,12 @@ export class DataStreamManagement { debug("Received setup write with command %s and transport type %s", SessionCommandType[sessionCommandType], TransportType[transportType]); if (sessionCommandType === SessionCommandType.START_SESSION) { - if (transportType !== TransportType.HOMEKIT_DATA_STREAM) { - callback(null, DataStreamManagement.buildSetupStatusResponse(DataStreamStatus.GENERIC_ERROR)); - return; - } - - if (!connectionID) { // we need the session for the shared secret to generate the encryption keys - callback(null, DataStreamManagement.buildSetupStatusResponse(DataStreamStatus.GENERIC_ERROR)); + if (transportType !== TransportType.HOMEKIT_DATA_STREAM || controllerKeySalt.length !== 32) { + callback(HAPStatus.INVALID_VALUE_IN_REQUEST); return; } - const session: Session = Session.getSession(connectionID); - if (!session) { // we need the session for the shared secret to generate the encryption keys - callback(null, DataStreamManagement.buildSetupStatusResponse(DataStreamStatus.GENERIC_ERROR)); - return; - } - - this.dataStreamServer.prepareSession(session, controllerKeySalt, preparedSession => { + this.dataStreamServer.prepareSession(connection, controllerKeySalt, preparedSession => { const listeningPort = tlv.encode(TransportSessionConfiguration.TCP_LISTENING_PORT, tlv.writeUInt16(preparedSession.port!)); let response: Buffer = Buffer.concat([ @@ -187,15 +177,11 @@ export class DataStreamManagement { callback(null, response.toString('base64')); }); } else { - callback(null, DataStreamManagement.buildSetupStatusResponse(DataStreamStatus.GENERIC_ERROR)); + callback(HAPStatus.INVALID_VALUE_IN_REQUEST); return; } } - private static buildSetupStatusResponse(status: DataStreamStatus) { - return tlv.encode(SetupDataStreamWriteResponseTypes.STATUS, status).toString('base64'); - } - private buildSupportedDataStreamTransportConfigurationTLV(supportedConfiguration: TransportType[]): string { const buffers: Buffer[] = []; supportedConfiguration.forEach(type => { @@ -219,12 +205,18 @@ export class DataStreamManagement { private setupServiceHandlers() { this.dataStreamTransportManagementService.getCharacteristic(Characteristic.SetupDataStreamTransport)! - .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { + .on(CharacteristicEventTypes.GET, callback => { callback(null, this.lastSetupDataStreamTransportResponse); }) - .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback, context?: any, connectionID?: SessionIdentifier) => { - this.handleSetupDataStreamTransportWrite(value, callback, connectionID); - }).getValue(); + .on(CharacteristicEventTypes.SET, (value, callback, context, connection) => { + if (!connection) { + debug("Set event handler for SetupDataStreamTransport cannot be called from plugin! Connection undefined!"); + callback(HAPStatus.INVALID_VALUE_IN_REQUEST); + return; + } + this.handleSetupDataStreamTransportWrite(value, callback, connection); + }) + .updateValue(this.lastSetupDataStreamTransportResponse); } } diff --git a/src/lib/datastream/DataStreamParser.ts b/src/lib/datastream/DataStreamParser.ts index a9f07a055..ba7a38237 100644 --- a/src/lib/datastream/DataStreamParser.ts +++ b/src/lib/datastream/DataStreamParser.ts @@ -80,8 +80,8 @@ export const enum DataFormatTags { DATA_LENGTH64LE = 0x94, DATA_TERMINATED = 0x9F, - DEDUPLICATION_START = 0xA0, - DEDUPLICATION_STOP = 0xCF, + COMPRESSION_START = 0xA0, + COMPRESSION_STOP = 0xCF, ARRAY_LENGTH_START = 0xD0, ARRAY_LENGTH_STOP = 0xDE, @@ -153,9 +153,9 @@ export namespace DataStreamParser { return buffer.readData_Length64LE(); } else if (tag === DataFormatTags.DATA_TERMINATED) { return buffer.readData_terminated(); - } else if (tag >= DataFormatTags.DEDUPLICATION_START && tag <= DataFormatTags.DEDUPLICATION_STOP) { - const index = tag - DataFormatTags.DEDUPLICATION_START; - return buffer.deduplicateData(index); + } else if (tag >= DataFormatTags.COMPRESSION_START && tag <= DataFormatTags.COMPRESSION_STOP) { + const index = tag - DataFormatTags.COMPRESSION_START; + return buffer.decompressData(index); } else if (tag >= DataFormatTags.ARRAY_LENGTH_START && tag <= DataFormatTags.ARRAY_LENGTH_STOP) { const length = tag - DataFormatTags.ARRAY_LENGTH_START; const array = []; @@ -296,7 +296,7 @@ export class DataStreamReader { private readonly data: Buffer; readerIndex: number; - private deduplicationData: any[] = []; + private trackedCompressedData: any[] = []; constructor(data: Buffer) { this.data = data; @@ -310,16 +310,16 @@ export class DataStreamReader { } } - deduplicateData(index: number) { - if (index >= this.deduplicationData.length) { - throw new Error("HDSDecoder: Tried deduplication of data for an index out of range (index " + index + " and got " + this.deduplicationData.length + " elements)"); + decompressData(index: number) { + if (index >= this.trackedCompressedData.length) { + throw new Error("HDSDecoder: Tried decompression of data for an index out of range (index " + index + " and got " + this.trackedCompressedData.length + " elements)"); } - return this.deduplicationData[index]; + return this.trackedCompressedData[index]; } - private cache(data: any) { - this.deduplicationData.push(data); + private trackData(data: any) { + this.trackedCompressedData.push(data); return data; } @@ -336,38 +336,38 @@ export class DataStreamReader { } readTrue() { - return this.cache(true); // do those tag encoded values get cached? + return this.trackData(true); // do those tag encoded values get cached? } readFalse() { - return this.cache(false); + return this.trackData(false); } readNegOne() { - return this.cache(-1); + return this.trackData(-1); } readIntRange(tag: number) { - return this.cache(tag - DataFormatTags.INTEGER_RANGE_START_0); // integer values from 0-39 + return this.trackData(tag - DataFormatTags.INTEGER_RANGE_START_0); // integer values from 0-39 } readInt8() { this.ensureLength(1); - return this.cache(this.data.readInt8(this.readerIndex++)); + return this.trackData(this.data.readInt8(this.readerIndex++)); } readInt16LE() { this.ensureLength(2); const value = this.data.readInt16LE(this.readerIndex); this.readerIndex += 2; - return this.cache(value); + return this.trackData(value); } readInt32LE() { this.ensureLength(4); const value = this.data.readInt32LE(this.readerIndex); this.readerIndex += 4; - return this.cache(value); + return this.trackData(value); } readInt64LE() { @@ -380,20 +380,20 @@ export class DataStreamReader { } this.readerIndex += 8; - return this.cache(value); + return this.trackData(value); } readFloat32LE() { this.ensureLength(4); const value = this.data.readFloatLE(this.readerIndex); this.readerIndex += 4; - return this.cache(value); + return this.trackData(value); } readFloat64LE() { this.ensureLength(8); const value = this.data.readDoubleLE(this.readerIndex); - return this.cache(value); + return this.trackData(value); } private readLength8() { @@ -429,7 +429,7 @@ export class DataStreamReader { this.ensureLength(length); const value = this.data.toString('utf8', this.readerIndex, this.readerIndex + length); this.readerIndex += length; - return this.cache(value); + return this.trackData(value); } readUTF8_Length8() { @@ -470,7 +470,7 @@ export class DataStreamReader { const value = this.data.toString('utf8', this.readerIndex, offset); this.readerIndex = offset + 1; - return this.cache(value); + return this.trackData(value); } readData(length: number) { @@ -478,7 +478,7 @@ export class DataStreamReader { const value = this.data.slice(this.readerIndex, this.readerIndex + length); this.readerIndex += length; - return this.cache(value); + return this.trackData(value); } readData_Length8() { @@ -519,7 +519,7 @@ export class DataStreamReader { const value = this.data.slice(this.readerIndex, offset); this.readerIndex = offset + 1; - return this.cache(value); + return this.trackData(value); } readSecondsSince2001_01_01() { @@ -531,7 +531,7 @@ export class DataStreamReader { this.ensureLength(16); const value = uuid.unparse(this.data, this.readerIndex); this.readerIndex += 16; - return this.cache(value); + return this.trackData(value); } } @@ -597,15 +597,15 @@ export class DataStreamWriter { } } - private checkDeduplication(data: any): boolean { + private compressDataIfPossible(data: any): boolean { const index = this.writtenData.indexOf(data); if (index < 0) { // data is not present yet this.writtenData.push(data); return false; - } else if (index <= DataFormatTags.DEDUPLICATION_STOP - DataFormatTags.DEDUPLICATION_START) { + } else if (index <= DataFormatTags.COMPRESSION_STOP - DataFormatTags.COMPRESSION_START) { // data was already written and the index is in the applicable range => shorten the payload - this.writeTag(DataFormatTags.DEDUPLICATION_START + index); + this.writeTag(DataFormatTags.COMPRESSION_START + index); return true; } @@ -644,7 +644,7 @@ export class DataStreamWriter { } writeInt8(int8: Int8) { - if (this.checkDeduplication(int8)) { + if (this.compressDataIfPossible(int8)) { return; } @@ -654,7 +654,7 @@ export class DataStreamWriter { } writeInt16LE(int16: Int16) { - if (this.checkDeduplication(int16)) { + if (this.compressDataIfPossible(int16)) { return; } @@ -665,7 +665,7 @@ export class DataStreamWriter { } writeInt32LE(int32: Int32) { - if (this.checkDeduplication(int32)) { + if (this.compressDataIfPossible(int32)) { return; } @@ -676,7 +676,7 @@ export class DataStreamWriter { } writeInt64LE(int64: Int64) { - if (this.checkDeduplication(int64)) { + if (this.compressDataIfPossible(int64)) { return; } @@ -688,7 +688,7 @@ export class DataStreamWriter { } writeFloat32LE(float32: Float32) { - if (this.checkDeduplication(float32)) { + if (this.compressDataIfPossible(float32)) { return; } @@ -699,7 +699,7 @@ export class DataStreamWriter { } writeFloat64LE(float64: Float64) { - if (this.checkDeduplication(float64)) { + if (this.compressDataIfPossible(float64)) { return; } @@ -733,7 +733,7 @@ export class DataStreamWriter { } writeUTF8(utf8: string) { - if (this.checkDeduplication(utf8)) { + if (this.compressDataIfPossible(utf8)) { return; } @@ -808,7 +808,7 @@ export class DataStreamWriter { } writeData(data: Buffer) { - if (this.checkDeduplication(data)) { + if (this.compressDataIfPossible(data)) { return; } @@ -876,7 +876,7 @@ export class DataStreamWriter { } writeSecondsSince2001_01_01(seconds: SecondsSince2001) { - if (this.checkDeduplication(seconds)) { + if (this.compressDataIfPossible(seconds)) { return; } @@ -888,7 +888,7 @@ export class DataStreamWriter { writeUUID(uuid_string: string) { assert(uuid.isValid(uuid_string), "supplied uuid is invalid"); - if (this.checkDeduplication(new UUID(uuid_string))) { + if (this.compressDataIfPossible(new UUID(uuid_string))) { return; } diff --git a/src/lib/datastream/DataStreamServer.ts b/src/lib/datastream/DataStreamServer.ts index 0efdca5c6..f326f3c56 100644 --- a/src/lib/datastream/DataStreamServer.ts +++ b/src/lib/datastream/DataStreamServer.ts @@ -1,20 +1,19 @@ -import createDebug from 'debug'; import assert from 'assert'; +import crypto from 'crypto'; +import createDebug from 'debug'; +import { EventEmitter, EventEmitter as NodeEventEmitter } from "events"; +import net, { Socket } from 'net'; +import { HAPConnection, HAPConnectionEvent } from "../util/eventedhttp"; import * as hapCrypto from '../util/hapCrypto'; -import {DataStreamParser, DataStreamReader, DataStreamWriter, Int64} from './DataStreamParser'; -import crypto from 'crypto'; -import net, {Socket} from 'net'; -import {HAPSessionEvents, Session} from "../util/eventedhttp"; -import {EventEmitter as NodeEventEmitter} from "events"; -import {EventEmitter} from "../EventEmitter"; +import { DataStreamParser, DataStreamReader, DataStreamWriter, Int64 } from './DataStreamParser'; import Timeout = NodeJS.Timeout; const debug = createDebug('HAP-NodeJS:DataStream:Server'); export type PreparedDataStreamSession = { - session: Session, // reference to the hap session which created the request + connection: HAPConnection, // reference to the hap session which created the request accessoryToControllerEncryptionKey: Buffer, controllerToAccessoryEncryptionKey: Buffer, @@ -60,6 +59,7 @@ export const enum Topics { // a collection of currently known topics grouped by } export enum HDSStatus { + // noinspection JSUnusedGlobalSymbols SUCCESS = 0, OUT_OF_MEMORY = 1, TIMEOUT = 2, @@ -70,6 +70,7 @@ export enum HDSStatus { } export enum DataSendCloseReason { // close reason used in the dataSend protocol + // noinspection JSUnusedGlobalSymbols NORMAL = 0, NOT_ALLOWED = 1, BUSY = 2, @@ -122,26 +123,29 @@ type DataStreamMessage = { } export const enum DataStreamServerEvents { + /** + * This event is emitted when a new client socket is received. At this point we have no idea to what + * hap session this connection will be matched. + */ CONNECTION_OPENED = "connection-opened", + /** + * This event is emitted when the socket of a connection gets closed. + */ CONNECTION_CLOSED = "connection-closed", } -export type DataStreamServerEventMap = { - [DataStreamServerEvents.CONNECTION_OPENED]: (connection: DataStreamConnection) => void; - [DataStreamServerEvents.CONNECTION_CLOSED]: (connection: DataStreamConnection) => void; +export declare interface DataStreamServer { + on(event: "connection-opened", listener: (connection: DataStreamConnection) => void): this; + on(event: "connection-closed", listener: (connection: DataStreamConnection) => void): this; + + emit(event: "connection-opened", connection: DataStreamConnection): boolean; + emit(event: "connection-closed", connection: DataStreamConnection): boolean; } /** * DataStreamServer which listens for incoming tcp connections and handles identification of new connections - * - * @event 'connection-opened': (connection: DataStreamConnection) => void - * This event is emitted when a new client socket is received. At this point we have no idea to what - * hap session this connection will be matched. - * - * @event 'connection-closed': (connection: DataStreamConnection) => void - * This event is emitted when the socket of a connection gets closed. */ -export class DataStreamServer extends EventEmitter { +export class DataStreamServer extends EventEmitter { // TODO removeAllEvent handlers on closing static readonly version = "1.0"; @@ -216,28 +220,29 @@ export class DataStreamServer extends EventEmitter { return this; } - prepareSession(session: Session, controllerKeySalt: Buffer, callback: (preparedSession: PreparedDataStreamSession) => void) { - debug("Preparing for incoming HDS connection from session %s", session.sessionID); + prepareSession(connection: HAPConnection, controllerKeySalt: Buffer, callback: (preparedSession: PreparedDataStreamSession) => void) { + debug("Preparing for incoming HDS connection from %s", connection.sessionID); const accessoryKeySalt = crypto.randomBytes(32); const salt = Buffer.concat([controllerKeySalt, accessoryKeySalt]); - const accessoryToControllerEncryptionKey = hapCrypto.HKDF("sha512", salt, session.encryption!.sharedSec, DataStreamServer.accessoryToControllerInfo, 32); - const controllerToAccessoryEncryptionKey = hapCrypto.HKDF("sha512", salt, session.encryption!.sharedSec, DataStreamServer.controllerToAccessoryInfo, 32); + const accessoryToControllerEncryptionKey = hapCrypto.HKDF("sha512", salt, connection.encryption!.sharedSecret, DataStreamServer.accessoryToControllerInfo, 32); + const controllerToAccessoryEncryptionKey = hapCrypto.HKDF("sha512", salt, connection.encryption!.sharedSecret, DataStreamServer.controllerToAccessoryInfo, 32); const preparedSession: PreparedDataStreamSession = { - session: session, + connection: connection, accessoryToControllerEncryptionKey: accessoryToControllerEncryptionKey, controllerToAccessoryEncryptionKey: controllerToAccessoryEncryptionKey, accessoryKeySalt: accessoryKeySalt, connectTimeout: setTimeout(() => this.timeoutPreparedSession(preparedSession), 10000), }; + preparedSession.connectTimeout!.unref(); this.preparedSessions.push(preparedSession); this.checkTCPServerEstablished(preparedSession, () => callback(preparedSession)); } private timeoutPreparedSession(preparedSession: PreparedDataStreamSession) { - debug("Prepared HDS session timed out out since no connection was opened for 10 seconds (%s)", preparedSession.session.sessionID); + debug("Prepared HDS session timed out out since no connection was opened for 10 seconds (%s)", preparedSession.connection.sessionID); const index = this.preparedSessions.indexOf(preparedSession); if (index >= 0) { this.preparedSessions.splice(index, 1); @@ -316,7 +321,7 @@ export class DataStreamServer extends EventEmitter { callback(identifiedSession); if (identifiedSession) { - debug("[%s] Connection was successfully identified (linked with sessionId: %s)", connection._remoteAddress, identifiedSession.session.sessionID); + debug("[%s] Connection was successfully identified (linked with sessionId: %s)", connection.remoteAddress, identifiedSession.connection.sessionID); const index = this.preparedSessions.indexOf(identifiedSession); if (index >= 0) { this.preparedSessions.splice(index, 1); @@ -327,9 +332,9 @@ export class DataStreamServer extends EventEmitter { // we have currently no experience with data stream connections, maybe it would be good to index active connections // by their hap sessionId in order to clear out old but still open connections when the controller opens a new one - // on the other han the keepAlive should handle that also :thinking: + // on the other hand the keepAlive should handle that also :thinking: } else { // we looped through all session and didn't find anything - debug("[%s] Could not identify connection. Terminating.", connection._remoteAddress); + debug("[%s] Could not identify connection. Terminating.", connection.remoteAddress); connection.close(); // disconnecting since first message was not a valid hello } } @@ -352,17 +357,17 @@ export class DataStreamServer extends EventEmitter { hadListeners = this.internalEventEmitter.emit(message.protocol + separator + message.topic, connection, ...args); } catch (error) { hadListeners = true; - debug("[%s] Error occurred while dispatching handler for HDS message: %o", connection._remoteAddress, message); + debug("[%s] Error occurred while dispatching handler for HDS message: %o", connection.remoteAddress, message); debug(error.stack); } if (!hadListeners) { - debug("[%s] WARNING no handler was found for message: %o", connection._remoteAddress, message); + debug("[%s] WARNING no handler was found for message: %o", connection.remoteAddress, message); } } private connectionClosed(connection: DataStreamConnection) { - debug("[%s] DataStream connection closed", connection._remoteAddress); + debug("[%s] DataStream connection closed", connection.remoteAddress); this.connections.splice(this.connections.indexOf(connection), 1); this.emit(DataStreamServerEvents.CONNECTION_CLOSED, connection); @@ -389,18 +394,22 @@ export class DataStreamServer extends EventEmitter { } +export type IdentificationCallback = (identifiedSession?: PreparedDataStreamSession) => void; + export const enum DataStreamConnectionEvents { IDENTIFICATION = "identification", HANDLE_MESSAGE_GLOBALLY = "handle-message-globally", CLOSED = "closed", } -export type IdentificationCallback = (identifiedSession?: PreparedDataStreamSession) => void; +export declare interface DataStreamConnection { + on(event: "identification", listener: (frame: HDSFrame, callback: IdentificationCallback) => void): this; + on(event: "handle-message-globally", listener: (message: DataStreamMessage) => void): this; + on(event: "closed", listener: () => void): this; -export type DataStreamConnectionEventMap = { - [DataStreamConnectionEvents.IDENTIFICATION]: (frame: HDSFrame, callback: IdentificationCallback) => void; - [DataStreamConnectionEvents.HANDLE_MESSAGE_GLOBALLY]: (message: DataStreamMessage) => void; - [DataStreamConnectionEvents.CLOSED]: () => void; + emit(event: "identification", frame: HDSFrame, callback: IdentificationCallback): boolean; + emit(event: "handle-message-globally", message: DataStreamMessage): boolean; + emit(event: "closed"): boolean; } /** @@ -419,18 +428,18 @@ export type DataStreamConnectionEventMap = { * @event 'closed': () => void * This event is emitted when the socket of the connection was closed. */ -export class DataStreamConnection extends EventEmitter { +export class DataStreamConnection extends EventEmitter { private static readonly MAX_PAYLOAD_LENGTH = 0b11111111111111111111; private socket: Socket; - private session?: Session; // reference to the hap session. is present when state > UNIDENTIFIED - readonly _remoteAddress: string; + private connection?: HAPConnection; // reference to the hap connection. is present when state > UNIDENTIFIED + readonly remoteAddress: string; /* Since our DataStream server does only listen on one port and this port is supplied to every client which wants to connect, we do not really know which client is who when we receive a tcp connection. Thus, we find the correct PreparedDataStreamSession object by testing the encryption keys of all available - prepared sessions. Then we can reference this connection with the correct session and mark it as identified. + prepared sessions. Then we can reference this hds connection with the correct hap connection and mark it as identified. */ private state: ConnectionState = ConnectionState.UNIDENTIFIED; @@ -454,7 +463,7 @@ export class DataStreamConnection extends EventEmitter { - debug("[%s] Hello message did not arrive in time. Killing the connection", this._remoteAddress); + debug("[%s] Hello message did not arrive in time. Killing the connection", this.remoteAddress); this.close(); }, 10000); @@ -482,7 +491,7 @@ export class DataStreamConnection extends EventEmitter) { // that hello is indeed the _first_ message received is verified in onSocketData(...) - debug("[%s] Received hello message from client", this._remoteAddress); + debug("[%s] Received hello message from client", this.remoteAddress); clearTimeout(this.helloTimer!); this.helloTimer = undefined; @@ -610,25 +619,26 @@ export class DataStreamConnection extends EventEmitter { if (identifiedSession) { - // horray, we found our session - this.session = identifiedSession.session; + // horray, we found our connection + this.connection = identifiedSession.connection; this.accessoryToControllerEncryptionKey = identifiedSession.accessoryToControllerEncryptionKey; this.controllerToAccessoryEncryptionKey = identifiedSession.controllerToAccessoryEncryptionKey; this.state = ConnectionState.EXPECTING_HELLO; - this.session.on(HAPSessionEvents.CLOSED, this.onHAPSessionClosed.bind(this)); // register close listener + // TODO unregister event again + this.connection.on(HAPConnectionEvent.CLOSED, this.onHAPSessionClosed.bind(this)); // register close listener } }); if (this.state === ConnectionState.UNIDENTIFIED) { - // did not find a prepared session, server already closed this connection; nothing to do here + // did not find a prepared connection, server already closed this connection; nothing to do here return; } } for (; frameIndex < frames.length; frameIndex++) { // decrypt all remaining frames if (!this.decryptHDSFrame(frames[frameIndex])) { - debug("[%s] HDS frame decryption or authentication failed. Connection will be terminated!", this._remoteAddress); + debug("[%s] HDS frame decryption or authentication failed. Connection will be terminated!", this.remoteAddress); this.close(); return; } @@ -641,7 +651,7 @@ export class DataStreamConnection extends EventEmitter DataStreamConnection.MAX_PAYLOAD_LENGTH) { - debug("[%s] Connection send payload with size bigger than the maximum allow for data stream", this._remoteAddress); + debug("[%s] Connection send payload with size bigger than the maximum allow for data stream", this.remoteAddress); this.close(); return []; } @@ -764,7 +774,7 @@ export class DataStreamConnection extends EventEmitter { + describe("AccessControlLevel", () => { + it("should be able to construct", () => { + new Characteristic.AccessControlLevel(); + }); + }); + + describe("AccessoryFlags", () => { + it("should be able to construct", () => { + new Characteristic.AccessoryFlags(); + }); + }); + + describe("AccessoryIdentifier", () => { + it("should be able to construct", () => { + new Characteristic.AccessoryIdentifier(); + }); + }); + + describe("Active", () => { + it("should be able to construct", () => { + new Characteristic.Active(); + }); + }); + + describe("ActiveIdentifier", () => { + it("should be able to construct", () => { + new Characteristic.ActiveIdentifier(); + }); + }); + + describe("ActivityInterval", () => { + it("should be able to construct", () => { + new Characteristic.ActivityInterval(); + }); + }); + + describe("AdministratorOnlyAccess", () => { + it("should be able to construct", () => { + new Characteristic.AdministratorOnlyAccess(); + }); + }); + + describe("AirParticulateDensity", () => { + it("should be able to construct", () => { + new Characteristic.AirParticulateDensity(); + }); + }); + + describe("AirParticulateSize", () => { + it("should be able to construct", () => { + new Characteristic.AirParticulateSize(); + }); + }); + + describe("AirQuality", () => { + it("should be able to construct", () => { + new Characteristic.AirQuality(); + }); + }); + + describe("AppMatchingIdentifier", () => { + it("should be able to construct", () => { + new Characteristic.AppMatchingIdentifier(); + }); + }); + + describe("AudioFeedback", () => { + it("should be able to construct", () => { + new Characteristic.AudioFeedback(); + }); + }); + + describe("BatteryLevel", () => { + it("should be able to construct", () => { + new Characteristic.BatteryLevel(); + }); + }); + + describe("Brightness", () => { + it("should be able to construct", () => { + new Characteristic.Brightness(); + }); + }); + + describe("ButtonEvent", () => { + it("should be able to construct", () => { + new Characteristic.ButtonEvent(); + }); + }); + + describe("CameraOperatingModeIndicator", () => { + it("should be able to construct", () => { + new Characteristic.CameraOperatingModeIndicator(); + }); + }); + + describe("CarbonDioxideDetected", () => { + it("should be able to construct", () => { + new Characteristic.CarbonDioxideDetected(); + }); + }); + + describe("CarbonDioxideLevel", () => { + it("should be able to construct", () => { + new Characteristic.CarbonDioxideLevel(); + }); + }); + + describe("CarbonDioxidePeakLevel", () => { + it("should be able to construct", () => { + new Characteristic.CarbonDioxidePeakLevel(); + }); + }); + + describe("CarbonMonoxideDetected", () => { + it("should be able to construct", () => { + new Characteristic.CarbonMonoxideDetected(); + }); + }); + + describe("CarbonMonoxideLevel", () => { + it("should be able to construct", () => { + new Characteristic.CarbonMonoxideLevel(); + }); + }); + + describe("CarbonMonoxidePeakLevel", () => { + it("should be able to construct", () => { + new Characteristic.CarbonMonoxidePeakLevel(); + }); + }); + + describe("CCAEnergyDetectThreshold", () => { + it("should be able to construct", () => { + new Characteristic.CCAEnergyDetectThreshold(); + }); + }); + + describe("CCASignalDetectThreshold", () => { + it("should be able to construct", () => { + new Characteristic.CCASignalDetectThreshold(); + }); + }); + + describe("CharacteristicValueActiveTransitionCount", () => { + it("should be able to construct", () => { + new Characteristic.CharacteristicValueActiveTransitionCount(); + }); + }); + + describe("CharacteristicValueTransitionControl", () => { + it("should be able to construct", () => { + new Characteristic.CharacteristicValueTransitionControl(); + }); + }); + + describe("ChargingState", () => { + it("should be able to construct", () => { + new Characteristic.ChargingState(); + }); + }); + + describe("ClosedCaptions", () => { + it("should be able to construct", () => { + new Characteristic.ClosedCaptions(); + }); + }); + + describe("ColorTemperature", () => { + it("should be able to construct", () => { + new Characteristic.ColorTemperature(); + }); + }); + + describe("ConfiguredName", () => { + it("should be able to construct", () => { + new Characteristic.ConfiguredName(); + }); + }); + + describe("ContactSensorState", () => { + it("should be able to construct", () => { + new Characteristic.ContactSensorState(); + }); + }); + + describe("CoolingThresholdTemperature", () => { + it("should be able to construct", () => { + new Characteristic.CoolingThresholdTemperature(); + }); + }); + + describe("CurrentAirPurifierState", () => { + it("should be able to construct", () => { + new Characteristic.CurrentAirPurifierState(); + }); + }); + + describe("CurrentAmbientLightLevel", () => { + it("should be able to construct", () => { + new Characteristic.CurrentAmbientLightLevel(); + }); + }); + + describe("CurrentDoorState", () => { + it("should be able to construct", () => { + new Characteristic.CurrentDoorState(); + }); + }); + + describe("CurrentFanState", () => { + it("should be able to construct", () => { + new Characteristic.CurrentFanState(); + }); + }); + + describe("CurrentHeaterCoolerState", () => { + it("should be able to construct", () => { + new Characteristic.CurrentHeaterCoolerState(); + }); + }); + + describe("CurrentHeatingCoolingState", () => { + it("should be able to construct", () => { + new Characteristic.CurrentHeatingCoolingState(); + }); + }); + + describe("CurrentHorizontalTiltAngle", () => { + it("should be able to construct", () => { + new Characteristic.CurrentHorizontalTiltAngle(); + }); + }); + + describe("CurrentHumidifierDehumidifierState", () => { + it("should be able to construct", () => { + new Characteristic.CurrentHumidifierDehumidifierState(); + }); + }); + + describe("CurrentMediaState", () => { + it("should be able to construct", () => { + new Characteristic.CurrentMediaState(); + }); + }); + + describe("CurrentPosition", () => { + it("should be able to construct", () => { + new Characteristic.CurrentPosition(); + }); + }); + + describe("CurrentRelativeHumidity", () => { + it("should be able to construct", () => { + new Characteristic.CurrentRelativeHumidity(); + }); + }); + + describe("CurrentSlatState", () => { + it("should be able to construct", () => { + new Characteristic.CurrentSlatState(); + }); + }); + + describe("CurrentTemperature", () => { + it("should be able to construct", () => { + new Characteristic.CurrentTemperature(); + }); + }); + + describe("CurrentTiltAngle", () => { + it("should be able to construct", () => { + new Characteristic.CurrentTiltAngle(); + }); + }); + + describe("CurrentTransport", () => { + it("should be able to construct", () => { + new Characteristic.CurrentTransport(); + }); + }); + + describe("CurrentVerticalTiltAngle", () => { + it("should be able to construct", () => { + new Characteristic.CurrentVerticalTiltAngle(); + }); + }); + + describe("CurrentVisibilityState", () => { + it("should be able to construct", () => { + new Characteristic.CurrentVisibilityState(); + }); + }); + + describe("DataStreamHAPTransport", () => { + it("should be able to construct", () => { + new Characteristic.DataStreamHAPTransport(); + }); + }); + + describe("DataStreamHAPTransportInterrupt", () => { + it("should be able to construct", () => { + new Characteristic.DataStreamHAPTransportInterrupt(); + }); + }); + + describe("DiagonalFieldOfView", () => { + it("should be able to construct", () => { + new Characteristic.DiagonalFieldOfView(); + }); + }); + + describe("DigitalZoom", () => { + it("should be able to construct", () => { + new Characteristic.DigitalZoom(); + }); + }); + + describe("DisplayOrder", () => { + it("should be able to construct", () => { + new Characteristic.DisplayOrder(); + }); + }); + + describe("EventRetransmissionMaximum", () => { + it("should be able to construct", () => { + new Characteristic.EventRetransmissionMaximum(); + }); + }); + + describe("EventSnapshotsActive", () => { + it("should be able to construct", () => { + new Characteristic.EventSnapshotsActive(); + }); + }); + + describe("EventTransmissionCounters", () => { + it("should be able to construct", () => { + new Characteristic.EventTransmissionCounters(); + }); + }); + + describe("FilterChangeIndication", () => { + it("should be able to construct", () => { + new Characteristic.FilterChangeIndication(); + }); + }); + + describe("FilterLifeLevel", () => { + it("should be able to construct", () => { + new Characteristic.FilterLifeLevel(); + }); + }); + + describe("FirmwareRevision", () => { + it("should be able to construct", () => { + new Characteristic.FirmwareRevision(); + }); + }); + + describe("HardwareRevision", () => { + it("should be able to construct", () => { + new Characteristic.HardwareRevision(); + }); + }); + + describe("HeartBeat", () => { + it("should be able to construct", () => { + new Characteristic.HeartBeat(); + }); + }); + + describe("HeatingThresholdTemperature", () => { + it("should be able to construct", () => { + new Characteristic.HeatingThresholdTemperature(); + }); + }); + + describe("HoldPosition", () => { + it("should be able to construct", () => { + new Characteristic.HoldPosition(); + }); + }); + + describe("HomeKitCameraActive", () => { + it("should be able to construct", () => { + new Characteristic.HomeKitCameraActive(); + }); + }); + + describe("Hue", () => { + it("should be able to construct", () => { + new Characteristic.Hue(); + }); + }); + + describe("Identifier", () => { + it("should be able to construct", () => { + new Characteristic.Identifier(); + }); + }); + + describe("Identify", () => { + it("should be able to construct", () => { + new Characteristic.Identify(); + }); + }); + + describe("ImageMirroring", () => { + it("should be able to construct", () => { + new Characteristic.ImageMirroring(); + }); + }); + + describe("ImageRotation", () => { + it("should be able to construct", () => { + new Characteristic.ImageRotation(); + }); + }); + + describe("InputDeviceType", () => { + it("should be able to construct", () => { + new Characteristic.InputDeviceType(); + }); + }); + + describe("InputSourceType", () => { + it("should be able to construct", () => { + new Characteristic.InputSourceType(); + }); + }); + + describe("InUse", () => { + it("should be able to construct", () => { + new Characteristic.InUse(); + }); + }); + + describe("IsConfigured", () => { + it("should be able to construct", () => { + new Characteristic.IsConfigured(); + }); + }); + + describe("LeakDetected", () => { + it("should be able to construct", () => { + new Characteristic.LeakDetected(); + }); + }); + + describe("ListPairings", () => { + it("should be able to construct", () => { + new Characteristic.ListPairings(); + // noinspection JSDeprecatedSymbols + new Characteristic.PairingPairings(); + }); + }); + + describe("LockControlPoint", () => { + it("should be able to construct", () => { + new Characteristic.LockControlPoint(); + }); + }); + + describe("LockCurrentState", () => { + it("should be able to construct", () => { + new Characteristic.LockCurrentState(); + }); + }); + + describe("LockLastKnownAction", () => { + it("should be able to construct", () => { + new Characteristic.LockLastKnownAction(); + }); + }); + + describe("LockManagementAutoSecurityTimeout", () => { + it("should be able to construct", () => { + new Characteristic.LockManagementAutoSecurityTimeout(); + }); + }); + + describe("LockPhysicalControls", () => { + it("should be able to construct", () => { + new Characteristic.LockPhysicalControls(); + }); + }); + + describe("LockTargetState", () => { + it("should be able to construct", () => { + new Characteristic.LockTargetState(); + }); + }); + + describe("Logs", () => { + it("should be able to construct", () => { + new Characteristic.Logs(); + }); + }); + + describe("MACRetransmissionMaximum", () => { + it("should be able to construct", () => { + new Characteristic.MACRetransmissionMaximum(); + }); + }); + + describe("MACTransmissionCounters", () => { + it("should be able to construct", () => { + new Characteristic.MACTransmissionCounters(); + }); + }); + + describe("ManagedNetworkEnable", () => { + it("should be able to construct", () => { + new Characteristic.ManagedNetworkEnable(); + }); + }); + + describe("ManuallyDisabled", () => { + it("should be able to construct", () => { + new Characteristic.ManuallyDisabled(); + }); + }); + + describe("Manufacturer", () => { + it("should be able to construct", () => { + new Characteristic.Manufacturer(); + }); + }); + + describe("MaximumTransmitPower", () => { + it("should be able to construct", () => { + new Characteristic.MaximumTransmitPower(); + }); + }); + + describe("Model", () => { + it("should be able to construct", () => { + new Characteristic.Model(); + }); + }); + + describe("MotionDetected", () => { + it("should be able to construct", () => { + new Characteristic.MotionDetected(); + }); + }); + + describe("Mute", () => { + it("should be able to construct", () => { + new Characteristic.Mute(); + }); + }); + + describe("Name", () => { + it("should be able to construct", () => { + new Characteristic.Name(); + }); + }); + + describe("NetworkAccessViolationControl", () => { + it("should be able to construct", () => { + new Characteristic.NetworkAccessViolationControl(); + }); + }); + + describe("NetworkClientProfileControl", () => { + it("should be able to construct", () => { + new Characteristic.NetworkClientProfileControl(); + }); + }); + + describe("NetworkClientStatusControl", () => { + it("should be able to construct", () => { + new Characteristic.NetworkClientStatusControl(); + }); + }); + + describe("NightVision", () => { + it("should be able to construct", () => { + new Characteristic.NightVision(); + }); + }); + + describe("NitrogenDioxideDensity", () => { + it("should be able to construct", () => { + new Characteristic.NitrogenDioxideDensity(); + }); + }); + + describe("ObstructionDetected", () => { + it("should be able to construct", () => { + new Characteristic.ObstructionDetected(); + }); + }); + + describe("OccupancyDetected", () => { + it("should be able to construct", () => { + new Characteristic.OccupancyDetected(); + }); + }); + + describe("On", () => { + it("should be able to construct", () => { + new Characteristic.On(); + }); + }); + + describe("OperatingStateResponse", () => { + it("should be able to construct", () => { + new Characteristic.OperatingStateResponse(); + }); + }); + + describe("OpticalZoom", () => { + it("should be able to construct", () => { + new Characteristic.OpticalZoom(); + }); + }); + + describe("OutletInUse", () => { + it("should be able to construct", () => { + new Characteristic.OutletInUse(); + }); + }); + + describe("OzoneDensity", () => { + it("should be able to construct", () => { + new Characteristic.OzoneDensity(); + }); + }); + + describe("PairingFeatures", () => { + it("should be able to construct", () => { + new Characteristic.PairingFeatures(); + }); + }); + + describe("PairSetup", () => { + it("should be able to construct", () => { + new Characteristic.PairSetup(); + }); + }); + + describe("PairVerify", () => { + it("should be able to construct", () => { + new Characteristic.PairVerify(); + }); + }); + + describe("PasswordSetting", () => { + it("should be able to construct", () => { + new Characteristic.PasswordSetting(); + }); + }); + + describe("PeriodicSnapshotsActive", () => { + it("should be able to construct", () => { + new Characteristic.PeriodicSnapshotsActive(); + }); + }); + + describe("PictureMode", () => { + it("should be able to construct", () => { + new Characteristic.PictureMode(); + }); + }); + + describe("Ping", () => { + it("should be able to construct", () => { + new Characteristic.Ping(); + }); + }); + + describe("PM10Density", () => { + it("should be able to construct", () => { + new Characteristic.PM10Density(); + }); + }); + + describe("PM2_5Density", () => { + it("should be able to construct", () => { + new Characteristic.PM2_5Density(); + }); + }); + + describe("PositionState", () => { + it("should be able to construct", () => { + new Characteristic.PositionState(); + }); + }); + + describe("PowerModeSelection", () => { + it("should be able to construct", () => { + new Characteristic.PowerModeSelection(); + }); + }); + + describe("ProductData", () => { + it("should be able to construct", () => { + new Characteristic.ProductData(); + }); + }); + + describe("ProgrammableSwitchEvent", () => { + it("should be able to construct", () => { + new Characteristic.ProgrammableSwitchEvent(); + }); + }); + + describe("ProgrammableSwitchOutputState", () => { + it("should be able to construct", () => { + new Characteristic.ProgrammableSwitchOutputState(); + }); + }); + + describe("ProgramMode", () => { + it("should be able to construct", () => { + new Characteristic.ProgramMode(); + }); + }); + + describe("ReceivedSignalStrengthIndication", () => { + it("should be able to construct", () => { + new Characteristic.ReceivedSignalStrengthIndication(); + }); + }); + + describe("ReceiverSensitivity", () => { + it("should be able to construct", () => { + new Characteristic.ReceiverSensitivity(); + }); + }); + + describe("RecordingAudioActive", () => { + it("should be able to construct", () => { + new Characteristic.RecordingAudioActive(); + }); + }); + + describe("RelativeHumidityDehumidifierThreshold", () => { + it("should be able to construct", () => { + new Characteristic.RelativeHumidityDehumidifierThreshold(); + }); + }); + + describe("RelativeHumidityHumidifierThreshold", () => { + it("should be able to construct", () => { + new Characteristic.RelativeHumidityHumidifierThreshold(); + }); + }); + + describe("RelayControlPoint", () => { + it("should be able to construct", () => { + new Characteristic.RelayControlPoint(); + }); + }); + + describe("RelayEnabled", () => { + it("should be able to construct", () => { + new Characteristic.RelayEnabled(); + }); + }); + + describe("RelayState", () => { + it("should be able to construct", () => { + new Characteristic.RelayState(); + }); + }); + + describe("RemainingDuration", () => { + it("should be able to construct", () => { + new Characteristic.RemainingDuration(); + }); + }); + + describe("RemoteKey", () => { + it("should be able to construct", () => { + new Characteristic.RemoteKey(); + }); + }); + + describe("ResetFilterIndication", () => { + it("should be able to construct", () => { + new Characteristic.ResetFilterIndication(); + }); + }); + + describe("RotationDirection", () => { + it("should be able to construct", () => { + new Characteristic.RotationDirection(); + }); + }); + + describe("RotationSpeed", () => { + it("should be able to construct", () => { + new Characteristic.RotationSpeed(); + }); + }); + + describe("RouterStatus", () => { + it("should be able to construct", () => { + new Characteristic.RouterStatus(); + }); + }); + + describe("Saturation", () => { + it("should be able to construct", () => { + new Characteristic.Saturation(); + }); + }); + + describe("SecuritySystemAlarmType", () => { + it("should be able to construct", () => { + new Characteristic.SecuritySystemAlarmType(); + }); + }); + + describe("SecuritySystemCurrentState", () => { + it("should be able to construct", () => { + new Characteristic.SecuritySystemCurrentState(); + }); + }); + + describe("SecuritySystemTargetState", () => { + it("should be able to construct", () => { + new Characteristic.SecuritySystemTargetState(); + }); + }); + + describe("SelectedAudioStreamConfiguration", () => { + it("should be able to construct", () => { + new Characteristic.SelectedAudioStreamConfiguration(); + }); + }); + + describe("SelectedCameraRecordingConfiguration", () => { + it("should be able to construct", () => { + new Characteristic.SelectedCameraRecordingConfiguration(); + }); + }); + + describe("SelectedRTPStreamConfiguration", () => { + it("should be able to construct", () => { + new Characteristic.SelectedRTPStreamConfiguration(); + }); + }); + + describe("SerialNumber", () => { + it("should be able to construct", () => { + new Characteristic.SerialNumber(); + }); + }); + + describe("ServiceLabelIndex", () => { + it("should be able to construct", () => { + new Characteristic.ServiceLabelIndex(); + }); + }); + + describe("ServiceLabelNamespace", () => { + it("should be able to construct", () => { + new Characteristic.ServiceLabelNamespace(); + }); + }); + + describe("SetDuration", () => { + it("should be able to construct", () => { + new Characteristic.SetDuration(); + }); + }); + + describe("SetupDataStreamTransport", () => { + it("should be able to construct", () => { + new Characteristic.SetupDataStreamTransport(); + }); + }); + + describe("SetupEndpoints", () => { + it("should be able to construct", () => { + new Characteristic.SetupEndpoints(); + }); + }); + + describe("SetupTransferTransport", () => { + it("should be able to construct", () => { + new Characteristic.SetupTransferTransport(); + }); + }); + + describe("SignalToNoiseRatio", () => { + it("should be able to construct", () => { + new Characteristic.SignalToNoiseRatio(); + }); + }); + + describe("SiriInputType", () => { + it("should be able to construct", () => { + new Characteristic.SiriInputType(); + }); + }); + + describe("SlatType", () => { + it("should be able to construct", () => { + new Characteristic.SlatType(); + }); + }); + + describe("SleepDiscoveryMode", () => { + it("should be able to construct", () => { + new Characteristic.SleepDiscoveryMode(); + }); + }); + + describe("SleepInterval", () => { + it("should be able to construct", () => { + new Characteristic.SleepInterval(); + }); + }); + + describe("SmokeDetected", () => { + it("should be able to construct", () => { + new Characteristic.SmokeDetected(); + }); + }); + + describe("SoftwareRevision", () => { + it("should be able to construct", () => { + new Characteristic.SoftwareRevision(); + }); + }); + + describe("StatusActive", () => { + it("should be able to construct", () => { + new Characteristic.StatusActive(); + }); + }); + + describe("StatusFault", () => { + it("should be able to construct", () => { + new Characteristic.StatusFault(); + }); + }); + + describe("StatusJammed", () => { + it("should be able to construct", () => { + new Characteristic.StatusJammed(); + }); + }); + + describe("StatusLowBattery", () => { + it("should be able to construct", () => { + new Characteristic.StatusLowBattery(); + }); + }); + + describe("StatusTampered", () => { + it("should be able to construct", () => { + new Characteristic.StatusTampered(); + }); + }); + + describe("StreamingStatus", () => { + it("should be able to construct", () => { + new Characteristic.StreamingStatus(); + }); + }); + + describe("SulphurDioxideDensity", () => { + it("should be able to construct", () => { + new Characteristic.SulphurDioxideDensity(); + }); + }); + + describe("SupportedAudioRecordingConfiguration", () => { + it("should be able to construct", () => { + new Characteristic.SupportedAudioRecordingConfiguration(); + }); + }); + + describe("SupportedAudioStreamConfiguration", () => { + it("should be able to construct", () => { + new Characteristic.SupportedAudioStreamConfiguration(); + }); + }); + + describe("SupportedCameraRecordingConfiguration", () => { + it("should be able to construct", () => { + new Characteristic.SupportedCameraRecordingConfiguration(); + }); + }); + + describe("SupportedCharacteristicValueTransitionConfiguration", () => { + it("should be able to construct", () => { + new Characteristic.SupportedCharacteristicValueTransitionConfiguration(); + }); + }); + + describe("SupportedDataStreamTransportConfiguration", () => { + it("should be able to construct", () => { + new Characteristic.SupportedDataStreamTransportConfiguration(); + }); + }); + + describe("SupportedDiagnosticsSnapshot", () => { + it("should be able to construct", () => { + new Characteristic.SupportedDiagnosticsSnapshot(); + }); + }); + + describe("SupportedRouterConfiguration", () => { + it("should be able to construct", () => { + new Characteristic.SupportedRouterConfiguration(); + }); + }); + + describe("SupportedRTPConfiguration", () => { + it("should be able to construct", () => { + new Characteristic.SupportedRTPConfiguration(); + }); + }); + + describe("SupportedTransferTransportConfiguration", () => { + it("should be able to construct", () => { + new Characteristic.SupportedTransferTransportConfiguration(); + }); + }); + + describe("SupportedVideoRecordingConfiguration", () => { + it("should be able to construct", () => { + new Characteristic.SupportedVideoRecordingConfiguration(); + }); + }); + + describe("SupportedVideoStreamConfiguration", () => { + it("should be able to construct", () => { + new Characteristic.SupportedVideoStreamConfiguration(); + }); + }); + + describe("SwingMode", () => { + it("should be able to construct", () => { + new Characteristic.SwingMode(); + }); + }); + + describe("TargetAirPurifierState", () => { + it("should be able to construct", () => { + new Characteristic.TargetAirPurifierState(); + }); + }); + + describe("TargetControlList", () => { + it("should be able to construct", () => { + new Characteristic.TargetControlList(); + }); + }); + + describe("TargetControlSupportedConfiguration", () => { + it("should be able to construct", () => { + new Characteristic.TargetControlSupportedConfiguration(); + }); + }); + + describe("TargetDoorState", () => { + it("should be able to construct", () => { + new Characteristic.TargetDoorState(); + }); + }); + + describe("TargetFanState", () => { + it("should be able to construct", () => { + new Characteristic.TargetFanState(); + }); + }); + + describe("TargetHeaterCoolerState", () => { + it("should be able to construct", () => { + new Characteristic.TargetHeaterCoolerState(); + }); + }); + + describe("TargetHeatingCoolingState", () => { + it("should be able to construct", () => { + new Characteristic.TargetHeatingCoolingState(); + }); + }); + + describe("TargetHorizontalTiltAngle", () => { + it("should be able to construct", () => { + new Characteristic.TargetHorizontalTiltAngle(); + }); + }); + + describe("TargetHumidifierDehumidifierState", () => { + it("should be able to construct", () => { + new Characteristic.TargetHumidifierDehumidifierState(); + }); + }); + + describe("TargetMediaState", () => { + it("should be able to construct", () => { + new Characteristic.TargetMediaState(); + }); + }); + + describe("TargetPosition", () => { + it("should be able to construct", () => { + new Characteristic.TargetPosition(); + }); + }); + + describe("TargetRelativeHumidity", () => { + it("should be able to construct", () => { + new Characteristic.TargetRelativeHumidity(); + }); + }); + + describe("TargetTemperature", () => { + it("should be able to construct", () => { + new Characteristic.TargetTemperature(); + }); + }); + + describe("TargetTiltAngle", () => { + it("should be able to construct", () => { + new Characteristic.TargetTiltAngle(); + }); + }); + + describe("TargetVerticalTiltAngle", () => { + it("should be able to construct", () => { + new Characteristic.TargetVerticalTiltAngle(); + }); + }); + + describe("TargetVisibilityState", () => { + it("should be able to construct", () => { + new Characteristic.TargetVisibilityState(); + }); + }); + + describe("TemperatureDisplayUnits", () => { + it("should be able to construct", () => { + new Characteristic.TemperatureDisplayUnits(); + }); + }); + + describe("ThirdPartyCameraActive", () => { + it("should be able to construct", () => { + new Characteristic.ThirdPartyCameraActive(); + }); + }); + + describe("ThreadControlPoint", () => { + it("should be able to construct", () => { + new Characteristic.ThreadControlPoint(); + }); + }); + + describe("ThreadNodeCapabilities", () => { + it("should be able to construct", () => { + new Characteristic.ThreadNodeCapabilities(); + }); + }); + + describe("ThreadOpenThreadVersion", () => { + it("should be able to construct", () => { + new Characteristic.ThreadOpenThreadVersion(); + }); + }); + + describe("ThreadStatus", () => { + it("should be able to construct", () => { + new Characteristic.ThreadStatus(); + }); + }); + + describe("TransmitPower", () => { + it("should be able to construct", () => { + new Characteristic.TransmitPower(); + }); + }); + + describe("TunnelConnectionTimeout", () => { + it("should be able to construct", () => { + new Characteristic.TunnelConnectionTimeout(); + }); + }); + + describe("TunneledAccessoryAdvertising", () => { + it("should be able to construct", () => { + new Characteristic.TunneledAccessoryAdvertising(); + }); + }); + + describe("TunneledAccessoryConnected", () => { + it("should be able to construct", () => { + new Characteristic.TunneledAccessoryConnected(); + }); + }); + + describe("TunneledAccessoryStateNumber", () => { + it("should be able to construct", () => { + new Characteristic.TunneledAccessoryStateNumber(); + }); + }); + + describe("ValveType", () => { + it("should be able to construct", () => { + new Characteristic.ValveType(); + }); + }); + + describe("Version", () => { + it("should be able to construct", () => { + new Characteristic.Version(); + }); + }); + + describe("VideoAnalysisActive", () => { + it("should be able to construct", () => { + new Characteristic.VideoAnalysisActive(); + }); + }); + + describe("VOCDensity", () => { + it("should be able to construct", () => { + new Characteristic.VOCDensity(); + }); + }); + + describe("Volume", () => { + it("should be able to construct", () => { + new Characteristic.Volume(); + }); + }); + + describe("VolumeControlType", () => { + it("should be able to construct", () => { + new Characteristic.VolumeControlType(); + }); + }); + + describe("VolumeSelector", () => { + it("should be able to construct", () => { + new Characteristic.VolumeSelector(); + }); + }); + + describe("WakeConfiguration", () => { + it("should be able to construct", () => { + new Characteristic.WakeConfiguration(); + }); + }); + + describe("WANConfigurationList", () => { + it("should be able to construct", () => { + new Characteristic.WANConfigurationList(); + }); + }); + + describe("WANStatusList", () => { + it("should be able to construct", () => { + new Characteristic.WANStatusList(); + }); + }); + + describe("WaterLevel", () => { + it("should be able to construct", () => { + new Characteristic.WaterLevel(); + }); + }); + + describe("WiFiCapabilities", () => { + it("should be able to construct", () => { + new Characteristic.WiFiCapabilities(); + }); + }); + + describe("WiFiConfigurationControl", () => { + it("should be able to construct", () => { + new Characteristic.WiFiConfigurationControl(); + }); + }); + + describe("WiFiSatelliteStatus", () => { + it("should be able to construct", () => { + new Characteristic.WiFiSatelliteStatus(); + }); + }); +}); diff --git a/src/lib/definitions/CharacteristicDefinitions.ts b/src/lib/definitions/CharacteristicDefinitions.ts new file mode 100644 index 000000000..148d6410c --- /dev/null +++ b/src/lib/definitions/CharacteristicDefinitions.ts @@ -0,0 +1,4347 @@ +// THIS FILE IS AUTO-GENERATED - DO NOT MODIFY +// V=856 + +import { Characteristic, Formats, Perms, Units } from "../Characteristic"; + +/** + * Characteristic "Access Control Level" + */ +export class AccessControlLevel extends Characteristic { + + public static readonly UUID: string = "000000E5-0000-1000-8000-0026BB765291"; + + constructor() { + super("Access Control Level", AccessControlLevel.UUID, { + format: Formats.UINT16, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + minValue: 0, + maxValue: 2, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.AccessControlLevel = AccessControlLevel; + +/** + * Characteristic "Accessory Flags" + */ +export class AccessoryFlags extends Characteristic { + + public static readonly UUID: string = "000000A6-0000-1000-8000-0026BB765291"; + + public static readonly REQUIRES_ADDITIONAL_SETUP_BIT_MASK = 1; + + constructor() { + super("Accessory Flags", AccessoryFlags.UUID, { + format: Formats.UINT32, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.AccessoryFlags = AccessoryFlags; + +/** + * Characteristic "Accessory Identifier" + */ +export class AccessoryIdentifier extends Characteristic { + + public static readonly UUID: string = "00000057-0000-1000-8000-0026BB765291"; + + constructor() { + super("Accessory Identifier", AccessoryIdentifier.UUID, { + format: Formats.STRING, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.AccessoryIdentifier = AccessoryIdentifier; + +/** + * Characteristic "Active" + */ +export class Active extends Characteristic { + + public static readonly UUID: string = "000000B0-0000-1000-8000-0026BB765291"; + + public static readonly INACTIVE = 0; + public static readonly ACTIVE = 1; + + constructor() { + super("Active", Active.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.Active = Active; + +/** + * Characteristic "Active Identifier" + */ +export class ActiveIdentifier extends Characteristic { + + public static readonly UUID: string = "000000E7-0000-1000-8000-0026BB765291"; + + constructor() { + super("Active Identifier", ActiveIdentifier.UUID, { + format: Formats.UINT32, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + minValue: 0, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ActiveIdentifier = ActiveIdentifier; + +/** + * Characteristic "Activity Interval" + * @since iOS 14 + */ +export class ActivityInterval extends Characteristic { + + public static readonly UUID: string = "0000023B-0000-1000-8000-0026BB765291"; + + constructor() { + super("Activity Interval", ActivityInterval.UUID, { + format: Formats.UINT32, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ActivityInterval = ActivityInterval; + +/** + * Characteristic "Administrator Only Access" + */ +export class AdministratorOnlyAccess extends Characteristic { + + public static readonly UUID: string = "00000001-0000-1000-8000-0026BB765291"; + + constructor() { + super("Administrator Only Access", AdministratorOnlyAccess.UUID, { + format: Formats.BOOL, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.AdministratorOnlyAccess = AdministratorOnlyAccess; + +/** + * Characteristic "Air Particulate Density" + */ +export class AirParticulateDensity extends Characteristic { + + public static readonly UUID: string = "00000064-0000-1000-8000-0026BB765291"; + + constructor() { + super("Air Particulate Density", AirParticulateDensity.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 1000, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.AirParticulateDensity = AirParticulateDensity; + +/** + * Characteristic "Air Particulate Size" + */ +export class AirParticulateSize extends Characteristic { + + public static readonly UUID: string = "00000065-0000-1000-8000-0026BB765291"; + + public static readonly _2_5_M = 0; + public static readonly _10_M = 1; + + constructor() { + super("Air Particulate Size", AirParticulateSize.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.AirParticulateSize = AirParticulateSize; + +/** + * Characteristic "Air Quality" + */ +export class AirQuality extends Characteristic { + + public static readonly UUID: string = "00000095-0000-1000-8000-0026BB765291"; + + public static readonly UNKNOWN = 0; + public static readonly EXCELLENT = 1; + public static readonly GOOD = 2; + public static readonly FAIR = 3; + public static readonly INFERIOR = 4; + public static readonly POOR = 5; + + constructor() { + super("Air Quality", AirQuality.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 5, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.AirQuality = AirQuality; + +/** + * Characteristic "App Matching Identifier" + */ +export class AppMatchingIdentifier extends Characteristic { + + public static readonly UUID: string = "000000A4-0000-1000-8000-0026BB765291"; + + constructor() { + super("App Matching Identifier", AppMatchingIdentifier.UUID, { + format: Formats.TLV8, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.AppMatchingIdentifier = AppMatchingIdentifier; + +/** + * Characteristic "Audio Feedback" + */ +export class AudioFeedback extends Characteristic { + + public static readonly UUID: string = "00000005-0000-1000-8000-0026BB765291"; + + constructor() { + super("Audio Feedback", AudioFeedback.UUID, { + format: Formats.BOOL, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.AudioFeedback = AudioFeedback; + +/** + * Characteristic "Battery Level" + */ +export class BatteryLevel extends Characteristic { + + public static readonly UUID: string = "00000068-0000-1000-8000-0026BB765291"; + + constructor() { + super("Battery Level", BatteryLevel.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + unit: Units.PERCENTAGE, + minValue: 0, + maxValue: 100, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.BatteryLevel = BatteryLevel; + +/** + * Characteristic "Brightness" + */ +export class Brightness extends Characteristic { + + public static readonly UUID: string = "00000008-0000-1000-8000-0026BB765291"; + + constructor() { + super("Brightness", Brightness.UUID, { + format: Formats.INT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + unit: Units.PERCENTAGE, + minValue: 0, + maxValue: 100, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.Brightness = Brightness; + +/** + * Characteristic "Button Event" + */ +export class ButtonEvent extends Characteristic { + + public static readonly UUID: string = "00000126-0000-1000-8000-0026BB765291"; + + constructor() { + super("Button Event", ButtonEvent.UUID, { + format: Formats.TLV8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ButtonEvent = ButtonEvent; + +/** + * Characteristic "Camera Operating Mode Indicator" + */ +export class CameraOperatingModeIndicator extends Characteristic { + + public static readonly UUID: string = "0000021D-0000-1000-8000-0026BB765291"; + + public static readonly DISABLE = 0; + public static readonly ENABLE = 1; + + constructor() { + super("Camera Operating Mode Indicator", CameraOperatingModeIndicator.UUID, { + format: Formats.BOOL, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.TIMED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CameraOperatingModeIndicator = CameraOperatingModeIndicator; + +/** + * Characteristic "Carbon Dioxide Detected" + */ +export class CarbonDioxideDetected extends Characteristic { + + public static readonly UUID: string = "00000092-0000-1000-8000-0026BB765291"; + + public static readonly CO2_LEVELS_NORMAL = 0; + public static readonly CO2_LEVELS_ABNORMAL = 1; + + constructor() { + super("Carbon Dioxide Detected", CarbonDioxideDetected.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CarbonDioxideDetected = CarbonDioxideDetected; + +/** + * Characteristic "Carbon Dioxide Level" + */ +export class CarbonDioxideLevel extends Characteristic { + + public static readonly UUID: string = "00000093-0000-1000-8000-0026BB765291"; + + constructor() { + super("Carbon Dioxide Level", CarbonDioxideLevel.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 100000, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CarbonDioxideLevel = CarbonDioxideLevel; + +/** + * Characteristic "Carbon Dioxide Peak Level" + */ +export class CarbonDioxidePeakLevel extends Characteristic { + + public static readonly UUID: string = "00000094-0000-1000-8000-0026BB765291"; + + constructor() { + super("Carbon Dioxide Peak Level", CarbonDioxidePeakLevel.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 100000, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CarbonDioxidePeakLevel = CarbonDioxidePeakLevel; + +/** + * Characteristic "Carbon Monoxide Detected" + */ +export class CarbonMonoxideDetected extends Characteristic { + + public static readonly UUID: string = "00000069-0000-1000-8000-0026BB765291"; + + public static readonly CO_LEVELS_NORMAL = 0; + public static readonly CO_LEVELS_ABNORMAL = 1; + + constructor() { + super("Carbon Monoxide Detected", CarbonMonoxideDetected.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CarbonMonoxideDetected = CarbonMonoxideDetected; + +/** + * Characteristic "Carbon Monoxide Level" + */ +export class CarbonMonoxideLevel extends Characteristic { + + public static readonly UUID: string = "00000090-0000-1000-8000-0026BB765291"; + + constructor() { + super("Carbon Monoxide Level", CarbonMonoxideLevel.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 100, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CarbonMonoxideLevel = CarbonMonoxideLevel; + +/** + * Characteristic "Carbon Monoxide Peak Level" + */ +export class CarbonMonoxidePeakLevel extends Characteristic { + + public static readonly UUID: string = "00000091-0000-1000-8000-0026BB765291"; + + constructor() { + super("Carbon Monoxide Peak Level", CarbonMonoxidePeakLevel.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 100, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CarbonMonoxidePeakLevel = CarbonMonoxidePeakLevel; + +/** + * Characteristic "CCA Energy Detect Threshold" + * @since iOS 14 + */ +export class CCAEnergyDetectThreshold extends Characteristic { + + public static readonly UUID: string = "00000246-0000-1000-8000-0026BB765291"; + + constructor() { + super("CCA Energy Detect Threshold", CCAEnergyDetectThreshold.UUID, { + format: Formats.INT, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CCAEnergyDetectThreshold = CCAEnergyDetectThreshold; + +/** + * Characteristic "CCA Signal Detect Threshold" + * @since iOS 14 + */ +export class CCASignalDetectThreshold extends Characteristic { + + public static readonly UUID: string = "00000245-0000-1000-8000-0026BB765291"; + + constructor() { + super("CCA Signal Detect Threshold", CCASignalDetectThreshold.UUID, { + format: Formats.INT, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CCASignalDetectThreshold = CCASignalDetectThreshold; + +/** + * Characteristic "Characteristic Value Active Transition Count" + * @since iOS 14 + */ +export class CharacteristicValueActiveTransitionCount extends Characteristic { + + public static readonly UUID: string = "0000024B-0000-1000-8000-0026BB765291"; + + constructor() { + super("Characteristic Value Active Transition Count", CharacteristicValueActiveTransitionCount.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CharacteristicValueActiveTransitionCount = CharacteristicValueActiveTransitionCount; + +/** + * Characteristic "Characteristic Value Transition Control" + * @since iOS 14 + */ +export class CharacteristicValueTransitionControl extends Characteristic { + + public static readonly UUID: string = "00000143-0000-1000-8000-0026BB765291"; + + constructor() { + super("Characteristic Value Transition Control", CharacteristicValueTransitionControl.UUID, { + format: Formats.TLV8, + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CharacteristicValueTransitionControl = CharacteristicValueTransitionControl; + +/** + * Characteristic "Charging State" + */ +export class ChargingState extends Characteristic { + + public static readonly UUID: string = "0000008F-0000-1000-8000-0026BB765291"; + + public static readonly NOT_CHARGING = 0; + public static readonly CHARGING = 1; + public static readonly NOT_CHARGEABLE = 2; + + constructor() { + super("Charging State", ChargingState.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 2, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ChargingState = ChargingState; + +/** + * Characteristic "Closed Captions" + */ +export class ClosedCaptions extends Characteristic { + + public static readonly UUID: string = "000000DD-0000-1000-8000-0026BB765291"; + + public static readonly DISABLED = 0; + public static readonly ENABLED = 1; + + constructor() { + super("Closed Captions", ClosedCaptions.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ClosedCaptions = ClosedCaptions; + +/** + * Characteristic "Color Temperature" + */ +export class ColorTemperature extends Characteristic { + + public static readonly UUID: string = "000000CE-0000-1000-8000-0026BB765291"; + + constructor() { + super("Color Temperature", ColorTemperature.UUID, { + format: Formats.INT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + minValue: 140, + maxValue: 500, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ColorTemperature = ColorTemperature; + +/** + * Characteristic "Configured Name" + */ +export class ConfiguredName extends Characteristic { + + public static readonly UUID: string = "000000E3-0000-1000-8000-0026BB765291"; + + constructor() { + super("Configured Name", ConfiguredName.UUID, { + format: Formats.STRING, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ConfiguredName = ConfiguredName; + +/** + * Characteristic "Contact Sensor State" + */ +export class ContactSensorState extends Characteristic { + + public static readonly UUID: string = "0000006A-0000-1000-8000-0026BB765291"; + + public static readonly CONTACT_DETECTED = 0; + public static readonly CONTACT_NOT_DETECTED = 1; + + constructor() { + super("Contact Sensor State", ContactSensorState.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ContactSensorState = ContactSensorState; + +/** + * Characteristic "Cooling Threshold Temperature" + */ +export class CoolingThresholdTemperature extends Characteristic { + + public static readonly UUID: string = "0000000D-0000-1000-8000-0026BB765291"; + + constructor() { + super("Cooling Threshold Temperature", CoolingThresholdTemperature.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + unit: Units.CELSIUS, + minValue: 10, + maxValue: 35, + minStep: 0.1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CoolingThresholdTemperature = CoolingThresholdTemperature; + +/** + * Characteristic "Current Air Purifier State" + */ +export class CurrentAirPurifierState extends Characteristic { + + public static readonly UUID: string = "000000A9-0000-1000-8000-0026BB765291"; + + public static readonly INACTIVE = 0; + public static readonly IDLE = 1; + public static readonly PURIFYING_AIR = 2; + + constructor() { + super("Current Air Purifier State", CurrentAirPurifierState.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 2, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CurrentAirPurifierState = CurrentAirPurifierState; + +/** + * Characteristic "Current Ambient Light Level" + */ +export class CurrentAmbientLightLevel extends Characteristic { + + public static readonly UUID: string = "0000006B-0000-1000-8000-0026BB765291"; + + constructor() { + super("Current Ambient Light Level", CurrentAmbientLightLevel.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + unit: Units.LUX, + minValue: 0.0001, + maxValue: 100000, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CurrentAmbientLightLevel = CurrentAmbientLightLevel; + +/** + * Characteristic "Current Door State" + */ +export class CurrentDoorState extends Characteristic { + + public static readonly UUID: string = "0000000E-0000-1000-8000-0026BB765291"; + + public static readonly OPEN = 0; + public static readonly CLOSED = 1; + public static readonly OPENING = 2; + public static readonly CLOSING = 3; + public static readonly STOPPED = 4; + + constructor() { + super("Current Door State", CurrentDoorState.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 4, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CurrentDoorState = CurrentDoorState; + +/** + * Characteristic "Current Fan State" + */ +export class CurrentFanState extends Characteristic { + + public static readonly UUID: string = "000000AF-0000-1000-8000-0026BB765291"; + + public static readonly INACTIVE = 0; + public static readonly IDLE = 1; + public static readonly BLOWING_AIR = 2; + + constructor() { + super("Current Fan State", CurrentFanState.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 2, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CurrentFanState = CurrentFanState; + +/** + * Characteristic "Current Heater-Cooler State" + */ +export class CurrentHeaterCoolerState extends Characteristic { + + public static readonly UUID: string = "000000B1-0000-1000-8000-0026BB765291"; + + public static readonly INACTIVE = 0; + public static readonly IDLE = 1; + public static readonly HEATING = 2; + public static readonly COOLING = 3; + + constructor() { + super("Current Heater-Cooler State", CurrentHeaterCoolerState.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 3, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CurrentHeaterCoolerState = CurrentHeaterCoolerState; + +/** + * Characteristic "Current Heating Cooling State" + */ +export class CurrentHeatingCoolingState extends Characteristic { + + public static readonly UUID: string = "0000000F-0000-1000-8000-0026BB765291"; + + public static readonly OFF = 0; + public static readonly HEAT = 1; + public static readonly COOL = 2; + + constructor() { + super("Current Heating Cooling State", CurrentHeatingCoolingState.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 2, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CurrentHeatingCoolingState = CurrentHeatingCoolingState; + +/** + * Characteristic "Current Horizontal Tilt Angle" + */ +export class CurrentHorizontalTiltAngle extends Characteristic { + + public static readonly UUID: string = "0000006C-0000-1000-8000-0026BB765291"; + + constructor() { + super("Current Horizontal Tilt Angle", CurrentHorizontalTiltAngle.UUID, { + format: Formats.INT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + unit: Units.ARC_DEGREE, + minValue: -90, + maxValue: 90, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CurrentHorizontalTiltAngle = CurrentHorizontalTiltAngle; + +/** + * Characteristic "Current Humidifier-Dehumidifier State" + */ +export class CurrentHumidifierDehumidifierState extends Characteristic { + + public static readonly UUID: string = "000000B3-0000-1000-8000-0026BB765291"; + + public static readonly INACTIVE = 0; + public static readonly IDLE = 1; + public static readonly HUMIDIFYING = 2; + public static readonly DEHUMIDIFYING = 3; + + constructor() { + super("Current Humidifier-Dehumidifier State", CurrentHumidifierDehumidifierState.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 3, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CurrentHumidifierDehumidifierState = CurrentHumidifierDehumidifierState; + +/** + * Characteristic "Current Media State" + */ +export class CurrentMediaState extends Characteristic { + + public static readonly UUID: string = "000000E0-0000-1000-8000-0026BB765291"; + + public static readonly PLAY = 0; + public static readonly PAUSE = 1; + public static readonly STOP = 2; + public static readonly LOADING = 4; + public static readonly INTERRUPTED = 5; + + constructor() { + super("Current Media State", CurrentMediaState.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 5, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CurrentMediaState = CurrentMediaState; + +/** + * Characteristic "Current Position" + */ +export class CurrentPosition extends Characteristic { + + public static readonly UUID: string = "0000006D-0000-1000-8000-0026BB765291"; + + constructor() { + super("Current Position", CurrentPosition.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + unit: Units.PERCENTAGE, + minValue: 0, + maxValue: 100, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CurrentPosition = CurrentPosition; + +/** + * Characteristic "Current Relative Humidity" + */ +export class CurrentRelativeHumidity extends Characteristic { + + public static readonly UUID: string = "00000010-0000-1000-8000-0026BB765291"; + + constructor() { + super("Current Relative Humidity", CurrentRelativeHumidity.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + unit: Units.PERCENTAGE, + minValue: 0, + maxValue: 100, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CurrentRelativeHumidity = CurrentRelativeHumidity; + +/** + * Characteristic "Current Slat State" + */ +export class CurrentSlatState extends Characteristic { + + public static readonly UUID: string = "000000AA-0000-1000-8000-0026BB765291"; + + public static readonly FIXED = 0; + public static readonly JAMMED = 1; + public static readonly SWINGING = 2; + + constructor() { + super("Current Slat State", CurrentSlatState.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 3, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CurrentSlatState = CurrentSlatState; + +/** + * Characteristic "Current Temperature" + */ +export class CurrentTemperature extends Characteristic { + + public static readonly UUID: string = "00000011-0000-1000-8000-0026BB765291"; + + constructor() { + super("Current Temperature", CurrentTemperature.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + unit: Units.CELSIUS, + minValue: 0, + maxValue: 100, + minStep: 0.1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CurrentTemperature = CurrentTemperature; + +/** + * Characteristic "Current Tilt Angle" + */ +export class CurrentTiltAngle extends Characteristic { + + public static readonly UUID: string = "000000C1-0000-1000-8000-0026BB765291"; + + constructor() { + super("Current Tilt Angle", CurrentTiltAngle.UUID, { + format: Formats.INT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + unit: Units.ARC_DEGREE, + minValue: -90, + maxValue: 90, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CurrentTiltAngle = CurrentTiltAngle; + +/** + * Characteristic "Current Transport" + * @since iOS 14 + */ +export class CurrentTransport extends Characteristic { + + public static readonly UUID: string = "0000022B-0000-1000-8000-0026BB765291"; + + constructor() { + super("Current Transport", CurrentTransport.UUID, { + format: Formats.BOOL, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CurrentTransport = CurrentTransport; + +/** + * Characteristic "Current Vertical Tilt Angle" + */ +export class CurrentVerticalTiltAngle extends Characteristic { + + public static readonly UUID: string = "0000006E-0000-1000-8000-0026BB765291"; + + constructor() { + super("Current Vertical Tilt Angle", CurrentVerticalTiltAngle.UUID, { + format: Formats.INT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + unit: Units.ARC_DEGREE, + minValue: -90, + maxValue: 90, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CurrentVerticalTiltAngle = CurrentVerticalTiltAngle; + +/** + * Characteristic "Current Visibility State" + */ +export class CurrentVisibilityState extends Characteristic { + + public static readonly UUID: string = "00000135-0000-1000-8000-0026BB765291"; + + public static readonly SHOWN = 0; + public static readonly HIDDEN = 1; + + constructor() { + super("Current Visibility State", CurrentVisibilityState.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 3, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.CurrentVisibilityState = CurrentVisibilityState; + +/** + * Characteristic "Data Stream HAP Transport" + * @since iOS 14 + */ +export class DataStreamHAPTransport extends Characteristic { + + public static readonly UUID: string = "00000138-0000-1000-8000-0026BB765291"; + + constructor() { + super("Data Stream HAP Transport", DataStreamHAPTransport.UUID, { + format: Formats.TLV8, + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.WRITE_RESPONSE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.DataStreamHAPTransport = DataStreamHAPTransport; + +/** + * Characteristic "Data Stream HAP Transport Interrupt" + * @since iOS 14 + */ +export class DataStreamHAPTransportInterrupt extends Characteristic { + + public static readonly UUID: string = "00000139-0000-1000-8000-0026BB765291"; + + constructor() { + super("Data Stream HAP Transport Interrupt", DataStreamHAPTransportInterrupt.UUID, { + format: Formats.TLV8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.DataStreamHAPTransportInterrupt = DataStreamHAPTransportInterrupt; + +/** + * Characteristic "Diagonal Field Of View" + * @since iOS 13.2 + */ +export class DiagonalFieldOfView extends Characteristic { + + public static readonly UUID: string = "00000224-0000-1000-8000-0026BB765291"; + + constructor() { + super("Diagonal Field Of View", DiagonalFieldOfView.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + unit: Units.ARC_DEGREE, + minValue: 0, + maxValue: 360, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.DiagonalFieldOfView = DiagonalFieldOfView; + +/** + * Characteristic "Digital Zoom" + */ +export class DigitalZoom extends Characteristic { + + public static readonly UUID: string = "0000011D-0000-1000-8000-0026BB765291"; + + constructor() { + super("Digital Zoom", DigitalZoom.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + minStep: 0.1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.DigitalZoom = DigitalZoom; + +/** + * Characteristic "Display Order" + */ +export class DisplayOrder extends Characteristic { + + public static readonly UUID: string = "00000136-0000-1000-8000-0026BB765291"; + + constructor() { + super("Display Order", DisplayOrder.UUID, { + format: Formats.TLV8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.DisplayOrder = DisplayOrder; + +/** + * Characteristic "Event Retransmission Maximum" + * @since iOS 14 + */ +export class EventRetransmissionMaximum extends Characteristic { + + public static readonly UUID: string = "0000023D-0000-1000-8000-0026BB765291"; + + constructor() { + super("Event Retransmission Maximum", EventRetransmissionMaximum.UUID, { + format: Formats.UINT8, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.EventRetransmissionMaximum = EventRetransmissionMaximum; + +/** + * Characteristic "Event Snapshots Active" + */ +export class EventSnapshotsActive extends Characteristic { + + public static readonly UUID: string = "00000223-0000-1000-8000-0026BB765291"; + + public static readonly DISABLE = 0; + public static readonly ENABLE = 1; + + constructor() { + super("Event Snapshots Active", EventSnapshotsActive.UUID, { + format: Formats.BOOL, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.TIMED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.EventSnapshotsActive = EventSnapshotsActive; + +/** + * Characteristic "Event Transmission Counters" + * @since iOS 14 + */ +export class EventTransmissionCounters extends Characteristic { + + public static readonly UUID: string = "0000023E-0000-1000-8000-0026BB765291"; + + constructor() { + super("Event Transmission Counters", EventTransmissionCounters.UUID, { + format: Formats.UINT32, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.EventTransmissionCounters = EventTransmissionCounters; + +/** + * Characteristic "Filter Change Indication" + */ +export class FilterChangeIndication extends Characteristic { + + public static readonly UUID: string = "000000AC-0000-1000-8000-0026BB765291"; + + public static readonly FILTER_OK = 0; + public static readonly CHANGE_FILTER = 1; + + constructor() { + super("Filter Change Indication", FilterChangeIndication.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.FilterChangeIndication = FilterChangeIndication; + +/** + * Characteristic "Filter Life Level" + */ +export class FilterLifeLevel extends Characteristic { + + public static readonly UUID: string = "000000AB-0000-1000-8000-0026BB765291"; + + constructor() { + super("Filter Life Level", FilterLifeLevel.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 100, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.FilterLifeLevel = FilterLifeLevel; + +/** + * Characteristic "Firmware Revision" + */ +export class FirmwareRevision extends Characteristic { + + public static readonly UUID: string = "00000052-0000-1000-8000-0026BB765291"; + + constructor() { + super("Firmware Revision", FirmwareRevision.UUID, { + format: Formats.STRING, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.FirmwareRevision = FirmwareRevision; + +/** + * Characteristic "Hardware Revision" + */ +export class HardwareRevision extends Characteristic { + + public static readonly UUID: string = "00000053-0000-1000-8000-0026BB765291"; + + constructor() { + super("Hardware Revision", HardwareRevision.UUID, { + format: Formats.STRING, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.HardwareRevision = HardwareRevision; + +/** + * Characteristic "Heart Beat" + * @since iOS 14 + */ +export class HeartBeat extends Characteristic { + + public static readonly UUID: string = "0000024A-0000-1000-8000-0026BB765291"; + + constructor() { + super("Heart Beat", HeartBeat.UUID, { + format: Formats.UINT32, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.HeartBeat = HeartBeat; + +/** + * Characteristic "Heating Threshold Temperature" + */ +export class HeatingThresholdTemperature extends Characteristic { + + public static readonly UUID: string = "00000012-0000-1000-8000-0026BB765291"; + + constructor() { + super("Heating Threshold Temperature", HeatingThresholdTemperature.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + unit: Units.CELSIUS, + minValue: 0, + maxValue: 25, + minStep: 0.1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.HeatingThresholdTemperature = HeatingThresholdTemperature; + +/** + * Characteristic "Hold Position" + */ +export class HoldPosition extends Characteristic { + + public static readonly UUID: string = "0000006F-0000-1000-8000-0026BB765291"; + + constructor() { + super("Hold Position", HoldPosition.UUID, { + format: Formats.BOOL, + perms: [Perms.PAIRED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.HoldPosition = HoldPosition; + +/** + * Characteristic "HomeKit Camera Active" + */ +export class HomeKitCameraActive extends Characteristic { + + public static readonly UUID: string = "0000021B-0000-1000-8000-0026BB765291"; + + public static readonly OFF = 0; + public static readonly ON = 1; + + constructor() { + super("HomeKit Camera Active", HomeKitCameraActive.UUID, { + format: Formats.BOOL, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.TIMED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.HomeKitCameraActive = HomeKitCameraActive; + +/** + * Characteristic "Hue" + */ +export class Hue extends Characteristic { + + public static readonly UUID: string = "00000013-0000-1000-8000-0026BB765291"; + + constructor() { + super("Hue", Hue.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + unit: Units.ARC_DEGREE, + minValue: 0, + maxValue: 360, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.Hue = Hue; + +/** + * Characteristic "Identifier" + */ +export class Identifier extends Characteristic { + + public static readonly UUID: string = "000000E6-0000-1000-8000-0026BB765291"; + + constructor() { + super("Identifier", Identifier.UUID, { + format: Formats.UINT32, + perms: [Perms.PAIRED_READ], + minValue: 0, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.Identifier = Identifier; + +/** + * Characteristic "Identify" + */ +export class Identify extends Characteristic { + + public static readonly UUID: string = "00000014-0000-1000-8000-0026BB765291"; + + constructor() { + super("Identify", Identify.UUID, { + format: Formats.BOOL, + perms: [Perms.PAIRED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.Identify = Identify; + +/** + * Characteristic "Image Mirroring" + */ +export class ImageMirroring extends Characteristic { + + public static readonly UUID: string = "0000011F-0000-1000-8000-0026BB765291"; + + constructor() { + super("Image Mirroring", ImageMirroring.UUID, { + format: Formats.BOOL, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ImageMirroring = ImageMirroring; + +/** + * Characteristic "Image Rotation" + */ +export class ImageRotation extends Characteristic { + + public static readonly UUID: string = "0000011E-0000-1000-8000-0026BB765291"; + + constructor() { + super("Image Rotation", ImageRotation.UUID, { + format: Formats.INT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + unit: Units.ARC_DEGREE, + minValue: 0, + maxValue: 360, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ImageRotation = ImageRotation; + +/** + * Characteristic "Input Device Type" + */ +export class InputDeviceType extends Characteristic { + + public static readonly UUID: string = "000000DC-0000-1000-8000-0026BB765291"; + + public static readonly OTHER = 0; + public static readonly TV = 1; + public static readonly RECORDING = 2; + public static readonly TUNER = 3; + public static readonly PLAYBACK = 4; + public static readonly AUDIO_SYSTEM = 5; + + constructor() { + super("Input Device Type", InputDeviceType.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 6, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.InputDeviceType = InputDeviceType; + +/** + * Characteristic "Input Source Type" + */ +export class InputSourceType extends Characteristic { + + public static readonly UUID: string = "000000DB-0000-1000-8000-0026BB765291"; + + public static readonly OTHER = 0; + public static readonly HOME_SCREEN = 1; + public static readonly TUNER = 2; + public static readonly HDMI = 3; + public static readonly COMPOSITE_VIDEO = 4; + public static readonly S_VIDEO = 5; + public static readonly COMPONENT_VIDEO = 6; + public static readonly DVI = 7; + public static readonly AIRPLAY = 8; + public static readonly USB = 9; + public static readonly APPLICATION = 10; + + constructor() { + super("Input Source Type", InputSourceType.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 10, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.InputSourceType = InputSourceType; + +/** + * Characteristic "In Use" + */ +export class InUse extends Characteristic { + + public static readonly UUID: string = "000000D2-0000-1000-8000-0026BB765291"; + + public static readonly NOT_IN_USE = 0; + public static readonly IN_USE = 1; + + constructor() { + super("In Use", InUse.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.InUse = InUse; + +/** + * Characteristic "Is Configured" + */ +export class IsConfigured extends Characteristic { + + public static readonly UUID: string = "000000D6-0000-1000-8000-0026BB765291"; + + public static readonly NOT_CONFIGURED = 0; + public static readonly CONFIGURED = 1; + + constructor() { + super("Is Configured", IsConfigured.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.IsConfigured = IsConfigured; + +/** + * Characteristic "Leak Detected" + */ +export class LeakDetected extends Characteristic { + + public static readonly UUID: string = "00000070-0000-1000-8000-0026BB765291"; + + public static readonly LEAK_NOT_DETECTED = 0; + public static readonly LEAK_DETECTED = 1; + + constructor() { + super("Leak Detected", LeakDetected.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.LeakDetected = LeakDetected; + +/** + * Characteristic "List Pairings" + */ +export class ListPairings extends Characteristic { + + public static readonly UUID: string = "00000050-0000-1000-8000-0026BB765291"; + + constructor() { + super("List Pairings", ListPairings.UUID, { + format: Formats.TLV8, + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +// noinspection JSDeprecatedSymbols +Characteristic.PairingPairings = ListPairings; +Characteristic.ListPairings = ListPairings; + +/** + * Characteristic "Lock Control Point" + */ +export class LockControlPoint extends Characteristic { + + public static readonly UUID: string = "00000019-0000-1000-8000-0026BB765291"; + + constructor() { + super("Lock Control Point", LockControlPoint.UUID, { + format: Formats.TLV8, + perms: [Perms.PAIRED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.LockControlPoint = LockControlPoint; + +/** + * Characteristic "Lock Current State" + */ +export class LockCurrentState extends Characteristic { + + public static readonly UUID: string = "0000001D-0000-1000-8000-0026BB765291"; + + public static readonly UNSECURED = 0; + public static readonly SECURED = 1; + public static readonly JAMMED = 2; + public static readonly UNKNOWN = 3; + + constructor() { + super("Lock Current State", LockCurrentState.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 3, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.LockCurrentState = LockCurrentState; + +/** + * Characteristic "Lock Last Known Action" + */ +export class LockLastKnownAction extends Characteristic { + + public static readonly UUID: string = "0000001C-0000-1000-8000-0026BB765291"; + + public static readonly SECURED_PHYSICALLY_INTERIOR = 0; + public static readonly UNSECURED_PHYSICALLY_INTERIOR = 1; + public static readonly SECURED_PHYSICALLY_EXTERIOR = 2; + public static readonly UNSECURED_PHYSICALLY_EXTERIOR = 3; + public static readonly SECURED_BY_KEYPAD = 4; + public static readonly UNSECURED_BY_KEYPAD = 5; + public static readonly SECURED_REMOTELY = 6; + public static readonly UNSECURED_REMOTELY = 7; + public static readonly SECURED_BY_AUTO_SECURE_TIMEOUT = 8; + + constructor() { + super("Lock Last Known Action", LockLastKnownAction.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 8, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.LockLastKnownAction = LockLastKnownAction; + +/** + * Characteristic "Lock Management Auto Security Timeout" + */ +export class LockManagementAutoSecurityTimeout extends Characteristic { + + public static readonly UUID: string = "0000001A-0000-1000-8000-0026BB765291"; + + constructor() { + super("Lock Management Auto Security Timeout", LockManagementAutoSecurityTimeout.UUID, { + format: Formats.UINT32, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + unit: Units.SECONDS, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.LockManagementAutoSecurityTimeout = LockManagementAutoSecurityTimeout; + +/** + * Characteristic "Lock Physical Controls" + */ +export class LockPhysicalControls extends Characteristic { + + public static readonly UUID: string = "000000A7-0000-1000-8000-0026BB765291"; + + public static readonly CONTROL_LOCK_DISABLED = 0; + public static readonly CONTROL_LOCK_ENABLED = 1; + + constructor() { + super("Lock Physical Controls", LockPhysicalControls.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.LockPhysicalControls = LockPhysicalControls; + +/** + * Characteristic "Lock Target State" + */ +export class LockTargetState extends Characteristic { + + public static readonly UUID: string = "0000001E-0000-1000-8000-0026BB765291"; + + public static readonly UNSECURED = 0; + public static readonly SECURED = 1; + + constructor() { + super("Lock Target State", LockTargetState.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.LockTargetState = LockTargetState; + +/** + * Characteristic "Logs" + */ +export class Logs extends Characteristic { + + public static readonly UUID: string = "0000001F-0000-1000-8000-0026BB765291"; + + constructor() { + super("Logs", Logs.UUID, { + format: Formats.TLV8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.Logs = Logs; + +/** + * Characteristic "MAC Retransmission Maximum" + * @since iOS 14 + */ +export class MACRetransmissionMaximum extends Characteristic { + + public static readonly UUID: string = "00000247-0000-1000-8000-0026BB765291"; + + constructor() { + super("MAC Retransmission Maximum", MACRetransmissionMaximum.UUID, { + format: Formats.UINT8, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.MACRetransmissionMaximum = MACRetransmissionMaximum; + +/** + * Characteristic "MAC Transmission Counters" + */ +export class MACTransmissionCounters extends Characteristic { + + public static readonly UUID: string = "00000248-0000-1000-8000-0026BB765291"; + + constructor() { + super("MAC Transmission Counters", MACTransmissionCounters.UUID, { + format: Formats.DATA, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.MACTransmissionCounters = MACTransmissionCounters; + +/** + * Characteristic "Managed Network Enable" + */ +export class ManagedNetworkEnable extends Characteristic { + + public static readonly UUID: string = "00000215-0000-1000-8000-0026BB765291"; + + public static readonly DISABLED = 0; + public static readonly ENABLED = 1; + + constructor() { + super("Managed Network Enable", ManagedNetworkEnable.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.TIMED_WRITE], + minValue: 0, + maxValue: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ManagedNetworkEnable = ManagedNetworkEnable; + +/** + * Characteristic "Manually Disabled" + */ +export class ManuallyDisabled extends Characteristic { + + public static readonly UUID: string = "00000227-0000-1000-8000-0026BB765291"; + + public static readonly ENABLED = 0; + public static readonly DISABLED = 1; + + constructor() { + super("Manually Disabled", ManuallyDisabled.UUID, { + format: Formats.BOOL, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ManuallyDisabled = ManuallyDisabled; + +/** + * Characteristic "Manufacturer" + */ +export class Manufacturer extends Characteristic { + + public static readonly UUID: string = "00000020-0000-1000-8000-0026BB765291"; + + constructor() { + super("Manufacturer", Manufacturer.UUID, { + format: Formats.STRING, + perms: [Perms.PAIRED_READ], + maxLen: 64, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.Manufacturer = Manufacturer; + +/** + * Characteristic "Maximum Transmit Power" + * @since iOS 14 + */ +export class MaximumTransmitPower extends Characteristic { + + public static readonly UUID: string = "00000243-0000-1000-8000-0026BB765291"; + + constructor() { + super("Maximum Transmit Power", MaximumTransmitPower.UUID, { + format: Formats.INT, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.MaximumTransmitPower = MaximumTransmitPower; + +/** + * Characteristic "Model" + */ +export class Model extends Characteristic { + + public static readonly UUID: string = "00000021-0000-1000-8000-0026BB765291"; + + constructor() { + super("Model", Model.UUID, { + format: Formats.STRING, + perms: [Perms.PAIRED_READ], + maxLen: 64, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.Model = Model; + +/** + * Characteristic "Motion Detected" + */ +export class MotionDetected extends Characteristic { + + public static readonly UUID: string = "00000022-0000-1000-8000-0026BB765291"; + + constructor() { + super("Motion Detected", MotionDetected.UUID, { + format: Formats.BOOL, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.MotionDetected = MotionDetected; + +/** + * Characteristic "Mute" + */ +export class Mute extends Characteristic { + + public static readonly UUID: string = "0000011A-0000-1000-8000-0026BB765291"; + + constructor() { + super("Mute", Mute.UUID, { + format: Formats.BOOL, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.Mute = Mute; + +/** + * Characteristic "Name" + */ +export class Name extends Characteristic { + + public static readonly UUID: string = "00000023-0000-1000-8000-0026BB765291"; + + constructor() { + super("Name", Name.UUID, { + format: Formats.STRING, + perms: [Perms.PAIRED_READ], + maxLen: 64, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.Name = Name; + +/** + * Characteristic "Network Access Violation Control" + */ +export class NetworkAccessViolationControl extends Characteristic { + + public static readonly UUID: string = "0000021F-0000-1000-8000-0026BB765291"; + + constructor() { + super("Network Access Violation Control", NetworkAccessViolationControl.UUID, { + format: Formats.TLV8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.TIMED_WRITE, Perms.WRITE_RESPONSE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.NetworkAccessViolationControl = NetworkAccessViolationControl; + +/** + * Characteristic "Network Client Profile Control" + */ +export class NetworkClientProfileControl extends Characteristic { + + public static readonly UUID: string = "0000020C-0000-1000-8000-0026BB765291"; + + constructor() { + super("Network Client Profile Control", NetworkClientProfileControl.UUID, { + format: Formats.TLV8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.TIMED_WRITE, Perms.WRITE_RESPONSE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.NetworkClientProfileControl = NetworkClientProfileControl; + +/** + * Characteristic "Network Client Status Control" + */ +export class NetworkClientStatusControl extends Characteristic { + + public static readonly UUID: string = "0000020D-0000-1000-8000-0026BB765291"; + + constructor() { + super("Network Client Status Control", NetworkClientStatusControl.UUID, { + format: Formats.TLV8, + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.WRITE_RESPONSE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.NetworkClientStatusControl = NetworkClientStatusControl; + +/** + * Characteristic "Night Vision" + */ +export class NightVision extends Characteristic { + + public static readonly UUID: string = "0000011B-0000-1000-8000-0026BB765291"; + + constructor() { + super("Night Vision", NightVision.UUID, { + format: Formats.BOOL, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.TIMED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.NightVision = NightVision; + +/** + * Characteristic "Nitrogen Dioxide Density" + */ +export class NitrogenDioxideDensity extends Characteristic { + + public static readonly UUID: string = "000000C4-0000-1000-8000-0026BB765291"; + + constructor() { + super("Nitrogen Dioxide Density", NitrogenDioxideDensity.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 1000, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.NitrogenDioxideDensity = NitrogenDioxideDensity; + +/** + * Characteristic "Obstruction Detected" + */ +export class ObstructionDetected extends Characteristic { + + public static readonly UUID: string = "00000024-0000-1000-8000-0026BB765291"; + + constructor() { + super("Obstruction Detected", ObstructionDetected.UUID, { + format: Formats.BOOL, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ObstructionDetected = ObstructionDetected; + +/** + * Characteristic "Occupancy Detected" + */ +export class OccupancyDetected extends Characteristic { + + public static readonly UUID: string = "00000071-0000-1000-8000-0026BB765291"; + + public static readonly OCCUPANCY_NOT_DETECTED = 0; + public static readonly OCCUPANCY_DETECTED = 1; + + constructor() { + super("Occupancy Detected", OccupancyDetected.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.OccupancyDetected = OccupancyDetected; + +/** + * Characteristic "On" + */ +export class On extends Characteristic { + + public static readonly UUID: string = "00000025-0000-1000-8000-0026BB765291"; + + constructor() { + super("On", On.UUID, { + format: Formats.BOOL, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.On = On; + +/** + * Characteristic "Operating State Response" + * @since iOS 14 + */ +export class OperatingStateResponse extends Characteristic { + + public static readonly UUID: string = "00000232-0000-1000-8000-0026BB765291"; + + constructor() { + super("Operating State Response", OperatingStateResponse.UUID, { + format: Formats.TLV8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.OperatingStateResponse = OperatingStateResponse; + +/** + * Characteristic "Optical Zoom" + */ +export class OpticalZoom extends Characteristic { + + public static readonly UUID: string = "0000011C-0000-1000-8000-0026BB765291"; + + constructor() { + super("Optical Zoom", OpticalZoom.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + minStep: 0.1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.OpticalZoom = OpticalZoom; + +/** + * Characteristic "Outlet In Use" + */ +export class OutletInUse extends Characteristic { + + public static readonly UUID: string = "00000026-0000-1000-8000-0026BB765291"; + + constructor() { + super("Outlet In Use", OutletInUse.UUID, { + format: Formats.BOOL, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.OutletInUse = OutletInUse; + +/** + * Characteristic "Ozone Density" + */ +export class OzoneDensity extends Characteristic { + + public static readonly UUID: string = "000000C3-0000-1000-8000-0026BB765291"; + + constructor() { + super("Ozone Density", OzoneDensity.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 1000, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.OzoneDensity = OzoneDensity; + +/** + * Characteristic "Pairing Features" + */ +export class PairingFeatures extends Characteristic { + + public static readonly UUID: string = "0000004F-0000-1000-8000-0026BB765291"; + + constructor() { + super("Pairing Features", PairingFeatures.UUID, { + format: Formats.UINT8, + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.PairingFeatures = PairingFeatures; + +/** + * Characteristic "Pair Setup" + */ +export class PairSetup extends Characteristic { + + public static readonly UUID: string = "0000004C-0000-1000-8000-0026BB765291"; + + constructor() { + super("Pair Setup", PairSetup.UUID, { + format: Formats.TLV8, + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.PairSetup = PairSetup; + +/** + * Characteristic "Pair Verify" + */ +export class PairVerify extends Characteristic { + + public static readonly UUID: string = "0000004E-0000-1000-8000-0026BB765291"; + + constructor() { + super("Pair Verify", PairVerify.UUID, { + format: Formats.TLV8, + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.PairVerify = PairVerify; + +/** + * Characteristic "Password Setting" + */ +export class PasswordSetting extends Characteristic { + + public static readonly UUID: string = "000000E4-0000-1000-8000-0026BB765291"; + + constructor() { + super("Password Setting", PasswordSetting.UUID, { + format: Formats.TLV8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.PasswordSetting = PasswordSetting; + +/** + * Characteristic "Periodic Snapshots Active" + */ +export class PeriodicSnapshotsActive extends Characteristic { + + public static readonly UUID: string = "00000225-0000-1000-8000-0026BB765291"; + + public static readonly DISABLE = 0; + public static readonly ENABLE = 1; + + constructor() { + super("Periodic Snapshots Active", PeriodicSnapshotsActive.UUID, { + format: Formats.BOOL, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.TIMED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.PeriodicSnapshotsActive = PeriodicSnapshotsActive; + +/** + * Characteristic "Picture Mode" + */ +export class PictureMode extends Characteristic { + + public static readonly UUID: string = "000000E2-0000-1000-8000-0026BB765291"; + + public static readonly OTHER = 0; + public static readonly STANDARD = 1; + public static readonly CALIBRATED = 2; + public static readonly CALIBRATED_DARK = 3; + public static readonly VIVID = 4; + public static readonly GAME = 5; + public static readonly COMPUTER = 6; + public static readonly CUSTOM = 7; + + constructor() { + super("Picture Mode", PictureMode.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + minValue: 0, + maxValue: 13, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.PictureMode = PictureMode; + +/** + * Characteristic "Ping" + * @since iOS 14 + */ +export class Ping extends Characteristic { + + public static readonly UUID: string = "0000023C-0000-1000-8000-0026BB765291"; + + constructor() { + super("Ping", Ping.UUID, { + format: Formats.DATA, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.Ping = Ping; + +/** + * Characteristic "PM10 Density" + */ +export class PM10Density extends Characteristic { + + public static readonly UUID: string = "000000C7-0000-1000-8000-0026BB765291"; + + constructor() { + super("PM10 Density", PM10Density.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 1000, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.PM10Density = PM10Density; + +/** + * Characteristic "PM2.5 Density" + */ +export class PM2_5Density extends Characteristic { + + public static readonly UUID: string = "000000C6-0000-1000-8000-0026BB765291"; + + constructor() { + super("PM2.5 Density", PM2_5Density.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 1000, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.PM2_5Density = PM2_5Density; + +/** + * Characteristic "Position State" + */ +export class PositionState extends Characteristic { + + public static readonly UUID: string = "00000072-0000-1000-8000-0026BB765291"; + + public static readonly DECREASING = 0; + public static readonly INCREASING = 1; + public static readonly STOPPED = 2; + + constructor() { + super("Position State", PositionState.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 2, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.PositionState = PositionState; + +/** + * Characteristic "Power Mode Selection" + */ +export class PowerModeSelection extends Characteristic { + + public static readonly UUID: string = "000000DF-0000-1000-8000-0026BB765291"; + + public static readonly SHOW = 0; + public static readonly HIDE = 1; + + constructor() { + super("Power Mode Selection", PowerModeSelection.UUID, { + format: Formats.UINT8, + perms: [Perms.PAIRED_WRITE], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.PowerModeSelection = PowerModeSelection; + +/** + * Characteristic "Product Data" + */ +export class ProductData extends Characteristic { + + public static readonly UUID: string = "00000220-0000-1000-8000-0026BB765291"; + + constructor() { + super("Product Data", ProductData.UUID, { + format: Formats.DATA, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ProductData = ProductData; + +/** + * Characteristic "Programmable Switch Event" + */ +export class ProgrammableSwitchEvent extends Characteristic { + + public static readonly UUID: string = "00000073-0000-1000-8000-0026BB765291"; + + public static readonly SINGLE_PRESS = 0; + public static readonly DOUBLE_PRESS = 1; + public static readonly LONG_PRESS = 2; + + constructor() { + super("Programmable Switch Event", ProgrammableSwitchEvent.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 2, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ProgrammableSwitchEvent = ProgrammableSwitchEvent; + +/** + * Characteristic "Programmable Switch Output State" + */ +export class ProgrammableSwitchOutputState extends Characteristic { + + public static readonly UUID: string = "00000074-0000-1000-8000-0026BB765291"; + + constructor() { + super("Programmable Switch Output State", ProgrammableSwitchOutputState.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ProgrammableSwitchOutputState = ProgrammableSwitchOutputState; + +/** + * Characteristic "Program Mode" + */ +export class ProgramMode extends Characteristic { + + public static readonly UUID: string = "000000D1-0000-1000-8000-0026BB765291"; + + public static readonly NO_PROGRAM_SCHEDULED = 0; + public static readonly PROGRAM_SCHEDULED = 1; + public static readonly PROGRAM_SCHEDULED_MANUAL_MODE_ = 2; + + constructor() { + super("Program Mode", ProgramMode.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 2, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ProgramMode = ProgramMode; + +/** + * Characteristic "Received Signal Strength Indication" + * @since iOS 14 + */ +export class ReceivedSignalStrengthIndication extends Characteristic { + + public static readonly UUID: string = "0000023F-0000-1000-8000-0026BB765291"; + + constructor() { + super("Received Signal Strength Indication", ReceivedSignalStrengthIndication.UUID, { + format: Formats.INT, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ReceivedSignalStrengthIndication = ReceivedSignalStrengthIndication; + +/** + * Characteristic "Receiver Sensitivity" + * @since iOS 14 + */ +export class ReceiverSensitivity extends Characteristic { + + public static readonly UUID: string = "00000244-0000-1000-8000-0026BB765291"; + + constructor() { + super("Receiver Sensitivity", ReceiverSensitivity.UUID, { + format: Formats.INT, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ReceiverSensitivity = ReceiverSensitivity; + +/** + * Characteristic "Recording Audio Active" + */ +export class RecordingAudioActive extends Characteristic { + + public static readonly UUID: string = "00000226-0000-1000-8000-0026BB765291"; + + public static readonly DISABLE = 0; + public static readonly ENABLE = 1; + + constructor() { + super("Recording Audio Active", RecordingAudioActive.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.TIMED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.RecordingAudioActive = RecordingAudioActive; + +/** + * Characteristic "Relative Humidity Dehumidifier Threshold" + */ +export class RelativeHumidityDehumidifierThreshold extends Characteristic { + + public static readonly UUID: string = "000000C9-0000-1000-8000-0026BB765291"; + + constructor() { + super("Relative Humidity Dehumidifier Threshold", RelativeHumidityDehumidifierThreshold.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + unit: Units.PERCENTAGE, + minValue: 0, + maxValue: 100, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.RelativeHumidityDehumidifierThreshold = RelativeHumidityDehumidifierThreshold; + +/** + * Characteristic "Relative Humidity Humidifier Threshold" + */ +export class RelativeHumidityHumidifierThreshold extends Characteristic { + + public static readonly UUID: string = "000000CA-0000-1000-8000-0026BB765291"; + + constructor() { + super("Relative Humidity Humidifier Threshold", RelativeHumidityHumidifierThreshold.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + unit: Units.PERCENTAGE, + minValue: 0, + maxValue: 100, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.RelativeHumidityHumidifierThreshold = RelativeHumidityHumidifierThreshold; + +/** + * Characteristic "Relay Control Point" + */ +export class RelayControlPoint extends Characteristic { + + public static readonly UUID: string = "0000005E-0000-1000-8000-0026BB765291"; + + constructor() { + super("Relay Control Point", RelayControlPoint.UUID, { + format: Formats.TLV8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.RelayControlPoint = RelayControlPoint; + +/** + * Characteristic "Relay Enabled" + */ +export class RelayEnabled extends Characteristic { + + public static readonly UUID: string = "0000005B-0000-1000-8000-0026BB765291"; + + constructor() { + super("Relay Enabled", RelayEnabled.UUID, { + format: Formats.BOOL, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.RelayEnabled = RelayEnabled; + +/** + * Characteristic "Relay State" + */ +export class RelayState extends Characteristic { + + public static readonly UUID: string = "0000005C-0000-1000-8000-0026BB765291"; + + constructor() { + super("Relay State", RelayState.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 5, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.RelayState = RelayState; + +/** + * Characteristic "Remaining Duration" + */ +export class RemainingDuration extends Characteristic { + + public static readonly UUID: string = "000000D4-0000-1000-8000-0026BB765291"; + + constructor() { + super("Remaining Duration", RemainingDuration.UUID, { + format: Formats.UINT32, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + unit: Units.SECONDS, + minValue: 0, + maxValue: 3600, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.RemainingDuration = RemainingDuration; + +/** + * Characteristic "Remote Key" + */ +export class RemoteKey extends Characteristic { + + public static readonly UUID: string = "000000E1-0000-1000-8000-0026BB765291"; + + public static readonly REWIND = 0; + public static readonly FAST_FORWARD = 1; + public static readonly NEXT_TRACK = 2; + public static readonly PREVIOUS_TRACK = 3; + public static readonly ARROW_UP = 4; + public static readonly ARROW_DOWN = 5; + public static readonly ARROW_LEFT = 6; + public static readonly ARROW_RIGHT = 7; + public static readonly SELECT = 8; + public static readonly BACK = 9; + public static readonly EXIT = 10; + public static readonly PLAY_PAUSE = 11; + public static readonly INFORMATION = 15; + + constructor() { + super("Remote Key", RemoteKey.UUID, { + format: Formats.UINT8, + perms: [Perms.PAIRED_WRITE], + minValue: 0, + maxValue: 16, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.RemoteKey = RemoteKey; + +/** + * Characteristic "Reset Filter Indication" + */ +export class ResetFilterIndication extends Characteristic { + + public static readonly UUID: string = "000000AD-0000-1000-8000-0026BB765291"; + + constructor() { + super("Reset Filter Indication", ResetFilterIndication.UUID, { + format: Formats.UINT8, + perms: [Perms.PAIRED_WRITE], + minValue: 1, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ResetFilterIndication = ResetFilterIndication; + +/** + * Characteristic "Rotation Direction" + */ +export class RotationDirection extends Characteristic { + + public static readonly UUID: string = "00000028-0000-1000-8000-0026BB765291"; + + public static readonly CLOCKWISE = 0; + public static readonly COUNTER_CLOCKWISE = 1; + + constructor() { + super("Rotation Direction", RotationDirection.UUID, { + format: Formats.INT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.RotationDirection = RotationDirection; + +/** + * Characteristic "Rotation Speed" + */ +export class RotationSpeed extends Characteristic { + + public static readonly UUID: string = "00000029-0000-1000-8000-0026BB765291"; + + constructor() { + super("Rotation Speed", RotationSpeed.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + minValue: 0, + maxValue: 100, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.RotationSpeed = RotationSpeed; + +/** + * Characteristic "Router Status" + */ +export class RouterStatus extends Characteristic { + + public static readonly UUID: string = "0000020E-0000-1000-8000-0026BB765291"; + + public static readonly READY = 0; + public static readonly NOT_READY = 1; + + constructor() { + super("Router Status", RouterStatus.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.RouterStatus = RouterStatus; + +/** + * Characteristic "Saturation" + */ +export class Saturation extends Characteristic { + + public static readonly UUID: string = "0000002F-0000-1000-8000-0026BB765291"; + + constructor() { + super("Saturation", Saturation.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + unit: Units.PERCENTAGE, + minValue: 0, + maxValue: 100, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.Saturation = Saturation; + +/** + * Characteristic "Security System Alarm Type" + */ +export class SecuritySystemAlarmType extends Characteristic { + + public static readonly UUID: string = "0000008E-0000-1000-8000-0026BB765291"; + + constructor() { + super("Security System Alarm Type", SecuritySystemAlarmType.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SecuritySystemAlarmType = SecuritySystemAlarmType; + +/** + * Characteristic "Security System Current State" + */ +export class SecuritySystemCurrentState extends Characteristic { + + public static readonly UUID: string = "00000066-0000-1000-8000-0026BB765291"; + + public static readonly STAY_ARM = 0; + public static readonly AWAY_ARM = 1; + public static readonly NIGHT_ARM = 2; + public static readonly DISARMED = 3; + public static readonly ALARM_TRIGGERED = 4; + + constructor() { + super("Security System Current State", SecuritySystemCurrentState.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 4, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SecuritySystemCurrentState = SecuritySystemCurrentState; + +/** + * Characteristic "Security System Target State" + */ +export class SecuritySystemTargetState extends Characteristic { + + public static readonly UUID: string = "00000067-0000-1000-8000-0026BB765291"; + + public static readonly STAY_ARM = 0; + public static readonly AWAY_ARM = 1; + public static readonly NIGHT_ARM = 2; + public static readonly DISARM = 3; + + constructor() { + super("Security System Target State", SecuritySystemTargetState.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + minValue: 0, + maxValue: 3, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SecuritySystemTargetState = SecuritySystemTargetState; + +/** + * Characteristic "Selected Audio Stream Configuration" + */ +export class SelectedAudioStreamConfiguration extends Characteristic { + + public static readonly UUID: string = "00000128-0000-1000-8000-0026BB765291"; + + constructor() { + super("Selected Audio Stream Configuration", SelectedAudioStreamConfiguration.UUID, { + format: Formats.TLV8, + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SelectedAudioStreamConfiguration = SelectedAudioStreamConfiguration; + +/** + * Characteristic "Selected Camera Recording Configuration" + */ +export class SelectedCameraRecordingConfiguration extends Characteristic { + + public static readonly UUID: string = "00000209-0000-1000-8000-0026BB765291"; + + constructor() { + super("Selected Camera Recording Configuration", SelectedCameraRecordingConfiguration.UUID, { + format: Formats.TLV8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SelectedCameraRecordingConfiguration = SelectedCameraRecordingConfiguration; + +/** + * Characteristic "Selected RTP Stream Configuration" + */ +export class SelectedRTPStreamConfiguration extends Characteristic { + + public static readonly UUID: string = "00000117-0000-1000-8000-0026BB765291"; + + constructor() { + super("Selected RTP Stream Configuration", SelectedRTPStreamConfiguration.UUID, { + format: Formats.TLV8, + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SelectedRTPStreamConfiguration = SelectedRTPStreamConfiguration; + +/** + * Characteristic "Serial Number" + */ +export class SerialNumber extends Characteristic { + + public static readonly UUID: string = "00000030-0000-1000-8000-0026BB765291"; + + constructor() { + super("Serial Number", SerialNumber.UUID, { + format: Formats.STRING, + perms: [Perms.PAIRED_READ], + maxLen: 64, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SerialNumber = SerialNumber; + +/** + * Characteristic "Service Label Index" + */ +export class ServiceLabelIndex extends Characteristic { + + public static readonly UUID: string = "000000CB-0000-1000-8000-0026BB765291"; + + constructor() { + super("Service Label Index", ServiceLabelIndex.UUID, { + format: Formats.UINT8, + perms: [Perms.PAIRED_READ], + minValue: 1, + maxValue: 255, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ServiceLabelIndex = ServiceLabelIndex; + +/** + * Characteristic "Service Label Namespace" + */ +export class ServiceLabelNamespace extends Characteristic { + + public static readonly UUID: string = "000000CD-0000-1000-8000-0026BB765291"; + + public static readonly DOTS = 0; + public static readonly ARABIC_NUMERALS = 1; + + constructor() { + super("Service Label Namespace", ServiceLabelNamespace.UUID, { + format: Formats.UINT8, + perms: [Perms.PAIRED_READ], + minValue: 0, + maxValue: 4, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ServiceLabelNamespace = ServiceLabelNamespace; + +/** + * Characteristic "Set Duration" + */ +export class SetDuration extends Characteristic { + + public static readonly UUID: string = "000000D3-0000-1000-8000-0026BB765291"; + + constructor() { + super("Set Duration", SetDuration.UUID, { + format: Formats.UINT32, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + unit: Units.SECONDS, + minValue: 0, + maxValue: 3600, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SetDuration = SetDuration; + +/** + * Characteristic "Setup Data Stream Transport" + */ +export class SetupDataStreamTransport extends Characteristic { + + public static readonly UUID: string = "00000131-0000-1000-8000-0026BB765291"; + + constructor() { + super("Setup Data Stream Transport", SetupDataStreamTransport.UUID, { + format: Formats.TLV8, + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.WRITE_RESPONSE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SetupDataStreamTransport = SetupDataStreamTransport; + +/** + * Characteristic "Setup Endpoints" + */ +export class SetupEndpoints extends Characteristic { + + public static readonly UUID: string = "00000118-0000-1000-8000-0026BB765291"; + + constructor() { + super("Setup Endpoints", SetupEndpoints.UUID, { + format: Formats.TLV8, + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SetupEndpoints = SetupEndpoints; + +/** + * Characteristic "Setup Transfer Transport" + * @since iOS 13.4 + */ +export class SetupTransferTransport extends Characteristic { + + public static readonly UUID: string = "00000201-0000-1000-8000-0026BB765291"; + + constructor() { + super("Setup Transfer Transport", SetupTransferTransport.UUID, { + format: Formats.TLV8, + perms: [Perms.PAIRED_WRITE, Perms.WRITE_RESPONSE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SetupTransferTransport = SetupTransferTransport; + +/** + * Characteristic "Signal To Noise Ratio" + * @since iOS 14 + */ +export class SignalToNoiseRatio extends Characteristic { + + public static readonly UUID: string = "00000241-0000-1000-8000-0026BB765291"; + + constructor() { + super("Signal To Noise Ratio", SignalToNoiseRatio.UUID, { + format: Formats.INT, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SignalToNoiseRatio = SignalToNoiseRatio; + +/** + * Characteristic "Siri Input Type" + */ +export class SiriInputType extends Characteristic { + + public static readonly UUID: string = "00000132-0000-1000-8000-0026BB765291"; + + public static readonly PUSH_BUTTON_TRIGGERED_APPLE_TV = 0; + + constructor() { + super("Siri Input Type", SiriInputType.UUID, { + format: Formats.UINT8, + perms: [Perms.PAIRED_READ], + minValue: 0, + maxValue: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SiriInputType = SiriInputType; + +/** + * Characteristic "Slat Type" + */ +export class SlatType extends Characteristic { + + public static readonly UUID: string = "000000C0-0000-1000-8000-0026BB765291"; + + public static readonly HORIZONTAL = 0; + public static readonly VERTICAL = 1; + + constructor() { + super("Slat Type", SlatType.UUID, { + format: Formats.UINT8, + perms: [Perms.PAIRED_READ], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SlatType = SlatType; + +/** + * Characteristic "Sleep Discovery Mode" + */ +export class SleepDiscoveryMode extends Characteristic { + + public static readonly UUID: string = "000000E8-0000-1000-8000-0026BB765291"; + + public static readonly NOT_DISCOVERABLE = 0; + public static readonly ALWAYS_DISCOVERABLE = 1; + + constructor() { + super("Sleep Discovery Mode", SleepDiscoveryMode.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SleepDiscoveryMode = SleepDiscoveryMode; + +/** + * Characteristic "Sleep Interval" + * @since iOS 14 + */ +export class SleepInterval extends Characteristic { + + public static readonly UUID: string = "0000023A-0000-1000-8000-0026BB765291"; + + constructor() { + super("Sleep Interval", SleepInterval.UUID, { + format: Formats.UINT32, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SleepInterval = SleepInterval; + +/** + * Characteristic "Smoke Detected" + */ +export class SmokeDetected extends Characteristic { + + public static readonly UUID: string = "00000076-0000-1000-8000-0026BB765291"; + + public static readonly SMOKE_NOT_DETECTED = 0; + public static readonly SMOKE_DETECTED = 1; + + constructor() { + super("Smoke Detected", SmokeDetected.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SmokeDetected = SmokeDetected; + +/** + * Characteristic "Software Revision" + */ +export class SoftwareRevision extends Characteristic { + + public static readonly UUID: string = "00000054-0000-1000-8000-0026BB765291"; + + constructor() { + super("Software Revision", SoftwareRevision.UUID, { + format: Formats.STRING, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SoftwareRevision = SoftwareRevision; + +/** + * Characteristic "Status Active" + */ +export class StatusActive extends Characteristic { + + public static readonly UUID: string = "00000075-0000-1000-8000-0026BB765291"; + + constructor() { + super("Status Active", StatusActive.UUID, { + format: Formats.BOOL, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.StatusActive = StatusActive; + +/** + * Characteristic "Status Fault" + */ +export class StatusFault extends Characteristic { + + public static readonly UUID: string = "00000077-0000-1000-8000-0026BB765291"; + + public static readonly NO_FAULT = 0; + public static readonly GENERAL_FAULT = 1; + + constructor() { + super("Status Fault", StatusFault.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.StatusFault = StatusFault; + +/** + * Characteristic "Status Jammed" + */ +export class StatusJammed extends Characteristic { + + public static readonly UUID: string = "00000078-0000-1000-8000-0026BB765291"; + + public static readonly NOT_JAMMED = 0; + public static readonly JAMMED = 1; + + constructor() { + super("Status Jammed", StatusJammed.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.StatusJammed = StatusJammed; + +/** + * Characteristic "Status Low Battery" + */ +export class StatusLowBattery extends Characteristic { + + public static readonly UUID: string = "00000079-0000-1000-8000-0026BB765291"; + + public static readonly BATTERY_LEVEL_NORMAL = 0; + public static readonly BATTERY_LEVEL_LOW = 1; + + constructor() { + super("Status Low Battery", StatusLowBattery.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.StatusLowBattery = StatusLowBattery; + +/** + * Characteristic "Status Tampered" + */ +export class StatusTampered extends Characteristic { + + public static readonly UUID: string = "0000007A-0000-1000-8000-0026BB765291"; + + public static readonly NOT_TAMPERED = 0; + public static readonly TAMPERED = 1; + + constructor() { + super("Status Tampered", StatusTampered.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.StatusTampered = StatusTampered; + +/** + * Characteristic "Streaming Status" + */ +export class StreamingStatus extends Characteristic { + + public static readonly UUID: string = "00000120-0000-1000-8000-0026BB765291"; + + constructor() { + super("Streaming Status", StreamingStatus.UUID, { + format: Formats.TLV8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.StreamingStatus = StreamingStatus; + +/** + * Characteristic "Sulphur Dioxide Density" + */ +export class SulphurDioxideDensity extends Characteristic { + + public static readonly UUID: string = "000000C5-0000-1000-8000-0026BB765291"; + + constructor() { + super("Sulphur Dioxide Density", SulphurDioxideDensity.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 1000, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SulphurDioxideDensity = SulphurDioxideDensity; + +/** + * Characteristic "Supported Audio Recording Configuration" + */ +export class SupportedAudioRecordingConfiguration extends Characteristic { + + public static readonly UUID: string = "00000207-0000-1000-8000-0026BB765291"; + + constructor() { + super("Supported Audio Recording Configuration", SupportedAudioRecordingConfiguration.UUID, { + format: Formats.TLV8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SupportedAudioRecordingConfiguration = SupportedAudioRecordingConfiguration; + +/** + * Characteristic "Supported Audio Stream Configuration" + */ +export class SupportedAudioStreamConfiguration extends Characteristic { + + public static readonly UUID: string = "00000115-0000-1000-8000-0026BB765291"; + + constructor() { + super("Supported Audio Stream Configuration", SupportedAudioStreamConfiguration.UUID, { + format: Formats.TLV8, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SupportedAudioStreamConfiguration = SupportedAudioStreamConfiguration; + +/** + * Characteristic "Supported Camera Recording Configuration" + */ +export class SupportedCameraRecordingConfiguration extends Characteristic { + + public static readonly UUID: string = "00000205-0000-1000-8000-0026BB765291"; + + constructor() { + super("Supported Camera Recording Configuration", SupportedCameraRecordingConfiguration.UUID, { + format: Formats.TLV8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SupportedCameraRecordingConfiguration = SupportedCameraRecordingConfiguration; + +/** + * Characteristic "Supported Characteristic Value Transition Configuration" + * @since iOS 14 + */ +export class SupportedCharacteristicValueTransitionConfiguration extends Characteristic { + + public static readonly UUID: string = "00000144-0000-1000-8000-0026BB765291"; + + constructor() { + super("Supported Characteristic Value Transition Configuration", SupportedCharacteristicValueTransitionConfiguration.UUID, { + format: Formats.TLV8, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SupportedCharacteristicValueTransitionConfiguration = SupportedCharacteristicValueTransitionConfiguration; + +/** + * Characteristic "Supported Data Stream Transport Configuration" + */ +export class SupportedDataStreamTransportConfiguration extends Characteristic { + + public static readonly UUID: string = "00000130-0000-1000-8000-0026BB765291"; + + constructor() { + super("Supported Data Stream Transport Configuration", SupportedDataStreamTransportConfiguration.UUID, { + format: Formats.TLV8, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SupportedDataStreamTransportConfiguration = SupportedDataStreamTransportConfiguration; + +/** + * Characteristic "Supported Diagnostics Snapshot" + * @since iOS 14 + */ +export class SupportedDiagnosticsSnapshot extends Characteristic { + + public static readonly UUID: string = "00000238-0000-1000-8000-0026BB765291"; + + constructor() { + super("Supported Diagnostics Snapshot", SupportedDiagnosticsSnapshot.UUID, { + format: Formats.TLV8, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SupportedDiagnosticsSnapshot = SupportedDiagnosticsSnapshot; + +/** + * Characteristic "Supported Router Configuration" + */ +export class SupportedRouterConfiguration extends Characteristic { + + public static readonly UUID: string = "00000210-0000-1000-8000-0026BB765291"; + + constructor() { + super("Supported Router Configuration", SupportedRouterConfiguration.UUID, { + format: Formats.TLV8, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SupportedRouterConfiguration = SupportedRouterConfiguration; + +/** + * Characteristic "Supported RTP Configuration" + */ +export class SupportedRTPConfiguration extends Characteristic { + + public static readonly UUID: string = "00000116-0000-1000-8000-0026BB765291"; + + constructor() { + super("Supported RTP Configuration", SupportedRTPConfiguration.UUID, { + format: Formats.TLV8, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SupportedRTPConfiguration = SupportedRTPConfiguration; + +/** + * Characteristic "Supported Transfer Transport Configuration" + * @since iOS 13.4 + */ +export class SupportedTransferTransportConfiguration extends Characteristic { + + public static readonly UUID: string = "00000202-0000-1000-8000-0026BB765291"; + + constructor() { + super("Supported Transfer Transport Configuration", SupportedTransferTransportConfiguration.UUID, { + format: Formats.TLV8, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SupportedTransferTransportConfiguration = SupportedTransferTransportConfiguration; + +/** + * Characteristic "Supported Video Recording Configuration" + */ +export class SupportedVideoRecordingConfiguration extends Characteristic { + + public static readonly UUID: string = "00000206-0000-1000-8000-0026BB765291"; + + constructor() { + super("Supported Video Recording Configuration", SupportedVideoRecordingConfiguration.UUID, { + format: Formats.TLV8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SupportedVideoRecordingConfiguration = SupportedVideoRecordingConfiguration; + +/** + * Characteristic "Supported Video Stream Configuration" + */ +export class SupportedVideoStreamConfiguration extends Characteristic { + + public static readonly UUID: string = "00000114-0000-1000-8000-0026BB765291"; + + constructor() { + super("Supported Video Stream Configuration", SupportedVideoStreamConfiguration.UUID, { + format: Formats.TLV8, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SupportedVideoStreamConfiguration = SupportedVideoStreamConfiguration; + +/** + * Characteristic "Swing Mode" + */ +export class SwingMode extends Characteristic { + + public static readonly UUID: string = "000000B6-0000-1000-8000-0026BB765291"; + + public static readonly SWING_DISABLED = 0; + public static readonly SWING_ENABLED = 1; + + constructor() { + super("Swing Mode", SwingMode.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.SwingMode = SwingMode; + +/** + * Characteristic "Target Air Purifier State" + */ +export class TargetAirPurifierState extends Characteristic { + + public static readonly UUID: string = "000000A8-0000-1000-8000-0026BB765291"; + + public static readonly MANUAL = 0; + public static readonly AUTO = 1; + + constructor() { + super("Target Air Purifier State", TargetAirPurifierState.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.TargetAirPurifierState = TargetAirPurifierState; + +/** + * Characteristic "Target Control List" + */ +export class TargetControlList extends Characteristic { + + public static readonly UUID: string = "00000124-0000-1000-8000-0026BB765291"; + + constructor() { + super("Target Control List", TargetControlList.UUID, { + format: Formats.TLV8, + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.WRITE_RESPONSE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.TargetControlList = TargetControlList; + +/** + * Characteristic "Target Control Supported Configuration" + */ +export class TargetControlSupportedConfiguration extends Characteristic { + + public static readonly UUID: string = "00000123-0000-1000-8000-0026BB765291"; + + constructor() { + super("Target Control Supported Configuration", TargetControlSupportedConfiguration.UUID, { + format: Formats.TLV8, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.TargetControlSupportedConfiguration = TargetControlSupportedConfiguration; + +/** + * Characteristic "Target Door State" + */ +export class TargetDoorState extends Characteristic { + + public static readonly UUID: string = "00000032-0000-1000-8000-0026BB765291"; + + public static readonly OPEN = 0; + public static readonly CLOSED = 1; + + constructor() { + super("Target Door State", TargetDoorState.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.TargetDoorState = TargetDoorState; + +/** + * Characteristic "Target Fan State" + */ +export class TargetFanState extends Characteristic { + + public static readonly UUID: string = "000000BF-0000-1000-8000-0026BB765291"; + + public static readonly MANUAL = 0; + public static readonly AUTO = 1; + + constructor() { + super("Target Fan State", TargetFanState.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.TargetFanState = TargetFanState; + +/** + * Characteristic "Target Heater-Cooler State" + */ +export class TargetHeaterCoolerState extends Characteristic { + + public static readonly UUID: string = "000000B2-0000-1000-8000-0026BB765291"; + + public static readonly AUTO = 0; + public static readonly HEAT = 1; + public static readonly COOL = 2; + + constructor() { + super("Target Heater-Cooler State", TargetHeaterCoolerState.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + minValue: 0, + maxValue: 2, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.TargetHeaterCoolerState = TargetHeaterCoolerState; + +/** + * Characteristic "Target Heating Cooling State" + */ +export class TargetHeatingCoolingState extends Characteristic { + + public static readonly UUID: string = "00000033-0000-1000-8000-0026BB765291"; + + public static readonly OFF = 0; + public static readonly HEAT = 1; + public static readonly COOL = 2; + public static readonly AUTO = 3; + + constructor() { + super("Target Heating Cooling State", TargetHeatingCoolingState.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + minValue: 0, + maxValue: 3, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.TargetHeatingCoolingState = TargetHeatingCoolingState; + +/** + * Characteristic "Target Horizontal Tilt Angle" + */ +export class TargetHorizontalTiltAngle extends Characteristic { + + public static readonly UUID: string = "0000007B-0000-1000-8000-0026BB765291"; + + constructor() { + super("Target Horizontal Tilt Angle", TargetHorizontalTiltAngle.UUID, { + format: Formats.INT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + unit: Units.ARC_DEGREE, + minValue: -90, + maxValue: 90, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.TargetHorizontalTiltAngle = TargetHorizontalTiltAngle; + +/** + * Characteristic "Target Humidifier-Dehumidifier State" + */ +export class TargetHumidifierDehumidifierState extends Characteristic { + + public static readonly UUID: string = "000000B4-0000-1000-8000-0026BB765291"; + + /** + * @deprecated Removed in iOS 11. Use {@link HUMIDIFIER_OR_DEHUMIDIFIER} instead. + */ + public static readonly AUTO = 0; + + public static readonly HUMIDIFIER_OR_DEHUMIDIFIER = 0; + public static readonly HUMIDIFIER = 1; + public static readonly DEHUMIDIFIER = 2; + + constructor() { + super("Target Humidifier-Dehumidifier State", TargetHumidifierDehumidifierState.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + minValue: 0, + maxValue: 2, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.TargetHumidifierDehumidifierState = TargetHumidifierDehumidifierState; + +/** + * Characteristic "Target Media State" + */ +export class TargetMediaState extends Characteristic { + + public static readonly UUID: string = "00000137-0000-1000-8000-0026BB765291"; + + public static readonly PLAY = 0; + public static readonly PAUSE = 1; + public static readonly STOP = 2; + + constructor() { + super("Target Media State", TargetMediaState.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + minValue: 0, + maxValue: 2, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.TargetMediaState = TargetMediaState; + +/** + * Characteristic "Target Position" + */ +export class TargetPosition extends Characteristic { + + public static readonly UUID: string = "0000007C-0000-1000-8000-0026BB765291"; + + constructor() { + super("Target Position", TargetPosition.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + unit: Units.PERCENTAGE, + minValue: 0, + maxValue: 100, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.TargetPosition = TargetPosition; + +/** + * Characteristic "Target Relative Humidity" + */ +export class TargetRelativeHumidity extends Characteristic { + + public static readonly UUID: string = "00000034-0000-1000-8000-0026BB765291"; + + constructor() { + super("Target Relative Humidity", TargetRelativeHumidity.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + unit: Units.PERCENTAGE, + minValue: 0, + maxValue: 100, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.TargetRelativeHumidity = TargetRelativeHumidity; + +/** + * Characteristic "Target Temperature" + */ +export class TargetTemperature extends Characteristic { + + public static readonly UUID: string = "00000035-0000-1000-8000-0026BB765291"; + + constructor() { + super("Target Temperature", TargetTemperature.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + unit: Units.CELSIUS, + minValue: 10, + maxValue: 38, + minStep: 0.1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.TargetTemperature = TargetTemperature; + +/** + * Characteristic "Target Tilt Angle" + */ +export class TargetTiltAngle extends Characteristic { + + public static readonly UUID: string = "000000C2-0000-1000-8000-0026BB765291"; + + constructor() { + super("Target Tilt Angle", TargetTiltAngle.UUID, { + format: Formats.INT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + unit: Units.ARC_DEGREE, + minValue: -90, + maxValue: 90, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.TargetTiltAngle = TargetTiltAngle; + +/** + * Characteristic "Target Vertical Tilt Angle" + */ +export class TargetVerticalTiltAngle extends Characteristic { + + public static readonly UUID: string = "0000007D-0000-1000-8000-0026BB765291"; + + constructor() { + super("Target Vertical Tilt Angle", TargetVerticalTiltAngle.UUID, { + format: Formats.INT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + unit: Units.ARC_DEGREE, + minValue: -90, + maxValue: 90, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.TargetVerticalTiltAngle = TargetVerticalTiltAngle; + +/** + * Characteristic "Target Visibility State" + */ +export class TargetVisibilityState extends Characteristic { + + public static readonly UUID: string = "00000134-0000-1000-8000-0026BB765291"; + + public static readonly SHOWN = 0; + public static readonly HIDDEN = 1; + + constructor() { + super("Target Visibility State", TargetVisibilityState.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.TargetVisibilityState = TargetVisibilityState; + +/** + * Characteristic "Temperature Display Units" + */ +export class TemperatureDisplayUnits extends Characteristic { + + public static readonly UUID: string = "00000036-0000-1000-8000-0026BB765291"; + + public static readonly CELSIUS = 0; + public static readonly FAHRENHEIT = 1; + + constructor() { + super("Temperature Display Units", TemperatureDisplayUnits.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.TemperatureDisplayUnits = TemperatureDisplayUnits; + +/** + * Characteristic "Third Party Camera Active" + */ +export class ThirdPartyCameraActive extends Characteristic { + + public static readonly UUID: string = "0000021C-0000-1000-8000-0026BB765291"; + + public static readonly OFF = 0; + public static readonly ON = 1; + + constructor() { + super("Third Party Camera Active", ThirdPartyCameraActive.UUID, { + format: Formats.BOOL, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ThirdPartyCameraActive = ThirdPartyCameraActive; + +/** + * Characteristic "Thread Control Point" + */ +export class ThreadControlPoint extends Characteristic { + + public static readonly UUID: string = "00000704-0000-1000-8000-0026BB765291"; + + constructor() { + super("Thread Control Point", ThreadControlPoint.UUID, { + format: Formats.TLV8, + perms: [Perms.PAIRED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ThreadControlPoint = ThreadControlPoint; + +/** + * Characteristic "Thread Node Capabilities" + */ +export class ThreadNodeCapabilities extends Characteristic { + + public static readonly UUID: string = "00000702-0000-1000-8000-0026BB765291"; + + constructor() { + super("Thread Node Capabilities", ThreadNodeCapabilities.UUID, { + format: Formats.UINT16, + perms: [Perms.PAIRED_READ], + minValue: 0, + maxValue: 31, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ThreadNodeCapabilities = ThreadNodeCapabilities; + +/** + * Characteristic "Thread OpenThread Version" + */ +export class ThreadOpenThreadVersion extends Characteristic { + + public static readonly UUID: string = "00000706-0000-1000-8000-0026BB765291"; + + constructor() { + super("Thread OpenThread Version", ThreadOpenThreadVersion.UUID, { + format: Formats.STRING, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ThreadOpenThreadVersion = ThreadOpenThreadVersion; + +/** + * Characteristic "Thread Status" + */ +export class ThreadStatus extends Characteristic { + + public static readonly UUID: string = "00000703-0000-1000-8000-0026BB765291"; + + constructor() { + super("Thread Status", ThreadStatus.UUID, { + format: Formats.UINT16, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 6, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ThreadStatus = ThreadStatus; + +/** + * Characteristic "Transmit Power" + * @since iOS 14 + */ +export class TransmitPower extends Characteristic { + + public static readonly UUID: string = "00000242-0000-1000-8000-0026BB765291"; + + constructor() { + super("Transmit Power", TransmitPower.UUID, { + format: Formats.INT, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.TransmitPower = TransmitPower; + +/** + * Characteristic "Tunnel Connection Timeout" + */ +export class TunnelConnectionTimeout extends Characteristic { + + public static readonly UUID: string = "00000061-0000-1000-8000-0026BB765291"; + + constructor() { + super("Tunnel Connection Timeout", TunnelConnectionTimeout.UUID, { + format: Formats.INT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.TunnelConnectionTimeout = TunnelConnectionTimeout; + +/** + * Characteristic "Tunneled Accessory Advertising" + */ +export class TunneledAccessoryAdvertising extends Characteristic { + + public static readonly UUID: string = "00000060-0000-1000-8000-0026BB765291"; + + constructor() { + super("Tunneled Accessory Advertising", TunneledAccessoryAdvertising.UUID, { + format: Formats.BOOL, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.TunneledAccessoryAdvertising = TunneledAccessoryAdvertising; + +/** + * Characteristic "Tunneled Accessory Connected" + */ +export class TunneledAccessoryConnected extends Characteristic { + + public static readonly UUID: string = "00000059-0000-1000-8000-0026BB765291"; + + constructor() { + super("Tunneled Accessory Connected", TunneledAccessoryConnected.UUID, { + format: Formats.BOOL, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.TunneledAccessoryConnected = TunneledAccessoryConnected; + +/** + * Characteristic "Tunneled Accessory State Number" + */ +export class TunneledAccessoryStateNumber extends Characteristic { + + public static readonly UUID: string = "00000058-0000-1000-8000-0026BB765291"; + + constructor() { + super("Tunneled Accessory State Number", TunneledAccessoryStateNumber.UUID, { + format: Formats.INT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.TunneledAccessoryStateNumber = TunneledAccessoryStateNumber; + +/** + * Characteristic "Valve Type" + */ +export class ValveType extends Characteristic { + + public static readonly UUID: string = "000000D5-0000-1000-8000-0026BB765291"; + + public static readonly GENERIC_VALVE = 0; + public static readonly IRRIGATION = 1; + public static readonly SHOWER_HEAD = 2; + public static readonly WATER_FAUCET = 3; + + constructor() { + super("Valve Type", ValveType.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 3, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.ValveType = ValveType; + +/** + * Characteristic "Version" + */ +export class Version extends Characteristic { + + public static readonly UUID: string = "00000037-0000-1000-8000-0026BB765291"; + + constructor() { + super("Version", Version.UUID, { + format: Formats.STRING, + perms: [Perms.PAIRED_READ], + maxLen: 64, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.Version = Version; + +/** + * Characteristic "Video Analysis Active" + * @since iOS 14 + */ +export class VideoAnalysisActive extends Characteristic { + + public static readonly UUID: string = "00000229-0000-1000-8000-0026BB765291"; + + constructor() { + super("Video Analysis Active", VideoAnalysisActive.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.VideoAnalysisActive = VideoAnalysisActive; + +/** + * Characteristic "VOC Density" + */ +export class VOCDensity extends Characteristic { + + public static readonly UUID: string = "000000C8-0000-1000-8000-0026BB765291"; + + constructor() { + super("VOC Density", VOCDensity.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 1000, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.VOCDensity = VOCDensity; + +/** + * Characteristic "Volume" + */ +export class Volume extends Characteristic { + + public static readonly UUID: string = "00000119-0000-1000-8000-0026BB765291"; + + constructor() { + super("Volume", Volume.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], + unit: Units.PERCENTAGE, + minValue: 0, + maxValue: 100, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.Volume = Volume; + +/** + * Characteristic "Volume Control Type" + */ +export class VolumeControlType extends Characteristic { + + public static readonly UUID: string = "000000E9-0000-1000-8000-0026BB765291"; + + public static readonly NONE = 0; + public static readonly RELATIVE = 1; + public static readonly RELATIVE_WITH_CURRENT = 2; + public static readonly ABSOLUTE = 3; + + constructor() { + super("Volume Control Type", VolumeControlType.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 3, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.VolumeControlType = VolumeControlType; + +/** + * Characteristic "Volume Selector" + */ +export class VolumeSelector extends Characteristic { + + public static readonly UUID: string = "000000EA-0000-1000-8000-0026BB765291"; + + public static readonly INCREMENT = 0; + public static readonly DECREMENT = 1; + + constructor() { + super("Volume Selector", VolumeSelector.UUID, { + format: Formats.UINT8, + perms: [Perms.PAIRED_WRITE], + minValue: 0, + maxValue: 1, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.VolumeSelector = VolumeSelector; + +/** + * Characteristic "Wake Configuration" + * @since iOS 13.4 + */ +export class WakeConfiguration extends Characteristic { + + public static readonly UUID: string = "00000222-0000-1000-8000-0026BB765291"; + + constructor() { + super("Wake Configuration", WakeConfiguration.UUID, { + format: Formats.TLV8, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.WakeConfiguration = WakeConfiguration; + +/** + * Characteristic "WAN Configuration List" + */ +export class WANConfigurationList extends Characteristic { + + public static readonly UUID: string = "00000211-0000-1000-8000-0026BB765291"; + + constructor() { + super("WAN Configuration List", WANConfigurationList.UUID, { + format: Formats.TLV8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.WANConfigurationList = WANConfigurationList; + +/** + * Characteristic "WAN Status List" + */ +export class WANStatusList extends Characteristic { + + public static readonly UUID: string = "00000212-0000-1000-8000-0026BB765291"; + + constructor() { + super("WAN Status List", WANStatusList.UUID, { + format: Formats.TLV8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.WANStatusList = WANStatusList; + +/** + * Characteristic "Water Level" + */ +export class WaterLevel extends Characteristic { + + public static readonly UUID: string = "000000B5-0000-1000-8000-0026BB765291"; + + constructor() { + super("Water Level", WaterLevel.UUID, { + format: Formats.FLOAT, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + unit: Units.PERCENTAGE, + minValue: 0, + maxValue: 100, + minStep: 1, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.WaterLevel = WaterLevel; + +/** + * Characteristic "Wi-Fi Capabilities" + * @since iOS 14 + */ +export class WiFiCapabilities extends Characteristic { + + public static readonly UUID: string = "0000022C-0000-1000-8000-0026BB765291"; + + constructor() { + super("Wi-Fi Capabilities", WiFiCapabilities.UUID, { + format: Formats.UINT32, + perms: [Perms.PAIRED_READ], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.WiFiCapabilities = WiFiCapabilities; + +/** + * Characteristic "Wi-Fi Configuration Control" + * @since iOS 14 + */ +export class WiFiConfigurationControl extends Characteristic { + + public static readonly UUID: string = "0000022D-0000-1000-8000-0026BB765291"; + + constructor() { + super("Wi-Fi Configuration Control", WiFiConfigurationControl.UUID, { + format: Formats.TLV8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.TIMED_WRITE, Perms.WRITE_RESPONSE], + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.WiFiConfigurationControl = WiFiConfigurationControl; + +/** + * Characteristic "Wi-Fi Satellite Status" + */ +export class WiFiSatelliteStatus extends Characteristic { + + public static readonly UUID: string = "0000021E-0000-1000-8000-0026BB765291"; + + public static readonly UNKNOWN = 0; + public static readonly CONNECTED = 1; + public static readonly NOT_CONNECTED = 2; + + constructor() { + super("Wi-Fi Satellite Status", WiFiSatelliteStatus.UUID, { + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + minValue: 0, + maxValue: 2, + }); + this.value = this.getDefaultValue(); + } +} +Characteristic.WiFiSatelliteStatus = WiFiSatelliteStatus; + diff --git a/src/lib/definitions/ServiceDefinitions.spec.ts b/src/lib/definitions/ServiceDefinitions.spec.ts new file mode 100644 index 000000000..a5ecd5ae6 --- /dev/null +++ b/src/lib/definitions/ServiceDefinitions.spec.ts @@ -0,0 +1,1474 @@ +// THIS FILE IS AUTO-GENERATED - DO NOT MODIFY +import "./"; + +import { Characteristic } from "../Characteristic"; +import { Service } from "../Service"; + +describe("ServiceDefinitions", () => { + describe("AccessControl", () => { + it("should be able to construct", () => { + const service0 = new Service.AccessControl(); + const service1 = new Service.AccessControl("test name"); + const service2 = new Service.AccessControl("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("AccessoryInformation", () => { + it("should be able to construct", () => { + const service0 = new Service.AccessoryInformation(); + const service1 = new Service.AccessoryInformation("test name"); + const service2 = new Service.AccessoryInformation("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("AccessoryRuntimeInformation", () => { + it("should be able to construct", () => { + const service0 = new Service.AccessoryRuntimeInformation(); + const service1 = new Service.AccessoryRuntimeInformation("test name"); + const service2 = new Service.AccessoryRuntimeInformation("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("AirPurifier", () => { + it("should be able to construct", () => { + const service0 = new Service.AirPurifier(); + const service1 = new Service.AirPurifier("test name"); + const service2 = new Service.AirPurifier("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("AirQualitySensor", () => { + it("should be able to construct", () => { + const service0 = new Service.AirQualitySensor(); + const service1 = new Service.AirQualitySensor("test name"); + const service2 = new Service.AirQualitySensor("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("AudioStreamManagement", () => { + it("should be able to construct", () => { + const service0 = new Service.AudioStreamManagement(); + const service1 = new Service.AudioStreamManagement("test name"); + const service2 = new Service.AudioStreamManagement("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("Battery", () => { + it("should be able to construct", () => { + const service0 = new Service.Battery(); + const service1 = new Service.Battery("test name"); + const service2 = new Service.Battery("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + // noinspection JSDeprecatedSymbols + + new Service.BatteryService(); + }); + }); + + describe("CameraControl", () => { + it("should be able to construct", () => { + const service0 = new Service.CameraControl(); + const service1 = new Service.CameraControl("test name"); + const service2 = new Service.CameraControl("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("CameraOperatingMode", () => { + it("should be able to construct", () => { + const service0 = new Service.CameraOperatingMode(); + const service1 = new Service.CameraOperatingMode("test name"); + const service2 = new Service.CameraOperatingMode("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("CameraRecordingManagement", () => { + it("should be able to construct", () => { + const service0 = new Service.CameraRecordingManagement(); + const service1 = new Service.CameraRecordingManagement("test name"); + const service2 = new Service.CameraRecordingManagement("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + // noinspection JSDeprecatedSymbols + + new Service.CameraEventRecordingManagement(); + }); + }); + + describe("CameraRTPStreamManagement", () => { + it("should be able to construct", () => { + const service0 = new Service.CameraRTPStreamManagement(); + const service1 = new Service.CameraRTPStreamManagement("test name"); + const service2 = new Service.CameraRTPStreamManagement("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("CarbonDioxideSensor", () => { + it("should be able to construct", () => { + const service0 = new Service.CarbonDioxideSensor(); + const service1 = new Service.CarbonDioxideSensor("test name"); + const service2 = new Service.CarbonDioxideSensor("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("CarbonMonoxideSensor", () => { + it("should be able to construct", () => { + const service0 = new Service.CarbonMonoxideSensor(); + const service1 = new Service.CarbonMonoxideSensor("test name"); + const service2 = new Service.CarbonMonoxideSensor("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("CloudRelay", () => { + it("should be able to construct", () => { + const service0 = new Service.CloudRelay(); + const service1 = new Service.CloudRelay("test name"); + const service2 = new Service.CloudRelay("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + // noinspection JSDeprecatedSymbols + + new Service.Relay(); + }); + }); + + describe("ContactSensor", () => { + it("should be able to construct", () => { + const service0 = new Service.ContactSensor(); + const service1 = new Service.ContactSensor("test name"); + const service2 = new Service.ContactSensor("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("DataStreamTransportManagement", () => { + it("should be able to construct", () => { + const service0 = new Service.DataStreamTransportManagement(); + const service1 = new Service.DataStreamTransportManagement("test name"); + const service2 = new Service.DataStreamTransportManagement("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("Diagnostics", () => { + it("should be able to construct", () => { + const service0 = new Service.Diagnostics(); + const service1 = new Service.Diagnostics("test name"); + const service2 = new Service.Diagnostics("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("Door", () => { + it("should be able to construct", () => { + const service0 = new Service.Door(); + const service1 = new Service.Door("test name"); + const service2 = new Service.Door("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("Doorbell", () => { + it("should be able to construct", () => { + const service0 = new Service.Doorbell(); + const service1 = new Service.Doorbell("test name"); + const service2 = new Service.Doorbell("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("Fan", () => { + it("should be able to construct", () => { + const service0 = new Service.Fan(); + const service1 = new Service.Fan("test name"); + const service2 = new Service.Fan("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("Fanv2", () => { + it("should be able to construct", () => { + const service0 = new Service.Fanv2(); + const service1 = new Service.Fanv2("test name"); + const service2 = new Service.Fanv2("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("Faucet", () => { + it("should be able to construct", () => { + const service0 = new Service.Faucet(); + const service1 = new Service.Faucet("test name"); + const service2 = new Service.Faucet("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("FilterMaintenance", () => { + it("should be able to construct", () => { + const service0 = new Service.FilterMaintenance(); + const service1 = new Service.FilterMaintenance("test name"); + const service2 = new Service.FilterMaintenance("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("GarageDoorOpener", () => { + it("should be able to construct", () => { + const service0 = new Service.GarageDoorOpener(); + const service1 = new Service.GarageDoorOpener("test name"); + const service2 = new Service.GarageDoorOpener("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("HeaterCooler", () => { + it("should be able to construct", () => { + const service0 = new Service.HeaterCooler(); + const service1 = new Service.HeaterCooler("test name"); + const service2 = new Service.HeaterCooler("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("HumidifierDehumidifier", () => { + it("should be able to construct", () => { + const service0 = new Service.HumidifierDehumidifier(); + const service1 = new Service.HumidifierDehumidifier("test name"); + const service2 = new Service.HumidifierDehumidifier("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("HumiditySensor", () => { + it("should be able to construct", () => { + const service0 = new Service.HumiditySensor(); + const service1 = new Service.HumiditySensor("test name"); + const service2 = new Service.HumiditySensor("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("InputSource", () => { + it("should be able to construct", () => { + const service0 = new Service.InputSource(); + const service1 = new Service.InputSource("test name"); + const service2 = new Service.InputSource("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("IrrigationSystem", () => { + it("should be able to construct", () => { + const service0 = new Service.IrrigationSystem(); + const service1 = new Service.IrrigationSystem("test name"); + const service2 = new Service.IrrigationSystem("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("LeakSensor", () => { + it("should be able to construct", () => { + const service0 = new Service.LeakSensor(); + const service1 = new Service.LeakSensor("test name"); + const service2 = new Service.LeakSensor("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("Lightbulb", () => { + it("should be able to construct", () => { + const service0 = new Service.Lightbulb(); + const service1 = new Service.Lightbulb("test name"); + const service2 = new Service.Lightbulb("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("LightSensor", () => { + it("should be able to construct", () => { + const service0 = new Service.LightSensor(); + const service1 = new Service.LightSensor("test name"); + const service2 = new Service.LightSensor("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("LockManagement", () => { + it("should be able to construct", () => { + const service0 = new Service.LockManagement(); + const service1 = new Service.LockManagement("test name"); + const service2 = new Service.LockManagement("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("LockMechanism", () => { + it("should be able to construct", () => { + const service0 = new Service.LockMechanism(); + const service1 = new Service.LockMechanism("test name"); + const service2 = new Service.LockMechanism("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("Microphone", () => { + it("should be able to construct", () => { + const service0 = new Service.Microphone(); + const service1 = new Service.Microphone("test name"); + const service2 = new Service.Microphone("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("MotionSensor", () => { + it("should be able to construct", () => { + const service0 = new Service.MotionSensor(); + const service1 = new Service.MotionSensor("test name"); + const service2 = new Service.MotionSensor("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("OccupancySensor", () => { + it("should be able to construct", () => { + const service0 = new Service.OccupancySensor(); + const service1 = new Service.OccupancySensor("test name"); + const service2 = new Service.OccupancySensor("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("Outlet", () => { + it("should be able to construct", () => { + const service0 = new Service.Outlet(); + const service1 = new Service.Outlet("test name"); + const service2 = new Service.Outlet("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("Pairing", () => { + it("should be able to construct", () => { + const service0 = new Service.Pairing(); + const service1 = new Service.Pairing("test name"); + const service2 = new Service.Pairing("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("PowerManagement", () => { + it("should be able to construct", () => { + const service0 = new Service.PowerManagement(); + const service1 = new Service.PowerManagement("test name"); + const service2 = new Service.PowerManagement("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("ProtocolInformation", () => { + it("should be able to construct", () => { + const service0 = new Service.ProtocolInformation(); + const service1 = new Service.ProtocolInformation("test name"); + const service2 = new Service.ProtocolInformation("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("SecuritySystem", () => { + it("should be able to construct", () => { + const service0 = new Service.SecuritySystem(); + const service1 = new Service.SecuritySystem("test name"); + const service2 = new Service.SecuritySystem("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("ServiceLabel", () => { + it("should be able to construct", () => { + const service0 = new Service.ServiceLabel(); + const service1 = new Service.ServiceLabel("test name"); + const service2 = new Service.ServiceLabel("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("Siri", () => { + it("should be able to construct", () => { + const service0 = new Service.Siri(); + const service1 = new Service.Siri("test name"); + const service2 = new Service.Siri("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("Slats", () => { + it("should be able to construct", () => { + const service0 = new Service.Slats(); + const service1 = new Service.Slats("test name"); + const service2 = new Service.Slats("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + // noinspection JSDeprecatedSymbols + + new Service.Slat(); + }); + }); + + describe("SmartSpeaker", () => { + it("should be able to construct", () => { + const service0 = new Service.SmartSpeaker(); + const service1 = new Service.SmartSpeaker("test name"); + const service2 = new Service.SmartSpeaker("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("SmokeSensor", () => { + it("should be able to construct", () => { + const service0 = new Service.SmokeSensor(); + const service1 = new Service.SmokeSensor("test name"); + const service2 = new Service.SmokeSensor("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("Speaker", () => { + it("should be able to construct", () => { + const service0 = new Service.Speaker(); + const service1 = new Service.Speaker("test name"); + const service2 = new Service.Speaker("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("StatefulProgrammableSwitch", () => { + it("should be able to construct", () => { + const service0 = new Service.StatefulProgrammableSwitch(); + const service1 = new Service.StatefulProgrammableSwitch("test name"); + const service2 = new Service.StatefulProgrammableSwitch("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("StatelessProgrammableSwitch", () => { + it("should be able to construct", () => { + const service0 = new Service.StatelessProgrammableSwitch(); + const service1 = new Service.StatelessProgrammableSwitch("test name"); + const service2 = new Service.StatelessProgrammableSwitch("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("Switch", () => { + it("should be able to construct", () => { + const service0 = new Service.Switch(); + const service1 = new Service.Switch("test name"); + const service2 = new Service.Switch("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("TargetControl", () => { + it("should be able to construct", () => { + const service0 = new Service.TargetControl(); + const service1 = new Service.TargetControl("test name"); + const service2 = new Service.TargetControl("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("TargetControlManagement", () => { + it("should be able to construct", () => { + const service0 = new Service.TargetControlManagement(); + const service1 = new Service.TargetControlManagement("test name"); + const service2 = new Service.TargetControlManagement("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("Television", () => { + it("should be able to construct", () => { + const service0 = new Service.Television(); + const service1 = new Service.Television("test name"); + const service2 = new Service.Television("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("TelevisionSpeaker", () => { + it("should be able to construct", () => { + const service0 = new Service.TelevisionSpeaker(); + const service1 = new Service.TelevisionSpeaker("test name"); + const service2 = new Service.TelevisionSpeaker("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("TemperatureSensor", () => { + it("should be able to construct", () => { + const service0 = new Service.TemperatureSensor(); + const service1 = new Service.TemperatureSensor("test name"); + const service2 = new Service.TemperatureSensor("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("Thermostat", () => { + it("should be able to construct", () => { + const service0 = new Service.Thermostat(); + const service1 = new Service.Thermostat("test name"); + const service2 = new Service.Thermostat("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("ThreadTransport", () => { + it("should be able to construct", () => { + const service0 = new Service.ThreadTransport(); + const service1 = new Service.ThreadTransport("test name"); + const service2 = new Service.ThreadTransport("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("TransferTransportManagement", () => { + it("should be able to construct", () => { + const service0 = new Service.TransferTransportManagement(); + const service1 = new Service.TransferTransportManagement("test name"); + const service2 = new Service.TransferTransportManagement("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("Tunnel", () => { + it("should be able to construct", () => { + const service0 = new Service.Tunnel(); + const service1 = new Service.Tunnel("test name"); + const service2 = new Service.Tunnel("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + // noinspection JSDeprecatedSymbols + + new Service.TunneledBTLEAccessoryService(); + }); + }); + + describe("Valve", () => { + it("should be able to construct", () => { + const service0 = new Service.Valve(); + const service1 = new Service.Valve("test name"); + const service2 = new Service.Valve("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("WiFiRouter", () => { + it("should be able to construct", () => { + const service0 = new Service.WiFiRouter(); + const service1 = new Service.WiFiRouter("test name"); + const service2 = new Service.WiFiRouter("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("WiFiSatellite", () => { + it("should be able to construct", () => { + const service0 = new Service.WiFiSatellite(); + const service1 = new Service.WiFiSatellite("test name"); + const service2 = new Service.WiFiSatellite("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("WiFiTransport", () => { + it("should be able to construct", () => { + const service0 = new Service.WiFiTransport(); + const service1 = new Service.WiFiTransport("test name"); + const service2 = new Service.WiFiTransport("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("Window", () => { + it("should be able to construct", () => { + const service0 = new Service.Window(); + const service1 = new Service.Window("test name"); + const service2 = new Service.Window("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); + + describe("WindowCovering", () => { + it("should be able to construct", () => { + const service0 = new Service.WindowCovering(); + const service1 = new Service.WindowCovering("test name"); + const service2 = new Service.WindowCovering("test name", "test sub type"); + + expect(service0.displayName).toBe(""); + expect(service0.testCharacteristic(Characteristic.Name)).toBe(false); + expect(service0.subtype).toBeUndefined(); + + expect(service1.displayName).toBe("test name"); + expect(service1.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service1.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service1.subtype).toBeUndefined(); + + expect(service2.displayName).toBe("test name"); + expect(service2.testCharacteristic(Characteristic.Name)).toBe(true); + expect(service2.getCharacteristic(Characteristic.Name).value).toBe("test name"); + expect(service2.subtype).toBe("test sub type"); + }); + }); +}); diff --git a/src/lib/definitions/ServiceDefinitions.ts b/src/lib/definitions/ServiceDefinitions.ts new file mode 100644 index 000000000..ddb472caf --- /dev/null +++ b/src/lib/definitions/ServiceDefinitions.ts @@ -0,0 +1,1494 @@ +// THIS FILE IS AUTO-GENERATED - DO NOT MODIFY +// V=856 + +import { Characteristic } from "../Characteristic"; +import { Service } from "../Service"; + +/** + * Service "Access Control" + */ +export class AccessControl extends Service { + + public static readonly UUID: string = "000000DA-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, AccessControl.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.AccessControlLevel); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.PasswordSetting); + } +} +Service.AccessControl = AccessControl; + +/** + * Service "Accessory Information" + */ +export class AccessoryInformation extends Service { + + public static readonly UUID: string = "0000003E-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, AccessoryInformation.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.Identify); + this.addCharacteristic(Characteristic.Manufacturer); + this.addCharacteristic(Characteristic.Model); + if (!this.testCharacteristic(Characteristic.Name)) { // workaround for Name characteristic collision in constructor + this.addCharacteristic(Characteristic.Name).updateValue("Unnamed Service"); + } + this.addCharacteristic(Characteristic.SerialNumber); + this.addCharacteristic(Characteristic.FirmwareRevision); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.AccessoryFlags); + this.addOptionalCharacteristic(Characteristic.AppMatchingIdentifier); + this.addOptionalCharacteristic(Characteristic.ConfiguredName); + this.addOptionalCharacteristic(Characteristic.HardwareRevision); + this.addOptionalCharacteristic(Characteristic.SoftwareRevision); + this.addOptionalCharacteristic(Characteristic.ProductData); + } +} +Service.AccessoryInformation = AccessoryInformation; + +/** + * Service "Accessory Runtime Information" + */ +export class AccessoryRuntimeInformation extends Service { + + public static readonly UUID: string = "00000239-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, AccessoryRuntimeInformation.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.Ping); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.ActivityInterval); + this.addOptionalCharacteristic(Characteristic.HeartBeat); + this.addOptionalCharacteristic(Characteristic.SleepInterval); + } +} +Service.AccessoryRuntimeInformation = AccessoryRuntimeInformation; + +/** + * Service "Air Purifier" + */ +export class AirPurifier extends Service { + + public static readonly UUID: string = "000000BB-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, AirPurifier.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.Active); + this.addCharacteristic(Characteristic.CurrentAirPurifierState); + this.addCharacteristic(Characteristic.TargetAirPurifierState); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.LockPhysicalControls); + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.RotationSpeed); + this.addOptionalCharacteristic(Characteristic.SwingMode); + } +} +Service.AirPurifier = AirPurifier; + +/** + * Service "Air Quality Sensor" + */ +export class AirQualitySensor extends Service { + + public static readonly UUID: string = "0000008D-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, AirQualitySensor.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.AirQuality); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.NitrogenDioxideDensity); + this.addOptionalCharacteristic(Characteristic.OzoneDensity); + this.addOptionalCharacteristic(Characteristic.PM10Density); + this.addOptionalCharacteristic(Characteristic.PM2_5Density); + this.addOptionalCharacteristic(Characteristic.SulphurDioxideDensity); + this.addOptionalCharacteristic(Characteristic.VOCDensity); + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.StatusActive); + this.addOptionalCharacteristic(Characteristic.StatusFault); + this.addOptionalCharacteristic(Characteristic.StatusLowBattery); + this.addOptionalCharacteristic(Characteristic.StatusTampered); + } +} +Service.AirQualitySensor = AirQualitySensor; + +/** + * Service "Audio Stream Management" + */ +export class AudioStreamManagement extends Service { + + public static readonly UUID: string = "00000127-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, AudioStreamManagement.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.SupportedAudioStreamConfiguration); + this.addCharacteristic(Characteristic.SelectedAudioStreamConfiguration); + } +} +Service.AudioStreamManagement = AudioStreamManagement; + +/** + * Service "Battery" + */ +export class Battery extends Service { + + public static readonly UUID: string = "00000096-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, Battery.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.StatusLowBattery); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.BatteryLevel); + this.addOptionalCharacteristic(Characteristic.ChargingState); + this.addOptionalCharacteristic(Characteristic.Name); + } +} +// noinspection JSDeprecatedSymbols +Service.BatteryService = Battery; +Service.Battery = Battery; + +/** + * Service "Camera Control" + * @deprecated This service has no usage anymore and will be ignored by iOS + */ +export class CameraControl extends Service { + + public static readonly UUID: string = "00000111-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, CameraControl.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.On); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.CurrentHorizontalTiltAngle); + this.addOptionalCharacteristic(Characteristic.CurrentVerticalTiltAngle); + this.addOptionalCharacteristic(Characteristic.TargetHorizontalTiltAngle); + this.addOptionalCharacteristic(Characteristic.TargetVerticalTiltAngle); + this.addOptionalCharacteristic(Characteristic.NightVision); + this.addOptionalCharacteristic(Characteristic.OpticalZoom); + this.addOptionalCharacteristic(Characteristic.DigitalZoom); + this.addOptionalCharacteristic(Characteristic.ImageRotation); + this.addOptionalCharacteristic(Characteristic.ImageMirroring); + this.addOptionalCharacteristic(Characteristic.Name); + } +} +Service.CameraControl = CameraControl; + +/** + * Service "Camera Operating Mode" + */ +export class CameraOperatingMode extends Service { + + public static readonly UUID: string = "0000021A-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, CameraOperatingMode.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.EventSnapshotsActive); + this.addCharacteristic(Characteristic.HomeKitCameraActive); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.CameraOperatingModeIndicator); + this.addOptionalCharacteristic(Characteristic.ManuallyDisabled); + this.addOptionalCharacteristic(Characteristic.NightVision); + this.addOptionalCharacteristic(Characteristic.PeriodicSnapshotsActive); + this.addOptionalCharacteristic(Characteristic.ThirdPartyCameraActive); + this.addOptionalCharacteristic(Characteristic.DiagonalFieldOfView); + } +} +Service.CameraOperatingMode = CameraOperatingMode; + +/** + * Service "Camera Recording Management" + */ +export class CameraRecordingManagement extends Service { + + public static readonly UUID: string = "00000204-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, CameraRecordingManagement.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.Active); + this.addCharacteristic(Characteristic.SupportedCameraRecordingConfiguration); + this.addCharacteristic(Characteristic.SupportedVideoRecordingConfiguration); + this.addCharacteristic(Characteristic.SupportedAudioRecordingConfiguration); + this.addCharacteristic(Characteristic.SelectedCameraRecordingConfiguration); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.RecordingAudioActive); + } +} +// noinspection JSDeprecatedSymbols +Service.CameraEventRecordingManagement = CameraRecordingManagement; +Service.CameraRecordingManagement = CameraRecordingManagement; + +/** + * Service "Camera RTP Stream Management" + */ +export class CameraRTPStreamManagement extends Service { + + public static readonly UUID: string = "00000110-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, CameraRTPStreamManagement.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.SelectedRTPStreamConfiguration); + this.addCharacteristic(Characteristic.SetupEndpoints); + this.addCharacteristic(Characteristic.StreamingStatus); + this.addCharacteristic(Characteristic.SupportedAudioStreamConfiguration); + this.addCharacteristic(Characteristic.SupportedRTPConfiguration); + this.addCharacteristic(Characteristic.SupportedVideoStreamConfiguration); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Active); + } +} +Service.CameraRTPStreamManagement = CameraRTPStreamManagement; + +/** + * Service "Carbon Dioxide Sensor" + */ +export class CarbonDioxideSensor extends Service { + + public static readonly UUID: string = "00000097-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, CarbonDioxideSensor.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.CarbonDioxideDetected); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.CarbonDioxideLevel); + this.addOptionalCharacteristic(Characteristic.CarbonDioxidePeakLevel); + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.StatusActive); + this.addOptionalCharacteristic(Characteristic.StatusFault); + this.addOptionalCharacteristic(Characteristic.StatusLowBattery); + this.addOptionalCharacteristic(Characteristic.StatusTampered); + } +} +Service.CarbonDioxideSensor = CarbonDioxideSensor; + +/** + * Service "Carbon Monoxide Sensor" + */ +export class CarbonMonoxideSensor extends Service { + + public static readonly UUID: string = "0000007F-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, CarbonMonoxideSensor.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.CarbonMonoxideDetected); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.CarbonMonoxideLevel); + this.addOptionalCharacteristic(Characteristic.CarbonMonoxidePeakLevel); + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.StatusActive); + this.addOptionalCharacteristic(Characteristic.StatusFault); + this.addOptionalCharacteristic(Characteristic.StatusLowBattery); + this.addOptionalCharacteristic(Characteristic.StatusTampered); + } +} +Service.CarbonMonoxideSensor = CarbonMonoxideSensor; + +/** + * Service "Cloud Relay" + */ +export class CloudRelay extends Service { + + public static readonly UUID: string = "0000005A-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, CloudRelay.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.RelayControlPoint); + this.addCharacteristic(Characteristic.RelayState); + this.addCharacteristic(Characteristic.RelayEnabled); + } +} +// noinspection JSDeprecatedSymbols +Service.Relay = CloudRelay; +Service.CloudRelay = CloudRelay; + +/** + * Service "Contact Sensor" + */ +export class ContactSensor extends Service { + + public static readonly UUID: string = "00000080-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, ContactSensor.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.ContactSensorState); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.StatusActive); + this.addOptionalCharacteristic(Characteristic.StatusFault); + this.addOptionalCharacteristic(Characteristic.StatusLowBattery); + this.addOptionalCharacteristic(Characteristic.StatusTampered); + } +} +Service.ContactSensor = ContactSensor; + +/** + * Service "Data Stream Transport Management" + */ +export class DataStreamTransportManagement extends Service { + + public static readonly UUID: string = "00000129-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, DataStreamTransportManagement.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.SetupDataStreamTransport); + this.addCharacteristic(Characteristic.SupportedDataStreamTransportConfiguration); + this.addCharacteristic(Characteristic.Version); + } +} +Service.DataStreamTransportManagement = DataStreamTransportManagement; + +/** + * Service "Diagnostics" + */ +export class Diagnostics extends Service { + + public static readonly UUID: string = "00000237-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, Diagnostics.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.SupportedDiagnosticsSnapshot); + } +} +Service.Diagnostics = Diagnostics; + +/** + * Service "Door" + */ +export class Door extends Service { + + public static readonly UUID: string = "00000081-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, Door.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.CurrentPosition); + this.addCharacteristic(Characteristic.PositionState); + this.addCharacteristic(Characteristic.TargetPosition); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.ObstructionDetected); + this.addOptionalCharacteristic(Characteristic.HoldPosition); + } +} +Service.Door = Door; + +/** + * Service "Doorbell" + */ +export class Doorbell extends Service { + + public static readonly UUID: string = "00000121-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, Doorbell.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.ProgrammableSwitchEvent); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Brightness); + this.addOptionalCharacteristic(Characteristic.Mute); + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.OperatingStateResponse); + this.addOptionalCharacteristic(Characteristic.Volume); + } +} +Service.Doorbell = Doorbell; + +/** + * Service "Fan" + */ +export class Fan extends Service { + + public static readonly UUID: string = "00000040-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, Fan.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.On); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.RotationDirection); + this.addOptionalCharacteristic(Characteristic.RotationSpeed); + } +} +Service.Fan = Fan; + +/** + * Service "Fanv2" + */ +export class Fanv2 extends Service { + + public static readonly UUID: string = "000000B7-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, Fanv2.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.Active); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.CurrentFanState); + this.addOptionalCharacteristic(Characteristic.TargetFanState); + this.addOptionalCharacteristic(Characteristic.LockPhysicalControls); + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.RotationDirection); + this.addOptionalCharacteristic(Characteristic.RotationSpeed); + this.addOptionalCharacteristic(Characteristic.SwingMode); + } +} +Service.Fanv2 = Fanv2; + +/** + * Service "Faucet" + */ +export class Faucet extends Service { + + public static readonly UUID: string = "000000D7-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, Faucet.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.Active); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.StatusFault); + } +} +Service.Faucet = Faucet; + +/** + * Service "Filter Maintenance" + */ +export class FilterMaintenance extends Service { + + public static readonly UUID: string = "000000BA-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, FilterMaintenance.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.FilterChangeIndication); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.FilterLifeLevel); + this.addOptionalCharacteristic(Characteristic.ResetFilterIndication); + this.addOptionalCharacteristic(Characteristic.Name); + } +} +Service.FilterMaintenance = FilterMaintenance; + +/** + * Service "Garage Door Opener" + */ +export class GarageDoorOpener extends Service { + + public static readonly UUID: string = "00000041-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, GarageDoorOpener.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.CurrentDoorState); + this.addCharacteristic(Characteristic.TargetDoorState); + this.addCharacteristic(Characteristic.ObstructionDetected); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.LockCurrentState); + this.addOptionalCharacteristic(Characteristic.LockTargetState); + this.addOptionalCharacteristic(Characteristic.Name); + } +} +Service.GarageDoorOpener = GarageDoorOpener; + +/** + * Service "Heater-Cooler" + */ +export class HeaterCooler extends Service { + + public static readonly UUID: string = "000000BC-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, HeaterCooler.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.Active); + this.addCharacteristic(Characteristic.CurrentHeaterCoolerState); + this.addCharacteristic(Characteristic.TargetHeaterCoolerState); + this.addCharacteristic(Characteristic.CurrentTemperature); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.LockPhysicalControls); + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.RotationSpeed); + this.addOptionalCharacteristic(Characteristic.SwingMode); + this.addOptionalCharacteristic(Characteristic.CoolingThresholdTemperature); + this.addOptionalCharacteristic(Characteristic.HeatingThresholdTemperature); + this.addOptionalCharacteristic(Characteristic.TemperatureDisplayUnits); + } +} +Service.HeaterCooler = HeaterCooler; + +/** + * Service "Humidifier-Dehumidifier" + */ +export class HumidifierDehumidifier extends Service { + + public static readonly UUID: string = "000000BD-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, HumidifierDehumidifier.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.Active); + this.addCharacteristic(Characteristic.CurrentHumidifierDehumidifierState); + this.addCharacteristic(Characteristic.TargetHumidifierDehumidifierState); + this.addCharacteristic(Characteristic.CurrentRelativeHumidity); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.LockPhysicalControls); + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.RelativeHumidityDehumidifierThreshold); + this.addOptionalCharacteristic(Characteristic.RelativeHumidityHumidifierThreshold); + this.addOptionalCharacteristic(Characteristic.RotationSpeed); + this.addOptionalCharacteristic(Characteristic.SwingMode); + this.addOptionalCharacteristic(Characteristic.WaterLevel); + } +} +Service.HumidifierDehumidifier = HumidifierDehumidifier; + +/** + * Service "Humidity Sensor" + */ +export class HumiditySensor extends Service { + + public static readonly UUID: string = "00000082-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, HumiditySensor.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.CurrentRelativeHumidity); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.StatusActive); + this.addOptionalCharacteristic(Characteristic.StatusFault); + this.addOptionalCharacteristic(Characteristic.StatusLowBattery); + this.addOptionalCharacteristic(Characteristic.StatusTampered); + } +} +Service.HumiditySensor = HumiditySensor; + +/** + * Service "Input Source" + */ +export class InputSource extends Service { + + public static readonly UUID: string = "000000D9-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, InputSource.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.ConfiguredName); + this.addCharacteristic(Characteristic.InputSourceType); + this.addCharacteristic(Characteristic.IsConfigured); + if (!this.testCharacteristic(Characteristic.Name)) { // workaround for Name characteristic collision in constructor + this.addCharacteristic(Characteristic.Name).updateValue("Unnamed Service"); + } + this.addCharacteristic(Characteristic.CurrentVisibilityState); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Identifier); + this.addOptionalCharacteristic(Characteristic.InputDeviceType); + this.addOptionalCharacteristic(Characteristic.TargetVisibilityState); + } +} +Service.InputSource = InputSource; + +/** + * Service "Irrigation-System" + */ +export class IrrigationSystem extends Service { + + public static readonly UUID: string = "000000CF-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, IrrigationSystem.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.Active); + this.addCharacteristic(Characteristic.ProgramMode); + this.addCharacteristic(Characteristic.InUse); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.RemainingDuration); + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.StatusFault); + } +} +Service.IrrigationSystem = IrrigationSystem; + +/** + * Service "Leak Sensor" + */ +export class LeakSensor extends Service { + + public static readonly UUID: string = "00000083-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, LeakSensor.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.LeakDetected); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.StatusActive); + this.addOptionalCharacteristic(Characteristic.StatusFault); + this.addOptionalCharacteristic(Characteristic.StatusLowBattery); + this.addOptionalCharacteristic(Characteristic.StatusTampered); + } +} +Service.LeakSensor = LeakSensor; + +/** + * Service "Lightbulb" + */ +export class Lightbulb extends Service { + + public static readonly UUID: string = "00000043-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, Lightbulb.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.On); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Brightness); + this.addOptionalCharacteristic(Characteristic.CharacteristicValueActiveTransitionCount); + this.addOptionalCharacteristic(Characteristic.CharacteristicValueTransitionControl); + this.addOptionalCharacteristic(Characteristic.ColorTemperature); + this.addOptionalCharacteristic(Characteristic.Hue); + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.Saturation); + this.addOptionalCharacteristic(Characteristic.SupportedCharacteristicValueTransitionConfiguration); + } +} +Service.Lightbulb = Lightbulb; + +/** + * Service "Light Sensor" + */ +export class LightSensor extends Service { + + public static readonly UUID: string = "00000084-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, LightSensor.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.CurrentAmbientLightLevel); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.StatusActive); + this.addOptionalCharacteristic(Characteristic.StatusFault); + this.addOptionalCharacteristic(Characteristic.StatusLowBattery); + this.addOptionalCharacteristic(Characteristic.StatusTampered); + } +} +Service.LightSensor = LightSensor; + +/** + * Service "Lock Management" + */ +export class LockManagement extends Service { + + public static readonly UUID: string = "00000044-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, LockManagement.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.LockControlPoint); + this.addCharacteristic(Characteristic.Version); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.AdministratorOnlyAccess); + this.addOptionalCharacteristic(Characteristic.AudioFeedback); + this.addOptionalCharacteristic(Characteristic.CurrentDoorState); + this.addOptionalCharacteristic(Characteristic.LockManagementAutoSecurityTimeout); + this.addOptionalCharacteristic(Characteristic.LockLastKnownAction); + this.addOptionalCharacteristic(Characteristic.Logs); + this.addOptionalCharacteristic(Characteristic.MotionDetected); + } +} +Service.LockManagement = LockManagement; + +/** + * Service "Lock Mechanism" + */ +export class LockMechanism extends Service { + + public static readonly UUID: string = "00000045-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, LockMechanism.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.LockCurrentState); + this.addCharacteristic(Characteristic.LockTargetState); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Name); + } +} +Service.LockMechanism = LockMechanism; + +/** + * Service "Microphone" + */ +export class Microphone extends Service { + + public static readonly UUID: string = "00000112-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, Microphone.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.Mute); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Volume); + } +} +Service.Microphone = Microphone; + +/** + * Service "Motion Sensor" + */ +export class MotionSensor extends Service { + + public static readonly UUID: string = "00000085-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, MotionSensor.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.MotionDetected); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.StatusActive); + this.addOptionalCharacteristic(Characteristic.StatusFault); + this.addOptionalCharacteristic(Characteristic.StatusLowBattery); + this.addOptionalCharacteristic(Characteristic.StatusTampered); + } +} +Service.MotionSensor = MotionSensor; + +/** + * Service "Occupancy Sensor" + */ +export class OccupancySensor extends Service { + + public static readonly UUID: string = "00000086-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, OccupancySensor.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.OccupancyDetected); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.StatusActive); + this.addOptionalCharacteristic(Characteristic.StatusFault); + this.addOptionalCharacteristic(Characteristic.StatusLowBattery); + this.addOptionalCharacteristic(Characteristic.StatusTampered); + } +} +Service.OccupancySensor = OccupancySensor; + +/** + * Service "Outlet" + * @since iOS 13 + */ +export class Outlet extends Service { + + public static readonly UUID: string = "00000047-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, Outlet.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.On); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.OutletInUse); + } +} +Service.Outlet = Outlet; + +/** + * Service "Pairing" + */ +export class Pairing extends Service { + + public static readonly UUID: string = "00000055-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, Pairing.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.ListPairings); + this.addCharacteristic(Characteristic.PairSetup); + this.addCharacteristic(Characteristic.PairVerify); + this.addCharacteristic(Characteristic.PairingFeatures); + } +} +Service.Pairing = Pairing; + +/** + * Service "Power Management" + */ +export class PowerManagement extends Service { + + public static readonly UUID: string = "00000221-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, PowerManagement.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.WakeConfiguration); + } +} +Service.PowerManagement = PowerManagement; + +/** + * Service "Protocol Information" + */ +export class ProtocolInformation extends Service { + + public static readonly UUID: string = "000000A2-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, ProtocolInformation.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.Version); + } +} +Service.ProtocolInformation = ProtocolInformation; + +/** + * Service "Security System" + */ +export class SecuritySystem extends Service { + + public static readonly UUID: string = "0000007E-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, SecuritySystem.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.SecuritySystemCurrentState); + this.addCharacteristic(Characteristic.SecuritySystemTargetState); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.SecuritySystemAlarmType); + this.addOptionalCharacteristic(Characteristic.StatusFault); + this.addOptionalCharacteristic(Characteristic.StatusTampered); + } +} +Service.SecuritySystem = SecuritySystem; + +/** + * Service "Service Label" + */ +export class ServiceLabel extends Service { + + public static readonly UUID: string = "000000CC-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, ServiceLabel.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.ServiceLabelNamespace); + } +} +Service.ServiceLabel = ServiceLabel; + +/** + * Service "Siri" + */ +export class Siri extends Service { + + public static readonly UUID: string = "00000133-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, Siri.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.SiriInputType); + } +} +Service.Siri = Siri; + +/** + * Service "Slats" + */ +export class Slats extends Service { + + public static readonly UUID: string = "000000B9-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, Slats.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.CurrentSlatState); + this.addCharacteristic(Characteristic.SlatType); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.SwingMode); + this.addOptionalCharacteristic(Characteristic.CurrentTiltAngle); + this.addOptionalCharacteristic(Characteristic.TargetTiltAngle); + } +} +// noinspection JSDeprecatedSymbols +Service.Slat = Slats; +Service.Slats = Slats; + +/** + * Service "Smart Speaker" + */ +export class SmartSpeaker extends Service { + + public static readonly UUID: string = "00000228-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, SmartSpeaker.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.CurrentMediaState); + this.addCharacteristic(Characteristic.TargetMediaState); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.ConfiguredName); + this.addOptionalCharacteristic(Characteristic.Mute); + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.Volume); + } +} +Service.SmartSpeaker = SmartSpeaker; + +/** + * Service "Smoke Sensor" + */ +export class SmokeSensor extends Service { + + public static readonly UUID: string = "00000087-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, SmokeSensor.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.SmokeDetected); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.StatusActive); + this.addOptionalCharacteristic(Characteristic.StatusFault); + this.addOptionalCharacteristic(Characteristic.StatusLowBattery); + this.addOptionalCharacteristic(Characteristic.StatusTampered); + } +} +Service.SmokeSensor = SmokeSensor; + +/** + * Service "Speaker" + * @since iOS 10 + */ +export class Speaker extends Service { + + public static readonly UUID: string = "00000113-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, Speaker.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.Mute); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Active); + this.addOptionalCharacteristic(Characteristic.Volume); + } +} +Service.Speaker = Speaker; + +/** + * Service "Stateful Programmable Switch" + */ +export class StatefulProgrammableSwitch extends Service { + + public static readonly UUID: string = "00000088-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, StatefulProgrammableSwitch.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.ProgrammableSwitchEvent); + this.addCharacteristic(Characteristic.ProgrammableSwitchOutputState); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Name); + } +} +Service.StatefulProgrammableSwitch = StatefulProgrammableSwitch; + +/** + * Service "Stateless Programmable Switch" + */ +export class StatelessProgrammableSwitch extends Service { + + public static readonly UUID: string = "00000089-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, StatelessProgrammableSwitch.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.ProgrammableSwitchEvent); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.ServiceLabelIndex); + } +} +Service.StatelessProgrammableSwitch = StatelessProgrammableSwitch; + +/** + * Service "Switch" + */ +export class Switch extends Service { + + public static readonly UUID: string = "00000049-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, Switch.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.On); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Name); + } +} +Service.Switch = Switch; + +/** + * Service "Target Control" + */ +export class TargetControl extends Service { + + public static readonly UUID: string = "00000125-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, TargetControl.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.Active); + this.addCharacteristic(Characteristic.ActiveIdentifier); + this.addCharacteristic(Characteristic.ButtonEvent); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Name); + } +} +Service.TargetControl = TargetControl; + +/** + * Service "Target Control Management" + */ +export class TargetControlManagement extends Service { + + public static readonly UUID: string = "00000122-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, TargetControlManagement.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.TargetControlSupportedConfiguration); + this.addCharacteristic(Characteristic.TargetControlList); + } +} +Service.TargetControlManagement = TargetControlManagement; + +/** + * Service "Television" + */ +export class Television extends Service { + + public static readonly UUID: string = "000000D8-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, Television.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.Active); + this.addCharacteristic(Characteristic.ActiveIdentifier); + this.addCharacteristic(Characteristic.ConfiguredName); + this.addCharacteristic(Characteristic.RemoteKey); + this.addCharacteristic(Characteristic.SleepDiscoveryMode); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Brightness); + this.addOptionalCharacteristic(Characteristic.ClosedCaptions); + this.addOptionalCharacteristic(Characteristic.DisplayOrder); + this.addOptionalCharacteristic(Characteristic.CurrentMediaState); + this.addOptionalCharacteristic(Characteristic.TargetMediaState); + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.PictureMode); + this.addOptionalCharacteristic(Characteristic.PowerModeSelection); + } +} +Service.Television = Television; + +/** + * Service "Television Speaker" + */ +export class TelevisionSpeaker extends Service { + + public static readonly UUID: string = "00000113-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, TelevisionSpeaker.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.Mute); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Active); + this.addOptionalCharacteristic(Characteristic.Volume); + this.addOptionalCharacteristic(Characteristic.VolumeControlType); + this.addOptionalCharacteristic(Characteristic.VolumeSelector); + } +} +Service.TelevisionSpeaker = TelevisionSpeaker; + +/** + * Service "Temperature Sensor" + */ +export class TemperatureSensor extends Service { + + public static readonly UUID: string = "0000008A-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, TemperatureSensor.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.CurrentTemperature); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.StatusActive); + this.addOptionalCharacteristic(Characteristic.StatusFault); + this.addOptionalCharacteristic(Characteristic.StatusLowBattery); + this.addOptionalCharacteristic(Characteristic.StatusTampered); + } +} +Service.TemperatureSensor = TemperatureSensor; + +/** + * Service "Thermostat" + */ +export class Thermostat extends Service { + + public static readonly UUID: string = "0000004A-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, Thermostat.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.CurrentHeatingCoolingState); + this.addCharacteristic(Characteristic.TargetHeatingCoolingState); + this.addCharacteristic(Characteristic.CurrentTemperature); + this.addCharacteristic(Characteristic.TargetTemperature); + this.addCharacteristic(Characteristic.TemperatureDisplayUnits); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.CurrentRelativeHumidity); + this.addOptionalCharacteristic(Characteristic.TargetRelativeHumidity); + this.addOptionalCharacteristic(Characteristic.CoolingThresholdTemperature); + this.addOptionalCharacteristic(Characteristic.HeatingThresholdTemperature); + } +} +Service.Thermostat = Thermostat; + +/** + * Service "Thread Transport" + */ +export class ThreadTransport extends Service { + + public static readonly UUID: string = "00000701-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, ThreadTransport.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.CurrentTransport); + this.addCharacteristic(Characteristic.ThreadControlPoint); + this.addCharacteristic(Characteristic.ThreadNodeCapabilities); + this.addCharacteristic(Characteristic.ThreadStatus); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.CCAEnergyDetectThreshold); + this.addOptionalCharacteristic(Characteristic.CCASignalDetectThreshold); + this.addOptionalCharacteristic(Characteristic.EventRetransmissionMaximum); + this.addOptionalCharacteristic(Characteristic.EventTransmissionCounters); + this.addOptionalCharacteristic(Characteristic.MACRetransmissionMaximum); + this.addOptionalCharacteristic(Characteristic.MACTransmissionCounters); + this.addOptionalCharacteristic(Characteristic.ReceiverSensitivity); + this.addOptionalCharacteristic(Characteristic.ReceivedSignalStrengthIndication); + this.addOptionalCharacteristic(Characteristic.SignalToNoiseRatio); + this.addOptionalCharacteristic(Characteristic.ThreadOpenThreadVersion); + this.addOptionalCharacteristic(Characteristic.TransmitPower); + this.addOptionalCharacteristic(Characteristic.MaximumTransmitPower); + } +} +Service.ThreadTransport = ThreadTransport; + +/** + * Service "Transfer Transport Management" + */ +export class TransferTransportManagement extends Service { + + public static readonly UUID: string = "00000203-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, TransferTransportManagement.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.SupportedTransferTransportConfiguration); + this.addCharacteristic(Characteristic.SetupTransferTransport); + } +} +Service.TransferTransportManagement = TransferTransportManagement; + +/** + * Service "Tunnel" + */ +export class Tunnel extends Service { + + public static readonly UUID: string = "00000056-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, Tunnel.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.AccessoryIdentifier); + this.addCharacteristic(Characteristic.TunnelConnectionTimeout); + this.addCharacteristic(Characteristic.TunneledAccessoryAdvertising); + this.addCharacteristic(Characteristic.TunneledAccessoryConnected); + this.addCharacteristic(Characteristic.TunneledAccessoryStateNumber); + } +} +// noinspection JSDeprecatedSymbols +Service.TunneledBTLEAccessoryService = Tunnel; +Service.Tunnel = Tunnel; + +/** + * Service "Valve" + */ +export class Valve extends Service { + + public static readonly UUID: string = "000000D0-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, Valve.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.Active); + this.addCharacteristic(Characteristic.InUse); + this.addCharacteristic(Characteristic.ValveType); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.IsConfigured); + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.RemainingDuration); + this.addOptionalCharacteristic(Characteristic.ServiceLabelIndex); + this.addOptionalCharacteristic(Characteristic.SetDuration); + this.addOptionalCharacteristic(Characteristic.StatusFault); + } +} +Service.Valve = Valve; + +/** + * Service "Wi-Fi Router" + */ +export class WiFiRouter extends Service { + + public static readonly UUID: string = "0000020A-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, WiFiRouter.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.ConfiguredName); + this.addCharacteristic(Characteristic.ManagedNetworkEnable); + this.addCharacteristic(Characteristic.NetworkAccessViolationControl); + this.addCharacteristic(Characteristic.NetworkClientProfileControl); + this.addCharacteristic(Characteristic.NetworkClientStatusControl); + this.addCharacteristic(Characteristic.RouterStatus); + this.addCharacteristic(Characteristic.SupportedRouterConfiguration); + this.addCharacteristic(Characteristic.WANConfigurationList); + this.addCharacteristic(Characteristic.WANStatusList); + } +} +Service.WiFiRouter = WiFiRouter; + +/** + * Service "Wi-Fi Satellite" + */ +export class WiFiSatellite extends Service { + + public static readonly UUID: string = "0000020F-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, WiFiSatellite.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.WiFiSatelliteStatus); + } +} +Service.WiFiSatellite = WiFiSatellite; + +/** + * Service "Wi-Fi Transport" + */ +export class WiFiTransport extends Service { + + public static readonly UUID: string = "0000022A-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, WiFiTransport.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.CurrentTransport); + this.addCharacteristic(Characteristic.WiFiCapabilities); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.WiFiConfigurationControl); + } +} +Service.WiFiTransport = WiFiTransport; + +/** + * Service "Window" + */ +export class Window extends Service { + + public static readonly UUID: string = "0000008B-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, Window.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.CurrentPosition); + this.addCharacteristic(Characteristic.PositionState); + this.addCharacteristic(Characteristic.TargetPosition); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.ObstructionDetected); + this.addOptionalCharacteristic(Characteristic.HoldPosition); + } +} +Service.Window = Window; + +/** + * Service "Window Covering" + */ +export class WindowCovering extends Service { + + public static readonly UUID: string = "0000008C-0000-1000-8000-0026BB765291"; + + constructor(displayName?: string, subtype?: string) { + super(displayName, WindowCovering.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.CurrentPosition); + this.addCharacteristic(Characteristic.PositionState); + this.addCharacteristic(Characteristic.TargetPosition); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.CurrentHorizontalTiltAngle); + this.addOptionalCharacteristic(Characteristic.TargetHorizontalTiltAngle); + this.addOptionalCharacteristic(Characteristic.Name); + this.addOptionalCharacteristic(Characteristic.ObstructionDetected); + this.addOptionalCharacteristic(Characteristic.HoldPosition); + this.addOptionalCharacteristic(Characteristic.CurrentVerticalTiltAngle); + this.addOptionalCharacteristic(Characteristic.TargetVerticalTiltAngle); + } +} +Service.WindowCovering = WindowCovering; + diff --git a/src/lib/definitions/generate-definitions.ts b/src/lib/definitions/generate-definitions.ts new file mode 100644 index 000000000..cb89a0861 --- /dev/null +++ b/src/lib/definitions/generate-definitions.ts @@ -0,0 +1,791 @@ +import assert from "assert"; +import { Command } from "commander"; +import fs from "fs"; +import path from "path"; +import plist from "simple-plist"; +import { Characteristic, Formats, Units } from "../Characteristic"; +import { toLongForm } from "../util/uuid"; +import { + CharacteristicClassAdditions, + CharacteristicDeprecatedNames, + CharacteristicHidden, + CharacteristicManualAdditions, + CharacteristicNameOverrides, + CharacteristicSinceInformation, + CharacteristicValidValuesOverride, ServiceCharacteristicConfigurationOverrides, + ServiceDeprecatedNames, + ServiceManualAdditions, + ServiceNameOverrides, + ServiceSinceInformation +} from "./generator-configuration"; + +// noinspection JSUnusedLocalSymbols +const temp = Characteristic; // this to have "../Characteristic" not being only type import, otherwise this would not result in a require statement + +const command = new Command("generate-definitions") + .version("1.0.0") + .option("-f, --force") + .option("-m, --metadata ", "Define a custom location for the plain-metadata.config file", + "/System/Library/PrivateFrameworks/HomeKitDaemon.framework/Resources/plain-metadata.config") + .requiredOption("-s, --simulator ", "Define the path to the accessory simulator."); + +command.parse(process.argv); +const options = command.opts(); + +const metadataFile: string = options.metadata; +const simulator: string = options.simulator; +if (!fs.existsSync(metadataFile)) { + console.warn(`The metadata file at '${metadataFile}' does not exist!`); + process.exit(1); +} +if (!fs.existsSync(simulator)) { + console.warn(`The simulator app directory '${simulator}' does not exist!`); + process.exit(1); +} + +const defaultPlist: string = path.resolve(simulator, "Contents/Frameworks/HAPAccessoryKit.framework/Resources/default.metadata.plist"); +const defaultMfiPlist: string = path.resolve(simulator, "Contents/Frameworks/HAPAccessoryKit.framework/Resources/default_mfi.metadata.plist"); + +interface CharacteristicDefinition { + DefaultDescription: string, + Format: string, + LocalizationKey: string, + Properties: number, + ShortUUID: string, + MaxValue?: number, + MinValue?: number, + MaxLength?: number, + // MinLength is another property present on the SerialNumber characteristic. Though we already have a special check for that + StepValue?: number, + Units?: string, +} + +interface SimulatorCharacteristicDefinition { + UUID: string; + Name: string; + Format: string; + Constraints?: Constraints; + Permissions: string[]; // stuff like "securedRead", "securedWrite", "writeResponse" or "timedWrite" + Properties: string[]; // stuff like "read", "write", "cnotify", "uncnotify" +} + +interface Constraints { + StepValue?: number; + MaximumValue?: number; + MinimumValue?: number; + ValidValues?: Record; + ValidBits?: Record; +} + +interface ServiceDefinition { + Characteristics: { + Optional: string[], + Required: string[] + }, + DefaultDescription: string, + LocalizationKey: string, + ShortUUID: string, +} + +interface PropertyDefinition { + DefaultDescription: string; + LocalizationKey: string; + Position: number; +} + +interface UnitDefinition { + DefaultDescription: string, + LocalizationKey: string; +} + +interface CategoryDefinition { + DefaultDescription: string; + Identifier: number; + UUID: string; +} + +export interface GeneratedCharacteristic { + id: string; + UUID: string, + name: string, + className: string, + deprecatedClassName?: string; + since?: string, + deprecatedNotice?: string; + + format: string, + units?: string, + properties: number, + maxValue?: number, + minValue?: number, + stepValue?: number, + maxLength?: number, + + validValues?: Record; // + validBitMasks?: Record; + + classAdditions?: string[], +} + +export interface GeneratedService { + id: string, + UUID: string, + name: string, + className: string, + deprecatedClassName?: string, + since?: string, + deprecatedNotice?: string, + + requiredCharacteristics: string[]; + optionalCharacteristics?: string[]; +} + +const plistData = plist.readFileSync(metadataFile); +const simulatorPlistData = plist.readFileSync(defaultPlist); +const simulatorMfiPlistData = fs.existsSync(defaultMfiPlist)? plist.readFileSync(defaultMfiPlist): undefined; + +if (plistData.SchemaVersion !== 1) { + console.warn(`Detected unsupported schema version ${plistData.SchemaVersion}!`); +} +if (plistData.PlistDictionary.SchemaVersion !== 1) { + console.warn(`Detect unsupported PlistDictionary schema version ${plistData.PlistDictionary.SchemaVersion}!`); +} + +console.log(`Parsing version ${plistData.Version}...`); + +const shouldParseCharacteristics = checkWrittenVersion("./CharacteristicDefinitions.ts", plistData.Version); +const shouldParseServices = checkWrittenVersion("./ServiceDefinitions.ts", plistData.Version); + +if (!options.force && (!shouldParseCharacteristics || !shouldParseServices)) { + console.log("Parsed schema version " + plistData.Version + " is older than what's already generated. " + + "User --force option to generate and overwrite nonetheless!"); + process.exit(1); +} + +const undefinedUnits: string[] = ["micrograms/m^3", "ppm"]; + +let characteristics: Record; +const simulatorCharacteristics: Map = new Map(); +let services: Record; +let units: Record; +let categories: Record; +const properties: Map = new Map(); +try { + characteristics = checkDefined(plistData.PlistDictionary.HAP.Characteristics); + services = checkDefined(plistData.PlistDictionary.HAP.Services); + units = checkDefined(plistData.PlistDictionary.HAP.Units); + categories = checkDefined(plistData.PlistDictionary.HomeKit.Categories); + + const props: Record = checkDefined(plistData.PlistDictionary.HAP.Properties); + // noinspection JSUnusedLocalSymbols + for (const [id, definition] of Object.entries(props).sort(([a, aDef], [b, bDef]) => aDef.Position - bDef.Position)) { + const perm = characteristicPerm(id); + if (perm) { + const num = 1 << definition.Position; + properties.set(num, perm); + } + } + + for (const characteristic of (simulatorPlistData.Characteristics as SimulatorCharacteristicDefinition[])) { + simulatorCharacteristics.set(characteristic.UUID, characteristic); + } + if (simulatorMfiPlistData) { + for (const characteristic of (simulatorMfiPlistData.Characteristics as SimulatorCharacteristicDefinition[])) { + simulatorCharacteristics.set(characteristic.UUID, characteristic); + } + } +} catch (error) { + console.log("Unexpected structure of the plist file!"); + throw error; +} + +// first step is to check if we are up to date on categories +for (const definition of Object.values(categories)) { + if (definition.Identifier > 36) { + console.log(`Detected a new category '${definition.DefaultDescription}' with id ${definition.Identifier}`); + } +} + +const characteristicOutput = fs.createWriteStream(path.join(__dirname, "CharacteristicDefinitions.ts")); + +characteristicOutput.write("// THIS FILE IS AUTO-GENERATED - DO NOT MODIFY\n"); +characteristicOutput.write(`// V=${plistData.Version}\n`); +characteristicOutput.write("\n"); + +characteristicOutput.write("import { Characteristic, Formats, Perms, Units } from \"../Characteristic\";\n\n"); + +/** + * Characteristics + */ + +const generatedCharacteristics: Record = {}; // indexed by id +const writtenCharacteristicEntries: Record = {}; // indexed by class name + +for (const [id, definition] of Object.entries(characteristics)) { + try { + if (CharacteristicHidden.has(id)) { + continue; + } + + // "Carbon dioxide Detected" -> "Carbon Dioxide Detected" + const name = (CharacteristicNameOverrides.get(id) ?? definition.DefaultDescription).split(" ").map(entry => entry[0].toUpperCase() + entry.slice(1)).join(" "); + const deprecatedName = CharacteristicDeprecatedNames.get(id); + + // "Target Door State" -> "TargetDoorState", "PM2.5" -> "PM2_5" + const className = name.replace(/[\s\-]/g, "").replace(/[.]/g, "_"); + const deprecatedClassName = deprecatedName?.replace(/[\s\-]/g, "").replace(/[.]/g, "_"); + const longUUID = toLongForm(definition.ShortUUID); + + const simulatorCharacteristic = simulatorCharacteristics.get(longUUID); + + const validValues = simulatorCharacteristic?.Constraints?.ValidValues || {}; + const validValuesOverride = CharacteristicValidValuesOverride.get(id); + if (validValuesOverride) { + for (const [key, value] of Object.entries(validValuesOverride)) { + validValues[key] = value; + } + } + for (const [value, name] of Object.entries(validValues)) { + let constName = name.toUpperCase().replace(/[^\w]+/g, "_"); + if (/^[1-9]/.test(constName)) { + constName = "_" + constName; // variables can't start with a number + } + validValues[value] = constName; + } + const validBits = simulatorCharacteristic?.Constraints?.ValidBits; + let validBitMasks: Record | undefined = undefined; + if (validBits) { + validBitMasks = {}; + for (const [value, name] of Object.entries(validBits)) { + let constName = name.toUpperCase().replace(/[^\w]+/g, "_"); + if (/^[1-9]/.test(constName)) { + constName = "_" + constName; // variables can't start with a number + } + validBitMasks["" + (1 << parseInt(value))] = constName + "_BIT_MASK"; + } + } + + const generatedCharacteristic: GeneratedCharacteristic = { + id: id, + UUID: longUUID, + name: name, + className: className, + deprecatedClassName: deprecatedClassName, + since: CharacteristicSinceInformation.get(id), + + format: definition.Format, + units: definition.Units, + properties: definition.Properties, + minValue: definition.MinValue, + maxValue: definition.MaxValue, + stepValue: definition.StepValue, + + maxLength: definition.MaxLength, + + validValues: Object.entries(validValues).length? validValues: undefined, + validBitMasks: validBitMasks, + classAdditions: CharacteristicClassAdditions.get(id), + }; + generatedCharacteristics[id] = generatedCharacteristic; + writtenCharacteristicEntries[className] = generatedCharacteristic; + if (deprecatedClassName) { + writtenCharacteristicEntries[deprecatedClassName] = generatedCharacteristic; + } + } catch (error) { + throw new Error("Error thrown generating characteristic '" + id + "' (" + definition.DefaultDescription + "): " + error.message); + } +} + +for (const [id, generated] of CharacteristicManualAdditions) { + generatedCharacteristics[id] = generated; + writtenCharacteristicEntries[generated.className] = generated; + if (generated.deprecatedClassName) { + writtenCharacteristicEntries[generated.deprecatedClassName] = generated; + } +} + +for (const generated of Object.values(generatedCharacteristics) + .sort((a, b) => a.className.localeCompare(b.className))) { + try { + characteristicOutput.write("/**\n"); + characteristicOutput.write(" * Characteristic \"" + generated.name + "\"\n"); + if (generated.since) { + characteristicOutput.write(" * @since iOS " + generated.since + "\n"); + } + if (generated.deprecatedNotice) { + characteristicOutput.write(" * @deprecated " + generated.deprecatedNotice + "\n"); + } + characteristicOutput.write(" */\n"); + + + characteristicOutput.write("export class " + generated.className + " extends Characteristic {\n\n"); + + characteristicOutput.write(" public static readonly UUID: string = \"" + generated.UUID + "\";\n\n"); + + const classAdditions = generated.classAdditions; + if (classAdditions) { + characteristicOutput.write(classAdditions.map(line => " " + line + "\n").join("") + "\n"); + } + + if (generated.validValues) { + for (let [value, name] of Object.entries(generated.validValues)) { + characteristicOutput.write(` public static readonly ${name} = ${value};\n`); + } + characteristicOutput.write("\n"); + } + if (generated.validBitMasks) { + for (let [value, name] of Object.entries(generated.validBitMasks)) { + characteristicOutput.write(` public static readonly ${name} = ${value};\n`); + } + characteristicOutput.write("\n"); + } + + characteristicOutput.write(" constructor() {\n"); + characteristicOutput.write(" super(\"" + generated.name + "\", " + generated.className + ".UUID, {\n"); + characteristicOutput.write(" format: Formats." + characteristicFormat(generated.format) + ",\n"); + characteristicOutput.write(" perms: [" + generatePermsString(generated.properties) + "],\n") + if (generated.units && !undefinedUnits.includes(generated.units)) { + characteristicOutput.write(" unit: Units." + characteristicUnit(generated.units) + ",\n"); + } + if (generated.minValue != null) { + characteristicOutput.write(" minValue: " + generated.minValue + ",\n"); + } + if (generated.maxValue != null) { + characteristicOutput.write(" maxValue: " + generated.maxValue + ",\n"); + } + if (generated.stepValue != null) { + characteristicOutput.write(" minStep: " + generated.stepValue + ",\n"); + } + if (generated.maxLength != null) { + characteristicOutput.write(" maxLen: " + generated.maxLength + ",\n"); + } + characteristicOutput.write(" });\n"); + characteristicOutput.write(" this.value = this.getDefaultValue();\n"); + characteristicOutput.write(" }\n"); + characteristicOutput.write("}\n"); + if (generated.deprecatedClassName) { + characteristicOutput.write("// noinspection JSDeprecatedSymbols\n"); + characteristicOutput.write("Characteristic." + generated.deprecatedClassName + " = " + generated.className + ";\n"); + } + characteristicOutput.write("Characteristic." + generated.className + " = " + generated.className + ";\n\n"); + } catch (error) { + throw new Error("Error thrown writing characteristic '" + generated.id + "' (" + generated.className + "): " + error.message); + } +} + +characteristicOutput.end(); + +const characteristicProperties = Object.entries(writtenCharacteristicEntries).sort(([a], [b]) => a.localeCompare(b)); +rewriteProperties("Characteristic", characteristicProperties); +writeCharacteristicTestFile(); + +/** + * Services + */ + +const serviceOutput = fs.createWriteStream(path.join(__dirname, "ServiceDefinitions.ts")); + +serviceOutput.write("// THIS FILE IS AUTO-GENERATED - DO NOT MODIFY\n"); +serviceOutput.write(`// V=${plistData.Version}\n`); +serviceOutput.write("\n"); + +serviceOutput.write("import { Characteristic } from \"../Characteristic\";\n"); +serviceOutput.write("import { Service } from \"../Service\";\n\n"); + +const generatedServices: Record = {}; // indexed by id +const writtenServiceEntries: Record = {}; // indexed by class name + +for (const [id, definition] of Object.entries(services)) { + try { + // "Carbon dioxide Sensor" -> "Carbon Dioxide Sensor" + const name = (ServiceNameOverrides.get(id) ?? definition.DefaultDescription).split(" ").map(entry => entry[0].toUpperCase() + entry.slice(1)).join(" "); + const deprecatedName = ServiceDeprecatedNames.get(id); + + const className = name.replace(/[\s\-]/g, "").replace(/[.]/g, "_"); + const deprecatedClassName = deprecatedName?.replace(/[\s\-]/g, "").replace(/[.]/g, "_"); + + const longUUID = toLongForm(definition.ShortUUID); + + const requiredCharacteristics = definition.Characteristics.Required; + const optionalCharacteristics = definition.Characteristics.Optional; + + const configurationOverride = ServiceCharacteristicConfigurationOverrides.get(id); + if (configurationOverride) { + if (configurationOverride.removedRequired) { + for (const entry of configurationOverride.removedRequired) { + const index = requiredCharacteristics.indexOf(entry); + if (index !== -1) { + requiredCharacteristics.splice(index, 1); + } + } + } + if (configurationOverride.removedOptional) { + for (const entry of configurationOverride.removedOptional) { + const index = optionalCharacteristics.indexOf(entry); + if (index !== -1) { + optionalCharacteristics.splice(index, 1); + } + } + } + + if (configurationOverride.addedRequired) { + for (const entry of configurationOverride.addedRequired) { + if (!requiredCharacteristics.includes(entry)) { + requiredCharacteristics.push(entry); + } + } + } + if (configurationOverride.addedOptional) { + for (const entry of configurationOverride.addedOptional) { + if (!optionalCharacteristics.includes(entry)) { + optionalCharacteristics.push(entry); + } + } + } + } + + const generatedService: GeneratedService = { + id: id, + UUID: longUUID, + name: name, + className: className, + deprecatedClassName: deprecatedClassName, + since: ServiceSinceInformation.get(id), + + requiredCharacteristics: requiredCharacteristics, + optionalCharacteristics: optionalCharacteristics, + }; + generatedServices[id] = generatedService; + writtenServiceEntries[className] = generatedService; + if (deprecatedClassName) { + writtenServiceEntries[deprecatedClassName] = generatedService; + } + } catch (error) { + throw new Error("Error thrown generating service '" + id + "' (" + definition.DefaultDescription + "): " + error.message); + } +} + +for (const [id, generated] of ServiceManualAdditions) { + generatedServices[id] = generated; + writtenServiceEntries[generated.className] = generated; + if (generated.deprecatedClassName) { + writtenServiceEntries[generated.deprecatedClassName] = generated; + } +} + +for (const generated of Object.values(generatedServices) + .sort((a, b) => a.className.localeCompare(b.className))) { + try { + serviceOutput.write("/**\n"); + serviceOutput.write(" * Service \"" + generated.name + "\"\n"); + if (generated.since) { + serviceOutput.write(" * @since iOS " + generated.since + "\n"); + } + if (generated.deprecatedNotice) { + serviceOutput.write(" * @deprecated " + generated.deprecatedNotice + "\n"); + } + serviceOutput.write(" */\n"); + + serviceOutput.write("export class " + generated.className + " extends Service {\n\n"); + + serviceOutput.write(" public static readonly UUID: string = \"" + generated.UUID + "\";\n\n"); + + serviceOutput.write(" constructor(displayName?: string, subtype?: string) {\n"); + serviceOutput.write(" super(displayName, " + generated.className + ".UUID, subtype);\n\n"); + + serviceOutput.write(" // Required Characteristics\n"); + for (const required of generated.requiredCharacteristics) { + const characteristic = generatedCharacteristics[required]; + if (!characteristic) { + console.warn("Could not find required characteristic " + required + " for " + generated.className); + continue; + } + + if (required === "name") { + serviceOutput.write(" if (!this.testCharacteristic(Characteristic.Name)) { // workaround for Name characteristic collision in constructor\n"); + serviceOutput.write(" this.addCharacteristic(Characteristic.Name).updateValue(\"Unnamed Service\");\n"); + serviceOutput.write(" }\n"); + } else { + serviceOutput.write(" this.addCharacteristic(Characteristic." + characteristic.className + ");\n"); + } + } + + if (generated.optionalCharacteristics) { + serviceOutput.write("\n // Optional Characteristics\n"); + for (const optional of generated.optionalCharacteristics) { + const characteristic = generatedCharacteristics[optional]; + if (!characteristic) { + console.warn("Could not find optional characteristic " + optional + " for " + generated.className); + continue; + } + serviceOutput.write(" this.addOptionalCharacteristic(Characteristic." + characteristic.className + ");\n"); + } + } + + serviceOutput.write(" }\n}\n"); + if (generated.deprecatedClassName) { + serviceOutput.write("// noinspection JSDeprecatedSymbols\n"); + serviceOutput.write("Service." + generated.deprecatedClassName + " = " + generated.className + ";\n"); + } + serviceOutput.write("Service." + generated.className + " = " + generated.className + ";\n\n"); + } catch (error) { + throw new Error("Error thrown writing service '" + generated.id + "' (" + generated.className + "): " + error.message); + } +} + +serviceOutput.end(); + + +const serviceProperties = Object.entries(writtenServiceEntries).sort(([a], [b]) => a.localeCompare(b)); +rewriteProperties("Service", serviceProperties); +writeServicesTestFile(); + +// ------------------------ utils ------------------------ +function checkDefined(input: T): T { + if (!input) { + throw new Error("value is undefined!"); + } + + return input; +} + +function characteristicFormat(format: string): string { + // @ts-expect-error + for (const [key, value] of Object.entries(Formats)) { + if (value === format) { + return key; + } + } + + throw new Error("Unknown characteristic format '" + format + "'"); +} + +function characteristicUnit(unit: string): string { + // @ts-expect-error + for (const [key, value] of Object.entries(Units)) { + if (value === unit) { + return key; + } + } + + throw new Error("Unknown characteristic format '" + unit + "'"); +} + +function characteristicPerm(id: string): string | undefined { + switch (id) { + case "aa": + return "ADDITIONAL_AUTHORIZATION"; + case "hidden": + return "HIDDEN"; + case "notify": + return "NOTIFY"; + case "read": + return "PAIRED_READ"; + case "timedWrite": + return "TIMED_WRITE"; + case "write": + return "PAIRED_WRITE"; + case "writeResponse": + return "WRITE_RESPONSE"; + case "broadcast": // used for bluetooth + return undefined; + default: + throw new Error("Received unknown perms id: " + id); + } +} + +function generatePermsString(propertiesBitMap: number): string { + const perms: string [] = []; + + for (const [bitMap, name] of properties) { + if (name === "ADDITIONAL_AUTHORIZATION") { + // aa set by homed just signals that aa may be supported. Setting up aa will always require a custom made app though + continue; + } + if ((propertiesBitMap | bitMap) === propertiesBitMap) { // if it stays the same the bit is set + perms.push("Perms." + name); + } + } + + const result = perms.join(", "); + assert(result != "", "perms string cannot be empty (" + propertiesBitMap + ")"); + return result; +} + +function checkWrittenVersion(filePath: string, parsingVersion: number): boolean { + filePath = path.resolve(__dirname, filePath); + + const content = fs.readFileSync(filePath, { encoding: "utf8" }).split("\n", 3); + const v = content[1]; + if (!v.startsWith("// V=")) { + throw new Error("Could not detect definition version for '" + filePath + "'"); + } + + const version = parseInt(v.replace("// V=", "")); + return parsingVersion >= version; +} + +function rewriteProperties(className: string, properties: [key: string, value: GeneratedCharacteristic | GeneratedService][]): void { + const filePath = path.resolve(__dirname, "../" + className + ".ts"); + if (!fs.existsSync(filePath)) { + throw new Error("File '" + filePath + "' does not exist!"); + } + + const file = fs.readFileSync(filePath, { encoding: "utf8"}); + const lines = file.split("\n"); + + let i = 0; + + let importStart = -1; + let importEnd = -1; + let foundImport = false; + + for (; i < lines.length; i++) { + const line = lines[i]; + if (line === "import {") { + importStart = i; // save last import start; + } else if (line === "} from \"./definitions\";") { + importEnd = i; + foundImport = true; + break; + } + } + if (!foundImport) { + throw new Error("Could not find import section!"); + } + + let startIndex = -1; + let stopIndex = -1; + + for (; i < lines.length; i++) { + if (lines[i] === " // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-") { + startIndex = i; + break; + } + } + if (startIndex === -1) { + throw new Error("Could not find start pattern in file!"); + } + for (; i < lines.length; i++) { + if (lines[i] === " // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=") { + stopIndex = i; + break; + } + } + if (stopIndex === -1) { + throw new Error("Could not find stop pattern in file!"); + } + + const importSize = importEnd - importStart - 1; + const newImports = properties + .filter(([key, value]) => key === value.className) + .map(([key]) => " " + key + ","); + lines.splice(importStart + 1, importSize, ...newImports); // remove current imports + + const importDelta = newImports.length - importSize; + + startIndex += importDelta; + stopIndex += importDelta; + + const amount = stopIndex - startIndex - 1; + const newContentLines = properties.map(([key, value]) => { + let line = ""; + + let deprecatedNotice = value.deprecatedNotice; + + if (key !== value.className) { + deprecatedNotice = "Please use {@link " + className + "." + value.className + "}." // prepend deprecated notice + + (deprecatedNotice? " " + deprecatedNotice: ""); + } + if (deprecatedNotice) { + line += " /**\n"; + line += " * @deprecated " + deprecatedNotice + "\n"; + line += " */\n"; + } + + line += " public static " + key + ": typeof " + value.className + ";"; + return line; + }); + lines.splice(startIndex + 1, amount, ...newContentLines); // insert new lines + + const resultContent = lines.join("\n"); + fs.writeFileSync(filePath, resultContent, { encoding: "utf8" }); +} + +function writeCharacteristicTestFile(): void { + const characteristics = Object.values(generatedCharacteristics).sort((a, b) => a.className.localeCompare(b.className)); + + const testOutput = fs.createWriteStream(path.resolve(__dirname, "./CharacteristicDefinitions.spec.ts"), { encoding: "utf8" }); + testOutput.write("// THIS FILE IS AUTO-GENERATED - DO NOT MODIFY\n"); + testOutput.write("import \"./\";\n\n"); + testOutput.write("import { Characteristic } from \"../Characteristic\";\n\n"); + testOutput.write("describe(\"CharacteristicDefinitions\", () => {"); + + for (const generated of characteristics) { + testOutput.write("\n"); + testOutput.write(" describe(\"" + generated.className + "\", () => {\n"); + + // first test is just calling the constructor + testOutput.write(" it(\"should be able to construct\", () => {\n"); + testOutput.write(" new Characteristic." + generated.className + "();\n"); + if (generated.deprecatedClassName) { + testOutput.write(" // noinspection JSDeprecatedSymbols\n"); + testOutput.write(" new Characteristic." + generated.deprecatedClassName + "();\n"); + } + testOutput.write(" });\n"); + + testOutput.write(" });\n"); + } + + testOutput.write("});\n"); + testOutput.end(); +} + +function writeServicesTestFile(): void { + const services = Object.values(generatedServices).sort((a, b) => a.className.localeCompare(b.className)); + + const testOutput = fs.createWriteStream(path.resolve(__dirname, "./ServiceDefinitions.spec.ts"), { encoding: "utf8" }); + testOutput.write("// THIS FILE IS AUTO-GENERATED - DO NOT MODIFY\n"); + testOutput.write("import \"./\";\n\n"); + testOutput.write("import { Characteristic } from \"../Characteristic\";\n"); + testOutput.write("import { Service } from \"../Service\";\n\n"); + testOutput.write("describe(\"ServiceDefinitions\", () => {"); + + for (const generated of services) { + testOutput.write("\n"); + testOutput.write(" describe(\"" + generated.className + "\", () => {\n"); + + // first test is just calling the constructor + testOutput.write(" it(\"should be able to construct\", () => {\n"); + + testOutput.write(" const service0 = new Service." + generated.className + "();\n"); + testOutput.write(" const service1 = new Service." + generated.className + "(\"test name\");\n"); + testOutput.write(" const service2 = new Service." + generated.className + "(\"test name\", \"test sub type\");\n\n"); + + testOutput.write(" expect(service0.displayName).toBe(\"\");\n"); + testOutput.write(" expect(service0.testCharacteristic(Characteristic.Name)).toBe(" + generated.requiredCharacteristics.includes("name") + ");\n"); + testOutput.write(" expect(service0.subtype).toBeUndefined();\n\n"); + + testOutput.write(" expect(service1.displayName).toBe(\"test name\");\n"); + testOutput.write(" expect(service1.testCharacteristic(Characteristic.Name)).toBe(true);\n"); + testOutput.write(" expect(service1.getCharacteristic(Characteristic.Name).value).toBe(\"test name\");\n"); + testOutput.write(" expect(service1.subtype).toBeUndefined();\n\n"); + + testOutput.write(" expect(service2.displayName).toBe(\"test name\");\n"); + testOutput.write(" expect(service2.testCharacteristic(Characteristic.Name)).toBe(true);\n"); + testOutput.write(" expect(service2.getCharacteristic(Characteristic.Name).value).toBe(\"test name\");\n"); + testOutput.write(" expect(service2.subtype).toBe(\"test sub type\");\n"); + + if (generated.deprecatedClassName) { + testOutput.write(" // noinspection JSDeprecatedSymbols\n"); + testOutput.write("\n new Service." + generated.deprecatedClassName + "();\n"); + } + + testOutput.write(" });\n"); + + testOutput.write(" });\n"); + } + + testOutput.write("});\n"); + testOutput.end(); +} diff --git a/src/lib/definitions/generator-configuration.ts b/src/lib/definitions/generator-configuration.ts new file mode 100644 index 000000000..ac3eb0ab7 --- /dev/null +++ b/src/lib/definitions/generator-configuration.ts @@ -0,0 +1,176 @@ +import { GeneratedCharacteristic, GeneratedService } from "./generate-definitions"; + +export const CharacteristicHidden: Set = new Set([ + "service-signature", // BLE +]); + +export const CharacteristicNameOverrides: Map = new Map([ + ["air-quality", "Air Quality"], + ["app-matching-identifier", "App Matching Identifier"], + ["cloud-relay.control-point", "Relay Control Point"], + ["cloud-relay.current-state", "Relay State"], + ["cloud-relay.enabled", "Relay Enabled"], + ["density.voc", "VOC Density"], + ["filter.reset-indication", "Reset Filter Indication"], // Filter Reset Change Indication + ["light-level.current", "Current Ambient Light Level"], + ["network-client-control", "Network Client Profile Control"], + ["on", "On"], + ["selected-stream-configuration", "Selected RTP Stream Configuration"], + ["service-label-index", "Service Label Index"], + ["service-label-namespace", "Service Label Namespace"], + ["setup-stream-endpoint", "Setup Endpoints"], + ["snr", "Signal To Noise Ratio"], + ["supported-target-configuration", "Target Control Supported Configuration"], + ["target-list", "Target Control List"], + ["tunneled-accessory.advertising", "Tunneled Accessory Advertising"], + ["tunneled-accessory.connected", "Tunneled Accessory Connected"], + ["water-level", "Water Level"], +]); + +export const CharacteristicDeprecatedNames: Map = new Map([ // keep in mind that the displayName will change + ["list-pairings", "Pairing Pairings"], +]); + +export const CharacteristicValidValuesOverride: Map> = new Map([ + ["closed-captions", { "0": "Disabled", "1": "Enabled" }], + ["input-device-type", { "0": "Other", "1": "TV", "2": "Recording", "3": "Tuner", "4": "Playback", "5": "Audio System"}], + ["input-source-type", { "0": "Other", "1": "Home Screen", "2": "Tuner", "3": "HDMI", "4": "Composite Video", "5": "S Video", + "6": "Component Video", "7": "DVI", "8": "AirPlay", "9": "USB", "10": "Application" }], + ["managed-network-enable", { "0": "Disabled", "1": "Enabled" }], + ["manually-disabled", { "0": "Enabled", "1": "Disabled" }], + ["media-state.current", { "0": "Play", "1": "Pause", "2": "Stop", "4": "LOADING", "5": "Interrupted" }], + ["media-state.target", { "0": "Play", "1": "Pause", "2": "Stop" }], + ["picture-mode", { "0": "Other", "1": "Standard", "2": "Calibrated", "3": "Calibrated Dark", "4": "Vivid", "5": "Game", "6": "Computer", "7": "Custom" }], + ["power-mode-selection", { "0": "Show", "1": "Hide" }], + ["recording-audio-active", { "0": "Disable", "1": "Enable"}], + ["remote-key", { "0": "Rewind", "1": "Fast Forward", "2": "Next Track", "3": "Previous Track", "4": "Arrow Up", "5": "Arrow Down", + "6": "Arrow Left", "7": "Arrow Right", "8": "Select", "9": "Back", "10": "Exit", "11": "Play Pause", "15": "Information" }], + ["router-status", { "0": "Ready", "1": "Not Ready" }], + ["siri-input-type", { "0": "Push Button Triggered Apple TV"}], + ["sleep-discovery-mode", { "0": "Not Discoverable", "1": "Always Discoverable" }], + ["visibility-state.current", { "0": "Shown", "1": "Hidden" }], + ["visibility-state.target", { "0": "Shown", "1": "Hidden" }], + ["volume-control-type", { "0": "None", "1": "Relative", "2": "Relative With Current", "3": "Absolute" }], + ["volume-selector", { "0": "Increment", "1": "Decrement" }], + ["wifi-satellite-status", { "0": "Unknown", "1": "Connected", "2": "Not Connected" }], +] as [string, Record][]); + +export const CharacteristicClassAdditions: Map = new Map([ + ["humidifier-dehumidifier.state.target", ["/**\n * @deprecated Removed in iOS 11. Use {@link HUMIDIFIER_OR_DEHUMIDIFIER} instead.\n */\n public static readonly AUTO = 0;"]] +]); + +export const CharacteristicManualAdditions: Map = new Map([ + ["diagonal-field-of-view", { + id: "diagonal-field-of-view", + UUID: "00000224-0000-1000-8000-0026BB765291", + name: "Diagonal Field Of View", + className: "DiagonalFieldOfView", + since: "13.2", + + format: "float", + units: "arcdegrees", + properties: 3, // notify, paired read + minValue: 0, + maxValue: 360, + }], + ["version", { // don't know why, but version has notify permission even if it shouldn't have one + id: "version", + UUID: "00000037-0000-1000-8000-0026BB765291", + name: "Version", + className: "Version", + + format: "string", + properties: 2, // paired read + maxLength: 64, + }] +]); + +export const ServiceNameOverrides: Map = new Map([ + ["accessory-information", "Accessory Information"], + ["camera-rtp-stream-management", "Camera RTP Stream Management"], + ["fanv2", "Fanv2"], + ["service-label", "Service Label"], + ["smart-speaker", "Smart Speaker"], + ["speaker", "Television Speaker"], // has some additional accessories +]); + +export const ServiceDeprecatedNames: Map = new Map([ + ["battery", "Battery Service"], + ["camera-recording-management", "Camera Event Recording Management"], + ["cloud-relay", "Relay"], + ["slats", "Slat"], + ["tunnel", "Tunneled BTLE Accessory Service"], +]); + +interface CharacteristicConfigurationOverride { + addedRequired?: string[], + removedRequired?: string[], + addedOptional?: string[], + removedOptional?: string[], +} + +export const ServiceCharacteristicConfigurationOverrides: Map = new Map([ + ["accessory-information", { addedRequired: ["firmware.revision"], removedOptional: ["firmware.revision"] }], + ["camera-operating-mode", { addedOptional: ["diagonal-field-of-view"] }], +]); + +export const ServiceManualAdditions: Map = new Map([ + ["og-speaker", { // the normal speaker is considered to be the "TelevisionSpeaker" + id: "og-speaker", + UUID: "00000113-0000-1000-8000-0026BB765291", + name: "Speaker", + className: "Speaker", + since: "10", + + requiredCharacteristics: ["mute"], + optionalCharacteristics: ["active", "volume"], + }], + ["camera-control", { + id: "camera-control", + UUID: "00000111-0000-1000-8000-0026BB765291", + name: "Camera Control", + className: "CameraControl", + deprecatedNotice: "This service has no usage anymore and will be ignored by iOS", + + requiredCharacteristics: ["on"], + optionalCharacteristics: ["horizontal-tilt.current", "vertical-tilt.current", "horizontal-tilt.target", "vertical-tilt.target", "night-vision", "optical-zoom", "digital-zoom", "image-rotation", "image-mirroring", "name"] + } + ], +]); + +export const CharacteristicSinceInformation: Map = new Map([ + ["activity-interval", "14"], + ["cca-energy-detect-threshold", "14"], + ["cca-signal-detect-threshold", "14"], + ["characteristic-value-active-transition-count", "14"], + ["characteristic-value-transition-control", "14"], + ["current-transport", "14"], + ["data-stream-hap-transport", "14"], + ["data-stream-hap-transport-interrupt", "14"], + ["event-retransmission-maximum", "14"], + ["event-transmission-counters", "14"], + ["heart-beat", "14"], + ["mac-retransmission-maximum", "14"], + ["mac-retransmission-counters", "14"], + ["operating-state-response", "14"], + ["ping", "14"], + ["receiver-sensitivity", "14"], + ["rssi", "14"], + ["setup-transfer-transport", "13.4"], + ["sleep-interval", "14"], + ["snr", "14"], + ["supported-characteristic-value-transition-configuration", "14"], + ["supported-diagnostics-snapshot", "14"], + ["supported-transfer-transport-configuration", "13.4"], + ["transmit-power", "14"], + ["transmit-power-maximum", "14"], + ["transfer-transport-management", "13.4"], + ["video-analysis-active", "14"], + ["wake-configuration", "13.4"], + ["wifi-capabilities", "14"], + ["wifi-configuration-control", "14"], +]); + +export const ServiceSinceInformation: Map = new Map([ + ["outlet", "13"], +]); diff --git a/src/lib/definitions/index.ts b/src/lib/definitions/index.ts new file mode 100644 index 000000000..9a5664a56 --- /dev/null +++ b/src/lib/definitions/index.ts @@ -0,0 +1,2 @@ +export * from "./CharacteristicDefinitions"; +export * from "./ServiceDefinitions"; diff --git a/src/lib/gen/HomeKit-Bridge.ts b/src/lib/gen/HomeKit-Bridge.ts deleted file mode 100644 index 737c2ef25..000000000 --- a/src/lib/gen/HomeKit-Bridge.ts +++ /dev/null @@ -1,665 +0,0 @@ -import { Characteristic, Formats, Perms } from '../Characteristic'; -import { Service } from '../Service'; - -/** - * - * Removed in iOS 11 - * - */ - -/** - * Characteristic "App Matching Identifier" - */ - -export class AppMatchingIdentifier extends Characteristic { - - static readonly UUID: string = '000000A4-0000-1000-8000-0026BB765291'; - - constructor() { - super('App Matching Identifier', AppMatchingIdentifier.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.READ] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.AppMatchingIdentifier = AppMatchingIdentifier; - -/** - * Characteristic "Programmable Switch Output State" - */ - -export class ProgrammableSwitchOutputState extends Characteristic { - - static readonly UUID: string = '00000074-0000-1000-8000-0026BB765291'; - - constructor() { - super('Programmable Switch Output State', ProgrammableSwitchOutputState.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.ProgrammableSwitchOutputState = ProgrammableSwitchOutputState; - -/** - * Characteristic "Software Revision" - */ - -export class SoftwareRevision extends Characteristic { - - static readonly UUID: string = '00000054-0000-1000-8000-0026BB765291'; - - constructor() { - super('Software Revision', SoftwareRevision.UUID); - this.setProps({ - format: Formats.STRING, - perms: [Perms.READ] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.SoftwareRevision = SoftwareRevision; - -/** - * Service "Camera Control" - */ - -export class CameraControl extends Service { - - static readonly UUID: string = '00000111-0000-1000-8000-0026BB765291' - - constructor(displayName: string, subtype: string) { - super(displayName, CameraControl.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.On); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.CurrentHorizontalTiltAngle); - this.addOptionalCharacteristic(Characteristic.CurrentVerticalTiltAngle); - this.addOptionalCharacteristic(Characteristic.TargetHorizontalTiltAngle); - this.addOptionalCharacteristic(Characteristic.TargetVerticalTiltAngle); - this.addOptionalCharacteristic(Characteristic.NightVision); - this.addOptionalCharacteristic(Characteristic.OpticalZoom); - this.addOptionalCharacteristic(Characteristic.DigitalZoom); - this.addOptionalCharacteristic(Characteristic.ImageRotation); - this.addOptionalCharacteristic(Characteristic.ImageMirroring); - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.CameraControl = CameraControl; - -/** - * Service "Stateful Programmable Switch" - */ - -export class StatefulProgrammableSwitch extends Service { - - static readonly UUID: string = '00000088-0000-1000-8000-0026BB765291' - - constructor(displayName: string, subtype: string) { - super(displayName, StatefulProgrammableSwitch.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.ProgrammableSwitchEvent); - this.addCharacteristic(Characteristic.ProgrammableSwitchOutputState); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.StatefulProgrammableSwitch = StatefulProgrammableSwitch; - -/** - * - * Removed in iOS 10 - * - */ - -/** - * Characteristic "Accessory Identifier" - */ - -export class AccessoryIdentifier extends Characteristic { - - static readonly UUID: string = '00000057-0000-1000-8000-0026BB765291'; - - constructor() { - super('Accessory Identifier', AccessoryIdentifier.UUID); - this.setProps({ - format: Formats.STRING, - perms: [Perms.READ] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.AccessoryIdentifier = AccessoryIdentifier; - -/** - * Characteristic "Category" - */ - -export class Category extends Characteristic { - - static readonly UUID: string = '000000A3-0000-1000-8000-0026BB765291'; - - constructor() { - super('Category', Category.UUID); - this.setProps({ - format: Formats.UINT16, - maxValue: 16, - minValue: 1, - minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.Category = Category; - -/** - * Characteristic "Configure Bridged Accessory" - */ - -export class ConfigureBridgedAccessory extends Characteristic { - - static readonly UUID: string = '000000A0-0000-1000-8000-0026BB765291'; - - constructor() { - super('Configure Bridged Accessory', ConfigureBridgedAccessory.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.WRITE] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.ConfigureBridgedAccessory = ConfigureBridgedAccessory; - -/** - * Characteristic "Configure Bridged Accessory Status" - */ - -export class ConfigureBridgedAccessoryStatus extends Characteristic { - - static readonly UUID: string = '0000009D-0000-1000-8000-0026BB765291'; - - constructor() { - super('Configure Bridged Accessory Status', ConfigureBridgedAccessoryStatus.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.ConfigureBridgedAccessoryStatus = ConfigureBridgedAccessoryStatus; - -/** - * Characteristic "Current Time" - */ - -export class CurrentTime extends Characteristic { - - static readonly UUID: string = '0000009B-0000-1000-8000-0026BB765291'; - - constructor() { - super('Current Time', CurrentTime.UUID); - this.setProps({ - format: Formats.STRING, - perms: [Perms.READ, Perms.WRITE] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.CurrentTime = CurrentTime; - -/** - * Characteristic "Day of the Week" - */ - -export class DayoftheWeek extends Characteristic { - - static readonly UUID: string = '00000098-0000-1000-8000-0026BB765291'; - - constructor() { - super('Day of the Week', DayoftheWeek.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 7, - minValue: 1, - minStep: 1, - perms: [Perms.READ, Perms.WRITE] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.DayoftheWeek = DayoftheWeek; - -/** - * Characteristic "Discover Bridged Accessories" - */ - -export class DiscoverBridgedAccessories extends Characteristic { - - // The value property of DiscoverBridgedAccessories must be one of the following: - static readonly START_DISCOVERY = 0; - static readonly STOP_DISCOVERY = 1; - - static readonly UUID: string = '0000009E-0000-1000-8000-0026BB765291'; - - constructor() { - super('Discover Bridged Accessories', DiscoverBridgedAccessories.UUID); - this.setProps({ - format: Formats.UINT8, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.DiscoverBridgedAccessories = DiscoverBridgedAccessories; - -/** - * Characteristic "Discovered Bridged Accessories" - */ - -export class DiscoveredBridgedAccessories extends Characteristic { - - static readonly UUID: string = '0000009F-0000-1000-8000-0026BB765291'; - - constructor() { - super('Discovered Bridged Accessories', DiscoveredBridgedAccessories.UUID); - this.setProps({ - format: Formats.UINT16, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.DiscoveredBridgedAccessories = DiscoveredBridgedAccessories; - -/** - * Characteristic "Link Quality" - */ - -export class LinkQuality extends Characteristic { - - static readonly UUID: string = '0000009C-0000-1000-8000-0026BB765291'; - - constructor() { - super('Link Quality', LinkQuality.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 4, - minValue: 1, - minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.LinkQuality = LinkQuality; - -/** - * Characteristic "Reachable" - */ - -export class Reachable extends Characteristic { - - static readonly UUID: string = '00000063-0000-1000-8000-0026BB765291'; - - constructor() { - super('Reachable', Reachable.UUID); - this.setProps({ - format: Formats.BOOL, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.Reachable = Reachable; - -/** - * Characteristic "Relay Control Point" - */ - -export class RelayControlPoint extends Characteristic { - - static readonly UUID: string = '0000005E-0000-1000-8000-0026BB765291'; - - constructor() { - super('Relay Control Point', RelayControlPoint.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.RelayControlPoint = RelayControlPoint; - -/** - * Characteristic "Relay Enabled" - */ - -export class RelayEnabled extends Characteristic { - - static readonly UUID: string = '0000005B-0000-1000-8000-0026BB765291'; - - constructor() { - super('Relay Enabled', RelayEnabled.UUID); - this.setProps({ - format: Formats.BOOL, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.RelayEnabled = RelayEnabled; - -/** - * Characteristic "Relay State" - */ - -export class RelayState extends Characteristic { - - static readonly UUID: string = '0000005C-0000-1000-8000-0026BB765291'; - - constructor() { - super('Relay State', RelayState.UUID); - this.setProps({ - format: Formats.UINT8, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.RelayState = RelayState; - -/** - * Characteristic "Time Update" - */ - -export class TimeUpdate extends Characteristic { - - static readonly UUID: string = '0000009A-0000-1000-8000-0026BB765291'; - - constructor() { - super('Time Update', TimeUpdate.UUID); - this.setProps({ - format: Formats.BOOL, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.TimeUpdate = TimeUpdate; - -/** - * Characteristic "Tunnel Connection Timeout " - */ - -export class TunnelConnectionTimeout extends Characteristic { - - static readonly UUID: string = '00000061-0000-1000-8000-0026BB765291'; - - constructor() { - super('Tunnel Connection Timeout ', TunnelConnectionTimeout.UUID); - this.setProps({ - format: Formats.UINT32, - perms: [Perms.WRITE, Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.TunnelConnectionTimeout = TunnelConnectionTimeout; - -/** - * Characteristic "Tunneled Accessory Advertising" - */ - -export class TunneledAccessoryAdvertising extends Characteristic { - - static readonly UUID: string = '00000060-0000-1000-8000-0026BB765291'; - - constructor() { - super('Tunneled Accessory Advertising', TunneledAccessoryAdvertising.UUID); - this.setProps({ - format: Formats.BOOL, - perms: [Perms.WRITE, Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.TunneledAccessoryAdvertising = TunneledAccessoryAdvertising; - -/** - * Characteristic "Tunneled Accessory Connected" - */ - -export class TunneledAccessoryConnected extends Characteristic { - - static readonly UUID: string = '00000059-0000-1000-8000-0026BB765291'; - - constructor() { - super('Tunneled Accessory Connected', TunneledAccessoryConnected.UUID); - this.setProps({ - format: Formats.BOOL, - perms: [Perms.WRITE, Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.TunneledAccessoryConnected = TunneledAccessoryConnected; - -/** - * Characteristic "Tunneled Accessory State Number" - */ - -export class TunneledAccessoryStateNumber extends Characteristic { - - static readonly UUID: string = '00000058-0000-1000-8000-0026BB765291'; - - constructor() { - super('Tunneled Accessory State Number', TunneledAccessoryStateNumber.UUID); - this.setProps({ - format: Formats.FLOAT, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.TunneledAccessoryStateNumber = TunneledAccessoryStateNumber; - -/** - * Service "Bridge Configuration" - */ - -export class BridgeConfiguration extends Service { - - static readonly UUID: string = '000000A1-0000-1000-8000-0026BB765291'; - - constructor(displayName: string, subtype: string) { - super(displayName, BridgeConfiguration.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.ConfigureBridgedAccessoryStatus); - this.addCharacteristic(Characteristic.DiscoverBridgedAccessories); - this.addCharacteristic(Characteristic.DiscoveredBridgedAccessories); - this.addCharacteristic(Characteristic.ConfigureBridgedAccessory); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.BridgeConfiguration = BridgeConfiguration; - -/** - * Service "Bridging State" - */ - -export class BridgingState extends Service { - - static readonly UUID: string = '00000062-0000-1000-8000-0026BB765291'; - - constructor(displayName: string, subtype: string) { - super(displayName, BridgingState.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.Reachable); - this.addCharacteristic(Characteristic.LinkQuality); - this.addCharacteristic(Characteristic.AccessoryIdentifier); - this.addCharacteristic(Characteristic.Category); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.BridgingState = BridgingState; - -/** - * Service "Pairing" - */ - -export class Pairing extends Service { - - static readonly UUID: string = '00000055-0000-1000-8000-0026BB765291'; - - constructor(displayName: string, subtype: string) { - super(displayName, Pairing.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.PairSetup); - this.addCharacteristic(Characteristic.PairVerify); - this.addCharacteristic(Characteristic.PairingFeatures); - this.addCharacteristic(Characteristic.PairingPairings); - - // Optional Characteristics - } -} - -Service.Pairing = Pairing; - -/** - * Service "Protocol Information" - */ - -export class ProtocolInformation extends Service { - - static readonly UUID: string = '000000A2-0000-1000-8000-0026BB765291'; - - constructor(displayName: string, subtype: string) { - super(displayName, ProtocolInformation.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.Version); - - // Optional Characteristics - } -} - -Service.ProtocolInformation = ProtocolInformation; - -/** - * Service "Relay" - */ - -export class Relay extends Service { - - static readonly UUID: string = '0000005A-0000-1000-8000-0026BB765291'; - - constructor(displayName: string, subtype: string) { - super(displayName, Relay.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.RelayEnabled); - this.addCharacteristic(Characteristic.RelayState); - this.addCharacteristic(Characteristic.RelayControlPoint); - - // Optional Characteristics - } -} - -Service.Relay = Relay; - -/** - * Service "Time Information" - */ - -export class TimeInformation extends Service { - - static readonly UUID: string = '00000099-0000-1000-8000-0026BB765291'; - - constructor(displayName: string, subtype: string) { - super(displayName, TimeInformation.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.CurrentTime); - this.addCharacteristic(Characteristic.DayoftheWeek); - this.addCharacteristic(Characteristic.TimeUpdate); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.TimeInformation = TimeInformation; - -/** - * Service "Tunneled BTLE Accessory Service" - */ - -export class TunneledBTLEAccessoryService extends Service { - - static readonly UUID: string = '00000056-0000-1000-8000-0026BB765291'; - - constructor(displayName: string, subtype: string) { - super(displayName, TunneledBTLEAccessoryService.UUID, subtype); - - // Required Characteristics - if (!this.testCharacteristic(Characteristic.Name)) { // workaround for name characteristic collision in constructor - this.addCharacteristic(Characteristic.Name); - } - this.addCharacteristic(Characteristic.AccessoryIdentifier); - this.addCharacteristic(Characteristic.TunneledAccessoryStateNumber); - this.addCharacteristic(Characteristic.TunneledAccessoryConnected); - this.addCharacteristic(Characteristic.TunneledAccessoryAdvertising); - this.addCharacteristic(Characteristic.TunnelConnectionTimeout); - - // Optional Characteristics - } -} - -Service.TunneledBTLEAccessoryService = TunneledBTLEAccessoryService; diff --git a/src/lib/gen/HomeKit-DataStream.ts b/src/lib/gen/HomeKit-DataStream.ts deleted file mode 100644 index f45a6c8c6..000000000 --- a/src/lib/gen/HomeKit-DataStream.ts +++ /dev/null @@ -1,68 +0,0 @@ -// manually created - -import {Characteristic, Formats, Perms} from '../Characteristic'; -import {Service} from "../Service"; - - -/** - * Characteristic "Supported Data Stream Transport Configuration" - */ - -export class SupportedDataStreamTransportConfiguration extends Characteristic { - - static readonly UUID: string = '00000130-0000-1000-8000-0026BB765291'; - - constructor() { - super('Supported Data Stream Transport Configuration', SupportedDataStreamTransportConfiguration.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.PAIRED_READ] - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.SupportedDataStreamTransportConfiguration = SupportedDataStreamTransportConfiguration; - -/** - * Characteristic "Setup Data Stream Transport" - */ - -export class SetupDataStreamTransport extends Characteristic { - - static readonly UUID: string = '00000131-0000-1000-8000-0026BB765291'; - - constructor() { - super('Setup Data Stream Transport', SetupDataStreamTransport.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.WRITE_RESPONSE] - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.SetupDataStreamTransport = SetupDataStreamTransport; - - -/** - * Service "Data Stream Transport Management" - */ - -export class DataStreamTransportManagement extends Service { - - static readonly UUID: string = '00000129-0000-1000-8000-0026BB765291'; - - constructor(displayName: string, subtype: string) { - super(displayName, DataStreamTransportManagement.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.SupportedDataStreamTransportConfiguration); - this.addCharacteristic(Characteristic.SetupDataStreamTransport); - this.addCharacteristic(Characteristic.Version); - } -} - -Service.DataStreamTransportManagement = DataStreamTransportManagement; diff --git a/src/lib/gen/HomeKit-Remote.ts b/src/lib/gen/HomeKit-Remote.ts deleted file mode 100644 index 03e9a619c..000000000 --- a/src/lib/gen/HomeKit-Remote.ts +++ /dev/null @@ -1,195 +0,0 @@ -// manually created - -import { Access, Characteristic, Formats, Perms } from '../Characteristic'; -import { Service } from '../Service'; - - -/** - * Characteristic "Target Control Supported Configuration" - */ - -export class TargetControlSupportedConfiguration extends Characteristic { - - static readonly UUID: string = '00000123-0000-1000-8000-0026BB765291'; - - constructor() { - super('Target Control Supported Configuration', TargetControlSupportedConfiguration.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.PAIRED_READ] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.TargetControlSupportedConfiguration = TargetControlSupportedConfiguration; - -/** - * Characteristic "Target Control List" - */ - -export class TargetControlList extends Characteristic { - - static readonly UUID: string = '00000124-0000-1000-8000-0026BB765291'; - - constructor() { - super('Target Control List', TargetControlList.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.PAIRED_WRITE, Perms.PAIRED_READ, Perms.WRITE_RESPONSE], - adminOnlyAccess: [Access.READ, Access.WRITE], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.TargetControlList = TargetControlList; - -/** - * Characteristic "Button Event" - */ - -export class ButtonEvent extends Characteristic { - - static readonly UUID: string = '00000126-0000-1000-8000-0026BB765291'; - - constructor() { - super('Button Event', ButtonEvent.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.PAIRED_READ, Perms.NOTIFY], - adminOnlyAccess: [Access.NOTIFY], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.ButtonEvent = ButtonEvent; - -/** - * Characteristic "Selected Audio Stream Configuration" - */ - -export class SelectedAudioStreamConfiguration extends Characteristic { - - static readonly UUID: string = '00000128-0000-1000-8000-0026BB765291'; - - constructor() { - super('Selected Audio Stream Configuration', SelectedAudioStreamConfiguration.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE] - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.SelectedAudioStreamConfiguration = SelectedAudioStreamConfiguration; - -/** - * Characteristic "Siri Input Type" - */ - -export class SiriInputType extends Characteristic { - - static readonly PUSH_BUTTON_TRIGGERED_APPLE_TV = 0; - - static readonly UUID: string = '00000132-0000-1000-8000-0026BB765291'; - - constructor() { - super('Siri Input Type', SiriInputType.UUID); - this.setProps({ - format: Formats.UINT8, - minValue: 0, - maxValue: 0, - validValues: [0], - perms: [Perms.PAIRED_READ] - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.SiriInputType = SiriInputType; - -/** - * Service "Target Control Management" - */ - -export class TargetControlManagement extends Service { - - static readonly UUID: string = '00000122-0000-1000-8000-0026BB765291'; - - constructor(displayName: string, subtype: string) { - super(displayName, TargetControlManagement.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.TargetControlSupportedConfiguration); - this.addCharacteristic(Characteristic.TargetControlList); - } -} - -Service.TargetControlManagement = TargetControlManagement; - -/** - * Service "Target Control" - */ - -export class TargetControl extends Service { - - static readonly UUID: string = '00000125-0000-1000-8000-0026BB765291'; - - constructor(displayName: string, subtype: string) { - super(displayName, TargetControl.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.ActiveIdentifier); - this.addCharacteristic(Characteristic.Active); - this.addCharacteristic(Characteristic.ButtonEvent); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.TargetControl = TargetControl; - -/** - * Service "Audio Stream Management" - */ - -export class AudioStreamManagement extends Service { - - static readonly UUID: string = '00000127-0000-1000-8000-0026BB765291'; - - constructor(displayName: string, subtype: string) { - super(displayName, AudioStreamManagement.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.SupportedAudioStreamConfiguration); - this.addCharacteristic(Characteristic.SelectedAudioStreamConfiguration); - } -} - -Service.AudioStreamManagement = AudioStreamManagement; - -/** - * Service "Siri" - */ - -export class Siri extends Service { - - static readonly UUID: string = '00000133-0000-1000-8000-0026BB765291'; - - constructor(displayName: string, subtype: string) { - super(displayName, Siri.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.SiriInputType); - } -} - -Service.Siri = Siri; diff --git a/src/lib/gen/HomeKit-TV.ts b/src/lib/gen/HomeKit-TV.ts deleted file mode 100644 index c3b72482b..000000000 --- a/src/lib/gen/HomeKit-TV.ts +++ /dev/null @@ -1,560 +0,0 @@ -// Manually created from metadata in HomeKitDaemon - -import { Characteristic, Perms, Formats } from '../Characteristic'; -import { Service } from '../Service'; - -/** - * Characteristic "Active Identifier" - */ - -export class ActiveIdentifier extends Characteristic { - - static readonly UUID: string = '000000E7-0000-1000-8000-0026BB765291'; - - constructor() { - super('Active Identifier', ActiveIdentifier.UUID); - this.setProps({ - format: Formats.UINT32, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.ActiveIdentifier = ActiveIdentifier; - -/** - * Characteristic "Configured Name" - */ - -export class ConfiguredName extends Characteristic { - - static readonly UUID: string = '000000E3-0000-1000-8000-0026BB765291'; - - constructor() { - super('Configured Name', ConfiguredName.UUID); - this.setProps({ - format: Formats.STRING, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.ConfiguredName = ConfiguredName; - -/** - * Characteristic "Sleep Discovery Mode" - */ - -export class SleepDiscoveryMode extends Characteristic { - -// The value property of SleepDiscoveryMode must be one of the following: - static readonly NOT_DISCOVERABLE = 0; - static readonly ALWAYS_DISCOVERABLE = 1; - - static readonly UUID: string = '000000E8-0000-1000-8000-0026BB765291'; - - constructor() { - super('Sleep Discovery Mode', SleepDiscoveryMode.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.SleepDiscoveryMode = SleepDiscoveryMode; - -/** - * Characteristic "Closed Captions" - */ - -export class ClosedCaptions extends Characteristic { - - // The value property of ClosedCaptions must be one of the following: - static readonly DISABLED = 0; - static readonly ENABLED = 1; - - static readonly UUID: string = '000000DD-0000-1000-8000-0026BB765291'; - - constructor() { - super('Closed Captions', ClosedCaptions.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.ClosedCaptions = ClosedCaptions; - -/** - * Characteristic "Display Order" - */ - -export class DisplayOrder extends Characteristic { - - static readonly UUID: string = '00000136-0000-1000-8000-0026BB765291'; - - constructor() { - super('Display Order', DisplayOrder.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.DisplayOrder = DisplayOrder; - -/** - * Characteristic "Current Media State" - */ - -export class CurrentMediaState extends Characteristic { - - static readonly PLAY = 0; - static readonly PAUSE = 1; - static readonly STOP = 2; - // 3 is unknown (maybe some Television specific value) - static readonly LOADING = 4; // seems to be SmartSpeaker specific - static readonly INTERRUPTED = 5; // seems to be SmartSpeaker specific - - static readonly UUID: string = '000000E0-0000-1000-8000-0026BB765291'; - - constructor() { - super('Current Media State', CurrentMediaState.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 5, - minValue: 0, - validValues: [0,1,2,3,4,5], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.CurrentMediaState = CurrentMediaState; - -/** - * Characteristic "Target Media State" - */ - -export class TargetMediaState extends Characteristic { - -// The value property of TargetMediaState must be one of the following: - static readonly PLAY = 0; - static readonly PAUSE = 1; - static readonly STOP = 2; - - static readonly UUID: string = '00000137-0000-1000-8000-0026BB765291'; - - constructor() { - super('Target Media State', TargetMediaState.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0,1,2,3], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.TargetMediaState = TargetMediaState; - -/** - * Characteristic "Picture Mode" - */ - -export class PictureMode extends Characteristic { - -// The value property of PictureMode must be one of the following: - static readonly OTHER = 0; - static readonly STANDARD = 1; - static readonly CALIBRATED = 2; - static readonly CALIBRATED_DARK = 3; - static readonly VIVID = 4; - static readonly GAME = 5; - static readonly COMPUTER = 6; - static readonly CUSTOM = 7; - - static readonly UUID: string = '000000E2-0000-1000-8000-0026BB765291'; - - constructor() { - super('Picture Mode', PictureMode.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 13, - minValue: 0, - validValues: [0,1,2,3,4,5,6,7,8,9,10,11,12,13], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.PictureMode = PictureMode; - -/** - * Characteristic "Power Mode Selection" - */ - -export class PowerModeSelection extends Characteristic { - - // The value property of PowerModeSelection must be one of the following: - static readonly SHOW = 0; - static readonly HIDE = 1; - - static readonly UUID: string = '000000DF-0000-1000-8000-0026BB765291'; - - constructor() { - super('Power Mode Selection', PowerModeSelection.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Perms.WRITE] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.PowerModeSelection = PowerModeSelection; - -/** - * Characteristic "Remote Key" - */ - -export class RemoteKey extends Characteristic { - -// The value property of RemoteKey must be one of the following: - static readonly REWIND = 0; - static readonly FAST_FORWARD = 1; - static readonly NEXT_TRACK = 2; - static readonly PREVIOUS_TRACK = 3; - static readonly ARROW_UP = 4; - static readonly ARROW_DOWN = 5; - static readonly ARROW_LEFT = 6; - static readonly ARROW_RIGHT = 7; - static readonly SELECT = 8; - static readonly BACK = 9; - static readonly EXIT = 10; - static readonly PLAY_PAUSE = 11; - static readonly INFORMATION = 15; - - static readonly UUID: string = '000000E1-0000-1000-8000-0026BB765291'; - - constructor() { - super('Remote Key', RemoteKey.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 16, - minValue: 0, - validValues: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16], - perms: [Perms.WRITE] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.RemoteKey = RemoteKey; - -/** - * Characteristic "Input Source Type" - */ - -export class InputSourceType extends Characteristic { - -// The value property of InputSourceType must be one of the following: - static readonly OTHER = 0; - static readonly HOME_SCREEN = 1; - static readonly TUNER = 2; - static readonly HDMI = 3; - static readonly COMPOSITE_VIDEO = 4; - static readonly S_VIDEO = 5; - static readonly COMPONENT_VIDEO = 6; - static readonly DVI = 7; - static readonly AIRPLAY = 8; - static readonly USB = 9; - static readonly APPLICATION = 10; - - static readonly UUID: string = '000000DB-0000-1000-8000-0026BB765291'; - - constructor() { - super('Input Source Type', InputSourceType.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 10, - minValue: 0, - validValues: [0,1,2,3,4,5,6,7,8,9,10], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.InputSourceType = InputSourceType; - -/** - * Characteristic "Input Device Type" - */ - -export class InputDeviceType extends Characteristic { - - // The value property of InputDeviceType must be one of the following: - static readonly OTHER = 0; - static readonly TV = 1; - static readonly RECORDING = 2; - static readonly TUNER = 3; - static readonly PLAYBACK = 4; - static readonly AUDIO_SYSTEM = 5; - static readonly UNKNOWN_6 = 6; // introduce in iOS 14; "UNKNOWN_6" is not stable API, changes as soon as the type is known - - static readonly UUID: string = '000000DC-0000-1000-8000-0026BB765291'; - - constructor() { - super('Input Device Type', InputDeviceType.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 6, - minValue: 0, - validValues: [0,1,2,3,4,5], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.InputDeviceType = InputDeviceType; - -/** - * Characteristic "Identifier" - */ - -export class Identifier extends Characteristic { - - static readonly UUID: string = '000000E6-0000-1000-8000-0026BB765291'; - - constructor() { - super('Identifier', Identifier.UUID); - this.setProps({ - format: Formats.UINT32, - minValue: 0, - minStep: 1, - perms: [Perms.READ] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.Identifier = Identifier; - -/** - * Characteristic "Current Visibility State" - */ - -export class CurrentVisibilityState extends Characteristic { - -// The value property of CurrentVisibilityState must be one of the following: - static readonly SHOWN = 0; - static readonly HIDDEN = 1; - - static readonly UUID: string = '00000135-0000-1000-8000-0026BB765291'; - - constructor() { - super('Current Visibility State', CurrentVisibilityState.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 3, - minValue: 0, - validValues: [0,1,2,3], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.CurrentVisibilityState = CurrentVisibilityState; - -/** - * Characteristic "Target Visibility State" - */ - -export class TargetVisibilityState extends Characteristic { - -// The value property of TargetVisibilityState must be one of the following: - static readonly SHOWN = 0; - static readonly HIDDEN = 1; - - static readonly UUID: string = '00000134-0000-1000-8000-0026BB765291'; - - constructor() { - super('Target Visibility State', TargetVisibilityState.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.TargetVisibilityState = TargetVisibilityState; - -/** - * Characteristic "Volume Control Type" - */ - -export class VolumeControlType extends Characteristic { - -// The value property of VolumeControlType must be one of the following: - static readonly NONE = 0; - static readonly RELATIVE = 1; - static readonly RELATIVE_WITH_CURRENT = 2; - static readonly ABSOLUTE = 3; - - static readonly UUID: string = '000000E9-0000-1000-8000-0026BB765291'; - - constructor() { - super('Volume Control Type', VolumeControlType.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 3, - minValue: 0, - validValues: [0,1,2,3], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.VolumeControlType = VolumeControlType; - -/** - * Characteristic "Volume Selector" - */ - -export class VolumeSelector extends Characteristic { - -// The value property of VolumeSelector must be one of the following: - static readonly INCREMENT = 0; - static readonly DECREMENT = 1; - - static readonly UUID: string = '000000EA-0000-1000-8000-0026BB765291'; - - constructor() { - super('Volume Selector', VolumeSelector.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Perms.WRITE] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.VolumeSelector = VolumeSelector; - - -/** - * Service "Television" - */ - -export class Television extends Service { - - static readonly UUID: string = '000000D8-0000-1000-8000-0026BB765291'; - - constructor(displayName: string, subtype: string) { - super(displayName, Television.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.Active); - this.addCharacteristic(Characteristic.ActiveIdentifier); - this.addCharacteristic(Characteristic.ConfiguredName); - this.addCharacteristic(Characteristic.RemoteKey); - this.addCharacteristic(Characteristic.SleepDiscoveryMode); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Brightness); - this.addOptionalCharacteristic(Characteristic.ClosedCaptions); - this.addOptionalCharacteristic(Characteristic.DisplayOrder); - this.addOptionalCharacteristic(Characteristic.CurrentMediaState); - this.addOptionalCharacteristic(Characteristic.TargetMediaState); - this.addOptionalCharacteristic(Characteristic.PictureMode); - this.addOptionalCharacteristic(Characteristic.PowerModeSelection); - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.Television = Television; - -/** - * Service "Input Source" - */ - -export class InputSource extends Service { - - static readonly UUID: string = '000000D9-0000-1000-8000-0026BB765291'; - - constructor(displayName: string, subtype: string) { - super(displayName, InputSource.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.ConfiguredName); - this.addCharacteristic(Characteristic.InputSourceType); - this.addCharacteristic(Characteristic.IsConfigured); - if (!this.testCharacteristic(Characteristic.Name)) { // workaround for name characteristic collision in constructor - this.addCharacteristic(Characteristic.Name).updateValue("Unnamed InputSource"); - } - this.addCharacteristic(Characteristic.CurrentVisibilityState); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Identifier); - this.addOptionalCharacteristic(Characteristic.InputDeviceType); - this.addOptionalCharacteristic(Characteristic.TargetVisibilityState); - } -} - -Service.InputSource = InputSource; - -/** - * Service "Television Speaker" - */ - -export class TelevisionSpeaker extends Service { - - static readonly UUID: string = '00000113-0000-1000-8000-0026BB765291'; - - constructor(displayName: string, subtype: string) { - super(displayName, TelevisionSpeaker.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.Mute); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Active); - this.addOptionalCharacteristic(Characteristic.Volume); - this.addOptionalCharacteristic(Characteristic.VolumeControlType); - this.addOptionalCharacteristic(Characteristic.VolumeSelector); - } -} - -Service.TelevisionSpeaker = TelevisionSpeaker; diff --git a/src/lib/gen/HomeKit.ts b/src/lib/gen/HomeKit.ts deleted file mode 100644 index 866c915a9..000000000 --- a/src/lib/gen/HomeKit.ts +++ /dev/null @@ -1,5383 +0,0 @@ -// THIS FILE IS AUTO-GENERATED - DO NOT MODIFY - -import { Characteristic, Formats, Perms, Units } from '../Characteristic'; -import { Service } from '../Service'; - -/** - * Characteristic "Access Control Level" - */ - -export class AccessControlLevel extends Characteristic { - - static readonly UUID: string = '000000E5-0000-1000-8000-0026BB765291'; - - constructor() { - super('Access Control Level', AccessControlLevel.UUID); - this.setProps({ - format: Formats.UINT16, - perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], - maxValue: 2, - minValue: 0, - minStep: 1, - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.AccessControlLevel = AccessControlLevel; - -/** - * Characteristic "Accessory Flags" - */ - -export class AccessoryFlags extends Characteristic { - - static readonly UUID: string = '000000A6-0000-1000-8000-0026BB765291'; - - constructor() { - super('Accessory Flags', AccessoryFlags.UUID); - this.setProps({ - format: Formats.UINT32, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.AccessoryFlags = AccessoryFlags; - -/** - * Characteristic "Product Data" - */ - -export class ProductData extends Characteristic { - - static readonly UUID: string = '00000220-0000-1000-8000-0026BB765291'; - - constructor() { - super('Product Data', ProductData.UUID); - this.setProps({ - format: Formats.DATA, - perms: [Perms.READ] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.ProductData = ProductData; - -/** - * Characteristic "Active" - */ - -export class Active extends Characteristic { - - // The value property of Active must be one of the following: - static readonly INACTIVE = 0; - static readonly ACTIVE = 1; - - static readonly UUID: string = '000000B0-0000-1000-8000-0026BB765291'; - - constructor() { - super('Active', Active.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.Active = Active; - -/** - * Characteristic "Administrator Only Access" - */ - -export class AdministratorOnlyAccess extends Characteristic { - - static readonly UUID: string = '00000001-0000-1000-8000-0026BB765291'; - - constructor() { - super('Administrator Only Access', AdministratorOnlyAccess.UUID); - this.setProps({ - format: Formats.BOOL, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.AdministratorOnlyAccess = AdministratorOnlyAccess; - -/** - * Characteristic "Air Particulate Density" - */ - -export class AirParticulateDensity extends Characteristic { - - static readonly UUID: string = '00000064-0000-1000-8000-0026BB765291'; - - constructor() { - super('Air Particulate Density', AirParticulateDensity.UUID); - this.setProps({ - format: Formats.FLOAT, - maxValue: 1000, - minValue: 0, - minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.AirParticulateDensity = AirParticulateDensity; - -/** - * Characteristic "Air Particulate Size" - */ - -export class AirParticulateSize extends Characteristic { - - // The value property of AirParticulateSize must be one of the following: - static readonly _2_5_M = 0; - static readonly _10_M = 1; - - static readonly UUID: string = '00000065-0000-1000-8000-0026BB765291'; - - constructor() { - super('Air Particulate Size', AirParticulateSize.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.AirParticulateSize = AirParticulateSize; - - -/** - * Characteristic "Air Quality" - */ - -export class AirQuality extends Characteristic { - - // The value property of AirQuality must be one of the following: - static readonly UNKNOWN = 0; - static readonly EXCELLENT = 1; - static readonly GOOD = 2; - static readonly FAIR = 3; - static readonly INFERIOR = 4; - static readonly POOR = 5; - - static readonly UUID: string = '00000095-0000-1000-8000-0026BB765291'; - - constructor() { - super('Air Quality', AirQuality.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 5, - minValue: 0, - validValues: [0, 1, 2, 3, 4, 5], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.AirQuality = AirQuality; - - -/** - * Characteristic "Audio Feedback" - */ - -export class AudioFeedback extends Characteristic { - - static readonly UUID: string = '00000005-0000-1000-8000-0026BB765291'; - - constructor() { - super('Audio Feedback', AudioFeedback.UUID); - this.setProps({ - format: Formats.BOOL, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.AudioFeedback = AudioFeedback; - -/** - * Characteristic "Battery Level" - */ - -export class BatteryLevel extends Characteristic { - - static readonly UUID: string = '00000068-0000-1000-8000-0026BB765291'; - - constructor() { - super('Battery Level', BatteryLevel.UUID); - this.setProps({ - format: Formats.UINT8, - unit: Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.BatteryLevel = BatteryLevel; - -/** - * Characteristic "Brightness" - */ - -export class Brightness extends Characteristic { - - static readonly UUID: string = '00000008-0000-1000-8000-0026BB765291'; - - constructor() { - super('Brightness', Brightness.UUID); - this.setProps({ - format: Formats.INT, - unit: Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.Brightness = Brightness; - -/** - * Characteristic "Carbon Dioxide Detected" - */ - -export class CarbonDioxideDetected extends Characteristic { - - // The value property of CarbonDioxideDetected must be one of the following: - static readonly CO2_LEVELS_NORMAL = 0; - static readonly CO2_LEVELS_ABNORMAL = 1; - - static readonly UUID: string = '00000092-0000-1000-8000-0026BB765291'; - - constructor() { - super('Carbon Dioxide Detected', CarbonDioxideDetected.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.CarbonDioxideDetected = CarbonDioxideDetected; - -/** - * Characteristic "Carbon Dioxide Level" - */ - -export class CarbonDioxideLevel extends Characteristic { - - static readonly UUID: string = '00000093-0000-1000-8000-0026BB765291'; - - constructor() { - super('Carbon Dioxide Level', CarbonDioxideLevel.UUID); - this.setProps({ - format: Formats.FLOAT, - maxValue: 100000, - minValue: 0, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.CarbonDioxideLevel = CarbonDioxideLevel; - -/** - * Characteristic "Carbon Dioxide Peak Level" - */ - -export class CarbonDioxidePeakLevel extends Characteristic { - - static readonly UUID: string = '00000094-0000-1000-8000-0026BB765291'; - - constructor() { - super('Carbon Dioxide Peak Level', CarbonDioxidePeakLevel.UUID); - this.setProps({ - format: Formats.FLOAT, - maxValue: 100000, - minValue: 0, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.CarbonDioxidePeakLevel = CarbonDioxidePeakLevel; - -/** - * Characteristic "Carbon Monoxide Detected" - */ - -export class CarbonMonoxideDetected extends Characteristic { - - // The value property of CarbonMonoxideDetected must be one of the following: - static readonly CO_LEVELS_NORMAL = 0; - static readonly CO_LEVELS_ABNORMAL = 1; - - static readonly UUID: string = '00000069-0000-1000-8000-0026BB765291'; - - constructor() { - super('Carbon Monoxide Detected', CarbonMonoxideDetected.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.CarbonMonoxideDetected = CarbonMonoxideDetected; - -/** - * Characteristic "Carbon Monoxide Level" - */ - -export class CarbonMonoxideLevel extends Characteristic { - - static readonly UUID: string = '00000090-0000-1000-8000-0026BB765291'; - - constructor() { - super('Carbon Monoxide Level', CarbonMonoxideLevel.UUID); - this.setProps({ - format: Formats.FLOAT, - maxValue: 100, - minValue: 0, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.CarbonMonoxideLevel = CarbonMonoxideLevel; - -/** - * Characteristic "Carbon Monoxide Peak Level" - */ - -export class CarbonMonoxidePeakLevel extends Characteristic { - - static readonly UUID: string = '00000091-0000-1000-8000-0026BB765291'; - - constructor() { - super('Carbon Monoxide Peak Level', CarbonMonoxidePeakLevel.UUID); - this.setProps({ - format: Formats.FLOAT, - maxValue: 100, - minValue: 0, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.CarbonMonoxidePeakLevel = CarbonMonoxidePeakLevel; - -/** - * Characteristic "Charging State" - */ - -export class ChargingState extends Characteristic { - - // The value property of ChargingState must be one of the following: - static readonly NOT_CHARGING = 0; - static readonly CHARGING = 1; - static readonly NOT_CHARGEABLE = 2; - - static readonly UUID: string = '0000008F-0000-1000-8000-0026BB765291'; - - constructor() { - super('Charging State', ChargingState.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0, 1, 2], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.ChargingState = ChargingState; - -/** - * Characteristic "Color Temperature" - */ - -export class ColorTemperature extends Characteristic { - - static readonly UUID: string = '000000CE-0000-1000-8000-0026BB765291'; - - constructor() { - super('Color Temperature', ColorTemperature.UUID); - this.setProps({ - format: Formats.UINT32, - maxValue: 500, - minValue: 140, - minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.ColorTemperature = ColorTemperature; - -/** - * Characteristic "Contact Sensor State" - */ - -export class ContactSensorState extends Characteristic { - - // The value property of ContactSensorState must be one of the following: - static readonly CONTACT_DETECTED = 0; - static readonly CONTACT_NOT_DETECTED = 1; - - static readonly UUID: string = '0000006A-0000-1000-8000-0026BB765291'; - - constructor() { - super('Contact Sensor State', ContactSensorState.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.ContactSensorState = ContactSensorState; - -/** - * Characteristic "Cooling Threshold Temperature" - */ - -export class CoolingThresholdTemperature extends Characteristic { - - static readonly UUID: string = '0000000D-0000-1000-8000-0026BB765291'; - - constructor() { - super('Cooling Threshold Temperature', CoolingThresholdTemperature.UUID); - this.setProps({ - format: Formats.FLOAT, - unit: Units.CELSIUS, - maxValue: 35, - minValue: 10, - minStep: 0.1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.CoolingThresholdTemperature = CoolingThresholdTemperature; - -/** - * Characteristic "Current Air Purifier State" - */ - -export class CurrentAirPurifierState extends Characteristic { - - // The value property of CurrentAirPurifierState must be one of the following: - static readonly INACTIVE = 0; - static readonly IDLE = 1; - static readonly PURIFYING_AIR = 2; - - static readonly UUID: string = '000000A9-0000-1000-8000-0026BB765291'; - - constructor() { - super('Current Air Purifier State', CurrentAirPurifierState.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0, 1, 2], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.CurrentAirPurifierState = CurrentAirPurifierState; - - -/** - * Characteristic "Current Ambient Light Level" - */ - -export class CurrentAmbientLightLevel extends Characteristic { - - static readonly UUID: string = '0000006B-0000-1000-8000-0026BB765291'; - - constructor() { - super('Current Ambient Light Level', CurrentAmbientLightLevel.UUID); - this.setProps({ - format: Formats.FLOAT, - unit: Units.LUX, - maxValue: 100000, - minValue: 0.0001, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.CurrentAmbientLightLevel = CurrentAmbientLightLevel; - -/** - * Characteristic "Current Door State" - */ - -export class CurrentDoorState extends Characteristic { - - // The value property of CurrentDoorState must be one of the following: - static readonly OPEN = 0; - static readonly CLOSED = 1; - static readonly OPENING = 2; - static readonly CLOSING = 3; - static readonly STOPPED = 4; - - static readonly UUID: string = '0000000E-0000-1000-8000-0026BB765291'; - - constructor() { - super('Current Door State', CurrentDoorState.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 4, - minValue: 0, - validValues: [0, 1, 2, 3, 4], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.CurrentDoorState = CurrentDoorState; - -/** - * Characteristic "Current Fan State" - */ - -export class CurrentFanState extends Characteristic { - - // The value property of CurrentFanState must be one of the following: - static readonly INACTIVE = 0; - static readonly IDLE = 1; - static readonly BLOWING_AIR = 2; - - static readonly UUID: string = '000000AF-0000-1000-8000-0026BB765291'; - - constructor() { - super('Current Fan State', CurrentFanState.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0, 1, 2], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.CurrentFanState = CurrentFanState; - -/** - * Characteristic "Current Heater Cooler State" - */ - -export class CurrentHeaterCoolerState extends Characteristic { - - // The value property of CurrentHeaterCoolerState must be one of the following: - static readonly INACTIVE = 0; - static readonly IDLE = 1; - static readonly HEATING = 2; - static readonly COOLING = 3; - - static readonly UUID: string = '000000B1-0000-1000-8000-0026BB765291'; - - constructor() { - super('Current Heater Cooler State', CurrentHeaterCoolerState.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 3, - minValue: 0, - validValues: [0, 1, 2, 3], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.CurrentHeaterCoolerState = CurrentHeaterCoolerState; - -/** - * Characteristic "Current Heating Cooling State" - */ - -export class CurrentHeatingCoolingState extends Characteristic { - - // The value property of CurrentHeatingCoolingState must be one of the following: - static readonly OFF = 0; - static readonly HEAT = 1; - static readonly COOL = 2; - - static readonly UUID: string = '0000000F-0000-1000-8000-0026BB765291'; - - constructor() { - super('Current Heating Cooling State', CurrentHeatingCoolingState.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0, 1, 2], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.CurrentHeatingCoolingState = CurrentHeatingCoolingState; - -/** - * Characteristic "Current Horizontal Tilt Angle" - */ - -export class CurrentHorizontalTiltAngle extends Characteristic { - - static readonly UUID: string = '0000006C-0000-1000-8000-0026BB765291'; - - constructor() { - super('Current Horizontal Tilt Angle', CurrentHorizontalTiltAngle.UUID); - this.setProps({ - format: Formats.INT, - unit: Units.ARC_DEGREE, - maxValue: 90, - minValue: -90, - minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.CurrentHorizontalTiltAngle = CurrentHorizontalTiltAngle; - -/** - * Characteristic "Current Humidifier Dehumidifier State" - */ - -export class CurrentHumidifierDehumidifierState extends Characteristic { - - // The value property of CurrentHumidifierDehumidifierState must be one of the following: - static readonly INACTIVE = 0; - static readonly IDLE = 1; - static readonly HUMIDIFYING = 2; - static readonly DEHUMIDIFYING = 3; - - static readonly UUID: string = '000000B3-0000-1000-8000-0026BB765291'; - - constructor() { - super('Current Humidifier Dehumidifier State', CurrentHumidifierDehumidifierState.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 3, - minValue: 0, - validValues: [0, 1, 2, 3], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.CurrentHumidifierDehumidifierState = CurrentHumidifierDehumidifierState; - -/** - * Characteristic "Current Position" - */ - -export class CurrentPosition extends Characteristic { - - static readonly UUID: string = '0000006D-0000-1000-8000-0026BB765291'; - - constructor() { - super('Current Position', CurrentPosition.UUID); - this.setProps({ - format: Formats.UINT8, - unit: Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.CurrentPosition = CurrentPosition; - -/** - * Characteristic "Current Relative Humidity" - */ - -export class CurrentRelativeHumidity extends Characteristic { - - static readonly UUID: string = '00000010-0000-1000-8000-0026BB765291'; - - constructor() { - super('Current Relative Humidity', CurrentRelativeHumidity.UUID); - this.setProps({ - format: Formats.FLOAT, - unit: Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.CurrentRelativeHumidity = CurrentRelativeHumidity; - -/** - * Characteristic "Current Slat State" - */ - -export class CurrentSlatState extends Characteristic { - - // The value property of CurrentSlatState must be one of the following: - static readonly FIXED = 0; - static readonly JAMMED = 1; - static readonly SWINGING = 2; - - static readonly UUID: string = '000000AA-0000-1000-8000-0026BB765291'; - - constructor() { - super('Current Slat State', CurrentSlatState.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0, 1, 2], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.CurrentSlatState = CurrentSlatState; - -/** - * Characteristic "Current Temperature" - */ - -export class CurrentTemperature extends Characteristic { - - static readonly UUID: string = '00000011-0000-1000-8000-0026BB765291'; - - constructor() { - super('Current Temperature', CurrentTemperature.UUID); - this.setProps({ - format: Formats.FLOAT, - unit: Units.CELSIUS, - maxValue: 100, - minValue: 0, - minStep: 0.1, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.CurrentTemperature = CurrentTemperature; - -/** - * Characteristic "Current Tilt Angle" - */ - -export class CurrentTiltAngle extends Characteristic { - - static readonly UUID: string = '000000C1-0000-1000-8000-0026BB765291'; - - constructor() { - super('Current Tilt Angle', CurrentTiltAngle.UUID); - this.setProps({ - format: Formats.INT, - unit: Units.ARC_DEGREE, - maxValue: 90, - minValue: -90, - minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.CurrentTiltAngle = CurrentTiltAngle; - -/** - * Characteristic "Current Vertical Tilt Angle" - */ - -export class CurrentVerticalTiltAngle extends Characteristic { - - static readonly UUID: string = '0000006E-0000-1000-8000-0026BB765291'; - - constructor() { - super('Current Vertical Tilt Angle', CurrentVerticalTiltAngle.UUID); - this.setProps({ - format: Formats.INT, - unit: Units.ARC_DEGREE, - maxValue: 90, - minValue: -90, - minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.CurrentVerticalTiltAngle = CurrentVerticalTiltAngle; - -/** - * Characteristic "Digital Zoom" - */ - -export class DigitalZoom extends Characteristic { - - static readonly UUID: string = '0000011D-0000-1000-8000-0026BB765291'; - - constructor() { - super('Digital Zoom', DigitalZoom.UUID); - this.setProps({ - format: Formats.FLOAT, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.DigitalZoom = DigitalZoom; - -/** - * Characteristic "Filter Change Indication" - */ - -export class FilterChangeIndication extends Characteristic { - - // The value property of FilterChangeIndication must be one of the following: - static readonly FILTER_OK = 0; - static readonly CHANGE_FILTER = 1; - - static readonly UUID: string = '000000AC-0000-1000-8000-0026BB765291'; - - constructor() { - super('Filter Change Indication', FilterChangeIndication.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.FilterChangeIndication = FilterChangeIndication; - -/** - * Characteristic "Filter Life Level" - */ - -export class FilterLifeLevel extends Characteristic { - - static readonly UUID: string = '000000AB-0000-1000-8000-0026BB765291'; - - constructor() { - super('Filter Life Level', FilterLifeLevel.UUID); - this.setProps({ - format: Formats.FLOAT, - maxValue: 100, - minValue: 0, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.FilterLifeLevel = FilterLifeLevel; - -/** - * Characteristic "Firmware Revision" - */ - -export class FirmwareRevision extends Characteristic { - - static readonly UUID: string = '00000052-0000-1000-8000-0026BB765291'; - - constructor() { - super('Firmware Revision', FirmwareRevision.UUID); - this.setProps({ - format: Formats.STRING, - perms: [Perms.READ] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.FirmwareRevision = FirmwareRevision; - -/** - * Characteristic "Hardware Revision" - */ - -export class HardwareRevision extends Characteristic { - - static readonly UUID: string = '00000053-0000-1000-8000-0026BB765291'; - - constructor() { - super('Hardware Revision', HardwareRevision.UUID); - this.setProps({ - format: Formats.STRING, - perms: [Perms.READ] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.HardwareRevision = HardwareRevision; - -/** - * Characteristic "Heating Threshold Temperature" - */ - -export class HeatingThresholdTemperature extends Characteristic { - - static readonly UUID: string = '00000012-0000-1000-8000-0026BB765291'; - - constructor() { - super('Heating Threshold Temperature', HeatingThresholdTemperature.UUID); - this.setProps({ - format: Formats.FLOAT, - unit: Units.CELSIUS, - maxValue: 25, - minValue: 0, - minStep: 0.1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.HeatingThresholdTemperature = HeatingThresholdTemperature; - -/** - * Characteristic "Hold Position" - */ - -export class HoldPosition extends Characteristic { - - static readonly UUID: string = '0000006F-0000-1000-8000-0026BB765291'; - - constructor() { - super('Hold Position', HoldPosition.UUID); - this.setProps({ - format: Formats.BOOL, - perms: [Perms.WRITE] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.HoldPosition = HoldPosition; - -/** - * Characteristic "Hue" - */ - -export class Hue extends Characteristic { - - static readonly UUID: string = '00000013-0000-1000-8000-0026BB765291'; - - constructor() { - super('Hue', Hue.UUID); - this.setProps({ - format: Formats.FLOAT, - unit: Units.ARC_DEGREE, - maxValue: 360, - minValue: 0, - minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.Hue = Hue; - -/** - * Characteristic "Identify" - */ - -export class Identify extends Characteristic { - - static readonly UUID: string = '00000014-0000-1000-8000-0026BB765291'; - - constructor() { - super('Identify', Identify.UUID); - this.setProps({ - format: Formats.BOOL, - perms: [Perms.WRITE] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.Identify = Identify; - -/** - * Characteristic "Image Mirroring" - */ - -export class ImageMirroring extends Characteristic { - - static readonly UUID: string = '0000011F-0000-1000-8000-0026BB765291'; - - constructor() { - super('Image Mirroring', ImageMirroring.UUID); - this.setProps({ - format: Formats.BOOL, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.ImageMirroring = ImageMirroring; - -/** - * Characteristic "Image Rotation" - */ - -export class ImageRotation extends Characteristic { - - static readonly UUID: string = '0000011E-0000-1000-8000-0026BB765291'; - - constructor() { - super('Image Rotation', ImageRotation.UUID); - this.setProps({ - format: Formats.FLOAT, - unit: Units.ARC_DEGREE, - maxValue: 270, - minValue: 0, - minStep: 90, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.ImageRotation = ImageRotation; - -/** - * Characteristic "In Use" - */ - -export class InUse extends Characteristic { - - // The value property of InUse must be one of the following: - static readonly NOT_IN_USE = 0; - static readonly IN_USE = 1; - - static readonly UUID: string = '000000D2-0000-1000-8000-0026BB765291'; - - constructor() { - super('In Use', InUse.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.InUse = InUse; - -/** - * Characteristic "Is Configured" - */ - -export class IsConfigured extends Characteristic { - - // The value property of IsConfigured must be one of the following: - static readonly NOT_CONFIGURED = 0; - static readonly CONFIGURED = 1; - - static readonly UUID: string = '000000D6-0000-1000-8000-0026BB765291'; - - constructor() { - super('Is Configured', IsConfigured.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.IsConfigured = IsConfigured; - -/** - * Characteristic "Leak Detected" - */ - -export class LeakDetected extends Characteristic { - - // The value property of LeakDetected must be one of the following: - static readonly LEAK_NOT_DETECTED = 0; - static readonly LEAK_DETECTED = 1; - - static readonly UUID: string = '00000070-0000-1000-8000-0026BB765291'; - - constructor() { - super('Leak Detected', LeakDetected.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.LeakDetected = LeakDetected; - -/** - * Characteristic "Lock Control Point" - */ - -export class LockControlPoint extends Characteristic { - - static readonly UUID: string = '00000019-0000-1000-8000-0026BB765291'; - - constructor() { - super('Lock Control Point', LockControlPoint.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.WRITE] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.LockControlPoint = LockControlPoint; - -/** - * Characteristic "Lock Current State" - */ - -export class LockCurrentState extends Characteristic { - - // The value property of LockCurrentState must be one of the following: - static readonly UNSECURED = 0; - static readonly SECURED = 1; - static readonly JAMMED = 2; - static readonly UNKNOWN = 3; - - static readonly UUID: string = '0000001D-0000-1000-8000-0026BB765291'; - - constructor() { - super('Lock Current State', LockCurrentState.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 3, - minValue: 0, - validValues: [0, 1, 2, 3], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.LockCurrentState = LockCurrentState; - -/** - * Characteristic "Lock Last Known Action" - */ - -export class LockLastKnownAction extends Characteristic { - - // The value property of LockLastKnownAction must be one of the following: - static readonly SECURED_PHYSICALLY_INTERIOR = 0; - static readonly UNSECURED_PHYSICALLY_INTERIOR = 1; - static readonly SECURED_PHYSICALLY_EXTERIOR = 2; - static readonly UNSECURED_PHYSICALLY_EXTERIOR = 3; - static readonly SECURED_BY_KEYPAD = 4; - static readonly UNSECURED_BY_KEYPAD = 5; - static readonly SECURED_REMOTELY = 6; - static readonly UNSECURED_REMOTELY = 7; - static readonly SECURED_BY_AUTO_SECURE_TIMEOUT = 8; - - static readonly UUID: string = '0000001C-0000-1000-8000-0026BB765291'; - - constructor() { - super('Lock Last Known Action', LockLastKnownAction.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 8, - minValue: 0, - validValues: [0, 1, 2, 3, 4, 5, 6, 7, 8], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.LockLastKnownAction = LockLastKnownAction; - -/** - * Characteristic "Lock Management Auto Security Timeout" - */ - -export class LockManagementAutoSecurityTimeout extends Characteristic { - - static readonly UUID: string = '0000001A-0000-1000-8000-0026BB765291'; - - constructor() { - super('Lock Management Auto Security Timeout', LockManagementAutoSecurityTimeout.UUID); - this.setProps({ - format: Formats.UINT32, - unit: Units.SECONDS, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.LockManagementAutoSecurityTimeout = LockManagementAutoSecurityTimeout; - -/** - * Characteristic "Lock Physical Controls" - */ - -export class LockPhysicalControls extends Characteristic { - - // The value property of LockPhysicalControls must be one of the following: - static readonly CONTROL_LOCK_DISABLED = 0; - static readonly CONTROL_LOCK_ENABLED = 1; - - static readonly UUID: string = '000000A7-0000-1000-8000-0026BB765291'; - - constructor() { - super('Lock Physical Controls', LockPhysicalControls.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.LockPhysicalControls = LockPhysicalControls; - -/** - * Characteristic "Lock Target State" - */ - -export class LockTargetState extends Characteristic { - - // The value property of LockTargetState must be one of the following: - static readonly UNSECURED = 0; - static readonly SECURED = 1; - - static readonly UUID: string = '0000001E-0000-1000-8000-0026BB765291'; - - constructor() { - super('Lock Target State', LockTargetState.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.LockTargetState = LockTargetState; - -/** - * Characteristic "Logs" - */ - -export class Logs extends Characteristic { - - static readonly UUID: string = '0000001F-0000-1000-8000-0026BB765291'; - - constructor() { - super('Logs', Logs.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.Logs = Logs; - -/** - * Characteristic "Manufacturer" - */ - -export class Manufacturer extends Characteristic { - - static readonly UUID: string = '00000020-0000-1000-8000-0026BB765291'; - - constructor() { - super('Manufacturer', Manufacturer.UUID); - this.setProps({ - format: Formats.STRING, - perms: [Perms.READ] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.Manufacturer = Manufacturer; - -/** - * Characteristic "Model" - */ - -export class Model extends Characteristic { - - static readonly UUID: string = '00000021-0000-1000-8000-0026BB765291'; - - constructor() { - super('Model', Model.UUID); - this.setProps({ - format: Formats.STRING, - perms: [Perms.READ] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.Model = Model; - -/** - * Characteristic "Motion Detected" - */ - -export class MotionDetected extends Characteristic { - - static readonly UUID: string = '00000022-0000-1000-8000-0026BB765291'; - - constructor() { - super('Motion Detected', MotionDetected.UUID); - this.setProps({ - format: Formats.BOOL, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.MotionDetected = MotionDetected; - -/** - * Characteristic "Mute" - */ - -export class Mute extends Characteristic { - - static readonly UUID: string = '0000011A-0000-1000-8000-0026BB765291'; - - constructor() { - super('Mute', Mute.UUID); - this.setProps({ - format: Formats.BOOL, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.Mute = Mute; - -/** - * Characteristic "Name" - */ - -export class Name extends Characteristic { - - static readonly UUID: string = '00000023-0000-1000-8000-0026BB765291'; - - constructor() { - super('Name', Name.UUID); - this.setProps({ - format: Formats.STRING, - perms: [Perms.READ] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.Name = Name; - -/** - * Characteristic "Night Vision" - */ - -export class NightVision extends Characteristic { - - static readonly UUID: string = '0000011B-0000-1000-8000-0026BB765291'; - - constructor() { - super('Night Vision', NightVision.UUID); - this.setProps({ - format: Formats.BOOL, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY, Perms.TIMED_WRITE] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.NightVision = NightVision; - -/** - * Characteristic "Nitrogen Dioxide Density" - */ - -export class NitrogenDioxideDensity extends Characteristic { - - static readonly UUID: string = '000000C4-0000-1000-8000-0026BB765291'; - - constructor() { - super('Nitrogen Dioxide Density', NitrogenDioxideDensity.UUID); - this.setProps({ - format: Formats.FLOAT, - maxValue: 1000, - minValue: 0, - minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.NitrogenDioxideDensity = NitrogenDioxideDensity; - -/** - * Characteristic "Obstruction Detected" - */ - -export class ObstructionDetected extends Characteristic { - - static readonly UUID: string = '00000024-0000-1000-8000-0026BB765291'; - - constructor() { - super('Obstruction Detected', ObstructionDetected.UUID); - this.setProps({ - format: Formats.BOOL, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.ObstructionDetected = ObstructionDetected; - -/** - * Characteristic "Occupancy Detected" - */ - -export class OccupancyDetected extends Characteristic { - - // The value property of OccupancyDetected must be one of the following: - static readonly OCCUPANCY_NOT_DETECTED = 0; - static readonly OCCUPANCY_DETECTED = 1; - - static readonly UUID: string = '00000071-0000-1000-8000-0026BB765291'; - - constructor() { - super('Occupancy Detected', OccupancyDetected.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.OccupancyDetected = OccupancyDetected; - -/** - * Characteristic "On" - */ - -export class On extends Characteristic { - - static readonly UUID: string = '00000025-0000-1000-8000-0026BB765291'; - - constructor() { - super('On', On.UUID); - this.setProps({ - format: Formats.BOOL, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.On = On; - -/** - * Characteristic "Optical Zoom" - */ - -export class OpticalZoom extends Characteristic { - - static readonly UUID: string = '0000011C-0000-1000-8000-0026BB765291'; - - constructor() { - super('Optical Zoom', OpticalZoom.UUID); - this.setProps({ - format: Formats.FLOAT, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.OpticalZoom = OpticalZoom; - -/** - * Characteristic "Outlet In Use" - */ - -export class OutletInUse extends Characteristic { - - static readonly UUID: string = '00000026-0000-1000-8000-0026BB765291'; - - constructor() { - super('Outlet In Use', OutletInUse.UUID); - this.setProps({ - format: Formats.BOOL, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.OutletInUse = OutletInUse; - -/** - * Characteristic "Ozone Density" - */ - -export class OzoneDensity extends Characteristic { - - static readonly UUID: string = '000000C3-0000-1000-8000-0026BB765291'; - - constructor() { - super('Ozone Density', OzoneDensity.UUID); - this.setProps({ - format: Formats.FLOAT, - maxValue: 1000, - minValue: 0, - minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.OzoneDensity = OzoneDensity; - -/** - * Characteristic "Pair Setup" - */ - -export class PairSetup extends Characteristic { - - static readonly UUID: string = '0000004C-0000-1000-8000-0026BB765291'; - - constructor() { - super('Pair Setup', PairSetup.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.READ, Perms.WRITE] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.PairSetup = PairSetup; - -/** - * Characteristic "Pair Verify" - */ - -export class PairVerify extends Characteristic { - - static readonly UUID: string = '0000004E-0000-1000-8000-0026BB765291'; - - constructor() { - super('Pair Verify', PairVerify.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.READ, Perms.WRITE] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.PairVerify = PairVerify; - -/** - * Characteristic "Pairing Features" - */ - -export class PairingFeatures extends Characteristic { - - static readonly UUID: string = '0000004F-0000-1000-8000-0026BB765291'; - - constructor() { - super('Pairing Features', PairingFeatures.UUID); - this.setProps({ - format: Formats.UINT8, - perms: [Perms.READ] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.PairingFeatures = PairingFeatures; - -/** - * Characteristic "Pairing Pairings" - */ - -export class PairingPairings extends Characteristic { - - static readonly UUID: string = '00000050-0000-1000-8000-0026BB765291'; - - constructor() { - super('Pairing Pairings', PairingPairings.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.READ, Perms.WRITE] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.PairingPairings = PairingPairings; - -/** - * Characteristic "Password Setting" - */ - -export class PasswordSetting extends Characteristic { - - static readonly UUID: string = '000000E4-0000-1000-8000-0026BB765291'; - - constructor() { - super('Password Setting', PasswordSetting.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.PasswordSetting = PasswordSetting; - -/** - * Characteristic "PM10 Density" - */ - -export class PM10Density extends Characteristic { - - static readonly UUID: string = '000000C7-0000-1000-8000-0026BB765291'; - - constructor() { - super('PM10 Density', PM10Density.UUID); - this.setProps({ - format: Formats.FLOAT, - maxValue: 1000, - minValue: 0, - minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.PM10Density = PM10Density; - -/** - * Characteristic "PM2.5 Density" - */ - -export class PM2_5Density extends Characteristic { - - static readonly UUID: string = '000000C6-0000-1000-8000-0026BB765291'; - - constructor() { - super('PM2.5 Density', PM2_5Density.UUID); - this.setProps({ - format: Formats.FLOAT, - maxValue: 1000, - minValue: 0, - minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.PM2_5Density = PM2_5Density; - -/** - * Characteristic "Position State" - */ - -export class PositionState extends Characteristic { - - // The value property of PositionState must be one of the following: - static readonly DECREASING = 0; - static readonly INCREASING = 1; - static readonly STOPPED = 2; - - static readonly UUID: string = '00000072-0000-1000-8000-0026BB765291'; - - constructor() { - super('Position State', PositionState.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0, 1, 2], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.PositionState = PositionState; - -/** - * Characteristic "Program Mode" - */ - -export class ProgramMode extends Characteristic { - - // The value property of ProgramMode must be one of the following: - static readonly NO_PROGRAM_SCHEDULED = 0; - static readonly PROGRAM_SCHEDULED = 1; - static readonly PROGRAM_SCHEDULED_MANUAL_MODE_ = 2; - - static readonly UUID: string = '000000D1-0000-1000-8000-0026BB765291'; - - constructor() { - super('Program Mode', ProgramMode.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0, 1, 2], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.ProgramMode = ProgramMode; - -/** - * Characteristic "Programmable Switch Event" - */ - -export class ProgrammableSwitchEvent extends Characteristic { - - // The value property of ProgrammableSwitchEvent must be one of the following: - static readonly SINGLE_PRESS = 0; - static readonly DOUBLE_PRESS = 1; - static readonly LONG_PRESS = 2; - - static readonly UUID: string = '00000073-0000-1000-8000-0026BB765291'; - - constructor() { - super('Programmable Switch Event', ProgrammableSwitchEvent.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0, 1, 2], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.eventOnlyCharacteristic = true; //Manual addition. - this.value = this.getDefaultValue(); - } -} - -Characteristic.ProgrammableSwitchEvent = ProgrammableSwitchEvent; - -/** - * Characteristic "Relative Humidity Dehumidifier Threshold" - */ - -export class RelativeHumidityDehumidifierThreshold extends Characteristic { - - static readonly UUID: string = '000000C9-0000-1000-8000-0026BB765291'; - - constructor() { - super('Relative Humidity Dehumidifier Threshold', RelativeHumidityDehumidifierThreshold.UUID); - this.setProps({ - format: Formats.FLOAT, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.RelativeHumidityDehumidifierThreshold = RelativeHumidityDehumidifierThreshold; - -/** - * Characteristic "Relative Humidity Humidifier Threshold" - */ - -export class RelativeHumidityHumidifierThreshold extends Characteristic { - - static readonly UUID: string = '000000CA-0000-1000-8000-0026BB765291'; - - constructor() { - super('Relative Humidity Humidifier Threshold', RelativeHumidityHumidifierThreshold.UUID); - this.setProps({ - format: Formats.FLOAT, - unit: Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.RelativeHumidityHumidifierThreshold = RelativeHumidityHumidifierThreshold; - -/** - * Characteristic "Remaining Duration" - */ - -export class RemainingDuration extends Characteristic { - - static readonly UUID: string = '000000D4-0000-1000-8000-0026BB765291'; - - constructor() { - super('Remaining Duration', RemainingDuration.UUID); - this.setProps({ - format: Formats.UINT32, - maxValue: 3600, - minValue: 0, - minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.RemainingDuration = RemainingDuration; - -/** - * Characteristic "Reset Filter Indication" - */ - -export class ResetFilterIndication extends Characteristic { - - static readonly UUID: string = '000000AD-0000-1000-8000-0026BB765291'; - - constructor() { - super('Reset Filter Indication', ResetFilterIndication.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 1, - minStep: 1, - perms: [Perms.WRITE] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.ResetFilterIndication = ResetFilterIndication; - -/** - * Characteristic "Rotation Direction" - */ - -export class RotationDirection extends Characteristic { - - // The value property of RotationDirection must be one of the following: - static readonly CLOCKWISE = 0; - static readonly COUNTER_CLOCKWISE = 1; - - static readonly UUID: string = '00000028-0000-1000-8000-0026BB765291'; - - constructor() { - super('Rotation Direction', RotationDirection.UUID); - this.setProps({ - format: Formats.INT, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.RotationDirection = RotationDirection; - -/** - * Characteristic "Rotation Speed" - */ - -export class RotationSpeed extends Characteristic { - - static readonly UUID: string = '00000029-0000-1000-8000-0026BB765291'; - - constructor() { - super('Rotation Speed', RotationSpeed.UUID); - this.setProps({ - format: Formats.FLOAT, - unit: Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.RotationSpeed = RotationSpeed; - -/** - * Characteristic "Saturation" - */ - -export class Saturation extends Characteristic { - - static readonly UUID: string = '0000002F-0000-1000-8000-0026BB765291'; - - constructor() { - super('Saturation', Saturation.UUID); - this.setProps({ - format: Formats.FLOAT, - unit: Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.Saturation = Saturation; - -/** - * Characteristic "Security System Alarm Type" - */ - -export class SecuritySystemAlarmType extends Characteristic { - - static readonly UUID: string = '0000008E-0000-1000-8000-0026BB765291'; - - constructor() { - super('Security System Alarm Type', SecuritySystemAlarmType.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.SecuritySystemAlarmType = SecuritySystemAlarmType; - -/** - * Characteristic "Security System Current State" - */ - -export class SecuritySystemCurrentState extends Characteristic { - - // The value property of SecuritySystemCurrentState must be one of the following: - static readonly STAY_ARM = 0; - static readonly AWAY_ARM = 1; - static readonly NIGHT_ARM = 2; - static readonly DISARMED = 3; - static readonly ALARM_TRIGGERED = 4; - - static readonly UUID: string = '00000066-0000-1000-8000-0026BB765291'; - - constructor() { - super('Security System Current State', SecuritySystemCurrentState.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 4, - minValue: 0, - validValues: [0, 1, 2, 3, 4], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.SecuritySystemCurrentState = SecuritySystemCurrentState; - -/** - * Characteristic "Security System Target State" - */ - -export class SecuritySystemTargetState extends Characteristic { - - // The value property of SecuritySystemTargetState must be one of the following: - static readonly STAY_ARM = 0; - static readonly AWAY_ARM = 1; - static readonly NIGHT_ARM = 2; - static readonly DISARM = 3; - - static readonly UUID: string = '00000067-0000-1000-8000-0026BB765291'; - - constructor() { - super('Security System Target State', SecuritySystemTargetState.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 3, - minValue: 0, - validValues: [0, 1, 2, 3], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.SecuritySystemTargetState = SecuritySystemTargetState; - -/** - * Characteristic "Selected RTP Stream Configuration" - */ - -export class SelectedRTPStreamConfiguration extends Characteristic { - - static readonly UUID: string = '00000117-0000-1000-8000-0026BB765291'; - - constructor() { - super('Selected RTP Stream Configuration', SelectedRTPStreamConfiguration.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.READ, Perms.WRITE] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.SelectedRTPStreamConfiguration = SelectedRTPStreamConfiguration; - -/** - * Characteristic "Serial Number" - */ - -export class SerialNumber extends Characteristic { - - static readonly UUID: string = '00000030-0000-1000-8000-0026BB765291'; - - constructor() { - super('Serial Number', SerialNumber.UUID); - this.setProps({ - format: Formats.STRING, - perms: [Perms.READ] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.SerialNumber = SerialNumber; - -/** - * Characteristic "Service Label Index" - */ - -export class ServiceLabelIndex extends Characteristic { - - static readonly UUID: string = '000000CB-0000-1000-8000-0026BB765291'; - - constructor() { - super('Service Label Index', ServiceLabelIndex.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 255, - minValue: 1, - minStep: 1, - perms: [Perms.READ] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.ServiceLabelIndex = ServiceLabelIndex; - -/** - * Characteristic "Service Label Namespace" - */ - -export class ServiceLabelNamespace extends Characteristic { - - // The value property of ServiceLabelNamespace must be one of the following: - static readonly DOTS = 0; - static readonly ARABIC_NUMERALS = 1; - - static readonly UUID: string = '000000CD-0000-1000-8000-0026BB765291'; - - constructor() { - super('Service Label Namespace', ServiceLabelNamespace.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.READ] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.ServiceLabelNamespace = ServiceLabelNamespace; - -/** - * Characteristic "Set Duration" - */ - -export class SetDuration extends Characteristic { - - static readonly UUID: string = '000000D3-0000-1000-8000-0026BB765291'; - - constructor() { - super('Set Duration', SetDuration.UUID); - this.setProps({ - format: Formats.UINT32, - maxValue: 3600, - minValue: 0, - minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.SetDuration = SetDuration; - -/** - * Characteristic "Setup Endpoints" - */ - -export class SetupEndpoints extends Characteristic { - - static readonly UUID: string = '00000118-0000-1000-8000-0026BB765291'; - - constructor() { - super('Setup Endpoints', SetupEndpoints.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.READ, Perms.WRITE] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.SetupEndpoints = SetupEndpoints; - -/** - * Characteristic "Slat Type" - */ - -export class SlatType extends Characteristic { - - // The value property of SlatType must be one of the following: - static readonly HORIZONTAL = 0; - static readonly VERTICAL = 1; - - static readonly UUID: string = '000000C0-0000-1000-8000-0026BB765291'; - - constructor() { - super('Slat Type', SlatType.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.READ] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.SlatType = SlatType; - -/** - * Characteristic "Smoke Detected" - */ - -export class SmokeDetected extends Characteristic { - - // The value property of SmokeDetected must be one of the following: - static readonly SMOKE_NOT_DETECTED = 0; - static readonly SMOKE_DETECTED = 1; - - static readonly UUID: string = '00000076-0000-1000-8000-0026BB765291'; - - constructor() { - super('Smoke Detected', SmokeDetected.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.SmokeDetected = SmokeDetected; - -/** - * Characteristic "Status Active" - */ - -export class StatusActive extends Characteristic { - - static readonly UUID: string = '00000075-0000-1000-8000-0026BB765291'; - - constructor() { - super('Status Active', StatusActive.UUID); - this.setProps({ - format: Formats.BOOL, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.StatusActive = StatusActive; - -/** - * Characteristic "Status Fault" - */ - -export class StatusFault extends Characteristic { - - // The value property of StatusFault must be one of the following: - static readonly NO_FAULT = 0; - static readonly GENERAL_FAULT = 1; - - static readonly UUID: string = '00000077-0000-1000-8000-0026BB765291'; - - constructor() { - super('Status Fault', StatusFault.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.StatusFault = StatusFault; - -/** - * Characteristic "Status Jammed" - */ - -export class StatusJammed extends Characteristic { - - // The value property of StatusJammed must be one of the following: - static readonly NOT_JAMMED = 0; - static readonly JAMMED = 1; - - static readonly UUID: string = '00000078-0000-1000-8000-0026BB765291'; - - constructor() { - super('Status Jammed', StatusJammed.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.StatusJammed = StatusJammed; - -/** - * Characteristic "Status Low Battery" - */ - -export class StatusLowBattery extends Characteristic { - - // The value property of StatusLowBattery must be one of the following: - static readonly BATTERY_LEVEL_NORMAL = 0; - static readonly BATTERY_LEVEL_LOW = 1; - - static readonly UUID: string = '00000079-0000-1000-8000-0026BB765291'; - - constructor() { - super('Status Low Battery', StatusLowBattery.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.StatusLowBattery = StatusLowBattery; - - -/** - * Characteristic "Status Tampered" - */ - -export class StatusTampered extends Characteristic { - - // The value property of StatusTampered must be one of the following: - static readonly NOT_TAMPERED = 0; - static readonly TAMPERED = 1; - - static readonly UUID: string = '0000007A-0000-1000-8000-0026BB765291'; - - constructor() { - super('Status Tampered', StatusTampered.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.StatusTampered = StatusTampered; - -/** - * Characteristic "Streaming Status" - */ - -export class StreamingStatus extends Characteristic { - - static readonly UUID: string = '00000120-0000-1000-8000-0026BB765291'; - - constructor() { - super('Streaming Status', StreamingStatus.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.StreamingStatus = StreamingStatus; - -/** - * Characteristic "Sulphur Dioxide Density" - */ - -export class SulphurDioxideDensity extends Characteristic { - - static readonly UUID: string = '000000C5-0000-1000-8000-0026BB765291'; - - constructor() { - super('Sulphur Dioxide Density', SulphurDioxideDensity.UUID); - this.setProps({ - format: Formats.FLOAT, - maxValue: 1000, - minValue: 0, - minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.SulphurDioxideDensity = SulphurDioxideDensity; - -/** - * Characteristic "Supported Audio Stream Configuration" - */ - -export class SupportedAudioStreamConfiguration extends Characteristic { - - static readonly UUID: string = '00000115-0000-1000-8000-0026BB765291'; - - constructor() { - super('Supported Audio Stream Configuration', SupportedAudioStreamConfiguration.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.READ] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.SupportedAudioStreamConfiguration = SupportedAudioStreamConfiguration; - -/** - * Characteristic "Supported RTP Configuration" - */ - -export class SupportedRTPConfiguration extends Characteristic { - - static readonly UUID: string = '00000116-0000-1000-8000-0026BB765291'; - - constructor() { - super('Supported RTP Configuration', SupportedRTPConfiguration.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.READ] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.SupportedRTPConfiguration = SupportedRTPConfiguration; - -/** - * Characteristic "Supported Video Stream Configuration" - */ - -export class SupportedVideoStreamConfiguration extends Characteristic { - - static readonly UUID: string = '00000114-0000-1000-8000-0026BB765291'; - - constructor() { - super('Supported Video Stream Configuration', SupportedVideoStreamConfiguration.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.READ] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.SupportedVideoStreamConfiguration = SupportedVideoStreamConfiguration; - -/** - * Characteristic "Swing Mode" - */ - -export class SwingMode extends Characteristic { - - // The value property of SwingMode must be one of the following: - static readonly SWING_DISABLED = 0; - static readonly SWING_ENABLED = 1; - - static readonly UUID: string = '000000B6-0000-1000-8000-0026BB765291'; - - constructor() { - super('Swing Mode', SwingMode.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.SwingMode = SwingMode; - -/** - * Characteristic "Target Air Purifier State" - */ - -export class TargetAirPurifierState extends Characteristic { - - // The value property of TargetAirPurifierState must be one of the following: - static readonly MANUAL = 0; - static readonly AUTO = 1; - - static readonly UUID: string = '000000A8-0000-1000-8000-0026BB765291'; - - constructor() { - super('Target Air Purifier State', TargetAirPurifierState.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.TargetAirPurifierState = TargetAirPurifierState; - -/** - * Characteristic "Target Air Quality" - */ - -export class TargetAirQuality extends Characteristic { - - // The value property of TargetAirQuality must be one of the following: - static readonly EXCELLENT = 0; - static readonly GOOD = 1; - static readonly FAIR = 2; - - static readonly UUID: string = '000000AE-0000-1000-8000-0026BB765291'; - - constructor() { - super('Target Air Quality', TargetAirQuality.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0, 1, 2], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.TargetAirQuality = TargetAirQuality; - -/** - * Characteristic "Target Door State" - */ - -export class TargetDoorState extends Characteristic { - - // The value property of TargetDoorState must be one of the following: - static readonly OPEN = 0; - static readonly CLOSED = 1; - - static readonly UUID: string = '00000032-0000-1000-8000-0026BB765291'; - - constructor() { - super('Target Door State', TargetDoorState.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.TargetDoorState = TargetDoorState; - -/** - * Characteristic "Target Fan State" - */ - -export class TargetFanState extends Characteristic { - - // The value property of TargetFanState must be one of the following: - static readonly MANUAL = 0; - static readonly AUTO = 1; - - static readonly UUID: string = '000000BF-0000-1000-8000-0026BB765291'; - - constructor() { - super('Target Fan State', TargetFanState.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.TargetFanState = TargetFanState; - -/** - * Characteristic "Target Heater Cooler State" - */ - -export class TargetHeaterCoolerState extends Characteristic { - - // The value property of TargetHeaterCoolerState must be one of the following: - static readonly AUTO = 0; - static readonly HEAT = 1; - static readonly COOL = 2; - - static readonly UUID: string = '000000B2-0000-1000-8000-0026BB765291'; - - constructor() { - super('Target Heater Cooler State', TargetHeaterCoolerState.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0, 1, 2], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.TargetHeaterCoolerState = TargetHeaterCoolerState; - -/** - * Characteristic "Target Heating Cooling State" - */ - -export class TargetHeatingCoolingState extends Characteristic { - - // The value property of TargetHeatingCoolingState must be one of the following: - static readonly OFF = 0; - static readonly HEAT = 1; - static readonly COOL = 2; - static readonly AUTO = 3; - - static readonly UUID: string = '00000033-0000-1000-8000-0026BB765291'; - - constructor() { - super('Target Heating Cooling State', TargetHeatingCoolingState.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 3, - minValue: 0, - validValues: [0, 1, 2, 3], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.TargetHeatingCoolingState = TargetHeatingCoolingState; - -/** - * Characteristic "Target Horizontal Tilt Angle" - */ - -export class TargetHorizontalTiltAngle extends Characteristic { - - static readonly UUID: string = '0000007B-0000-1000-8000-0026BB765291'; - - constructor() { - super('Target Horizontal Tilt Angle', TargetHorizontalTiltAngle.UUID); - this.setProps({ - format: Formats.INT, - unit: Units.ARC_DEGREE, - maxValue: 90, - minValue: -90, - minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.TargetHorizontalTiltAngle = TargetHorizontalTiltAngle; - -/** - * Characteristic "Target Humidifier Dehumidifier State" - */ - -export class TargetHumidifierDehumidifierState extends Characteristic { - - /** - * @deprecated Removed in iOS 11. Use HUMIDIFIER_OR_DEHUMIDIFIER instead. - */ - static readonly AUTO = 0; - - // The value property of TargetHumidifierDehumidifierState must be one of the following: - static readonly HUMIDIFIER_OR_DEHUMIDIFIER = 0; - static readonly HUMIDIFIER = 1; - static readonly DEHUMIDIFIER = 2; - - static readonly UUID: string = '000000B4-0000-1000-8000-0026BB765291'; - - constructor() { - super('Target Humidifier Dehumidifier State', TargetHumidifierDehumidifierState.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0, 1, 2], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.TargetHumidifierDehumidifierState = TargetHumidifierDehumidifierState; - -/** - * Characteristic "Target Position" - */ - -export class TargetPosition extends Characteristic { - - static readonly UUID: string = '0000007C-0000-1000-8000-0026BB765291'; - - constructor() { - super('Target Position', TargetPosition.UUID); - this.setProps({ - format: Formats.UINT8, - unit: Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.TargetPosition = TargetPosition; - -/** - * Characteristic "Target Relative Humidity" - */ - -export class TargetRelativeHumidity extends Characteristic { - - static readonly UUID: string = '00000034-0000-1000-8000-0026BB765291'; - - constructor() { - super('Target Relative Humidity', TargetRelativeHumidity.UUID); - this.setProps({ - format: Formats.FLOAT, - unit: Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.TargetRelativeHumidity = TargetRelativeHumidity; - -/** - * Characteristic "Target Slat State" - */ - -export class TargetSlatState extends Characteristic { - - // The value property of TargetSlatState must be one of the following: - static readonly MANUAL = 0; - static readonly AUTO = 1; - - static readonly UUID: string = '000000BE-0000-1000-8000-0026BB765291'; - - constructor() { - super('Target Slat State', TargetSlatState.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.TargetSlatState = TargetSlatState; - -/** - * Characteristic "Target Temperature" - */ - -export class TargetTemperature extends Characteristic { - - static readonly UUID: string = '00000035-0000-1000-8000-0026BB765291'; - - constructor() { - super('Target Temperature', TargetTemperature.UUID); - this.setProps({ - format: Formats.FLOAT, - unit: Units.CELSIUS, - maxValue: 38, - minValue: 10, - minStep: 0.1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.TargetTemperature = TargetTemperature; - -/** - * Characteristic "Target Tilt Angle" - */ - -export class TargetTiltAngle extends Characteristic { - - static readonly UUID: string = '000000C2-0000-1000-8000-0026BB765291'; - - constructor() { - super('Target Tilt Angle', TargetTiltAngle.UUID); - this.setProps({ - format: Formats.INT, - unit: Units.ARC_DEGREE, - maxValue: 90, - minValue: -90, - minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.TargetTiltAngle = TargetTiltAngle; - -/** - * Characteristic "Target Vertical Tilt Angle" - */ - -export class TargetVerticalTiltAngle extends Characteristic { - - static readonly UUID: string = '0000007D-0000-1000-8000-0026BB765291'; - - constructor() { - super('Target Vertical Tilt Angle', TargetVerticalTiltAngle.UUID); - this.setProps({ - format: Formats.INT, - unit: Units.ARC_DEGREE, - maxValue: 90, - minValue: -90, - minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.TargetVerticalTiltAngle = TargetVerticalTiltAngle; - -/** - * Characteristic "Temperature Display Units" - */ - -export class TemperatureDisplayUnits extends Characteristic { - - // The value property of TemperatureDisplayUnits must be one of the following: - static readonly CELSIUS = 0; - static readonly FAHRENHEIT = 1; - - static readonly UUID: string = '00000036-0000-1000-8000-0026BB765291'; - - constructor() { - super('Temperature Display Units', TemperatureDisplayUnits.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.TemperatureDisplayUnits = TemperatureDisplayUnits; - -/** - * Characteristic "Valve Type" - */ - -export class ValveType extends Characteristic { - - // The value property of ValveType must be one of the following: - static readonly GENERIC_VALVE = 0; - static readonly IRRIGATION = 1; - static readonly SHOWER_HEAD = 2; - static readonly WATER_FAUCET = 3; - - static readonly UUID: string = '000000D5-0000-1000-8000-0026BB765291'; - - constructor() { - super('Valve Type', ValveType.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 3, - minValue: 0, - validValues: [0, 1, 2, 3], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.ValveType = ValveType; - -/** - * Characteristic "Version" - */ - -export class Version extends Characteristic { - - static readonly UUID: string = '00000037-0000-1000-8000-0026BB765291'; - - constructor() { - super('Version', Version.UUID); - this.setProps({ - format: Formats.STRING, - perms: [Perms.PAIRED_READ] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.Version = Version; - -/** - * Characteristic "VOC Density" - */ - -export class VOCDensity extends Characteristic { - - static readonly UUID: string = '000000C8-0000-1000-8000-0026BB765291'; - - constructor() { - super('VOC Density', VOCDensity.UUID); - this.setProps({ - format: Formats.FLOAT, - maxValue: 1000, - minValue: 0, - minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.VOCDensity = VOCDensity; - -/** - * Characteristic "Volume" - */ - -export class Volume extends Characteristic { - - static readonly UUID: string = '00000119-0000-1000-8000-0026BB765291'; - - constructor() { - super('Volume', Volume.UUID); - this.setProps({ - format: Formats.UINT8, - unit: Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.Volume = Volume; - -/** - * Characteristic "Water Level" - */ - -export class WaterLevel extends Characteristic { - - static readonly UUID: string = '000000B5-0000-1000-8000-0026BB765291'; - - constructor() { - super('Water Level', WaterLevel.UUID); - this.setProps({ - format: Formats.FLOAT, - maxValue: 100, - minValue: 0, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.WaterLevel = WaterLevel; - -/** - * Characteristic "Recording Audio Active" - */ - -export class RecordingAudioActive extends Characteristic { - - static readonly DISABLE = 0; - static readonly ENABLE = 1; - - static readonly UUID: string = '00000226-0000-1000-8000-0026BB765291'; - - constructor() { - super('Recording Audio Active', RecordingAudioActive.UUID); - this.setProps({ - format: Formats.UINT8, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.RecordingAudioActive = RecordingAudioActive; - -/** - * Characteristic "Supported Camera Recording Configuration" - */ - -export class SupportedCameraRecordingConfiguration extends Characteristic { - - static readonly UUID: string = '00000205-0000-1000-8000-0026BB765291'; - - constructor() { - super('Supported Camera Recording Configuration', SupportedCameraRecordingConfiguration.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.SupportedCameraRecordingConfiguration = SupportedCameraRecordingConfiguration; - -/** - * Characteristic "Supported Video Recording Configuration" - */ - -export class SupportedVideoRecordingConfiguration extends Characteristic { - - static readonly UUID: string = '00000206-0000-1000-8000-0026BB765291'; - - constructor() { - super('Supported Video Recording Configuration', SupportedVideoRecordingConfiguration.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.SupportedVideoRecordingConfiguration = SupportedVideoRecordingConfiguration; - -/** - * Characteristic "Supported Audio Recording Configuration" - */ - -export class SupportedAudioRecordingConfiguration extends Characteristic { - - static readonly UUID: string = '00000207-0000-1000-8000-0026BB765291'; - - constructor() { - super('Supported Audio Recording Configuration', SupportedAudioRecordingConfiguration.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.SupportedAudioRecordingConfiguration = SupportedAudioRecordingConfiguration; - -/** - * Characteristic "Selected Camera Recording Configuration" - */ - -export class SelectedCameraRecordingConfiguration extends Characteristic { - - static readonly UUID: string = '00000209-0000-1000-8000-0026BB765291'; - - constructor() { - super('Selected Camera Recording Configuration', SelectedCameraRecordingConfiguration.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.SelectedCameraRecordingConfiguration = SelectedCameraRecordingConfiguration; - -/** - * Characteristic "Camera Operating Mode Indicator" - */ - -export class CameraOperatingModeIndicator extends Characteristic { - - static readonly DISABLE = 0; - static readonly ENABLE = 1; - - static readonly UUID: string = '0000021D-0000-1000-8000-0026BB765291'; - - constructor() { - super('Camera Operating Mode Indicator', CameraOperatingModeIndicator.UUID); - this.setProps({ - format: Formats.UINT8, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY, Perms.TIMED_WRITE] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.CameraOperatingModeIndicator = CameraOperatingModeIndicator; - -/** - * Characteristic "Event Snapshots Active" - */ - -export class EventSnapshotsActive extends Characteristic { - - static readonly DISABLE = 0; - static readonly ENABLE = 1; - - static readonly UUID: string = '00000223-0000-1000-8000-0026BB765291'; - - constructor() { - super('Event Snapshots Active', EventSnapshotsActive.UUID); - this.setProps({ - format: Formats.UINT8, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.EventSnapshotsActive = EventSnapshotsActive; - -/** - * Characteristic "Diagonal Field Of View" - * - * @deprecated was removed again - */ - -export class DiagonalFieldOfView extends Characteristic { - - static readonly UUID: string = '00000224-0000-1000-8000-0026BB765291'; - - constructor() { - super('Diagonal Field Of View', DiagonalFieldOfView.UUID); - this.setProps({ - format: Formats.FLOAT, - unit: Units.ARC_DEGREE, - maxValue: 360, - minValue: 0, - minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.DiagonalFieldOfView = DiagonalFieldOfView; - -/** - * Characteristic "HomeKit Camera Active" - */ - -export class HomeKitCameraActive extends Characteristic { - - static readonly OFF = 0; - static readonly ON = 1; - - static readonly UUID: string = '0000021B-0000-1000-8000-0026BB765291'; - - constructor() { - super('HomeKit Camera Active', HomeKitCameraActive.UUID); - this.setProps({ - format: Formats.UINT8, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY, Perms.TIMED_WRITE] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.HomeKitCameraActive = HomeKitCameraActive; - -/** - * Characteristic "Manually disabled" - */ - -export class ManuallyDisabled extends Characteristic { - - static readonly ENABLED = 0; - static readonly DISABLED = 1; - - static readonly UUID: string = '00000227-0000-1000-8000-0026BB765291'; - - constructor() { - super('Manually disabled', ManuallyDisabled.UUID); - this.setProps({ - format: Formats.BOOL, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.ManuallyDisabled = ManuallyDisabled; - -/** - * Characteristic "Third Party Camera Active" - */ - -export class ThirdPartyCameraActive extends Characteristic { - - static readonly OFF = 0; - static readonly ON = 1; - - static readonly UUID: string = '0000021C-0000-1000-8000-0026BB765291'; - - constructor() { - super('Third Party Camera Active', ThirdPartyCameraActive.UUID); - this.setProps({ - format: Formats.UINT8, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.ThirdPartyCameraActive = ThirdPartyCameraActive; - -/** - * Characteristic "Periodic Snapshots Active" - */ - -export class PeriodicSnapshotsActive extends Characteristic { - - static readonly DISABLE = 0; - static readonly ENABLE = 1; - - static readonly UUID: string = '00000225-0000-1000-8000-0026BB765291'; - - constructor() { - super('Periodic Snapshots Active', PeriodicSnapshotsActive.UUID); - this.setProps({ - format: Formats.UINT8, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.PeriodicSnapshotsActive = PeriodicSnapshotsActive; - -/** - * Characteristic "Network Client Profile Control" - */ - -export class NetworkClientProfileControl extends Characteristic { - - static readonly UUID: string = '0000020C-0000-1000-8000-0026BB765291'; - - constructor() { - super('Network Client Profile Control', NetworkClientProfileControl.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY, Perms.TIMED_WRITE, Perms.WRITE_RESPONSE] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.NetworkClientProfileControl = NetworkClientProfileControl; - -/** - * Characteristic "Network Client Status Control" - */ - -export class NetworkClientStatusControl extends Characteristic { - - static readonly UUID: string = '0000020D-0000-1000-8000-0026BB765291'; - - constructor() { - super('Network Client Status Control', NetworkClientStatusControl.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.READ, Perms.WRITE, Perms.WRITE_RESPONSE] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.NetworkClientStatusControl = NetworkClientStatusControl; - -/** - * Characteristic "Router Status" - */ - -export class RouterStatus extends Characteristic { - - static readonly READY = 0; - static readonly NOT_READY = 1; - - static readonly UUID: string = '0000020E-0000-1000-8000-0026BB765291'; - - constructor() { - super('Router Status', RouterStatus.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.RouterStatus = RouterStatus; - -/** - * Characteristic "Supported Router Configuration" - */ - -export class SupportedRouterConfiguration extends Characteristic { - - static readonly UUID: string = '00000210-0000-1000-8000-0026BB765291'; - - constructor() { - super('Supported Router Configuration', SupportedRouterConfiguration.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.READ] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.SupportedRouterConfiguration = SupportedRouterConfiguration; - -/** - * Characteristic "WAN Configuration List" - */ - -export class WANConfigurationList extends Characteristic { - - static readonly UUID: string = '00000211-0000-1000-8000-0026BB765291'; - - constructor() { - super('WAN Configuration List', WANConfigurationList.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.WANConfigurationList = WANConfigurationList; - -/** - * Characteristic "WAN Status List" - */ - -export class WANStatusList extends Characteristic { - - static readonly UUID: string = '00000212-0000-1000-8000-0026BB765291'; - - constructor() { - super('WAN Status List', WANStatusList.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.WANStatusList = WANStatusList; - -/** - * Characteristic "Managed Network Enable" - */ - -export class ManagedNetworkEnable extends Characteristic { - static readonly DISABLED = 0; - static readonly ENABLED = 1; - static readonly UNKNOWN = 2; - - static readonly UUID: string = '00000215-0000-1000-8000-0026BB765291'; - - constructor() { - super('Managed Network Enable', ManagedNetworkEnable.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY, Perms.TIMED_WRITE] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.ManagedNetworkEnable = ManagedNetworkEnable; - -/** - * Characteristic "Network Access Violation Control" - */ - -export class NetworkAccessViolationControl extends Characteristic { - - static readonly UUID: string = '0000021F-0000-1000-8000-0026BB765291'; - - constructor() { - super('Network Access Violation Control', NetworkAccessViolationControl.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY, Perms.TIMED_WRITE, Perms.WRITE_RESPONSE] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.NetworkAccessViolationControl = NetworkAccessViolationControl; - -/** - * Characteristic "Wi-Fi Satellite Status" - */ - -export class WiFiSatelliteStatus extends Characteristic { - // The value property of WiFiSatelliteStatus must be one of the following: - static readonly UNKNOWN = 0; - static readonly CONNECTED = 1; - static readonly NOT_CONNECTED = 2; - - static readonly UUID: string = '0000021E-0000-1000-8000-0026BB765291'; - - constructor() { - super('Wi-Fi Satellite Status', WiFiSatelliteStatus.UUID); - this.setProps({ - format: Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0,1,2], - perms: [Perms.PAIRED_READ, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.WiFiSatelliteStatus = WiFiSatelliteStatus; - -/** - * Characteristic "Wake Configuration" - */ - -export class WakeConfiguration extends Characteristic { - - static readonly UUID: string = '00000222-0000-1000-8000-0026BB765291'; - - constructor() { - super('Wake Configuration', WakeConfiguration.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.PAIRED_READ], - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.WakeConfiguration = WakeConfiguration; - -/** - * Characteristic "Supported Transfer Transport Configuration" - */ - -export class SupportedTransferTransportConfiguration extends Characteristic { - - static readonly UUID: string = '00000202-0000-1000-8000-0026BB765291'; - - constructor() { - super('Supported Transfer Transport Configuration', SupportedTransferTransportConfiguration.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.PAIRED_READ], - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.SupportedTransferTransportConfiguration = SupportedTransferTransportConfiguration; - -/** - * Characteristic "Setup Transfer Transport" - */ - -export class SetupTransferTransport extends Characteristic { - - static readonly UUID: string = '00000201-0000-1000-8000-0026BB765291'; - - constructor() { - super('Setup Transfer Transport', SetupTransferTransport.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.PAIRED_WRITE, Perms.WRITE_RESPONSE], - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.SetupTransferTransport = SetupTransferTransport; - -/** - * Characteristic "Activity Interval" - */ -export class ActivityInterval extends Characteristic { - - static readonly UUID: string = '0000021E-0000-1000-8000-0000023B'; - - constructor() { - super("Activity Interval", ActivityInterval.UUID); - this.setProps({ - format: Formats.UINT32, - minValue: 0, - minStep: 1, - perms: [Perms.PAIRED_READ, Perms.NOTIFY], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.ActivityInterval = ActivityInterval; - -/** - * Characteristic "CCA Energy Detect Threshold" - */ -export class CCAEnergyDetectThreshold extends Characteristic { - - static readonly UUID: string = '0000021E-0000-1000-8000-00000246'; - - constructor() { - super("CCA Energy Detect Threshold", CCAEnergyDetectThreshold.UUID); - this.setProps({ - format: Formats.INT, - perms: [Perms.PAIRED_READ], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.CCAEnergyDetectThreshold = CCAEnergyDetectThreshold; - -/** - * Characteristic "CCA Signal Detect Threshold" - */ -export class CCASignalDetectThreshold extends Characteristic { - - static readonly UUID: string = '0000021E-0000-1000-8000-00000245'; - - constructor() { - super("CCA Signal Detect Threshold", CCASignalDetectThreshold.UUID); - this.setProps({ - format: Formats.INT, - perms: [Perms.PAIRED_READ], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.CCASignalDetectThreshold = CCASignalDetectThreshold; - -/** - * Characteristic "Characteristic Value Transition Control" - */ -export class CharacteristicValueTransitionControl extends Characteristic { - - static readonly UUID: string = '0000021E-0000-1000-8000-00000143'; - - constructor() { - super("Characteristic Value Transition Control", CharacteristicValueTransitionControl.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.CharacteristicValueTransitionControl = CharacteristicValueTransitionControl; - -/** - * Characteristic "Supported Characteristic Value Transition Configuration" - */ -export class SupportedCharacteristicValueTransitionConfiguration extends Characteristic { - - static readonly UUID: string = '0000021E-0000-1000-8000-00000144'; - - constructor() { - super("Supported Characteristic Value Transition Configuration", SupportedCharacteristicValueTransitionConfiguration.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.PAIRED_READ], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.SupportedCharacteristicValueTransitionConfiguration = SupportedCharacteristicValueTransitionConfiguration; - -/** - * Characteristic "Current Transport" - */ -export class CurrentTransport extends Characteristic { - - static readonly UUID: string = '0000021E-0000-1000-8000-0000022B'; - - constructor() { - super("Current Transport", CurrentTransport.UUID); - this.setProps({ - format: Formats.BOOL, - perms: [Perms.PAIRED_READ], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.CurrentTransport = CurrentTransport; - -/** - * Characteristic "Data Stream HAP Transport" - */ -export class DataStreamHAPTransport extends Characteristic { - - static readonly UUID: string = '0000021E-0000-1000-8000-00000138'; - - constructor() { - super("Data Stream HAP Transport", DataStreamHAPTransport.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.WRITE_RESPONSE], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.DataStreamHAPTransport = DataStreamHAPTransport; - -/** - * Characteristic "Data Stream HAP Transport Interrupt" - */ -export class DataStreamHAPTransportInterrupt extends Characteristic { - - static readonly UUID: string = '0000021E-0000-1000-8000-00000139'; - - constructor() { - super("Data Stream HAP Transport Interrupt", DataStreamHAPTransportInterrupt.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.PAIRED_READ, Perms.NOTIFY], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.DataStreamHAPTransportInterrupt = DataStreamHAPTransportInterrupt; - -/** - * Characteristic "Event Retransmission Maximum" - */ -export class EventRetransmissionMaximum extends Characteristic { - - static readonly UUID: string = '0000021E-0000-1000-8000-0000023D'; - - constructor() { - super("Event Retransmission Maximum", EventRetransmissionMaximum.UUID); - this.setProps({ - format: Formats.UINT8, - perms: [Perms.PAIRED_READ], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.EventRetransmissionMaximum = EventRetransmissionMaximum; - -/** - * Characteristic "Event Transmission Counters" - */ -export class EventTransmissionCounters extends Characteristic { - - static readonly UUID: string = '0000021E-0000-1000-8000-0000023E'; - - constructor() { - super("Event Transmission Counters", EventTransmissionCounters.UUID); - this.setProps({ - format: Formats.UINT32, - perms: [Perms.PAIRED_READ], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.EventTransmissionCounters = EventTransmissionCounters; - -/** - * Characteristic "Heart Beat" - */ -export class HeartBeat extends Characteristic { - - static readonly UUID: string = '0000021E-0000-1000-8000-0000024A'; - - constructor() { - super("Heart Beat", HeartBeat.UUID); - this.setProps({ - format: Formats.UINT32, - perms: [Perms.NOTIFY, Perms.PAIRED_READ], - }) - this.value = this.getDefaultValue(); - } - -} - -Characteristic.HeartBeat = HeartBeat; - -/** - * Characteristic "MAC Retransmission Maximum" - */ -export class MACRetransmissionMaximum extends Characteristic { - - static readonly UUID: string = '0000021E-0000-1000-8000-00000247'; - - constructor() { - super("MAC Retransmission Maximum", MACRetransmissionMaximum.UUID); - this.setProps({ - format: Formats.UINT8, - perms: [Perms.PAIRED_READ], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.MACRetransmissionMaximum = MACRetransmissionMaximum; - -/** - * Characteristic "MAC Transmission Counters" - */ -export class MACTransmissionCounters extends Characteristic { - - static readonly UUID: string = '0000021E-0000-1000-8000-00000248'; - - constructor() { - super("MAC Transmission Counters", MACTransmissionCounters.UUID); - this.setProps({ - format: Formats.DATA, - perms: [Perms.PAIRED_READ], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.MACTransmissionCounters = MACTransmissionCounters; - -/** - * Characteristic "Operating State Response" - */ -export class OperatingStateResponse extends Characteristic { - - static readonly UUID: string = '0000021E-0000-1000-8000-00000232'; - - constructor() { - super("Operating State Response", OperatingStateResponse.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.PAIRED_READ, Perms.NOTIFY], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.OperatingStateResponse = OperatingStateResponse; - -/** - * Characteristic "Ping" - */ -export class Ping extends Characteristic { - - static readonly UUID: string = '0000021E-0000-1000-8000-0000023C'; - - constructor() { - super("Ping", Ping.UUID); - this.setProps({ - format: Formats.DATA, - perms: [Perms.PAIRED_READ], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.Ping = Ping; - -/** - * Characteristic "Receiver Sensitivity" - */ -export class ReceiverSensitivity extends Characteristic { - - static readonly UUID: string = '0000021E-0000-1000-8000-00000244'; - - constructor() { - super("Receiver Sensitivity", ReceiverSensitivity.UUID); - this.setProps({ - format: Formats.INT, - perms: [Perms.PAIRED_READ], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.ReceiverSensitivity = ReceiverSensitivity; - -/** - * Characteristic "Received Signal Strength Indication" - */ -export class ReceivedSignalStrengthIndication extends Characteristic { - - static readonly UUID: string = '0000021E-0000-1000-8000-0000023F'; - - constructor() { - super("Received Signal Strength Indication", ReceivedSignalStrengthIndication.UUID); - this.setProps({ - format: Formats.INT, - perms: [Perms.PAIRED_READ], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.ReceivedSignalStrengthIndication = ReceivedSignalStrengthIndication; - -/** - * Characteristic "Sleep Interval" - */ -export class SleepInterval extends Characteristic { - - static readonly UUID: string = '0000021E-0000-1000-8000-0000023A'; - - constructor() { - super("Sleep Interval", SleepInterval.UUID); - this.setProps({ - format: Formats.UINT32, - minValue: 0, - minStep: 1, - perms: [Perms.PAIRED_READ, Perms.NOTIFY], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.SleepInterval = SleepInterval; - -/** - * Characteristic "Signal-to-noise Ration" - */ -export class SignalToNoiseRatio extends Characteristic { - - static readonly UUID: string = '0000021E-0000-1000-8000-00000241'; - - constructor() { - super("Signal-to-noise Ration", SignalToNoiseRatio.UUID); - this.setProps({ - format: Formats.INT, - perms: [Perms.PAIRED_READ], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.SignalToNoiseRatio = SignalToNoiseRatio; - -/** - * Characteristic "Supported Diagnostics Snapshot" - */ -export class SupportedDiagnosticsSnapshot extends Characteristic { - - static readonly UUID: string = '0000021E-0000-1000-8000-00000238'; - - constructor() { - super("Supported Diagnostics Snapshot", SupportedDiagnosticsSnapshot.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.PAIRED_READ], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.SupportedDiagnosticsSnapshot = SupportedDiagnosticsSnapshot; - -/** - * Characteristic "Transmit Power" - */ -export class TransmitPower extends Characteristic { - - static readonly UUID: string = '0000021E-0000-1000-8000-00000242'; - - constructor() { - super("Transmit Power", TransmitPower.UUID); - this.setProps({ - format: Formats.INT, - perms: [Perms.PAIRED_READ], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.TransmitPower = TransmitPower; - -/** - * Characteristic "Transmit Power Maximum" - */ -export class TransmitPowerMaximum extends Characteristic { - - static readonly UUID: string = '0000021E-0000-1000-8000-00000243'; - - constructor() { - super("Transmit Power Maximum", TransmitPowerMaximum.UUID); - this.setProps({ - format: Formats.INT, - perms: [Perms.PAIRED_READ], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.TransmitPowerMaximum = TransmitPowerMaximum; - -/** - * Characteristic "Video Analysis Active" - */ -export class VideoAnalysisActive extends Characteristic { - - static readonly UUID: string = '0000021E-0000-1000-8000-00000229'; - - constructor() { - super("Video Analysis Active", VideoAnalysisActive.UUID); - this.setProps({ - format: Formats.UINT8, - perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.VideoAnalysisActive = VideoAnalysisActive; - -/** - * Characteristic "Wi-Fi Capabilities" - */ -export class WiFiCapabilities extends Characteristic { - - static readonly UUID: string = '0000021E-0000-1000-8000-0000022C'; - - constructor() { - super("Wi-Fi Capabilities", WiFiCapabilities.UUID); - this.setProps({ - format: Formats.UINT32, - perms: [Perms.PAIRED_READ], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.WiFiCapabilities = WiFiCapabilities; - -/** - * Characteristic "Wi-Fi Configuration Control" - */ -export class WiFiConfigurationControl extends Characteristic { - - static readonly UUID: string = '0000021E-0000-1000-8000-0000022D'; - - constructor() { - super("Wi-Fi Configuration Control", WiFiConfigurationControl.UUID); - this.setProps({ - format: Formats.TLV8, - perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY, Perms.TIMED_WRITE, Perms.WRITE_RESPONSE], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.WiFiConfigurationControl = WiFiConfigurationControl; - -/** - * Service "Access Control" - */ - -export class AccessControl extends Service { - - static UUID: string = '000000DA-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, AccessControl.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.AccessControlLevel); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.PasswordSetting); - } -} - -Service.AccessControl = AccessControl; - -/** - * Service "Accessory Information" - */ - -export class AccessoryInformation extends Service { - - static UUID: string = '0000003E-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, AccessoryInformation.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.Identify); - this.addCharacteristic(Characteristic.Manufacturer).updateValue("Default-Manufacturer"); - this.addCharacteristic(Characteristic.Model).updateValue("Default-Model"); - this.addCharacteristic(Characteristic.Name); - this.addCharacteristic(Characteristic.SerialNumber).updateValue("Default-SerialNumber"); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.AccessoryFlags); - this.addOptionalCharacteristic(Characteristic.AppMatchingIdentifier); - this.addOptionalCharacteristic(Characteristic.ConfiguredName); - this.addOptionalCharacteristic(Characteristic.FirmwareRevision); - this.addOptionalCharacteristic(Characteristic.HardwareRevision); - this.addOptionalCharacteristic(Characteristic.SoftwareRevision); - this.addOptionalCharacteristic(Characteristic.ProductData); - - // Firmware Revision is defined to be a optional characteristics but is actually REQUIRED - this.getCharacteristic(Characteristic.FirmwareRevision).updateValue("0.0.0"); - } -} - -Service.AccessoryInformation = AccessoryInformation; - -/** - * Service "Air Purifier" - */ - -export class AirPurifier extends Service { - - static UUID: string = '000000BB-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, AirPurifier.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.Active); - this.addCharacteristic(Characteristic.CurrentAirPurifierState); - this.addCharacteristic(Characteristic.TargetAirPurifierState); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.LockPhysicalControls); - this.addOptionalCharacteristic(Characteristic.Name); - this.addOptionalCharacteristic(Characteristic.SwingMode); - this.addOptionalCharacteristic(Characteristic.RotationSpeed); - } -} - -Service.AirPurifier = AirPurifier; - -/** - * Service "Air Quality Sensor" - */ - -export class AirQualitySensor extends Service { - - static UUID: string = '0000008D-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, AirQualitySensor.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.AirQuality); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.StatusActive); - this.addOptionalCharacteristic(Characteristic.StatusFault); - this.addOptionalCharacteristic(Characteristic.StatusTampered); - this.addOptionalCharacteristic(Characteristic.StatusLowBattery); - this.addOptionalCharacteristic(Characteristic.Name); - this.addOptionalCharacteristic(Characteristic.OzoneDensity); - this.addOptionalCharacteristic(Characteristic.NitrogenDioxideDensity); - this.addOptionalCharacteristic(Characteristic.SulphurDioxideDensity); - this.addOptionalCharacteristic(Characteristic.PM2_5Density); - this.addOptionalCharacteristic(Characteristic.PM10Density); - this.addOptionalCharacteristic(Characteristic.VOCDensity); - } -} - -Service.AirQualitySensor = AirQualitySensor; - -/** - * Service "Battery Service" - */ - -export class BatteryService extends Service { - - static UUID: string = '00000096-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, BatteryService.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.BatteryLevel); // this is actually optional since iOS 14 - this.addCharacteristic(Characteristic.ChargingState); // this is actually optional since iOS 14 - this.addCharacteristic(Characteristic.StatusLowBattery); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.BatteryService = BatteryService; - -/** - * Service "Camera RTP Stream Management" - */ - -export class CameraRTPStreamManagement extends Service { - - static UUID: string = '00000110-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, CameraRTPStreamManagement.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.SupportedVideoStreamConfiguration); - this.addCharacteristic(Characteristic.SupportedAudioStreamConfiguration); - this.addCharacteristic(Characteristic.SupportedRTPConfiguration); - this.addCharacteristic(Characteristic.SelectedRTPStreamConfiguration); - this.addCharacteristic(Characteristic.StreamingStatus); - this.addCharacteristic(Characteristic.SetupEndpoints); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Active); - } -} - -Service.CameraRTPStreamManagement = CameraRTPStreamManagement; - -/** - * Service "Carbon Dioxide Sensor" - */ - -export class CarbonDioxideSensor extends Service { - - static UUID: string = '00000097-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, CarbonDioxideSensor.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.CarbonDioxideDetected); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.StatusActive); - this.addOptionalCharacteristic(Characteristic.StatusFault); - this.addOptionalCharacteristic(Characteristic.StatusLowBattery); - this.addOptionalCharacteristic(Characteristic.StatusTampered); - this.addOptionalCharacteristic(Characteristic.CarbonDioxideLevel); - this.addOptionalCharacteristic(Characteristic.CarbonDioxidePeakLevel); - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.CarbonDioxideSensor = CarbonDioxideSensor; - -/** - * Service "Carbon Monoxide Sensor" - */ - -export class CarbonMonoxideSensor extends Service { - - static UUID: string = '0000007F-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, CarbonMonoxideSensor.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.CarbonMonoxideDetected); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.StatusActive); - this.addOptionalCharacteristic(Characteristic.StatusFault); - this.addOptionalCharacteristic(Characteristic.StatusLowBattery); - this.addOptionalCharacteristic(Characteristic.StatusTampered); - this.addOptionalCharacteristic(Characteristic.CarbonMonoxideLevel); - this.addOptionalCharacteristic(Characteristic.CarbonMonoxidePeakLevel); - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.CarbonMonoxideSensor = CarbonMonoxideSensor; - -/** - * Service "Contact Sensor" - */ - -export class ContactSensor extends Service { - - static UUID: string = '00000080-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, ContactSensor.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.ContactSensorState); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.StatusActive); - this.addOptionalCharacteristic(Characteristic.StatusFault); - this.addOptionalCharacteristic(Characteristic.StatusTampered); - this.addOptionalCharacteristic(Characteristic.StatusLowBattery); - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.ContactSensor = ContactSensor; - -/** - * Service "Door" - */ - -export class Door extends Service { - - static UUID: string = '00000081-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, Door.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.CurrentPosition); - this.addCharacteristic(Characteristic.PositionState); - this.addCharacteristic(Characteristic.TargetPosition); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.HoldPosition); - this.addOptionalCharacteristic(Characteristic.ObstructionDetected); - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.Door = Door; - -/** - * Service "Doorbell" - */ - -export class Doorbell extends Service { - - static UUID: string = '00000121-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, Doorbell.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.ProgrammableSwitchEvent); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Brightness); - this.addOptionalCharacteristic(Characteristic.Mute); - this.addOptionalCharacteristic(Characteristic.OperatingStateResponse); - this.addOptionalCharacteristic(Characteristic.Volume); - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.Doorbell = Doorbell; - -/** - * Service "Fan" - */ - -export class Fan extends Service { - - static UUID: string = '00000040-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, Fan.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.On); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.RotationDirection); - this.addOptionalCharacteristic(Characteristic.RotationSpeed); - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.Fan = Fan; - -/** - * Service "Fan v2" - */ - -export class Fanv2 extends Service { - - static UUID: string = '000000B7-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, Fanv2.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.Active); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.CurrentFanState); - this.addOptionalCharacteristic(Characteristic.TargetFanState); - this.addOptionalCharacteristic(Characteristic.LockPhysicalControls); - this.addOptionalCharacteristic(Characteristic.Name); - this.addOptionalCharacteristic(Characteristic.RotationDirection); - this.addOptionalCharacteristic(Characteristic.RotationSpeed); - this.addOptionalCharacteristic(Characteristic.SwingMode); - } -} - -Service.Fanv2 = Fanv2; - -/** - * Service "Filter Maintenance" - */ - -export class FilterMaintenance extends Service { - - static UUID: string = '000000BA-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, FilterMaintenance.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.FilterChangeIndication); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.FilterLifeLevel); - this.addOptionalCharacteristic(Characteristic.ResetFilterIndication); - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.FilterMaintenance = FilterMaintenance; - -/** - * Service "Faucet" - */ - -export class Faucet extends Service { - - static UUID: string = '000000D7-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, Faucet.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.Active); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Name); - this.addOptionalCharacteristic(Characteristic.StatusFault); - } -} - -Service.Faucet = Faucet; - -/** - * Service "Garage Door Opener" - */ - -export class GarageDoorOpener extends Service { - - static UUID: string = '00000041-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, GarageDoorOpener.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.CurrentDoorState); - this.addCharacteristic(Characteristic.TargetDoorState); - this.addCharacteristic(Characteristic.ObstructionDetected); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.LockCurrentState); - this.addOptionalCharacteristic(Characteristic.LockTargetState); - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.GarageDoorOpener = GarageDoorOpener; - -/** - * Service "Heater Cooler" - */ - -export class HeaterCooler extends Service { - - static UUID: string = '000000BC-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, HeaterCooler.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.Active); - this.addCharacteristic(Characteristic.CurrentHeaterCoolerState); - this.addCharacteristic(Characteristic.TargetHeaterCoolerState); - this.addCharacteristic(Characteristic.CurrentTemperature); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.LockPhysicalControls); - this.addOptionalCharacteristic(Characteristic.Name); - this.addOptionalCharacteristic(Characteristic.SwingMode); - this.addOptionalCharacteristic(Characteristic.CoolingThresholdTemperature); - this.addOptionalCharacteristic(Characteristic.HeatingThresholdTemperature); - this.addOptionalCharacteristic(Characteristic.TemperatureDisplayUnits); - this.addOptionalCharacteristic(Characteristic.RotationSpeed); - } -} - -Service.HeaterCooler = HeaterCooler; - -/** - * Service "Humidifier Dehumidifier" - */ - -export class HumidifierDehumidifier extends Service { - - static UUID: string = '000000BD-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, HumidifierDehumidifier.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.CurrentRelativeHumidity); - this.addCharacteristic(Characteristic.CurrentHumidifierDehumidifierState); - this.addCharacteristic(Characteristic.TargetHumidifierDehumidifierState); - this.addCharacteristic(Characteristic.Active); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.LockPhysicalControls); - this.addOptionalCharacteristic(Characteristic.Name); - this.addOptionalCharacteristic(Characteristic.SwingMode); - this.addOptionalCharacteristic(Characteristic.WaterLevel); - this.addOptionalCharacteristic(Characteristic.RelativeHumidityDehumidifierThreshold); - this.addOptionalCharacteristic(Characteristic.RelativeHumidityHumidifierThreshold); - this.addOptionalCharacteristic(Characteristic.RotationSpeed); - } -} - -Service.HumidifierDehumidifier = HumidifierDehumidifier; - -/** - * Service "Humidity Sensor" - */ - -export class HumiditySensor extends Service { - - static UUID: string = '00000082-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, HumiditySensor.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.CurrentRelativeHumidity); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.StatusActive); - this.addOptionalCharacteristic(Characteristic.StatusFault); - this.addOptionalCharacteristic(Characteristic.StatusTampered); - this.addOptionalCharacteristic(Characteristic.StatusLowBattery); - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.HumiditySensor = HumiditySensor; - -/** - * Service "Irrigation System" - */ - -export class IrrigationSystem extends Service { - - static UUID: string = '000000CF-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, IrrigationSystem.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.Active); - this.addCharacteristic(Characteristic.ProgramMode); - this.addCharacteristic(Characteristic.InUse); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Name); - this.addOptionalCharacteristic(Characteristic.RemainingDuration); - this.addOptionalCharacteristic(Characteristic.StatusFault); - } -} - -Service.IrrigationSystem = IrrigationSystem; - -/** - * Service "Leak Sensor" - */ - -export class LeakSensor extends Service { - - static UUID: string = '00000083-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, LeakSensor.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.LeakDetected); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.StatusActive); - this.addOptionalCharacteristic(Characteristic.StatusFault); - this.addOptionalCharacteristic(Characteristic.StatusTampered); - this.addOptionalCharacteristic(Characteristic.StatusLowBattery); - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.LeakSensor = LeakSensor; - -/** - * Service "Light Sensor" - */ - -export class LightSensor extends Service { - - static UUID: string = '00000084-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, LightSensor.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.CurrentAmbientLightLevel); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.StatusActive); - this.addOptionalCharacteristic(Characteristic.StatusFault); - this.addOptionalCharacteristic(Characteristic.StatusTampered); - this.addOptionalCharacteristic(Characteristic.StatusLowBattery); - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.LightSensor = LightSensor; - -/** - * Service "Lightbulb" - */ - -export class Lightbulb extends Service { - - static UUID: string = '00000043-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, Lightbulb.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.On); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Brightness); - this.addOptionalCharacteristic(Characteristic.Hue); - this.addOptionalCharacteristic(Characteristic.Saturation); - this.addOptionalCharacteristic(Characteristic.Name); - this.addOptionalCharacteristic(Characteristic.ColorTemperature); - this.addOptionalCharacteristic(Characteristic.CharacteristicValueTransitionControl); // Ambient Lightning - this.addOptionalCharacteristic(Characteristic.SupportedCharacteristicValueTransitionConfiguration); // Ambient Lightning - } -} - -Service.Lightbulb = Lightbulb; - -/** - * Service "Lock Management" - */ - -export class LockManagement extends Service { - - static UUID: string = '00000044-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, LockManagement.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.LockControlPoint); - this.addCharacteristic(Characteristic.Version); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Logs); - this.addOptionalCharacteristic(Characteristic.AudioFeedback); - this.addOptionalCharacteristic(Characteristic.LockManagementAutoSecurityTimeout); - this.addOptionalCharacteristic(Characteristic.AdministratorOnlyAccess); - this.addOptionalCharacteristic(Characteristic.LockLastKnownAction); - this.addOptionalCharacteristic(Characteristic.CurrentDoorState); - this.addOptionalCharacteristic(Characteristic.MotionDetected); - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.LockManagement = LockManagement; - -/** - * Service "Lock Mechanism" - */ - -export class LockMechanism extends Service { - - static UUID: string = '00000045-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, LockMechanism.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.LockCurrentState); - this.addCharacteristic(Characteristic.LockTargetState); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.LockMechanism = LockMechanism; - -/** - * Service "Microphone" - */ - -export class Microphone extends Service { - - static UUID: string = '00000112-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, Microphone.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.Mute); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Volume); - } -} - -Service.Microphone = Microphone; - -/** - * Service "Motion Sensor" - */ - -export class MotionSensor extends Service { - - static UUID: string = '00000085-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, MotionSensor.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.MotionDetected); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.StatusActive); - this.addOptionalCharacteristic(Characteristic.StatusFault); - this.addOptionalCharacteristic(Characteristic.StatusTampered); - this.addOptionalCharacteristic(Characteristic.StatusLowBattery); - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.MotionSensor = MotionSensor; - -/** - * Service "Occupancy Sensor" - */ - -export class OccupancySensor extends Service { - - static UUID: string = '00000086-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, OccupancySensor.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.OccupancyDetected); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.StatusActive); - this.addOptionalCharacteristic(Characteristic.StatusFault); - this.addOptionalCharacteristic(Characteristic.StatusTampered); - this.addOptionalCharacteristic(Characteristic.StatusLowBattery); - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.OccupancySensor = OccupancySensor; - -/** - * Service "Outlet" - */ - -export class Outlet extends Service { - - static UUID: string = '00000047-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, Outlet.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.On); - this.addCharacteristic(Characteristic.OutletInUse); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.Outlet = Outlet; - -/** - * Service "Security System" - */ - -export class SecuritySystem extends Service { - - static UUID: string = '0000007E-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, SecuritySystem.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.SecuritySystemCurrentState); - this.addCharacteristic(Characteristic.SecuritySystemTargetState); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.StatusFault); - this.addOptionalCharacteristic(Characteristic.StatusTampered); - this.addOptionalCharacteristic(Characteristic.SecuritySystemAlarmType); - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.SecuritySystem = SecuritySystem; - -/** - * Service "Service Label" - */ - -export class ServiceLabel extends Service { - - static UUID: string = '000000CC-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, ServiceLabel.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.ServiceLabelNamespace); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.ServiceLabel = ServiceLabel; - -/** - * Service "Slat" - */ - -export class Slat extends Service { - - static UUID: string = '000000B9-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, Slat.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.SlatType); - this.addCharacteristic(Characteristic.CurrentSlatState); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Name); - this.addOptionalCharacteristic(Characteristic.CurrentTiltAngle); - this.addOptionalCharacteristic(Characteristic.TargetTiltAngle); - this.addOptionalCharacteristic(Characteristic.SwingMode); - } -} - -Service.Slat = Slat; - -/** - * Service "Smoke Sensor" - */ - -export class SmokeSensor extends Service { - - static UUID: string = '00000087-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, SmokeSensor.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.SmokeDetected); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.StatusActive); - this.addOptionalCharacteristic(Characteristic.StatusFault); - this.addOptionalCharacteristic(Characteristic.StatusTampered); - this.addOptionalCharacteristic(Characteristic.StatusLowBattery); - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.SmokeSensor = SmokeSensor; - -/** - * Service "Smart Speaker" - */ - -export class SmartSpeaker extends Service { - - static UUID: string = '00000228-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, SmartSpeaker.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.CurrentMediaState); - this.addCharacteristic(Characteristic.TargetMediaState); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Name); - this.addOptionalCharacteristic(Characteristic.ConfiguredName); - this.addOptionalCharacteristic(Characteristic.Volume); - this.addOptionalCharacteristic(Characteristic.Mute); - } -} - -Service.SmartSpeaker = SmartSpeaker; - -/** - * Service "Speaker" - * - * {@see TelevisionSpeaker} for the same Service defined with {@link VolumeControlType}, - * {@link VolumeSelector} and {@link Active} characteristics. - */ - -export class Speaker extends Service { - - static UUID: string = '00000113-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, Speaker.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.Mute); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Volume); - } -} - -Service.Speaker = Speaker; - -/** - * Service "Stateless Programmable Switch" - */ - -export class StatelessProgrammableSwitch extends Service { - - static UUID: string = '00000089-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, StatelessProgrammableSwitch.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.ProgrammableSwitchEvent); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Name); - this.addOptionalCharacteristic(Characteristic.ServiceLabelIndex); - } -} - -Service.StatelessProgrammableSwitch = StatelessProgrammableSwitch; - -/** - * Service "Switch" - */ - -export class Switch extends Service { - - static UUID: string = '00000049-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, Switch.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.On); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.Switch = Switch; - -/** - * Service "Temperature Sensor" - */ - -export class TemperatureSensor extends Service { - - static UUID: string = '0000008A-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, TemperatureSensor.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.CurrentTemperature); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.StatusActive); - this.addOptionalCharacteristic(Characteristic.StatusFault); - this.addOptionalCharacteristic(Characteristic.StatusLowBattery); - this.addOptionalCharacteristic(Characteristic.StatusTampered); - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.TemperatureSensor = TemperatureSensor; - -/** - * Service "Thermostat" - */ - -export class Thermostat extends Service { - - static UUID: string = '0000004A-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, Thermostat.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.CurrentHeatingCoolingState); - this.addCharacteristic(Characteristic.TargetHeatingCoolingState); - this.addCharacteristic(Characteristic.CurrentTemperature); - this.addCharacteristic(Characteristic.TargetTemperature); - this.addCharacteristic(Characteristic.TemperatureDisplayUnits); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.CurrentRelativeHumidity); - this.addOptionalCharacteristic(Characteristic.TargetRelativeHumidity); - this.addOptionalCharacteristic(Characteristic.CoolingThresholdTemperature); - this.addOptionalCharacteristic(Characteristic.HeatingThresholdTemperature); - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.Thermostat = Thermostat; - -/** - * Service "Valve" - */ - -export class Valve extends Service { - - static UUID: string = '000000D0-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, Valve.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.Active); - this.addCharacteristic(Characteristic.InUse); - this.addCharacteristic(Characteristic.ValveType); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.SetDuration); - this.addOptionalCharacteristic(Characteristic.RemainingDuration); - this.addOptionalCharacteristic(Characteristic.IsConfigured); - this.addOptionalCharacteristic(Characteristic.ServiceLabelIndex); - this.addOptionalCharacteristic(Characteristic.StatusFault); - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.Valve = Valve; - -/** - * Service "Window" - */ - -export class Window extends Service { - - static UUID: string = '0000008B-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, Window.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.CurrentPosition); - this.addCharacteristic(Characteristic.TargetPosition); - this.addCharacteristic(Characteristic.PositionState); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.HoldPosition); - this.addOptionalCharacteristic(Characteristic.ObstructionDetected); - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.Window = Window; - -/** - * Service "Window Covering" - */ - -export class WindowCovering extends Service { - - static UUID: string = '0000008C-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, WindowCovering.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.CurrentPosition); - this.addCharacteristic(Characteristic.TargetPosition); - this.addCharacteristic(Characteristic.PositionState); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.HoldPosition); - this.addOptionalCharacteristic(Characteristic.TargetHorizontalTiltAngle); - this.addOptionalCharacteristic(Characteristic.TargetVerticalTiltAngle); - this.addOptionalCharacteristic(Characteristic.CurrentHorizontalTiltAngle); - this.addOptionalCharacteristic(Characteristic.CurrentVerticalTiltAngle); - this.addOptionalCharacteristic(Characteristic.ObstructionDetected); - this.addOptionalCharacteristic(Characteristic.Name); - } -} - -Service.WindowCovering = WindowCovering; - -/** - * Service "Camera Operating Mode" - */ - -export class CameraOperatingMode extends Service { - - static UUID: string = '0000021A-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, CameraOperatingMode.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.EventSnapshotsActive); - this.addCharacteristic(Characteristic.HomeKitCameraActive); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.ManuallyDisabled); - this.addOptionalCharacteristic(Characteristic.NightVision); - this.addOptionalCharacteristic(Characteristic.ThirdPartyCameraActive); - this.addOptionalCharacteristic(Characteristic.CameraOperatingModeIndicator); - this.addOptionalCharacteristic(Characteristic.PeriodicSnapshotsActive); - } -} - -Service.CameraOperatingMode = CameraOperatingMode; - -/** - * Service "Camera Event Recording Management" - */ - -export class CameraEventRecordingManagement extends Service { - - static UUID: string = '00000204-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, CameraEventRecordingManagement.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.Active); - this.addCharacteristic(Characteristic.SupportedCameraRecordingConfiguration); - this.addCharacteristic(Characteristic.SupportedVideoRecordingConfiguration); - this.addCharacteristic(Characteristic.SupportedAudioRecordingConfiguration); - this.addCharacteristic(Characteristic.SelectedCameraRecordingConfiguration); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.RecordingAudioActive); - } -} - -Service.CameraEventRecordingManagement = CameraEventRecordingManagement; - -/** - * Service "Wi-Fi Router" - */ - -export class WiFiRouter extends Service { - - static UUID: string = '0000020A-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, WiFiRouter.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.ConfiguredName); - this.addCharacteristic(Characteristic.ManagedNetworkEnable); - this.addCharacteristic(Characteristic.NetworkAccessViolationControl); - this.addCharacteristic(Characteristic.NetworkClientProfileControl); - this.addCharacteristic(Characteristic.NetworkClientStatusControl); - this.addCharacteristic(Characteristic.RouterStatus); - this.addCharacteristic(Characteristic.SupportedRouterConfiguration); - this.addCharacteristic(Characteristic.WANConfigurationList); - this.addCharacteristic(Characteristic.WANStatusList); - - } -} - -Service.WiFiRouter = WiFiRouter; - -/** - * Service "Wi-Fi Satellite" - */ - -export class WiFiSatellite extends Service { - - static readonly UUID: string = '0000020F-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, WiFiSatellite.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.WiFiSatelliteStatus); - } -} - -Service.WiFiSatellite = WiFiSatellite; - -/** - * Service "Power Management" - */ - -export class PowerManagement extends Service { - - static readonly UUID: string = '00000221-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, PowerManagement.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.WakeConfiguration); - } -} - -Service.PowerManagement = PowerManagement; - -/** - * Service "Transfer Transport Management" - */ - -export class TransferTransportManagement extends Service { - - static readonly UUID: string = '00000203-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, TransferTransportManagement.UUID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.SupportedTransferTransportConfiguration); - this.addCharacteristic(Characteristic.SetupTransferTransport); - } -} - -Service.TransferTransportManagement = TransferTransportManagement; - -/** - * Service "Accessory Runtime Information" - */ -export class AccessoryRuntimeInformation extends Service { - - static readonly UUID: string = '00000203-0000-1000-8000-00000239'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, AccessoryRuntimeInformation.UUID, subtype); - - // Require Characteristics - this.addCharacteristic(Characteristic.Ping); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.ActivityInterval); - this.addOptionalCharacteristic(Characteristic.HeartBeat); - this.addOptionalCharacteristic(Characteristic.SleepInterval); - } - -} - -Service.AccessoryRuntimeInformation = AccessoryRuntimeInformation; - -/** - * Service "Diagnostics" - */ -export class Diagnostics extends Service { - - static readonly UUID: string = '00000203-0000-1000-8000-00000237'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, Diagnostics.UUID, subtype); - - // Require Characteristics - this.addCharacteristic(Characteristic.SupportedDiagnosticsSnapshot); - } - -} - -Service.Diagnostics = Diagnostics; - -/** - * Service "Wi-Fi Transport" - */ -export class WiFiTransport extends Service { - - static readonly UUID: string = '00000203-0000-1000-8000-0000022A'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, WiFiTransport.UUID, subtype); - - // Require Characteristics - this.addCharacteristic(Characteristic.CurrentTransport); - this.addCharacteristic(Characteristic.WiFiCapabilities); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.WiFiConfigurationControl); - } - -} - -Service.WiFiTransport = WiFiTransport; diff --git a/src/lib/gen/import.js b/src/lib/gen/import.js deleted file mode 100644 index a1b25b398..000000000 --- a/src/lib/gen/import.js +++ /dev/null @@ -1,190 +0,0 @@ -'use strict'; - -var path = require('path'); -var fs = require('fs'); -var plist = require('simple-plist'); -var Characteristic = require('../Characteristic').Characteristic; - -/** - * This module is intended to be run from the command line. It is a script that extracts Apple's Service - * and Characteristic UUIDs and structures from Apple's own HomeKit Accessory Simulator app. - */ - -// assumed location of the plist we need (might want to make this a command-line argument at some point) -var plistPath = '/Applications/HomeKit Accessory Simulator.app/Contents/Frameworks/HAPAccessoryKit.framework/Versions/A/Resources/default.metadata.plist'; -var metadata = plist.readFileSync(plistPath); - -// begin writing the output file -var outputPath = path.join(__dirname, 'HomeKitTypes.js'); -var output = fs.createWriteStream(outputPath); - -output.write("// THIS FILE IS AUTO-GENERATED - DO NOT MODIFY\n"); -output.write("\n"); -output.write("var inherits = require('util').inherits;\n"); -output.write("var Characteristic = require('../Characteristic').Characteristic;\n"); -output.write("var Service = require('../Service').Service;\n"); -output.write("\n"); - -/** - * Characteristics - */ - -// index Characteristics for quick access while building Services -var characteristics = {}; // characteristics[UUID] = classyName - -for (var index in metadata.Characteristics) { - var characteristic = metadata.Characteristics[index]; - var classyName = characteristic.Name.replace(/[\s\-]/g, ""); // "Target Door State" -> "TargetDoorState" - classyName = classyName.replace(/[.]/g, "_"); // "PM2.5" -> "PM2_5" - - // index classyName for when we want to declare these in Services below - characteristics[characteristic.UUID] = classyName; - - output.write("/**\n * Characteristic \"" + characteristic.Name + "\"\n */\n\n"); - output.write("Characteristic." + classyName + " = function() {\n"); - output.write(" Characteristic.call(this, '" + characteristic.Name + "', '" + characteristic.UUID + "');\n"); - - // apply Characteristic properties - output.write(" this.setProps({\n"); - output.write(" format: Characteristic.Formats." + getCharacteristicFormatsKey(characteristic.Format)); - - // special unit type? - if (characteristic.Unit) - output.write(",\n unit: Characteristic.Units." + getCharacteristicUnitsKey(characteristic.Unit)); - - // apply any basic constraints if present - if (characteristic.Constraints && typeof characteristic.Constraints.MaximumValue !== 'undefined') - output.write(",\n maxValue: " + characteristic.Constraints.MaximumValue); - - if (characteristic.Constraints && typeof characteristic.Constraints.MinimumValue !== 'undefined') - output.write(",\n minValue: " + characteristic.Constraints.MinimumValue); - - if (characteristic.Constraints && typeof characteristic.Constraints.StepValue !== 'undefined') - output.write(",\n minStep: " + characteristic.Constraints.StepValue); - - output.write(",\n perms: ["); - var sep = "" - for (var i in characteristic.Properties) { - var perms = getCharacteristicPermsKey(characteristic.Properties[i]); - if (perms) { - output.write(sep + "Characteristic.Perms." + getCharacteristicPermsKey(characteristic.Properties[i])); - sep = ", " - } - } - output.write("]"); - - output.write("\n });\n"); - - // set default value - output.write(" this.value = this.getDefaultValue();\n"); - - output.write("};\n\n"); - output.write("inherits(Characteristic." + classyName + ", Characteristic);\n\n"); - output.write("Characteristic." + classyName + ".UUID = '" + characteristic.UUID + "';\n\n"); - - if (characteristic.Constraints && characteristic.Constraints.ValidValues) { - // this characteristic can only have one of a defined set of values (like an enum). Define the values - // as static members of our subclass. - output.write("// The value property of " + classyName + " must be one of the following:\n"); - - for (var value in characteristic.Constraints.ValidValues) { - var name = characteristic.Constraints.ValidValues[value]; - - var constName = name.toUpperCase().replace(/[^\w]+/g, '_'); - if ((/^[1-9]/).test(constName)) constName = "_" + constName; // variables can't start with a number - output.write("Characteristic." + classyName + "." + constName + " = " + value + ";\n"); - } - - output.write("\n"); - } -} - - -/** - * Services - */ - -for (var index in metadata.Services) { - var service = metadata.Services[index]; - var classyName = service.Name.replace(/[\s\-]/g, ""); // "Smoke Sensor" -> "SmokeSensor" - - output.write("/**\n * Service \"" + service.Name + "\"\n */\n\n"); - output.write("Service." + classyName + " = function(displayName, subtype) {\n"); - // call superclass constructor - output.write(" Service.call(this, displayName, '" + service.UUID + "', subtype);\n"); - - // add Characteristics for this Service - if (service.RequiredCharacteristics) { - output.write("\n // Required Characteristics\n"); - - for (var index in service.RequiredCharacteristics) { - var characteristicUUID = service.RequiredCharacteristics[index]; - - // look up the classyName from the hash we built above - var characteristicClassyName = characteristics[characteristicUUID]; - - output.write(" this.addCharacteristic(Characteristic." + characteristicClassyName + ");\n"); - } - } - - // add "Optional" Characteristics for this Service - if (service.OptionalCharacteristics) { - output.write("\n // Optional Characteristics\n"); - - for (var index in service.OptionalCharacteristics) { - var characteristicUUID = service.OptionalCharacteristics[index]; - - // look up the classyName from the hash we built above - var characteristicClassyName = characteristics[characteristicUUID]; - - output.write(" this.addOptionalCharacteristic(Characteristic." + characteristicClassyName + ");\n"); - } - } - - output.write("};\n\n"); - output.write("inherits(Service." + classyName + ", Service);\n\n"); - output.write("Service." + classyName + ".UUID = '" + service.UUID + "';\n\n"); -} - -output.write("var HomeKitTypesBridge = require('./HomeKitTypes-Bridge');\n\n"); - -/** - * Done! - */ - -output.end(); - -/** - * Useful functions - */ - -function getCharacteristicFormatsKey(format) { - // coerce 'int32' to 'int' - if (format == 'int32') format = 'int'; - - // look up the key in our known-formats dict - for (var key in Characteristic.Formats) - if (Characteristic.Formats[key] == format) - return key; - - throw new Error("Unknown characteristic format '" + format + "'"); -} - -function getCharacteristicUnitsKey(units) { - // look up the key in our known-units dict - for (var key in Characteristic.Units) - if (Characteristic.Units[key] == units) - return key; - - throw new Error("Unknown characteristic units '" + units + "'"); -} - -function getCharacteristicPermsKey(perm) { - switch (perm) { - case "read": return "READ"; - case "write": return "WRITE"; - case "cnotify": return "NOTIFY"; - case "uncnotify": return undefined; - default: throw new Error("Unknown characteristic permission '" + perm + "'"); - } -} diff --git a/src/lib/gen/importAsClasses.ts b/src/lib/gen/importAsClasses.ts deleted file mode 100644 index d00b215a2..000000000 --- a/src/lib/gen/importAsClasses.ts +++ /dev/null @@ -1,214 +0,0 @@ -/// -import fs from 'fs'; -import path from 'path'; - -import plist from 'simple-plist'; - -import { Characteristic, Formats, Units } from "../Characteristic"; - -/** - * This module is intended to be run from the command line. It is a script that extracts Apple's Service - * and Characteristic UUIDs and structures from Apple's own HomeKit Accessory Simulator app. - */ - -// assumed location of the plist we need (might want to make this a command-line argument at some point) -var plistPath = '/Applications/HomeKit Accessory Simulator.app/Contents/Frameworks/HAPAccessoryKit.framework/Versions/A/Resources/default.metadata.plist'; -var metadata = plist.readFileSync(plistPath); - -// begin writing the output file -var outputPath = path.join(__dirname, '..', '..', '..', 'src', 'lib', 'gen', 'HomeKitTypes.generated.ts'); -var output = fs.createWriteStream(outputPath); - -output.write("// THIS FILE IS AUTO-GENERATED - DO NOT MODIFY\n"); -output.write("\n"); -// output.write("var inherits = require('util').inherits;\n"); -output.write("import {\n"); -output.write(" Characteristic,\n"); -output.write(" CharacteristicProps,\n"); -output.write(" Formats,\n"); -output.write(" Perms,\n"); -output.write(" Units,\n"); -output.write("} from '../Characteristic';\n"); -output.write("import { Service } from '../Service';\n"); -output.write("\n"); - -/** - * Characteristics - */ - -// index Characteristics for quick access while building Services -const characteristics: Record = {}; // characteristics[UUID] = classyName - -for (var index in metadata.Characteristics) { - var characteristic = metadata.Characteristics[index]; - var classyName = characteristic.Name.replace(/[\s\-]/g, ""); // "Target Door State" -> "TargetDoorState" - classyName = classyName.replace(/[.]/g, "_"); // "PM2.5" -> "PM2_5" - - // index classyName for when we want to declare these in Services below - characteristics[characteristic.UUID] = classyName; - - output.write(`/**\n * Characteristic "${characteristic.Name}"\n */\n\n`); - output.write(`export class ${classyName} extends Characteristic {\n\n`); - output.write(` static readonly UUID: string = "${characteristic.UUID}";\n\n`); - - if (characteristic.Constraints && characteristic.Constraints.ValidValues) { - // this characteristic can only have one of a defined set of values (like an enum). Define the values - // as static members of our subclass. - output.write(" // The value property of " + classyName + " must be one of the following:\n"); - - for (var value in characteristic.Constraints.ValidValues) { - var name = characteristic.Constraints.ValidValues[value]; - - var constName = name.toUpperCase().replace(/[^\w]+/g, '_'); - if ((/^[1-9]/).test(constName)) constName = "_" + constName; // variables can't start with a number - output.write(` static readonly ${constName} = ${value};\n`); - } - output.write('\n'); - } - - // constructor - output.write(" constructor(\n"); - output.write(" displayName = \"\",\n"); - output.write(" props?: CharacteristicProps,\n"); - output.write(" ) {\n"); - output.write(" props = props || {\n"); - - // apply Characteristic properties - // output.write(" this.setProps({\n"); - output.write(" format: Formats." + getCharacteristicFormatsKey(characteristic.Format)); - - // special unit type? - if (characteristic.Unit) - output.write(",\n unit: Units." + getCharacteristicUnitsKey(characteristic.Unit)); - - // apply any basic constraints if present - if (characteristic.Constraints && typeof characteristic.Constraints.MaximumValue !== 'undefined') - output.write(",\n maxValue: " + characteristic.Constraints.MaximumValue); - - if (characteristic.Constraints && typeof characteristic.Constraints.MinimumValue !== 'undefined') - output.write(",\n minValue: " + characteristic.Constraints.MinimumValue); - - if (characteristic.Constraints && typeof characteristic.Constraints.StepValue !== 'undefined') - output.write(",\n minStep: " + characteristic.Constraints.StepValue); - - output.write(",\n perms: ["); - var sep = "" - for (var i in characteristic.Properties) { - var perms = getCharacteristicPermsKey(characteristic.Properties[i]); - if (perms) { - output.write(sep + "Perms." + getCharacteristicPermsKey(characteristic.Properties[i])); - sep = ", " - } - } - output.write("]"); - - output.write("\n };\n"); - output.write(` super(displayName, ${classyName}.UUID, props);\n\n`); - output.write(" this.value = this.getDefaultValue();\n"); - output.write(` }\n`); - - // set default value - - output.write("};\n\n"); - // output.write("inherits(Characteristic." + classyName + ", Characteristic);\n\n"); - // output.write("Characteristic." + classyName + ".UUID = '" + characteristic.UUID + "';\n\n"); -} - -/** - * Services - */ - -for (var index in metadata.Services) { - var service = metadata.Services[index]; - var classyName = service.Name.replace(/[\s\-]/g, ""); // "Smoke Sensor" -> "SmokeSensor" - - output.write(`/**\n * Service "${service.Name}"\n */\n\n`); - output.write(`export class ${classyName} extends Service {\n\n`); - output.write(` static readonly UUID = '${service.UUID}';\n\n`); - - // constructor - output.write(" constructor(displayName: string, subtype: string) {\n"); - output.write(" super(displayName, ${classyName}.UUID, subtype);\n\n"); - - // add Characteristics for this Service - if (service.RequiredCharacteristics) { - output.write("\n // Required Characteristics\n"); - - for (var index in service.RequiredCharacteristics) { - var characteristicUUID = service.RequiredCharacteristics[index]; - - // look up the classyName from the hash we built above - var characteristicClassyName = characteristics[characteristicUUID]; - - output.write(" this.addCharacteristic(Characteristic." + characteristicClassyName + ");\n"); - } - } - - // add "Optional" Characteristics for this Service - if (service.OptionalCharacteristics) { - output.write("\n // Optional Characteristics\n"); - - for (var index in service.OptionalCharacteristics) { - var characteristicUUID = service.OptionalCharacteristics[index]; - - // look up the classyName from the hash we built above - var characteristicClassyName = characteristics[characteristicUUID]; - - output.write(" this.addOptionalCharacteristic(Characteristic." + characteristicClassyName + ");\n"); - } - } - - output.write(" }\n"); - output.write("}\n\n"); -} - -output.write("var HomeKitTypesBridge = require('./HomeKitTypes-Bridge');\n\n"); - -/** - * Done! - */ - -output.end(); - -/** - * Useful functions - */ - -function getCharacteristicFormatsKey(format: string) { - // coerce 'int32' to 'int' - if (format == 'int32') format = 'int'; - - // look up the key in our known-formats dict - // @ts-ignore - for (var key in Characteristic.Formats) { - // @ts-ignore - if (Characteristic.Formats[key as keyof typeof Characteristic.Formats] == format) { - return key; - } - } - - throw new Error("Unknown characteristic format '" + format + "'"); -} - -function getCharacteristicUnitsKey(units: string) { - // look up the key in our known-units dict - // @ts-ignore - for (var key in Characteristic.Units) { - // @ts-ignore - if (Characteristic.Units[key as keyof typeof Characteristic.Units] == units) { - return key; - } - } - - throw new Error("Unknown characteristic units '" + units + "'"); -} - -function getCharacteristicPermsKey(perm: string) { - switch (perm) { - case "read": return "READ"; - case "write": return "WRITE"; - case "cnotify": return "NOTIFY"; - case "uncnotify": return undefined; - default: throw new Error("Unknown characteristic permission '" + perm + "'"); - } -} diff --git a/src/lib/gen/index.ts b/src/lib/gen/index.ts deleted file mode 100644 index 1dea3a5c3..000000000 --- a/src/lib/gen/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as gen from './HomeKit'; -import * as bridged from './HomeKit-Bridge'; -import * as tv from './HomeKit-TV'; -import * as remote from './HomeKit-Remote'; -import * as dataStream from './HomeKit-DataStream'; - -export const BASE_UUID = '-0000-1000-8000-0026BB765291'; - -export const Generated = gen; -export const Bridged = bridged; -export const TV = tv; -export const Remote = remote; -export const DataStream = dataStream; diff --git a/src/lib/model/AccessoryInfo.ts b/src/lib/model/AccessoryInfo.ts index 94a5d1b36..d43b3cb0c 100644 --- a/src/lib/model/AccessoryInfo.ts +++ b/src/lib/model/AccessoryInfo.ts @@ -1,19 +1,24 @@ -import util from 'util'; import assert from 'assert'; +import crypto from "crypto"; import tweetnacl from 'tweetnacl'; - -import { Categories } from '../Accessory'; -import { Session } from "../util/eventedhttp"; +import util from 'util'; +import { AccessoryJsonObject } from "../../internal-types"; import { MacAddress } from "../../types"; +import { Categories } from '../Accessory'; +import { EventedHTTPServer, HAPConnection, HAPUsername } from "../util/eventedhttp"; import { HAPStorage } from "./HAPStorage"; + +const packageJson = require("../../../package.json"); + export const enum PermissionTypes { + // noinspection JSUnusedGlobalSymbols USER = 0x00, - ADMIN = 0x01, // admins are the only ones who can add/remove/list pairings (also some characteristics are restricted) + ADMIN = 0x01, // admins are the only ones who can add/remove/list pairings (additionally some characteristics are restricted) } export type PairingInformation = { - username: string, + username: HAPUsername, publicKey: Buffer, permission: PermissionTypes, } @@ -33,11 +38,12 @@ export class AccessoryInfo { pincode: string | {salt: Buffer; verifier: Buffer} | null; signSk: Buffer; signPk: Buffer; - pairedClients: Record; + pairedClients: Record; pairedAdminClients: number; private configVersion: number = 1; - configHash: string; + private configHash: string; setupID: string; + private lastFirmwareVersion: string = ""; private constructor(username: MacAddress) { this.username = username; @@ -57,22 +63,23 @@ export class AccessoryInfo { /** * Add a paired client to memory. - * @param {string} username + * @param {HAPUsername} username * @param {Buffer} publicKey * @param {PermissionTypes} permission */ - addPairedClient = (username: string, publicKey: Buffer, permission: PermissionTypes) => { + public addPairedClient(username: HAPUsername, publicKey: Buffer, permission: PermissionTypes): void { this.pairedClients[username] = { username: username, publicKey: publicKey, permission: permission }; - if (permission === PermissionTypes.ADMIN) + if (permission === PermissionTypes.ADMIN) { this.pairedAdminClients++; + } }; - updatePermission = (username: string, permission: PermissionTypes) => { + public updatePermission(username: HAPUsername, permission: PermissionTypes): void { const pairingInformation = this.pairedClients[username]; if (pairingInformation) { @@ -87,8 +94,8 @@ export class AccessoryInfo { } }; - listPairings = () => { - const array = [] as PairingInformation[]; + public listPairings(): PairingInformation[] { + const array: PairingInformation[] = []; for (const username in this.pairedClients) { const pairingInformation = this.pairedClients[username] as PairingInformation; @@ -100,43 +107,43 @@ export class AccessoryInfo { /** * Remove a paired client from memory. - * @param controller - the session of the controller initiated the removal of the pairing + * @param connection - the session of the connection initiated the removal of the pairing * @param {string} username */ - removePairedClient = (controller: Session, username: string) => { - this._removePairedClient0(controller, username); + public removePairedClient(connection: HAPConnection, username: HAPUsername): void { + this._removePairedClient0(connection, username); if (this.pairedAdminClients === 0) { // if we don't have any admin clients left paired it is required to kill all normal clients for (const username0 in this.pairedClients) { - this._removePairedClient0(controller, username0); + this._removePairedClient0(connection, username0); } } }; - _removePairedClient0 = (controller: Session, username: string) => { + private _removePairedClient0(connection: HAPConnection, username: HAPUsername): void { if (this.pairedClients[username] && this.pairedClients[username].permission === PermissionTypes.ADMIN) this.pairedAdminClients--; delete this.pairedClients[username]; - Session.destroyExistingConnectionsAfterUnpair(controller, username); + EventedHTTPServer.destroyExistingConnectionsAfterUnpair(connection, username); }; /** * Check if username is paired * @param username */ - isPaired = (username: string) => { + public isPaired(username: HAPUsername): boolean { return !!this.pairedClients[username]; }; - hasAdminPermissions = (username: string) => { + public hasAdminPermissions(username: HAPUsername): boolean { if (!username) return false; const pairingInformation = this.pairedClients[username]; return !!pairingInformation && pairingInformation.permission === PermissionTypes.ADMIN; }; - // Gets the public key for a paired client as a Buffer, or falsey value if not paired. - getClientPublicKey = (username: string) => { + // Gets the public key for a paired client as a Buffer, or falsy value if not paired. + public getClientPublicKey(username: HAPUsername): Buffer | undefined { const pairingInformation = this.pairedClients[username]; if (pairingInformation) { return pairingInformation.publicKey; @@ -150,12 +157,40 @@ export class AccessoryInfo { return Object.keys(this.pairedClients).length > 0; // if we have any paired clients, we're paired. } - public updateConfigHash(hash: string): void { - this.configVersion++; - this.ensureConfigVersionBounds(); + /** + * Checks based on the current accessory configuration if the current configuration number needs to be incremented. + * Additionally, if desired, it checks if the firmware version was incremented (aka the HAP-NodeJS) version did grow. + * + * @param configuration - The current accessory configuration. + * @param checkFirmwareIncrement + * @returns True if the current configuration number was incremented and thus a new TXT must be advertised. + */ + public checkForCurrentConfigurationNumberIncrement(configuration: AccessoryJsonObject[], checkFirmwareIncrement?: boolean): boolean { + const shasum = crypto.createHash('sha1'); + shasum.update(JSON.stringify(configuration)); + const configHash = shasum.digest('hex'); + + let changed = false; - this.configHash = hash; - this.save(); + if (configHash !== this.configHash) { + this.configVersion++; + this.configHash = configHash; + + this.ensureConfigVersionBounds(); + changed = true; + } + if (this.lastFirmwareVersion !== packageJson.version) { + // we only check if it is different and not only if it is incremented + // HomeKit spec prohibits firmware downgrades, but with hap-nodejs it's possible lol + this.lastFirmwareVersion = packageJson.version; + changed = true; + } + + if (changed) { + this.save(); + } + + return changed; } public getConfigVersion(): number { @@ -172,7 +207,7 @@ export class AccessoryInfo { } save = () => { - var saved = { + const saved = { displayName: this.displayName, category: this.category, pincode: typeof this.pincode === 'object' && this.pincode ? @@ -188,17 +223,18 @@ export class AccessoryInfo { configVersion: this.configVersion, configHash: this.configHash, setupID: this.setupID, + lastFirmwareVersion: this.lastFirmwareVersion, }; - for (var username in this.pairedClients) { - const pairingInformation = this.pairedClients[username]; + for (let username in this.pairedClients) { + const pairingInformation: PairingInformation = this.pairedClients[username]; //@ts-ignore - saved.pairedClients[username] = pairingInformation.publicKey.toString('hex'); + saved.pairedClients[username] = pairingInformation.publicKey.toString("hex"); // @ts-ignore saved.pairedClientsPermission[username] = pairingInformation.permission; } - var key = AccessoryInfo.persistKey(this.username); + const key = AccessoryInfo.persistKey(this.username); HAPStorage.storage().setItemSync(key, saved); } @@ -210,10 +246,12 @@ export class AccessoryInfo { static create = (username: MacAddress) => { AccessoryInfo.assertValidUsername(username); - var accessoryInfo = new AccessoryInfo(username); + const accessoryInfo = new AccessoryInfo(username); + + accessoryInfo.lastFirmwareVersion = packageJson.version; // Create a new unique key pair for this accessory. - var keyPair = tweetnacl.sign.keyPair(); + const keyPair = tweetnacl.sign.keyPair(); accessoryInfo.signSk = Buffer.from(keyPair.secretKey); accessoryInfo.signPk = Buffer.from(keyPair.publicKey); @@ -224,11 +262,11 @@ export class AccessoryInfo { static load = (username: MacAddress) => { AccessoryInfo.assertValidUsername(username); - var key = AccessoryInfo.persistKey(username); - var saved = HAPStorage.storage().getItem(key); + const key = AccessoryInfo.persistKey(username); + const saved = HAPStorage.storage().getItem(key); if (saved) { - var info = new AccessoryInfo(username); + const info = new AccessoryInfo(username); info.displayName = saved.displayName || ""; info.category = saved.category || ""; info.pincode = saved.pincode && typeof saved.pincode === 'object' && 'verifier' in saved.pincode && 'salt' in saved.pincode ? @@ -238,8 +276,8 @@ export class AccessoryInfo { info.signPk = Buffer.from(saved.signPk || '', 'hex'); info.pairedClients = {}; - for (var username in saved.pairedClients || {}) { - var publicKey = saved.pairedClients[username]; + for (let username in saved.pairedClients || {}) { + const publicKey = saved.pairedClients[username]; let permission = saved.pairedClientsPermission? saved.pairedClientsPermission[username]: undefined; if (permission === undefined) permission = PermissionTypes.ADMIN; // defaulting to admin permissions is the only suitable solution, there is no way to recover permissions @@ -258,11 +296,12 @@ export class AccessoryInfo { info.setupID = saved.setupID || ""; + info.lastFirmwareVersion = saved.lastFirmwareVersion || packageJson.version; + info.ensureConfigVersionBounds(); return info; - } - else { + } else { return null; } } diff --git a/src/lib/model/ControllerStorage.ts b/src/lib/model/ControllerStorage.ts index c52f29466..0a35064aa 100644 --- a/src/lib/model/ControllerStorage.ts +++ b/src/lib/model/ControllerStorage.ts @@ -20,7 +20,7 @@ interface ControllerData { data: any, /* This property and the exact sequence this property is accessed solves the following problems: - - Orphaned ControllerData won't be there forever and get's cleared at some point + - Orphaned ControllerData won't be there forever and gets cleared at some point - When storage is loaded, there is no fixed time frame after which Controllers need to be configured */ purgeOnNextLoad?: boolean, @@ -152,7 +152,7 @@ export class ControllerStorage { }); } - public load(username: MacAddress) { // will be called once accessory get's published + public load(username: MacAddress) { // will be called once accessory gets published if (this.username) { throw new Error("ControllerStorage was already loaded!"); } diff --git a/src/lib/model/IdentifierCache.ts b/src/lib/model/IdentifierCache.ts index 1733cd976..51bbcc128 100644 --- a/src/lib/model/IdentifierCache.ts +++ b/src/lib/model/IdentifierCache.ts @@ -16,7 +16,7 @@ export class IdentifierCache { _cache: Record = {}; // cache[key:string] = id:number _usedCache: Record | null = null; // for usage tracking and expiring old keys - _savedCacheHash: string = ""; // for checking if new cache neeed to be saved + _savedCacheHash: string = ""; // for checking if new cache need to be saved constructor(public username: MacAddress) { } @@ -32,7 +32,7 @@ export class IdentifierCache { } getCache = (key: string) => { - var value = this._cache[key]; + const value = this._cache[key]; // track this cache item if needed if (this._usedCache && typeof value !== 'undefined') this._usedCache[key] = value; @@ -48,14 +48,14 @@ export class IdentifierCache { } getAID = (accessoryUUID: string) => { - var key = accessoryUUID; + const key = accessoryUUID; // ensure that our "next AID" field is not expired this.getCache('|nextAID'); return this.getCache(key) || this.setCache(key, this.getNextAID()); } getIID = (accessoryUUID: string, serviceUUID: string, serviceSubtype?: string, characteristicUUID?: string) => { - var key = accessoryUUID + const key = accessoryUUID + '|' + serviceUUID + (serviceSubtype ? '|' + serviceSubtype : '') + (characteristicUUID ? '|' + characteristicUUID : ''); @@ -65,26 +65,26 @@ export class IdentifierCache { } getNextAID = () => { - var key = '|nextAID'; - var nextAID = this.getCache(key) || 2; // start at 2 because the root Accessory or Bridge must be 1 + const key = '|nextAID'; + const nextAID = this.getCache(key) || 2; // start at 2 because the root Accessory or Bridge must be 1 this.setCache(key, nextAID + 1); // increment return nextAID; } getNextIID = (accessoryUUID: string) => { - var key = accessoryUUID + '|nextIID'; - var nextIID = this.getCache(key) || 2; // iid 1 is reserved for the Accessory Information service + const key = accessoryUUID + '|nextIID'; + const nextIID = this.getCache(key) || 2; // iid 1 is reserved for the Accessory Information service this.setCache(key, nextIID + 1); // increment return nextIID; } save = () => { - var newCacheHash = crypto.createHash('sha1').update(JSON.stringify(this._cache)).digest('hex'); //calculate hash of new cache + const newCacheHash = crypto.createHash('sha1').update(JSON.stringify(this._cache)).digest('hex'); //calculate hash of new cache if (newCacheHash != this._savedCacheHash) { //check if cache need to be saved and proceed accordingly - var saved = { + const saved = { cache: this._cache }; - var key = IdentifierCache.persistKey(this.username); + const key = IdentifierCache.persistKey(this.username); HAPStorage.storage().setItemSync(key, saved); this._savedCacheHash = newCacheHash; //update hash of saved cache for future use } @@ -99,12 +99,12 @@ export class IdentifierCache { } static load = (username: MacAddress) => { - var key = IdentifierCache.persistKey(username); - var saved = HAPStorage.storage().getItem(key); + const key = IdentifierCache.persistKey(username); + const saved = HAPStorage.storage().getItem(key); if (saved) { - var info = new IdentifierCache(username); + const info = new IdentifierCache(username); info._cache = saved.cache; - info._savedCacheHash = crypto.createHash('sha1').update(JSON.stringify(info._cache)).digest('hex'); //calculate hash of the saved hash to decide in future if saving of new cache is neeeded + info._savedCacheHash = crypto.createHash('sha1').update(JSON.stringify(info._cache)).digest('hex'); //calculate hash of the saved hash to decide in future if saving of new cache is needed return info; } else { return null; diff --git a/src/lib/tv/AccessControlManagement.ts b/src/lib/tv/AccessControlManagement.ts index 3f045e0a9..349c28eb2 100644 --- a/src/lib/tv/AccessControlManagement.ts +++ b/src/lib/tv/AccessControlManagement.ts @@ -1,13 +1,13 @@ -import { AccessControl } from "../gen/HomeKit"; -import { Service } from "../Service"; -import { EventEmitter } from "../EventEmitter"; +import { EventEmitter } from "events"; +import { CharacteristicValue } from "../../types"; import { Characteristic, CharacteristicEventTypes, CharacteristicGetCallback, CharacteristicSetCallback } from "../Characteristic"; -import { CharacteristicValue } from "../../types"; +import type { AccessControl } from '../definitions'; +import { Service } from "../Service"; import * as tlv from "../util/tlv"; const enum AccessControlTypes { @@ -20,6 +20,7 @@ const enum AccessControlTypes { * so this information is not really useful. */ export const enum AccessLevel { + // noinspection JSUnusedGlobalSymbols /** * This access level is set when the users selects "Anyone" or "Anyone On The Same Network" * in the Access Control settings. @@ -43,12 +44,15 @@ export const enum AccessControlEvent { PASSWORD_SETTING_UPDATED = "update-password", } -export type AccessControlEventMap = { - [AccessControlEvent.ACCESS_LEVEL_UPDATED]: (accessLevel: AccessLevel) => void; - [AccessControlEvent.PASSWORD_SETTING_UPDATED]: (password: string | undefined, passwordRequired: boolean) => void; +export declare interface AccessControlManagement { + on(event: "update-control-level", listener: (accessLevel: AccessLevel) => void): this; + on(event: "update-password", listener: (password: string | undefined, passwordRequired: boolean) => void): this; + + emit(event: "update-control-level", accessLevel: AccessLevel): boolean; + emit(event: "update-password", password: string | undefined, passwordRequired: boolean): boolean; } -export class AccessControlManagement extends EventEmitter { +export class AccessControlManagement extends EventEmitter { private readonly accessControlService: AccessControl; diff --git a/src/lib/util/clone.ts b/src/lib/util/clone.ts index 297a122af..d4b1d3e30 100644 --- a/src/lib/util/clone.ts +++ b/src/lib/util/clone.ts @@ -4,15 +4,15 @@ */ export function clone(object: T, extend?: U): T & U { - var cloned = {} as Record; + const cloned = {} as Record; - for (var key in object) { + for (let key in object) { cloned[key] = object[key]; } - for (var key2 in extend) { + for (let key2 in extend) { cloned[key2] = extend[key2]; } return cloned; -}; +} diff --git a/src/lib/util/eventedhttp.ts b/src/lib/util/eventedhttp.ts index 20f6531c8..3156eb2b6 100644 --- a/src/lib/util/eventedhttp.ts +++ b/src/lib/util/eventedhttp.ts @@ -1,32 +1,70 @@ +import { getNetAddress } from "@homebridge/ciao/lib/util/domain-formatter"; +import assert from "assert"; import createDebug from 'debug'; +import { EventEmitter } from "events"; import { SrpServer } from "fast-srp-hap"; -import http, { IncomingMessage, OutgoingMessage, ServerResponse } from 'http'; +import http, { IncomingMessage, ServerResponse } from 'http'; import net, { AddressInfo, Socket } from 'net'; import os from "os"; -import { Nullable, SessionIdentifier } from '../../types'; -import { EventEmitter } from '../EventEmitter'; -import { HAPEncryption } from '../HAPServer'; +import { CharacteristicEventNotification, EventNotification } from "../../internal-types"; +import { CharacteristicValue, Nullable, SessionIdentifier } from '../../types'; +import * as hapCrypto from "./hapCrypto"; +import { getOSLoopbackAddress } from "./net-utils"; import * as uuid from './uuid'; +import Timeout = NodeJS.Timeout; const debug = createDebug('HAP-NodeJS:EventedHTTPServer'); -export const enum EventedHTTPServerEvents { +export type HAPUsername = string; +export type EventName = string; // "." + +/** + * Simple struct to hold vars needed to support HAP encryption. + */ + +export class HAPEncryption { + readonly clientPublicKey: Buffer; + readonly secretKey: Buffer; + readonly publicKey: Buffer; + readonly sharedSecret: Buffer; + readonly hkdfPairEncryptionKey: Buffer; + + accessoryToControllerCount: number = 0; + controllerToAccessoryCount: number = 0; + accessoryToControllerKey = Buffer.alloc(0); + controllerToAccessoryKey = Buffer.alloc(0); + + incompleteFrame?: Buffer; + + public constructor(clientPublicKey: Buffer, secretKey: Buffer, publicKey: Buffer, sharedSecret: Buffer, hkdfPairEncryptionKey: Buffer) { + this.clientPublicKey = clientPublicKey; + this.secretKey = secretKey; + this.publicKey = publicKey; + this.sharedSecret = sharedSecret; + this.hkdfPairEncryptionKey = hkdfPairEncryptionKey; + } +} + +export const enum EventedHTTPServerEvent { LISTENING = 'listening', - REQUEST = 'request', - DECRYPT = 'decrypt', - ENCRYPT = 'encrypt', - CLOSE = 'close', - SESSION_CLOSE = 'session-close', + CONNECTION_OPENED = "connection-opened", + REQUEST = "request", + CONNECTION_CLOSED = "connection-closed", } -export type Events = { - [EventedHTTPServerEvents.LISTENING]: (port: number) => void; - [EventedHTTPServerEvents.REQUEST]: (request: IncomingMessage, response: ServerResponse, session: Session, events: any) => void; - [EventedHTTPServerEvents.DECRYPT]: (data: Buffer, decrypted: { data: Buffer; error: Error | null }, session: Session) => void; - [EventedHTTPServerEvents.ENCRYPT]: (data: Buffer, encrypted: { data: number | Buffer; }, session: Session) => void; - [EventedHTTPServerEvents.CLOSE]: (events: any) => void; - [EventedHTTPServerEvents.SESSION_CLOSE]: (sessionID: SessionIdentifier, events: any) => void; -}; +export declare interface EventedHTTPServer { + + on(event: "listening", listener: (port: number, address: string) => void): this; + on(event: "connection-opened", listener: (connection: HAPConnection) => void): this; + on(event: "request", listener: (connection: HAPConnection, request: IncomingMessage, response: ServerResponse) => void): this; + on(event: "connection-closed", listener: (connection: HAPConnection) => void): this; + + emit(event: "listening", port: number, address: string): boolean; + emit(event: "connection-opened", connection: HAPConnection): boolean; + emit(event: "request", connection: HAPConnection, request: IncomingMessage, response: ServerResponse): boolean; + emit(event: "connection-closed", connection: HAPConnection): boolean; + +} /** * EventedHTTPServer provides an HTTP-like server that supports HAP "extensions" for security and events. @@ -44,269 +82,245 @@ export type Events = { * * Each connection to the main TCP server gets its own internal HTTP server, so we can track ongoing requests/responses * for safe event insertion. - * - * @event 'listening' => function() { } - * Emitted when the server is fully set up and ready to receive connections. - * - * @event 'request' => function(request, response, session, events) { } - * Just like the 'http' module, request is http.IncomingMessage and response is http.ServerResponse. - * The 'session' param is an arbitrary object that you can use to store data associated with this connection; - * it will not be used by this class. The 'events' param is an object where the keys are the names of - * events that this connection has signed up for. It is initially empty and listeners are expected to manage it. - * - * @event 'decrypt' => function(data, {decrypted.data}, session) { } - * Fired when we receive data from the client device. You may detemine whether the data is encrypted, and if - * so, you can decrypt the data and store it into a new 'data' property of the 'decrypted' argument. If data is not - * encrypted, you can simply leave 'data' as null and the original data will be passed through as-is. - * - * @event 'encrypt' => function(data, {encrypted.data}, session) { } - * Fired when we wish to send data to the client device. If necessary, set the 'data' property of the - * 'encrypted' argument to be the encrypted data and it will be sent instead. */ -export class EventedHTTPServer extends EventEmitter { +export class EventedHTTPServer extends EventEmitter { - _tcpServer: net.Server; - _connections: EventedHTTPServerConnection[]; + private static readonly CONNECTION_TIMEOUT_LIMIT = 16; // if we have more (or equal) # connections we start the timeout + private static readonly MAX_CONNECTION_IDLE_TIME = 60 * 60 * 1000; // 1h + + private readonly tcpServer: net.Server; + /** + * Set of all currently connected HAP connections. + */ + private readonly connections: Set = new Set(); /** * Session dictionary indexed by username/identifier. The username uniquely identifies every person added to the home. * So there can be multiple sessions open for a single username (multiple devices connected to the same Apple ID). */ - sessions: Record = {}; + private readonly connectionsByUsername: Map = new Map(); + private connectionIdleTimeout?: Timeout; constructor() { super(); - this._tcpServer = net.createServer(); - this._connections = []; // track all open connections (for sending events) + this.tcpServer = net.createServer(); + const interval = setInterval(() => { // TODO to be removed + let connectionString = ""; + for (const connection of this.connections) { + if (connectionString) { + connectionString += ", "; + } + connectionString += connection.remoteAddress + ":" + connection.remotePort; + } + debug("Current " + this.connections.size + " hap connections open: " + connectionString); + }, 60000); + interval.unref(); } - listen = (targetPort: number) => { - this._tcpServer.listen(targetPort); + private scheduleNextConnectionIdleTimeout(): void { + this.connectionIdleTimeout = undefined; - this._tcpServer.on('listening', () => { - const address = this._tcpServer.address(); + if (!this.tcpServer.listening) { + return; + } - if (address && typeof address !== 'string') { - const port = address.port; - debug("Server listening on port %s", port); - this.emit(EventedHTTPServerEvents.LISTENING, port); - } + debug("Running idle timeout timer..."); - }); + const currentTime = new Date().getTime(); + let nextTimeout: number = -1; - this._tcpServer.on('connection', this._onConnection); - } + for (const connection of this.connections) { + const timeDelta = currentTime - connection.lastSocketOperation; - stop = () => { - this._tcpServer.close(); - this._connections.forEach((connection) => { - connection.close(); - }); - this._connections = []; - } + if (timeDelta >= EventedHTTPServer.MAX_CONNECTION_IDLE_TIME) { + debug("[%s] Closing connection as it was inactive for " + timeDelta + "ms"); + connection.close(); + } else { + nextTimeout = Math.max(nextTimeout, EventedHTTPServer.MAX_CONNECTION_IDLE_TIME - timeDelta); + } + } - sendEvent = ( - event: string, - data: Buffer | string, - contentType: string, - exclude?: Record - ) => { - for (const connection of this._connections) { - connection.sendEvent(event, data, contentType, exclude); + if (this.connections.size >= EventedHTTPServer.CONNECTION_TIMEOUT_LIMIT) { + this.connectionIdleTimeout = setTimeout(this.scheduleNextConnectionIdleTimeout.bind(this), nextTimeout); } } -// Called by net.Server when a new client connects. We will set up a new EventedHTTPServerConnection to manage the -// lifetime of this connection. - _onConnection = (socket: Socket) => { - const connection = new EventedHTTPServerConnection(this, socket); + public listen(targetPort: number, hostname?: string): void { + this.tcpServer.listen(targetPort, hostname, () => { + const address = this.tcpServer.address() as AddressInfo; // address() is only a string when listening to unix domain sockets + + debug("Server listening on %s:%s", address.family === "IPv6"? `[${address.address}]`: address.address, address.port); + this.emit(EventedHTTPServerEvent.LISTENING, address.port, address.address); + }); - // pass on session events to our listeners directly - connection.on(EventedHTTPServerEvents.REQUEST, (request: IncomingMessage, response: ServerResponse, session: Session, events: any) => { this.emit(EventedHTTPServerEvents.REQUEST, request, response, session, events); }); - connection.on(EventedHTTPServerEvents.ENCRYPT, (data: Buffer, encrypted: { data: Buffer; }, session: Session) => { this.emit(EventedHTTPServerEvents.ENCRYPT, data, encrypted, session); }); - connection.on(EventedHTTPServerEvents.DECRYPT, (data: Buffer, decrypted: { data: number | Buffer; }, session: Session) => { this.emit(EventedHTTPServerEvents.DECRYPT, data, decrypted, session); }); - connection.on(EventedHTTPServerEvents.CLOSE, (events: any) => { this._handleConnectionClose(connection, events); }); - this._connections.push(connection); + this.tcpServer.on("connection", this.onConnection.bind(this)); } - _handleConnectionClose = ( - connection: EventedHTTPServerConnection, - events: Record - ) => { - this.emit(EventedHTTPServerEvents.SESSION_CLOSE, connection.sessionID, events); + public stop(): void { + this.tcpServer.close(); + for (const connection of this.connections) { + connection.close(); + } - // remove it from our array of connections for events - this._connections.splice(this._connections.indexOf(connection), 1); + this.removeAllListeners(); } -} -export const enum HAPSessionEvents { - CLOSED = "closed", -} + /** + * Send a even notification for given characteristic and changed value to all connected clients. + * If {@param originator} is specified, the given {@link HAPConnection} will be excluded from the broadcast. + * + * @param aid - The accessory id of the updated characteristic. + * @param iid - The instance id of the updated characteristic. + * @param value - The newly set value of the characteristic. + * @param originator - If specified, the connection will not get a event message. + * @param immediateDelivery - The HAP spec requires some characteristics to be delivery immediately. + * Namely for the {@link ButtonEvent} and the {@link ProgrammableSwitchEvent} characteristics. + */ + public broadcastEvent(aid: number, iid: number, value: Nullable, originator?: HAPConnection, immediateDelivery?: boolean): void { + for (const connection of this.connections) { + if (connection === originator) { + debug("[%s] Muting event '%s' notification for this connection since it originated here.", connection.remoteAddress, aid + "." + iid); + continue; + } -export type HAPSessionEventMap = { - [HAPSessionEvents.CLOSED]: () => void; -} + connection.sendEvent(aid, iid, value, immediateDelivery); + } + } -export class Session extends EventEmitter { + private onConnection(socket: Socket): void { + const connection = new HAPConnection(this, socket); - readonly _server: EventedHTTPServer; - readonly _connection: EventedHTTPServerConnection; - /* - Session dictionary indexed by sessionID. SessionID is a custom generated id by HAP-NodeJS unique to every open connection. - SessionID gets passed to get/set handlers for characteristics. We mainly need this dictionary in order - to access the sharedSecret in the HAPEncryption object from the SetupDataStreamTransport characteristic set handler. - */ - private static sessionsBySessionID: Record = {}; + connection.on(HAPConnectionEvent.REQUEST, (request, response) => { + this.emit(EventedHTTPServerEvent.REQUEST, connection, request, response); + }); + connection.on(HAPConnectionEvent.AUTHENTICATED, this.handleConnectionAuthenticated.bind(this, connection)); + connection.on(HAPConnectionEvent.CLOSED, this.handleConnectionClose.bind(this, connection)); - sessionID: SessionIdentifier; // uuid unique to every HAP connection - _pairSetupState?: number; - _pairSetupFlags?: { - raw: Buffer | undefined; - flags: number | null; - transient: boolean; - split: boolean; - }; - srpServer?: SrpServer; - _pairVerifyState?: number; - encryption?: HAPEncryption; - authenticated = false; - username?: string; // username is unique to every user in the home + this.connections.add(connection); - timedWritePid?: number; - timedWriteTimeout?: NodeJS.Timeout; + debug("[%s] New connection from client on interface %s", connection.remoteAddress, connection.networkInterface); - constructor(connection: EventedHTTPServerConnection) { - super(); - this._server = connection.server; - this._connection = connection; - this.sessionID = connection.sessionID; + this.emit(EventedHTTPServerEvent.CONNECTION_OPENED, connection); - Session.sessionsBySessionID[this.sessionID] = this; + if (this.connections.size >= EventedHTTPServer.CONNECTION_TIMEOUT_LIMIT && !this.connectionIdleTimeout) { + this.scheduleNextConnectionIdleTimeout(); + } } - public getLocalAddress(ipVersion: "ipv4" | "ipv6"): string { - const infos = os.networkInterfaces()[this._connection.networkInterface]; + private handleConnectionAuthenticated(connection: HAPConnection, username: HAPUsername): void { + const connections: HAPConnection[] | undefined = this.connectionsByUsername.get(username); + if (!connections) { + this.connectionsByUsername.set(username, [connection]); + } else if (!connections.includes(connection)) { // ensure this doesn't get added more than one time + connections.push(connection); + } + } - if (ipVersion === "ipv4") { - for (const info of infos) { - if (info.family === "IPv4") { - return info.address; - } - } + private handleConnectionClose(connection: HAPConnection): void { + this.emit(EventedHTTPServerEvent.CONNECTION_CLOSED, connection); - throw new Error("Could not find " + ipVersion + " address for interface " + this._connection.networkInterface); - } else { - let localUniqueAddress: string | undefined = undefined; + this.connections.delete(connection); - for (const info of infos) { - if (info.family === "IPv6") { - if (!info.scopeid) { - return info.address; - } else if (!localUniqueAddress) { - localUniqueAddress = info.address; - } + if (connection.username) { // aka connection was authenticated + const connections = this.connectionsByUsername.get(connection.username); + if (connections) { + const index = connections.indexOf(connection); + if (index !== -1) { + connections.splice(index, 1); } - } - if (!localUniqueAddress) { - throw new Error("Could not find " + ipVersion + " address for interface " + this._connection.networkInterface); + if (connections.length === 0) { + this.connectionsByUsername.delete(connection.username); + } } - return localUniqueAddress; } } - /** - * establishSession gets called after a pair verify. - * establishSession does not get called after the first pairing gets added, as any HomeKit controller will initiate a - * pair verify after the pair setup procedure. - */ - establishSession = (username: string) => { - this.authenticated = true; - this.username = username; + public static destroyExistingConnectionsAfterUnpair(initiator: HAPConnection, username: string): void { + const connections: HAPConnection[] | undefined = initiator.server.connectionsByUsername.get(username); - let sessions: Session[] = this._server.sessions[username]; - if (!sessions) { - sessions = []; - this._server.sessions[username] = sessions; - } - - if (sessions.includes(this)) { - return; // ensure this doesn't get added more than one time + if (connections) { + for (const connection of connections) { + connection.closeConnectionAsOfUnpair(initiator); + } } - - sessions.push(this); }; - // called when socket of this session is destroyed - _connectionDestroyed = () => { - delete Session.sessionsBySessionID[this.sessionID]; - - if (this.username) { - const sessions: Session[] = this._server.sessions[this.username]; - if (sessions) { - const index = sessions.indexOf(this); - if (index >= 0) { - sessions[index].authenticated = false; - sessions.splice(index, 1); - } - if (!sessions.length) delete this._server.sessions[this.username]; - } - } +} - this.emit(HAPSessionEvents.CLOSED); - }; +/** + * @internal + */ +export const enum HAPConnectionState { + CONNECTING, // initial state, setup is going on + FULLY_SET_UP, // internal http server is running and connection is established + AUTHENTICATED, // encryption is set up + // above signals are represent a alive connection + + // below states are considered "closed or soon closed" + TO_BE_TEARED_DOWN, // when in this state, connection should be closed down after response was sent out + CLOSING, // close was called + CLOSED, // dead +} - static destroyExistingConnectionsAfterUnpair = (initiator: Session, username: string) => { - const sessions: Session[] = initiator._server.sessions[username]; - - if (sessions) { - sessions.forEach(session => { - session.authenticated = false; - if (initiator.sessionID === session.sessionID) { - // the session which initiated the unpair removed it's own username, wait until the unpair request is finished - // until we kill his connection - session._connection._killSocketAfterWrite = true; - } else { - // as HomeKit requires it, destroy any active session which got unpaired - session._connection._clientSocket.destroy(); - } - }); - } - }; +export const enum HAPConnectionEvent { + REQUEST = "request", + AUTHENTICATED = "authenticated", + CLOSED = "closed", +} - static getSession(sessionID: SessionIdentifier) { - return this.sessionsBySessionID[sessionID]; - } +export declare interface HAPConnection { + on(event: "request", listener: (request: IncomingMessage, response: ServerResponse) => void): this; + on(event: "authenticated", listener: (username: HAPUsername) => void): this; + on(event: "closed", listener: () => void): this; + emit(event: "request", request: IncomingMessage, response: ServerResponse): boolean; + emit(event: "authenticated", username: HAPUsername): boolean; + emit(event: "closed"): boolean; } /** * Manages a single iOS-initiated HTTP connection during its lifetime. - * - * @event 'request' => function(request, response) { } - * @event 'decrypt' => function(data, {decrypted.data}, session) { } - * @event 'encrypt' => function(data, {encrypted.data}, session) { } - * @event 'close' => function() { } + * @internal */ -class EventedHTTPServerConnection extends EventEmitter { +export class HAPConnection extends EventEmitter { readonly server: EventedHTTPServer; - readonly sessionID: SessionIdentifier; - readonly remoteAddress: string; + + readonly sessionID: SessionIdentifier; // uuid unique to every HAP connection + private state: HAPConnectionState = HAPConnectionState.CONNECTING; + readonly remoteAddress: string; // cache because it becomes undefined in 'onClientSocketClose' readonly remotePort: number; - readonly remoteFamily: string; readonly networkInterface: string; - _pendingClientSocketData: Nullable; - _fullySetup: boolean; - _writingResponse: boolean; - _killSocketAfterWrite: boolean; - _pendingEventData: Buffer[]; - _clientSocket: Socket; - _httpServer: http.Server; - _serverSocket: Nullable; - _session: Session; - _events: Record; - _httpPort?: number; + + private readonly tcpSocket: Socket; + private readonly internalHttpServer: http.Server; + private httpSocket?: Socket; // set when in state FULLY_SET_UP + private internalHttpServerPort?: number; + + lastSocketOperation: number = new Date().getTime(); + + private pendingClientSocketData?: Buffer = Buffer.alloc(0); // data received from client before HTTP proxy is fully setup + private handlingRequest: boolean = false; // true while we are composing an HTTP response (so events can wait) + + username?: HAPUsername; // username is unique to every user in the home, basically identifies an Apple Id + encryption?: HAPEncryption; // created in handlePairVerifyStepOne + srpServer?: SrpServer; + _pairSetupState?: number; // TODO ensure those two states are always correctly reset? + _pairSetupFlags?: { + raw: Buffer | undefined; + flags: number | null; + transient: boolean; + split: boolean; + }; + _pairVerifyState?: number; + + private registeredEvents: Set = new Set(); + private eventsTimer?: Timeout; + private readonly queuedEvents: Map = new Map(); + private readonly pendingEventData: Buffer[] = []; // queue of unencrypted event data waiting to be sent until after an in-progress HTTP response is being written + + timedWritePid?: number; + timedWriteTimeout?: NodeJS.Timeout; constructor(server: EventedHTTPServer, clientSocket: Socket) { super(); @@ -315,239 +329,384 @@ class EventedHTTPServerConnection extends EventEmitter { this.sessionID = uuid.generate(clientSocket.remoteAddress + ':' + clientSocket.remotePort); this.remoteAddress = clientSocket.remoteAddress!; // cache because it becomes undefined in 'onClientSocketClose' this.remotePort = clientSocket.remotePort!; - this.remoteFamily = clientSocket.remoteFamily!; - this.networkInterface = EventedHTTPServerConnection.getLocalNetworkInterface(clientSocket); - this._pendingClientSocketData = Buffer.alloc(0); // data received from client before HTTP proxy is fully setup - this._fullySetup = false; // true when we are finished establishing connections - this._writingResponse = false; // true while we are composing an HTTP response (so events can wait) - this._killSocketAfterWrite = false; - this._pendingEventData = []; // queue of unencrypted event data waiting to be sent until after an in-progress HTTP response is being written + this.networkInterface = HAPConnection.getLocalNetworkInterface(clientSocket); + // clientSocket is the socket connected to the actual iOS device - this._clientSocket = clientSocket; - this._clientSocket.on('data', this._onClientSocketData); - this._clientSocket.on('close', this._onClientSocketClose); - this._clientSocket.on('error', this._onClientSocketError); // we MUST register for this event, otherwise the error will bubble up to the top and crash the node process entirely. - this._clientSocket.setNoDelay(true); // disable Nagle algorithm - this._clientSocket.setKeepAlive(true); - - // serverSocket is our connection to our own internal httpServer - this._serverSocket = null; // created after httpServer 'listening' event + this.tcpSocket = clientSocket; + this.tcpSocket.on('data', this.onTCPSocketData.bind(this)); + this.tcpSocket.on('close', this.onTCPSocketClose.bind(this)); + this.tcpSocket.on('error', this.onTCPSocketError.bind(this)); // we MUST register for this event, otherwise the error will bubble up to the top and crash the node process entirely. + this.tcpSocket.setNoDelay(true); // disable Nagle algorithm + // "HAP accessory servers must not use keepalive messages, which periodically wake up iOS devices". + // Thus we don't configure any tcp keepalive + // create our internal HTTP server for this connection that we will proxy data to and from - this._httpServer = http.createServer(); - this._httpServer.timeout = 0; // clients expect to hold connections open as long as they want - this._httpServer.keepAliveTimeout = 0; // workaround for https://github.com/nodejs/node/issues/13391 - this._httpServer.on('listening', this._onHttpServerListening); - this._httpServer.on('request', this._onHttpServerRequest); - this._httpServer.on('error', this._onHttpServerError); - this._httpServer.listen(0); - // an arbitrary dict that users of this class can store values in to associate with this particular connection - this._session = new Session(this); - // a collection of event names subscribed to by this connection - this._events = {}; // this._events[eventName] = true (value is arbitrary, but must be truthy) - debug("[%s] New connection from client at interface %s", this.remoteAddress, this.networkInterface); + this.internalHttpServer = http.createServer(); + this.internalHttpServer.timeout = 0; // clients expect to hold connections open as long as they want + this.internalHttpServer.keepAliveTimeout = 0; // workaround for https://github.com/nodejs/node/issues/13391 + this.internalHttpServer.on("listening", this.onHttpServerListening.bind(this)); + this.internalHttpServer.on('request', this.handleHttpServerRequest.bind(this)); + this.internalHttpServer.on('error', this.onHttpServerError.bind(this)); + // close event is added later on the "connect" event as possible listen retries would throw unnecessary close events + this.internalHttpServer.listen(0, getOSLoopbackAddress); } get connected() { - return this.server._connections.includes(this); + return this.state !== HAPConnectionState.CONNECTING && this.state !== HAPConnectionState.CLOSED; } - sendEvent = (event: string, data: Buffer | string, contentType: string, excludeEvents?: Record) => { - // has this connection subscribed to the given event? if not, nothing to do! - if (!this._events[event]) { - return; + /** + * This method is called once the connection has gone through pair-verify. + * As any HomeKit controller will initiate a pair-verify after the pair-setup procedure, this method gets + * not called on the initial pair-setup. + * + * Once this method has been called, the connection is authenticated and encryption is turned on. + */ + public connectionAuthenticated(username: HAPUsername): void { + this.state = HAPConnectionState.AUTHENTICATED; + this.username = username; + + this.emit(HAPConnectionEvent.AUTHENTICATED, username); + }; + + public isAuthenticated(): boolean { + return this.state === HAPConnectionState.AUTHENTICATED; + } + + public close(): void { + if (this.state >= HAPConnectionState.CLOSING) { + return; // already closed/closing } - // does this connection's 'events' object match the excludeEvents object? if so, don't send the event. - if (excludeEvents === this._events) { - debug("[%s] Muting event '%s' notification for this connection since it originated here.", this.remoteAddress, event); - return; + + this.state = HAPConnectionState.CLOSING; + this.tcpSocket.destroy(); + } + + public closeConnectionAsOfUnpair(initiator: HAPConnection): void { + if (this === initiator) { + // the initiator of the unpair request is this connection, meaning it unpaired itself. + // we still need to send the response packet to the unpair request. + this.state = HAPConnectionState.TO_BE_TEARED_DOWN; + } else { + // as HomeKit requires it, destroy any active session which got unpaired + this.close(); } - debug("[%s] Sending HTTP event '%s' with data: %s", this.remoteAddress, event, data.toString('utf8')); - // ensure data is a Buffer - if (typeof data === 'string') { - data = Buffer.from(data); + } + + public sendEvent(aid: number, iid: number, value: Nullable, immediateDelivery?: boolean): void { + assert(aid != undefined, "HAPConnection.sendEvent: aid must be defined!"); + assert(iid != undefined, "HAPConnection.sendEvent: iid must be defined!"); + + const eventName = aid + "." + iid; + + if (!this.registeredEvents.has(eventName)) { + return; } - // format this payload as an HTTP response - const linebreak = Buffer.from("0D0A", "hex"); - data = Buffer.concat([ - Buffer.from('EVENT/1.0 200 OK'), linebreak, - Buffer.from('Content-Type: ' + contentType), linebreak, - Buffer.from('Content-Length: ' + data.length), linebreak, - linebreak, - data - ]); - - // if we're in the middle of writing an HTTP response already, put this event in the queue for when - // we're done. otherwise send it immediately. - if (this._writingResponse) { - this._pendingEventData.push(data); + + if (immediateDelivery) { + // some characteristics are required to deliver notifications immediately + this.writeEventNotification({ + characteristics: [{ + aid: aid, + iid: iid, + value: value, + }], + }); } else { - // give listeners an opportunity to encrypt this data before sending it to the client - const encrypted = {data: null}; - this.emit(EventedHTTPServerEvents.ENCRYPT, data, encrypted, this._session); - if (encrypted.data) { - // @ts-ignore - data = encrypted.data as Buffer; + // TODO should a new event not remove a previous event (to support censor open -> censor closed :thinking:) + // any only remove previous events if the same value was set? + this.queuedEvents.set(eventName, { + aid: aid, + iid: iid, + value: value, + }); + if (!this.handlingRequest && !this.eventsTimer) { // if we are handling a request or there is already a timer running we just add it in the queue + this.eventsTimer = setTimeout(this.handleEventsTimeout.bind(this), 250); + this.eventsTimer.unref(); } + } + } - this._clientSocket.write(data); + private handleEventsTimeout(): void { + this.eventsTimer = undefined; + if (this.state > HAPConnectionState.AUTHENTICATED || this.handlingRequest) { + // connection is closed or about to be closed. no need to send any further events + // OR we are currently sending a response + return; } + + this.writeQueuedEventNotifications(); } - close = () => { - this._clientSocket.end(); + private writePendingEventNotifications(): void { + for (const buffer of this.pendingEventData) { + this.tcpSocket.write(this.encrypt(buffer)); + } + this.pendingEventData.splice(0, this.pendingEventData.length); } - _sendPendingEvents = () => { - if (this._pendingEventData.length === 0) { - return; + private writeQueuedEventNotifications(): void { + if (this.queuedEvents.size === 0 || this.eventsTimer) { + return; // don't send empty event notifications or if there is a timeout running } - // an existing HTTP response was finished, so let's flush our pending event buffer if necessary! - debug("[%s] Writing pending HTTP event data", this.remoteAddress); - this._pendingEventData.forEach(event => { - const encrypted = {data: null}; - this.emit(EventedHTTPServerEvents.ENCRYPT, event, encrypted, this._session); - if (encrypted.data) { - // @ts-ignore - event = encrypted.data as Buffer; + const eventData: EventNotification = { characteristics: [] }; + for (const [eventName, characteristic] of this.queuedEvents) { + if (!this.registeredEvents.has(eventName)) { // client unregistered events in the mean time + continue; } + eventData.characteristics.push(characteristic); + } + this.queuedEvents.clear(); - this._clientSocket.write(event); - }); + this.writeEventNotification(eventData); + } + + /** + * This will create an EVENT/1.0 notification header with the provided event notification. + * If currently a HTTP request is in progress the assembled packet will be + * added to the pending events list. + * + * @param notification - The event which should be sent out + */ + private writeEventNotification(notification: EventNotification): void { + debug("[%s] Sending HAP event notifications %o", this.remoteAddress, notification.characteristics); + + const dataBuffer = Buffer.from(JSON.stringify(notification), "utf8"); + const header = Buffer.from( + "EVENT/1.0 200 OK\r\n" + + "Content-Type: application/hap+json\r\n" + + "Content-Length: " + dataBuffer.length + "\r\n" + + "\r\n", + "utf8" // buffer encoding + ); + + const buffer = Buffer.concat([header, dataBuffer]); + if (this.handlingRequest) { + // it is important that we not encrypt the pending event data. This would increment the nonce used in encryption + this.pendingEventData.push(buffer); + } else { + this.tcpSocket.write(this.encrypt(buffer), this.handleTCPSocketWriteFulfilled.bind(this)); + } + } - // clear the queue - this._pendingEventData = []; + public enableEventNotifications(aid: number, iid: number): void { + this.registeredEvents.add(aid + "." + iid); } - // Called only once right after constructor finishes - _onHttpServerListening = () => { - this._httpPort = (this._httpServer.address() as AddressInfo).port; - debug("[%s] HTTP server listening on port %s", this.remoteAddress, this._httpPort); - // closes before this are due to retrying listening, which don't need to be handled - this._httpServer.on('close', this._onHttpServerClose); - // now we can establish a connection to this running HTTP server for proxying data - this._serverSocket = net.createConnection(this._httpPort); - this._serverSocket.on('connect', this._onServerSocketConnect); - this._serverSocket.on('data', this._onServerSocketData); - this._serverSocket.on('close', this._onServerSocketClose); - this._serverSocket.on('error', this._onServerSocketError); // we MUST register for this event, otherwise the error will bubble up to the top and crash the node process entirely. - - this._serverSocket.setNoDelay(true); // disable Nagle algorithm - this._serverSocket.setKeepAlive(true); - } - - // Called only once right after onHttpServerListening - _onServerSocketConnect = () => { - // we are now fully set up: - // - clientSocket is connected to the iOS device - // - serverSocket is connected to the httpServer - // - ready to proxy data! - this._fullySetup = true; - // start by flushing any pending buffered data received from the client while we were setting up - if (this._pendingClientSocketData && this._pendingClientSocketData.length > 0) { - this._serverSocket && this._serverSocket.write(this._pendingClientSocketData); - this._pendingClientSocketData = null; + public disableEventNotifications(aid: number, iid: number): void { + this.registeredEvents.delete(aid + "." + iid); + } + + public hasEventNotifications(aid: number, iid: number): boolean { + return this.registeredEvents.has(aid + "." + iid); + } + + public getRegisteredEvents(): Set { + return this.registeredEvents; + } + + public clearRegisteredEvents(): void { + this.registeredEvents.clear(); + } + + private encrypt(data: Buffer): Buffer { + // if accessoryToControllerKey is not empty, then encryption is enabled for this connection. However, we'll + // need to be careful to ensure that we don't encrypt the last few bytes of the response from handlePairVerifyStepTwo. + // Since all communication calls are asynchronous, we could easily receive this 'encrypt' event for those bytes. + // So we want to make sure that we aren't encrypting data until we have *received* some encrypted data from the client first. + if (this.encryption && this.encryption.accessoryToControllerKey.length > 0 && this.encryption.controllerToAccessoryCount > 0) { + return hapCrypto.layerEncrypt(data, this.encryption); } + return data; // otherwise we don't encrypt and return plaintext } - // Received data from client (iOS) - _onClientSocketData = (data: Buffer) => { - // _writingResponse is reverted to false in _onHttpServerRequest(...) after response was written - this._writingResponse = true; + private decrypt(data: Buffer): Buffer { + if (this.encryption && this.encryption.controllerToAccessoryKey.length > 0) { + // below call may throw an error if decryption failed + return hapCrypto.layerDecrypt(data, this.encryption); + } + return data; // otherwise we don't decrypt and return plaintext + } + + private onHttpServerListening() { + const addressInfo = this.internalHttpServer.address() as AddressInfo; // address() is only a string when listening to unix domain sockets + const addressString = addressInfo.family === "IPv6"? `[${addressInfo.address}]`: addressInfo.address; + this.internalHttpServerPort = addressInfo.port; + + debug("[%s] Internal HTTP server listening on %s:%s", this.remoteAddress, addressString, addressInfo.port); - // give listeners an opportunity to decrypt this data before processing it as HTTP - const decrypted: { data: Buffer | null, error: Error | null } = {data: null, error: null}; - this.emit(EventedHTTPServerEvents.DECRYPT, data, decrypted, this._session); + this.internalHttpServer.on('close', this.onHttpServerClose.bind(this)); - if (decrypted.error) { - // decryption and/or verification failed, disconnect the client - debug("[%s] Error occurred trying to decrypt incoming packet: %s", this.remoteAddress, decrypted.error.message); + // now we can establish a connection to this running HTTP server for proxying data + this.httpSocket = net.createConnection(this.internalHttpServerPort, getOSLoopbackAddress()); // previously we used addressInfo.address + this.httpSocket.setNoDelay(true); // disable Nagle algorithm + + this.httpSocket.on('data', this.handleHttpServerResponse.bind(this)); + this.httpSocket.on('error', this.onHttpSocketError.bind(this)); // we MUST register for this event, otherwise the error will bubble up to the top and crash the node process entirely. + this.httpSocket.on('close', this.onHttpSocketClose.bind(this)); + this.httpSocket.on('connect', () => { + // we are now fully set up: + // - clientSocket is connected to the iOS device + // - serverSocket is connected to the httpServer + // - ready to proxy data! + this.state = HAPConnectionState.FULLY_SET_UP; + + // start by flushing any pending buffered data received from the client while we were setting up + if (this.pendingClientSocketData && this.pendingClientSocketData.length > 0) { + this.httpSocket!.write(this.pendingClientSocketData); + } + this.pendingClientSocketData = undefined; + }); + } + + /** + * This event handler is called when we receive data from a HomeKit controller on our tcp socket. + * We store the data if the internal http server is not read yet, or forward it to the http server. + */ + private onTCPSocketData(data: Buffer): void { + if (this.state > HAPConnectionState.AUTHENTICATED) { + // don't accept data of a connection which is about to be closed or already closed + return; + } + + this.handlingRequest = true; // reverted to false once response was sent out + this.lastSocketOperation = new Date().getTime(); + + try { + data = this.decrypt(data); + } catch (error) { // decryption and/or verification failed, disconnect the client + debug("[%s] Error occurred trying to decrypt incoming packet: %s", this.remoteAddress, error.message); this.close(); + return; + } + + if (this.state < HAPConnectionState.FULLY_SET_UP) { // we're not setup yet, so add this data to our intermediate buffer + this.pendingClientSocketData = Buffer.concat([this.pendingClientSocketData!, data]); } else { - if (decrypted.data) { - data = decrypted.data; - } + this.httpSocket!.write(data); // proxy it along to the HTTP server + } + } - if (this._fullySetup) { - // proxy it along to the HTTP server - this._serverSocket && this._serverSocket.write(data); - } else { - // we're not setup yet, so add this data to our buffer - this._pendingClientSocketData = Buffer.concat([this._pendingClientSocketData!, data]); - } + /** + * This event handler is called when the internal http server receives a request. + * Meaning we received data from the HomeKit controller in {@link onTCPSocketData}, which then send the + * data unencrypted to the internal http server. And now it landed here, fully parsed as a http request. + */ + private handleHttpServerRequest(request: IncomingMessage, response: ServerResponse): void { + if (this.state > HAPConnectionState.AUTHENTICATED) { + // don't accept data of a connection which is about to be closed or already closed + return; } + + request.socket.setNoDelay(true); + response.connection.setNoDelay(true); // deprecated since 13.0.0 + + debug("[%s] HTTP request: %s", this.remoteAddress, request.url); + this.emit(HAPConnectionEvent.REQUEST, request, response); } - // Received data from HTTP Server - _onServerSocketData = (data: Buffer | string) => { - // give listeners an opportunity to encrypt this data before sending it to the client - const encrypted = {data: null}; - this.emit(EventedHTTPServerEvents.ENCRYPT, data, encrypted, this._session); - if (encrypted.data) - data = encrypted.data!; - // proxy it along to the client (iOS) - this._clientSocket.write(data); - - if (this._killSocketAfterWrite) { - setTimeout(() => { - this._clientSocket.destroy(); - }, 10); + /** + * This event handler is called by the socket which is connected to our internal http server. + * It is called with the response returned from the http server. + * In this method we have to encrypt and forward the message back to the HomeKit controller. + */ + private handleHttpServerResponse(data: Buffer): void { + data = this.encrypt(data); + this.tcpSocket.write(data, this.handleTCPSocketWriteFulfilled.bind(this)); + + debug("[%s] HTTP Response is finished", this.remoteAddress); + this.handlingRequest = false; + + if (this.state === HAPConnectionState.TO_BE_TEARED_DOWN) { + setTimeout(() => this.close(), 10); + } else if (this.state < HAPConnectionState.TO_BE_TEARED_DOWN) { + this.writePendingEventNotifications(); + this.writeQueuedEventNotifications(); } } - // Our internal HTTP Server has been closed (happens after we call this._httpServer.close() below) - _onServerSocketClose = () => { - debug("[%s] HTTP connection was closed", this.remoteAddress); - // make sure the iOS side is closed as well - this._clientSocket.destroy(); - // we only support a single long-lived connection to our internal HTTP server. Since it's closed, - // we'll need to shut it down entirely. - this._httpServer.close(); + private handleTCPSocketWriteFulfilled(): void { + this.lastSocketOperation = new Date().getTime(); } - // Our internal HTTP Server has been closed (happens after we call this._httpServer.close() below) - _onServerSocketError = (err: Error) => { - debug("[%s] HTTP connection error: ", this.remoteAddress, err.message); - // _onServerSocketClose will be called next + private onTCPSocketError(err: Error): void { + debug("[%s] Client connection error: %s", this.remoteAddress, err.message); + // onTCPSocketClose will be called next } - _onHttpServerRequest = (request: IncomingMessage, response: OutgoingMessage) => { - debug("[%s] HTTP request: %s", this.remoteAddress, request.url); + private onTCPSocketClose(): void { + this.state = HAPConnectionState.CLOSED; - // sign up to know when the response is ended, so we can safely send EVENT responses - response.on('finish', () => { - debug("[%s] HTTP Response is finished", this.remoteAddress); - this._writingResponse = false; - this._sendPendingEvents(); - }); - // pass it along to listeners - this.emit(EventedHTTPServerEvents.REQUEST, request, response, this._session, this._events); - } + debug("[%s] Client connection closed", this.remoteAddress); - _onHttpServerClose = () => { - debug("[%s] HTTP server was closed", this.remoteAddress); - // notify listeners that we are completely closed - this.emit(EventedHTTPServerEvents.CLOSE, this._events); + if (this.httpSocket) { + this.httpSocket.destroy(); + } + this.internalHttpServer.close(); + + this.emit(HAPConnectionEvent.CLOSED); + + this.removeAllListeners(); // cleanup listeners, we are officially dead now } - _onHttpServerError = (err: Error & { code?: string }) => { + private onHttpServerError(err: Error & { code?: string }): void { debug("[%s] HTTP server error: %s", this.remoteAddress, err.message); if (err.code === 'EADDRINUSE') { - this._httpServer.close(); - this._httpServer.listen(0); + this.internalHttpServerPort = undefined; + + this.internalHttpServer.close(); + this.internalHttpServer.listen(0, getOSLoopbackAddress); } } - _onClientSocketClose = () => { - debug("[%s] Client connection closed", this.remoteAddress); - // shutdown the other side - this._serverSocket && this._serverSocket.destroy(); - this._session._connectionDestroyed(); + private onHttpServerClose(): void { + debug("[%s] HTTP server was closed", this.remoteAddress); + // make sure the iOS side is closed as well + this.close(); } - _onClientSocketError = (err: Error) => { - debug("[%s] Client connection error: %s", this.remoteAddress, err.message); - // _onClientSocketClose will be called next + private onHttpSocketError(err: Error): void { + debug("[%s] HTTP connection error: ", this.remoteAddress, err.message); + // onHttpSocketClose will be called next + } + + private onHttpSocketClose(): void { + debug("[%s] HTTP connection was closed", this.remoteAddress); + // we only support a single long-lived connection to our internal HTTP server. Since it's closed, + // we'll need to shut it down entirely. + this.internalHttpServer.close(); + } + + public getLocalAddress(ipVersion: "ipv4" | "ipv6"): string { + const infos = os.networkInterfaces()[this.networkInterface]; + + if (ipVersion === "ipv4") { + for (const info of infos) { + if (info.family === "IPv4") { + return info.address; + } + } + + throw new Error("Could not find " + ipVersion + " address for interface " + this.networkInterface); + } else { + let localUniqueAddress: string | undefined = undefined; + + for (const info of infos) { + if (info.family === "IPv6") { + if (!info.scopeid) { + return info.address; + } else if (!localUniqueAddress) { + localUniqueAddress = info.address; + } + } + } + + if (!localUniqueAddress) { + throw new Error("Could not find " + ipVersion + " address for interface " + this.networkInterface); + } + return localUniqueAddress; + } } private static getLocalNetworkInterface(socket: Socket): string { @@ -571,7 +730,22 @@ class EventedHTTPServerConnection extends EventEmitter { } } - console.log(`WARNING couldn't map socket coming from ${socket.remoteAddress}:${socket.remotePort} at local address ${socket.localAddress} to a interface!`); + // we couldn't map the address from above, we try now to match subnets (see https://github.com/homebridge/HAP-NodeJS/issues/847) + const family = net.isIPv4(localAddress)? "IPv4": "IPv6"; + for (const [name, infos] of Object.entries(interfaces)) { + for (const info of infos) { + if (info.family !== family) { + continue; + } + + // check if the localAddress is in the same subnet + if (getNetAddress(localAddress, info.netmask) === getNetAddress(info.address, info.netmask)) { + return name; + } + } + } + + console.log(`WARNING couldn't map socket coming from remote address ${socket.remoteAddress}:${socket.remotePort} at local address ${socket.localAddress} to a interface!`); return Object.keys(interfaces)[1]; // just use the first interface after the loopback interface as fallback } diff --git a/src/lib/util/hapCrypto.ts b/src/lib/util/hapCrypto.ts index 04ff0c39c..c5f926330 100644 --- a/src/lib/util/hapCrypto.ts +++ b/src/lib/util/hapCrypto.ts @@ -1,7 +1,13 @@ -import crypto from 'crypto'; -import tweetnacl from 'tweetnacl'; import assert from 'assert'; +import crypto from 'crypto'; import hkdf from "futoin-hkdf"; +import tweetnacl from 'tweetnacl'; +import { HAPEncryption } from "./eventedhttp"; + +if (!crypto.getCiphers().includes("chacha20-poly1305")) { + assert.fail("The cipher 'chacha20-poly1305' is not supported with your current running nodejs version v" + process.version + ". " + + "At least a nodejs version of v10.17.0 (excluding v11.0 and v11.1) is required!"); +} export function generateCurve25519KeyPair() { return tweetnacl.box.keyPair(); @@ -21,18 +27,18 @@ type Count = { value: any; } -export function layerEncrypt(data: Buffer, count: Count, key: Buffer) { - var result = Buffer.alloc(0); - var total = data.length; - for (var offset = 0; offset < total; ) { - var length = Math.min(total - offset, 0x400); - var leLength = Buffer.alloc(2); +export function layerEncrypt(data: Buffer, encryption: HAPEncryption) { + let result = Buffer.alloc(0); + const total = data.length; + for (let offset = 0; offset < total; ) { + const length = Math.min(total - offset, 0x400); + const leLength = Buffer.alloc(2); leLength.writeUInt16LE(length,0); - var nonce = Buffer.alloc(8); - writeUInt64LE(count.value++, nonce, 0); + const nonce = Buffer.alloc(8); + writeUInt64LE(encryption.accessoryToControllerCount++, nonce, 0); - const encrypted = chacha20_poly1305_encryptAndSeal(key, nonce, leLength, data.slice(offset, offset + length)); + const encrypted = chacha20_poly1305_encryptAndSeal(encryption.accessoryToControllerKey, nonce, leLength, data.slice(offset, offset + length)); offset += length; result = Buffer.concat([result,leLength,encrypted.ciphertext,encrypted.authTag]); @@ -40,31 +46,28 @@ export function layerEncrypt(data: Buffer, count: Count, key: Buffer) { return result; } -export function layerDecrypt(packet: Buffer, count: Count, key: Buffer, extraInfo: Record) { - // Handle Extra Info - if (extraInfo.leftoverData != undefined) { - packet = Buffer.concat([extraInfo.leftoverData, packet]); +export function layerDecrypt(packet: Buffer, encryption: HAPEncryption) { + if (encryption.incompleteFrame) { + packet = Buffer.concat([encryption.incompleteFrame, packet]); + encryption.incompleteFrame = undefined; } - var result = Buffer.alloc(0); - var total = packet.length; + let result = Buffer.alloc(0); + const total = packet.length; - for (var offset = 0; offset < total;) { - var realDataLength = packet.slice(offset,offset+2).readUInt16LE(0); + for (let offset = 0; offset < total;) { + const realDataLength = packet.slice(offset, offset + 2).readUInt16LE(0); - var availableDataLength = total - offset - 2 - 16; - if (realDataLength > availableDataLength) { - // Fragmented packet - extraInfo.leftoverData = packet.slice(offset); + const availableDataLength = total - offset - 2 - 16; + if (realDataLength > availableDataLength) { // Fragmented packet + encryption.incompleteFrame = packet.slice(offset); break; - } else { - extraInfo.leftoverData = undefined; } - var nonce = Buffer.alloc(8); - writeUInt64LE(count.value++, nonce, 0); + const nonce = Buffer.alloc(8); + writeUInt64LE(encryption.controllerToAccessoryCount++, nonce, 0); - const plaintext = chacha20_poly1305_decryptAndVerify(key, nonce, packet.slice(offset,offset+2), packet.slice(offset + 2, offset + 2 + realDataLength), packet.slice(offset + 2 + realDataLength, offset + 2 + realDataLength + 16)); + const plaintext = chacha20_poly1305_decryptAndVerify(encryption.controllerToAccessoryKey, nonce, packet.slice(offset,offset+2), packet.slice(offset + 2, offset + 2 + realDataLength), packet.slice(offset + 2 + realDataLength, offset + 2 + realDataLength + 16)); result = Buffer.concat([result, plaintext]); offset += (18 + realDataLength); } @@ -103,8 +106,8 @@ export function chacha20_poly1305_encryptAndSeal(key: Buffer, nonce: Buffer, aad }; } -var MAX_UINT32 = 0x00000000FFFFFFFF -var MAX_INT53 = 0x001FFFFFFFFFFFFF +const MAX_UINT32 = 0x00000000FFFFFFFF; +const MAX_INT53 = 0x001FFFFFFFFFFFFF; function onesComplement(number: number) { number = ~number @@ -117,9 +120,9 @@ function onesComplement(number: number) { function uintHighLow(number: number) { assert(number > -1 && number <= MAX_INT53, "number out of range") assert(Math.floor(number) === number, "number must be an integer") - var high = 0 - var signbit = number & 0xFFFFFFFF - var low = signbit < 0 ? (number & 0x7FFFFFFF) + 0x80000000 : signbit + let high = 0; + const signbit = number & 0xFFFFFFFF; + const low = signbit < 0 ? (number & 0x7FFFFFFF) + 0x80000000 : signbit; if (number > MAX_UINT32) { high = (number - low) / (MAX_UINT32 + 1) } @@ -130,9 +133,9 @@ function intHighLow(number: number) { if (number > -1) { return uintHighLow(number) } - var hl = uintHighLow(-number) - var high = onesComplement(hl[0]) - var low = onesComplement(hl[1]) + const hl = uintHighLow(-number); + let high = onesComplement(hl[0]); + let low = onesComplement(hl[1]); if (low === MAX_UINT32) { high += 1 low = 0 @@ -144,13 +147,13 @@ function intHighLow(number: number) { } function writeUInt64BE(number: number, buffer: Buffer, offset: number = 0) { - var hl = uintHighLow(number) + const hl = uintHighLow(number); buffer.writeUInt32BE(hl[0], offset) buffer.writeUInt32BE(hl[1], offset + 4) } export function writeUInt64LE (number: number, buffer: Buffer, offset: number = 0) { - var hl = uintHighLow(number) + const hl = uintHighLow(number); buffer.writeUInt32LE(hl[1], offset) buffer.writeUInt32LE(hl[0], offset + 4) } diff --git a/src/lib/util/hapStatusError.ts b/src/lib/util/hapStatusError.ts new file mode 100644 index 000000000..040961313 --- /dev/null +++ b/src/lib/util/hapStatusError.ts @@ -0,0 +1,26 @@ +import { HAPStatus } from "../HAPServer"; + +/** + * Throws a HAP status error that is sent back to HomeKit. + * + * @example + * ```ts + * throw new HapStatusError(HAPStatus.OPERATION_TIMED_OUT); + * ``` + */ +export class HapStatusError extends Error { + public hapStatus: HAPStatus; + + constructor(status: HAPStatus) { + super('HAP Status Error: ' + status); + + Object.setPrototypeOf(this, HapStatusError.prototype); + + if (status >= HAPStatus.INSUFFICIENT_PRIVILEGES && status <= HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE) { + this.hapStatus = status; + } else { + this.hapStatus = HAPStatus.SERVICE_COMMUNICATION_FAILURE; + } + } +} + diff --git a/src/lib/util/net-utils.spec.ts b/src/lib/util/net-utils.spec.ts new file mode 100644 index 000000000..91ea3bd94 --- /dev/null +++ b/src/lib/util/net-utils.spec.ts @@ -0,0 +1,107 @@ +import { findLoopbackAddress } from "./net-utils"; +import os from "os"; + +const mock = jest.spyOn(os, "networkInterfaces"); + +describe("net-utils", () => { + describe(findLoopbackAddress, () => { + it("should find ipv4 only loopback address", () => { + mock.mockImplementationOnce(() => ({ + "lo": [{ + address: "127.0.0.1", + netmask: "255.0.0.0", + family: "IPv4", + mac: "00:00:00:00:00:00", + internal: true, + cidr: "127.0.0.1/8", + }], + "eth0": [{ + address: "192.168.0.3", + netmask: "255.255.255.0", + family: "IPv4", + mac: "00:00:03:04:00:02", + internal: false, + cidr: "192.168.0.3/24", + }], + })); + + expect(findLoopbackAddress()).toBe("127.0.0.1"); + }); + + it("should prioritize ipv6 link local loopback address", () => { + mock.mockImplementationOnce(() => ({ + "lo": [ + { + address: "127.0.0.1", + netmask: "255.0.0.0", + family: "IPv4", + mac: "00:00:00:00:00:00", + internal: true, + cidr: "127.0.0.1/8", + }, + { + address: "fe80::1", + netmask: 'ffff:ffff:ffff:ffff::', + family: 'IPv6', + mac: '00:00:00:00:00:00', + internal: true, + cidr: 'fe80::1/64', + scopeid: 1 + }, + ], + })); + + expect(findLoopbackAddress()).toBe("fe80::1%lo"); + }); + + it("should prioritize ipv6 loopback address", () => { + mock.mockImplementationOnce(() => ({ + "lo": [ + { + address: "127.0.0.1", + netmask: "255.0.0.0", + family: "IPv4", + mac: "00:00:00:00:00:00", + internal: true, + cidr: "127.0.0.1/8", + }, + { + address: "fe80::1", + netmask: 'ffff:ffff:ffff:ffff::', + family: 'IPv6', + mac: '00:00:00:00:00:00', + internal: true, + cidr: 'fe80::1/64', + scopeid: 1 + }, + { + address: '::1', + netmask: 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', + family: 'IPv6', + mac: '00:00:00:00:00:00', + internal: true, + cidr: '::1/128', + scopeid: 0 + }, + ], + })); + + expect(findLoopbackAddress()).toBe("::1"); + }); + + it("should throw an error if it can't find one", () => { + mock.mockImplementationOnce(() => ({ + "eth0": [{ + address: "192.168.0.3", + netmask: "255.255.255.0", + family: "IPv4", + mac: "00:00:03:04:00:02", + internal: false, + cidr: "192.168.0.3/24", + }], + })); + + expect(() => findLoopbackAddress()).toThrowError(); + }); + }); +}); diff --git a/src/lib/util/net-utils.ts b/src/lib/util/net-utils.ts new file mode 100644 index 000000000..c34fa3844 --- /dev/null +++ b/src/lib/util/net-utils.ts @@ -0,0 +1,46 @@ +import os from "os"; + +export function findLoopbackAddress(): string { + let ipv6: string | undefined = undefined; // ::1/128 + let ipv6LinkLocal: string | undefined = undefined; // fe80::/10 + let ipv4: string | undefined = undefined; // 127.0.0.1/8 + + for (const [name, infos] of Object.entries(os.networkInterfaces())) { + let internal = false; + for (const info of infos) { + if (!info.internal) { + continue; + } + + internal = true; + if (info.family === "IPv4") { + if (!ipv4) { + ipv4 = info.address; + } + } else if (info.family === "IPv6") { + if (info.scopeid) { + if (!ipv6LinkLocal) { + ipv6LinkLocal = info.address + "%" + name; // ipv6 link local addresses are only valid with a scope + } + } else if (!ipv6) { + ipv6 = info.address; + } + } + } + + if (internal) { + break; + } + } + + const address = ipv6 || ipv6LinkLocal || ipv4; + if (!address) { + throw new Error("Could not find a valid loopback address on the platform!"); + } + return address; +} +let loopbackAddress: string | undefined = undefined; // loopback addressed used for the internal http server (::1 or 127.0.0.1) + +export function getOSLoopbackAddress(): string { + return loopbackAddress ?? (loopbackAddress = findLoopbackAddress()); +} diff --git a/src/lib/util/once.ts b/src/lib/util/once.ts index 8d42c2fad..29dfb0dca 100644 --- a/src/lib/util/once.ts +++ b/src/lib/util/once.ts @@ -1,11 +1,10 @@ -export function once(func: Function) { - var called = false; +export function once(func: T) { + let called = false; - return (...args: any[]) => { + return (...args: unknown[]) => { if (called) { throw new Error("This callback function has already been called by someone else; it can only be called one time."); - } - else { + } else { called = true; return func(...args); } diff --git a/src/lib/util/tlv.ts b/src/lib/util/tlv.ts index fddac2a13..a72f582ff 100644 --- a/src/lib/util/tlv.ts +++ b/src/lib/util/tlv.ts @@ -7,7 +7,7 @@ export const EMPTY_TLV_TYPE = 0x00; // and empty tlv with id 0 is usually used a export function encode(type: number, data: Buffer | number | string, ...args: any[]) { - var encodedTLVBuffer = Buffer.alloc(0); + let encodedTLVBuffer = Buffer.alloc(0); // coerce data to Buffer if needed if (typeof data === 'number') @@ -18,9 +18,9 @@ export function encode(type: number, data: Buffer | number | string, ...args: an if (data.length <= 255) { encodedTLVBuffer = Buffer.concat([Buffer.from([type,data.length]),data]); } else { - var leftLength = data.length; - var tempBuffer = Buffer.alloc(0); - var currentStart = 0; + let leftLength = data.length; + let tempBuffer = Buffer.alloc(0); + let currentStart = 0; for (; leftLength > 0;) { if (leftLength >= 255) { @@ -52,18 +52,18 @@ export function encode(type: number, data: Buffer | number | string, ...args: an export function decode(data: Buffer) { - var objects: Record = {}; + const objects: Record = {}; - var leftLength = data.length; - var currentIndex = 0; + let leftLength = data.length; + let currentIndex = 0; for (; leftLength > 0;) { - var type = data[currentIndex]; - var length = data[currentIndex+1]; + const type = data[currentIndex]; + const length = data[currentIndex + 1]; currentIndex += 2; leftLength -= 2; - var newData = data.slice(currentIndex, currentIndex+length); + const newData = data.slice(currentIndex, currentIndex + length); if (objects[type]) { objects[type] = Buffer.concat([objects[type],newData]); diff --git a/src/lib/util/uuid.ts b/src/lib/util/uuid.ts index af52b6fc4..c27387d88 100644 --- a/src/lib/util/uuid.ts +++ b/src/lib/util/uuid.ts @@ -3,6 +3,8 @@ import crypto from 'crypto'; type Binary = Buffer | NodeJS.TypedArray | DataView; export type BinaryLike = string | Binary; +export const BASE_UUID = '-0000-1000-8000-0026BB765291'; + // http://stackoverflow.com/a/25951500/66673 export function generate(data: BinaryLike) { const sha1sum = crypto.createHash('sha1'); @@ -68,7 +70,7 @@ export function write(uuid: string, buf: Buffer = Buffer.alloc(16), offset: numb const SHORT_FORM_REGEX = /^0*([0-9a-f]{1,8})-([0-9a-f]{4}-){3}[0-9a-f]{12}$/i; -export function toShortForm(uuid: string, base?: string) { +export function toShortForm(uuid: string, base: string = BASE_UUID) { if (!isValid(uuid)) throw new TypeError('uuid was not a valid UUID or short form UUID'); if (base && !isValid('00000000' + base)) throw new TypeError('base was not a valid base UUID'); if (base && !uuid.endsWith(base)) return uuid.toUpperCase(); @@ -78,7 +80,7 @@ export function toShortForm(uuid: string, base?: string) { const VALID_SHORT_REGEX = /^[0-9a-f]{1,8}$/i; -export function toLongForm(uuid: string, base: string) { +export function toLongForm(uuid: string, base: string = BASE_UUID) { if (isValid(uuid)) return uuid.toUpperCase(); if (!VALID_SHORT_REGEX.test(uuid)) throw new TypeError('uuid was not a valid UUID or short form UUID'); if (!isValid('00000000' + base)) throw new TypeError('base was not a valid base UUID'); diff --git a/src/types.ts b/src/types.ts index 2a6e1ec10..f7a897405 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,59 +1,31 @@ -import { Status } from './lib/HAPServer'; -import { Characteristic, CharacteristicProps } from './lib/Characteristic'; - export type Nullable = T | null; export type WithUUID = T & { UUID: string }; -export interface ToHAPOptions { - omitValues: boolean; -} - -export type SessionIdentifier = string; // uuid string uniquely identifying every HAP connection -export type MacAddress = string; // format like 'XX:XX:XX:XX:XX:XX' with XX being a valid hexadecimal string +/** + * UUID string uniquely identifying every HAP connection. + */ +export type SessionIdentifier = string; +/** + * Defines a mac address. + * Must have a format like 'XX:XX:XX:XX:XX:XX' with XX being a valid hexadecimal string + */ +export type MacAddress = string; +/** + * Defines a pincode for the HAP accessory. + * Must have a format like "XXX-XX-XXX". + */ +export type HAPPincode = string; +export type InterfaceName = string; +export type IPv4Address = string; +export type IPv6Address = string; +export type IPAddress = IPv4Address | IPv6Address; export type Callback = (...args: any[]) => void; export type NodeCallback = (err: Nullable | undefined, data?: T) => void; export type VoidCallback = (err?: Nullable) => void; -export type PairingsCallback = (err: number, data?: T) => void; export type PrimitiveTypes = string | number | boolean; - -type HAPProps = - Pick - & Pick -export type HapCharacteristic = HAPProps & { - iid: number; - type: string; - value: string | number | {} | null; -} export type CharacteristicValue = PrimitiveTypes | PrimitiveTypes[] | { [key: string]: PrimitiveTypes }; -export type CharacteristicChange = { - newValue: CharacteristicValue; - oldValue: CharacteristicValue; - context?: any; - characteristic: Characteristic; -}; -export type HapService = { - iid: number; - type: string; - - characteristics: HapCharacteristic[]; - primary: boolean; - hidden: boolean; - linked: number[]; -} - -export type CharacteristicData = { - aid: number; - iid: number; - v?: string; - value?: string; - s?: Status; - status?: Status; - e?: string; - ev?: boolean; - r?: boolean; -} /** * @deprecated replaced by {@link AudioStreamingCodec} */ @@ -73,12 +45,14 @@ export type VideoCodec = { */ export type StreamAudioParams = { comfort_noise: boolean; + // noinspection JSDeprecatedSymbols codecs: AudioCodec[]; }; /** * @deprecated replaced by {@link VideoStreamingOptions} */ export type StreamVideoParams = { + // noinspection JSDeprecatedSymbols codec?: VideoCodec; resolutions: [number, number, number][]; // width, height, framerate }; diff --git a/src/types/simple-plist.d.ts b/src/types/simple-plist.d.ts new file mode 100644 index 000000000..818af4c88 --- /dev/null +++ b/src/types/simple-plist.d.ts @@ -0,0 +1,23 @@ +declare module "simple-plist" { + + import { WriteFileOptions } from "fs"; + + export function parse(content: Buffer | string, path: string): any; + + export function readFileSync(path: string): any; + + export function readFile(path: string, callback: (err: Error | null, result: any) => void): void; + + export function writeFileSync(path: string, object: any, options?: WriteFileOptions): void + + export function writeFile(path: string, object: any, callback: (err: NodeJS.ErrnoException | null) => void): void; + + export function writeFile(path: string, object: any, options: WriteFileOptions, callback: (err: NodeJS.ErrnoException | null) => void): void; + + export function writeBinaryFileSync(path: string, object: any, options?: WriteFileOptions): void + + export function writeBinaryFile(path: string, object: any, callback: (err: NodeJS.ErrnoException | null) => void): void; + + export function writeBinaryFile(path: string, object: any, options: WriteFileOptions, callback: (err: NodeJS.ErrnoException | null) => void): void; + +} diff --git a/tsconfig.json b/tsconfig.json index 155f14b77..f514332f4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,8 @@ "compilerOptions": { "target": "ES5", "module": "commonjs", + "downlevelIteration": true, + "importHelpers": true, "lib": [ "es2015", "es2016",