From 00bf51dd25f0a43afd1528719bfd97eb6a82dc27 Mon Sep 17 00:00:00 2001 From: Supereg Date: Mon, 21 Sep 2020 14:20:30 +0200 Subject: [PATCH 01/70] Mark the start of beta 0.8.3 --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 068639034..9c071971c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "hap-nodejs", "version": "0.8.2", + "betaVersion": "0.8.3", "description": "HAP-NodeJS is a Node.js implementation of HomeKit Accessory Server.", "main": "dist/index.js", "types": "dist/index.d.ts", From 52f61fd6b7327f80954a0e98e7eb561bcf822188 Mon Sep 17 00:00:00 2001 From: Supereg Date: Mon, 21 Sep 2020 14:42:07 +0200 Subject: [PATCH 02/70] Doing a subnet check as fallback if local interface detection fails for HAP socket --- src/lib/util/eventedhttp.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/lib/util/eventedhttp.ts b/src/lib/util/eventedhttp.ts index a92d64f07..1b5502bed 100644 --- a/src/lib/util/eventedhttp.ts +++ b/src/lib/util/eventedhttp.ts @@ -1,3 +1,4 @@ +import { getNetAddress } from "@homebridge/ciao/lib/util/domain-formatter"; import createDebug from 'debug'; import { SrpServer } from "fast-srp-hap"; import http, { IncomingMessage, OutgoingMessage, ServerResponse } from 'http'; @@ -558,7 +559,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 } From eb9dd8a785f08f4fc9439a235fb32c52f03b39f3 Mon Sep 17 00:00:00 2001 From: Supereg Date: Mon, 21 Sep 2020 14:56:15 +0200 Subject: [PATCH 03/70] Adding version note to some of the latest characteristics and services --- src/lib/gen/HomeKit.ts | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/lib/gen/HomeKit.ts b/src/lib/gen/HomeKit.ts index 866c915a9..520bb336e 100644 --- a/src/lib/gen/HomeKit.ts +++ b/src/lib/gen/HomeKit.ts @@ -3559,6 +3559,7 @@ Characteristic.WiFiSatelliteStatus = WiFiSatelliteStatus; /** * Characteristic "Wake Configuration" + * @since iOS 13.4 */ export class WakeConfiguration extends Characteristic { @@ -3579,6 +3580,7 @@ Characteristic.WakeConfiguration = WakeConfiguration; /** * Characteristic "Supported Transfer Transport Configuration" + * @since iOS 13.4 */ export class SupportedTransferTransportConfiguration extends Characteristic { @@ -3599,6 +3601,7 @@ Characteristic.SupportedTransferTransportConfiguration = SupportedTransferTransp /** * Characteristic "Setup Transfer Transport" + * @since iOS 13.4 */ export class SetupTransferTransport extends Characteristic { @@ -3619,6 +3622,7 @@ Characteristic.SetupTransferTransport = SetupTransferTransport; /** * Characteristic "Activity Interval" + * @since iOS 14 */ export class ActivityInterval extends Characteristic { @@ -3641,6 +3645,7 @@ Characteristic.ActivityInterval = ActivityInterval; /** * Characteristic "CCA Energy Detect Threshold" + * @since iOS 14 */ export class CCAEnergyDetectThreshold extends Characteristic { @@ -3661,6 +3666,7 @@ Characteristic.CCAEnergyDetectThreshold = CCAEnergyDetectThreshold; /** * Characteristic "CCA Signal Detect Threshold" + * @since iOS 14 */ export class CCASignalDetectThreshold extends Characteristic { @@ -3681,6 +3687,7 @@ Characteristic.CCASignalDetectThreshold = CCASignalDetectThreshold; /** * Characteristic "Characteristic Value Transition Control" + * @since iOS 14 */ export class CharacteristicValueTransitionControl extends Characteristic { @@ -3690,7 +3697,7 @@ export class CharacteristicValueTransitionControl extends Characteristic { super("Characteristic Value Transition Control", CharacteristicValueTransitionControl.UUID); this.setProps({ format: Formats.TLV8, - perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY], + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE], }); this.value = this.getDefaultValue(); } @@ -3701,6 +3708,7 @@ Characteristic.CharacteristicValueTransitionControl = CharacteristicValueTransit /** * Characteristic "Supported Characteristic Value Transition Configuration" + * @since iOS 14 */ export class SupportedCharacteristicValueTransitionConfiguration extends Characteristic { @@ -3721,6 +3729,7 @@ Characteristic.SupportedCharacteristicValueTransitionConfiguration = SupportedCh /** * Characteristic "Current Transport" + * @since iOS 14 */ export class CurrentTransport extends Characteristic { @@ -3741,6 +3750,7 @@ Characteristic.CurrentTransport = CurrentTransport; /** * Characteristic "Data Stream HAP Transport" + * @since iOS 14 */ export class DataStreamHAPTransport extends Characteristic { @@ -3761,6 +3771,7 @@ Characteristic.DataStreamHAPTransport = DataStreamHAPTransport; /** * Characteristic "Data Stream HAP Transport Interrupt" + * @since iOS 14 */ export class DataStreamHAPTransportInterrupt extends Characteristic { @@ -3781,6 +3792,7 @@ Characteristic.DataStreamHAPTransportInterrupt = DataStreamHAPTransportInterrupt /** * Characteristic "Event Retransmission Maximum" + * @since iOS 14 */ export class EventRetransmissionMaximum extends Characteristic { @@ -3801,6 +3813,7 @@ Characteristic.EventRetransmissionMaximum = EventRetransmissionMaximum; /** * Characteristic "Event Transmission Counters" + * @since iOS 14 */ export class EventTransmissionCounters extends Characteristic { @@ -3821,6 +3834,7 @@ Characteristic.EventTransmissionCounters = EventTransmissionCounters; /** * Characteristic "Heart Beat" + * @since iOS 14 */ export class HeartBeat extends Characteristic { @@ -3841,6 +3855,7 @@ Characteristic.HeartBeat = HeartBeat; /** * Characteristic "MAC Retransmission Maximum" + * @since iOS 14 */ export class MACRetransmissionMaximum extends Characteristic { @@ -3861,6 +3876,7 @@ Characteristic.MACRetransmissionMaximum = MACRetransmissionMaximum; /** * Characteristic "MAC Transmission Counters" + * @since iOS 14 */ export class MACTransmissionCounters extends Characteristic { @@ -3881,6 +3897,7 @@ Characteristic.MACTransmissionCounters = MACTransmissionCounters; /** * Characteristic "Operating State Response" + * @since iOS 14 */ export class OperatingStateResponse extends Characteristic { @@ -3901,6 +3918,7 @@ Characteristic.OperatingStateResponse = OperatingStateResponse; /** * Characteristic "Ping" + * @since iOS 14 */ export class Ping extends Characteristic { @@ -3921,6 +3939,7 @@ Characteristic.Ping = Ping; /** * Characteristic "Receiver Sensitivity" + * @since iOS 14 */ export class ReceiverSensitivity extends Characteristic { @@ -3941,6 +3960,7 @@ Characteristic.ReceiverSensitivity = ReceiverSensitivity; /** * Characteristic "Received Signal Strength Indication" + * @since iOS 14 */ export class ReceivedSignalStrengthIndication extends Characteristic { @@ -3961,6 +3981,7 @@ Characteristic.ReceivedSignalStrengthIndication = ReceivedSignalStrengthIndicati /** * Characteristic "Sleep Interval" + * @since iOS 14 */ export class SleepInterval extends Characteristic { @@ -3983,6 +4004,7 @@ Characteristic.SleepInterval = SleepInterval; /** * Characteristic "Signal-to-noise Ration" + * @since iOS 14 */ export class SignalToNoiseRatio extends Characteristic { @@ -4003,6 +4025,7 @@ Characteristic.SignalToNoiseRatio = SignalToNoiseRatio; /** * Characteristic "Supported Diagnostics Snapshot" + * @since iOS 14 */ export class SupportedDiagnosticsSnapshot extends Characteristic { @@ -4023,6 +4046,7 @@ Characteristic.SupportedDiagnosticsSnapshot = SupportedDiagnosticsSnapshot; /** * Characteristic "Transmit Power" + * @since iOS 14 */ export class TransmitPower extends Characteristic { @@ -4043,6 +4067,7 @@ Characteristic.TransmitPower = TransmitPower; /** * Characteristic "Transmit Power Maximum" + * @since iOS 14 */ export class TransmitPowerMaximum extends Characteristic { @@ -4063,6 +4088,7 @@ Characteristic.TransmitPowerMaximum = TransmitPowerMaximum; /** * Characteristic "Video Analysis Active" + * @since iOS 14 */ export class VideoAnalysisActive extends Characteristic { @@ -4083,6 +4109,7 @@ Characteristic.VideoAnalysisActive = VideoAnalysisActive; /** * Characteristic "Wi-Fi Capabilities" + * @since iOS 14 */ export class WiFiCapabilities extends Characteristic { @@ -4103,6 +4130,7 @@ Characteristic.WiFiCapabilities = WiFiCapabilities; /** * Characteristic "Wi-Fi Configuration Control" + * @since iOS 14 */ export class WiFiConfigurationControl extends Characteristic { @@ -4960,6 +4988,7 @@ Service.SmokeSensor = SmokeSensor; /** * Service "Smart Speaker" + * @since iOS 13.4 */ export class SmartSpeaker extends Service { @@ -5284,6 +5313,7 @@ Service.WiFiSatellite = WiFiSatellite; /** * Service "Power Management" + * @since iOS 13.4 */ export class PowerManagement extends Service { @@ -5302,6 +5332,7 @@ Service.PowerManagement = PowerManagement; /** * Service "Transfer Transport Management" + * @since iOS 13.4 */ export class TransferTransportManagement extends Service { @@ -5321,6 +5352,7 @@ Service.TransferTransportManagement = TransferTransportManagement; /** * Service "Accessory Runtime Information" + * @since iOS 14 */ export class AccessoryRuntimeInformation extends Service { @@ -5344,6 +5376,7 @@ Service.AccessoryRuntimeInformation = AccessoryRuntimeInformation; /** * Service "Diagnostics" + * @since iOS 14 */ export class Diagnostics extends Service { @@ -5362,6 +5395,7 @@ Service.Diagnostics = Diagnostics; /** * Service "Wi-Fi Transport" + * @since iOS 14 */ export class WiFiTransport extends Service { From 059def94974dc475745f1d5d34fca00c783a2b05 Mon Sep 17 00:00:00 2001 From: Supereg Date: Mon, 21 Sep 2020 14:59:53 +0200 Subject: [PATCH 04/70] Updated service and characteristic definitions to latest iOS 14 release --- src/lib/Characteristic.ts | 1 + src/lib/gen/HomeKit.ts | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts index ece0bc6ae..d8ad9bb94 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -376,6 +376,7 @@ export class Characteristic extends EventEmitter { static CCASignalDetectThreshold: typeof HomeKitTypes.Generated.CCASignalDetectThreshold; static CharacteristicValueTransitionControl: typeof HomeKitTypes.Generated.CharacteristicValueTransitionControl; static SupportedCharacteristicValueTransitionConfiguration: typeof HomeKitTypes.Generated.SupportedCharacteristicValueTransitionConfiguration; + static CharacteristicValueActiveTransitionCount: typeof HomeKitTypes.Generated.CharacteristicValueActiveTransitionCount; static CurrentTransport: typeof HomeKitTypes.Generated.CurrentTransport; static DataStreamHAPTransport: typeof HomeKitTypes.Generated.DataStreamHAPTransport; static DataStreamHAPTransportInterrupt: typeof HomeKitTypes.Generated.DataStreamHAPTransportInterrupt; diff --git a/src/lib/gen/HomeKit.ts b/src/lib/gen/HomeKit.ts index 520bb336e..cc262d0b2 100644 --- a/src/lib/gen/HomeKit.ts +++ b/src/lib/gen/HomeKit.ts @@ -3727,6 +3727,27 @@ export class SupportedCharacteristicValueTransitionConfiguration extends Charact Characteristic.SupportedCharacteristicValueTransitionConfiguration = SupportedCharacteristicValueTransitionConfiguration; +/** + * Characteristic "Characteristic Value Active Transition Count" + * @since iOS 14 + */ +export class CharacteristicValueActiveTransitionCount extends Characteristic { + + static readonly UUID: string = '0000021E-0000-1000-8000-0000024B'; + + constructor() { + super("Characteristic Value Active Transition Count", CharacteristicValueActiveTransitionCount.UUID); + this.setProps({ + format: Formats.UINT8, + perms: [Perms.NOTIFY, Perms.PAIRED_READ], + }) + this.value = this.getDefaultValue(); + } + +} + +Characteristic.CharacteristicValueActiveTransitionCount = CharacteristicValueActiveTransitionCount; + /** * Characteristic "Current Transport" * @since iOS 14 @@ -4739,6 +4760,7 @@ export class Lightbulb extends Service { this.addOptionalCharacteristic(Characteristic.Saturation); this.addOptionalCharacteristic(Characteristic.Name); this.addOptionalCharacteristic(Characteristic.ColorTemperature); + this.addOptionalCharacteristic(Characteristic.CharacteristicValueActiveTransitionCount); // Ambient Lightning this.addOptionalCharacteristic(Characteristic.CharacteristicValueTransitionControl); // Ambient Lightning this.addOptionalCharacteristic(Characteristic.SupportedCharacteristicValueTransitionConfiguration); // Ambient Lightning } From 8ee888800c9f690d779c3aaaaeccbccdca242662 Mon Sep 17 00:00:00 2001 From: Supereg Date: Mon, 21 Sep 2020 15:13:30 +0200 Subject: [PATCH 05/70] Notify active streaming session that the application is about to exit --- src/lib/Accessory.ts | 4 ++++ src/lib/camera/RTPStreamManagement.ts | 8 +++++++- src/lib/controller/CameraController.ts | 4 ++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index 61a383f08..01438ab43 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -1017,6 +1017,10 @@ export class Accessory extends EventEmitter { } unpublish = () => { + if (this.activeCameraController) { + this.activeCameraController.handleShutdown(); + } + if (this._server) { this._server.stop(); this._server = undefined; diff --git a/src/lib/camera/RTPStreamManagement.ts b/src/lib/camera/RTPStreamManagement.ts index 32c992139..46216c5e3 100644 --- a/src/lib/camera/RTPStreamManagement.ts +++ b/src/lib/camera/RTPStreamManagement.ts @@ -531,12 +531,18 @@ export class RTPStreamManagement { // Private - handleCloseConnection(connectionID: SessionIdentifier) { + handleCloseConnection(connectionID: SessionIdentifier): void { if (this.connectionID && this.connectionID === connectionID) { this._handleStopStream(); } } + handleShutdown(): void { + if (this.connectionID) { + this._handleStopStream(); + } + } + handleFactoryReset() { this.selectedConfiguration = null; this.setupEndpointsResponse = RTPStreamManagement.initialSetupEndpointsResponse(); diff --git a/src/lib/controller/CameraController.ts b/src/lib/controller/CameraController.ts index 6c57c48ef..da643df42 100644 --- a/src/lib/controller/CameraController.ts +++ b/src/lib/controller/CameraController.ts @@ -362,4 +362,8 @@ export class CameraController extends EventEmitter imp } } + handleShutdown(): void { + this.streamManagements.forEach(management => management.handleShutdown()); + } + } From 0afceab360ef3cbda03605a321632887cd9cd539 Mon Sep 17 00:00:00 2001 From: Supereg Date: Mon, 21 Sep 2020 15:37:38 +0200 Subject: [PATCH 06/70] Adding source-map-support for better stack strace translation --- package-lock.json | 49 +++++++++++++++++++++++++---------------------- package.json | 13 +++++++------ src/index.ts | 1 + 3 files changed, 34 insertions(+), 29 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4de4cce0c..2e278044a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -396,9 +396,9 @@ } }, "@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.0.10", + "resolved": "https://registry.npmjs.org/@homebridge/ciao/-/ciao-1.0.10.tgz", + "integrity": "sha512-Fe6VdnckLUPsDk8ty0d1YtfeGFEpe3d4x/mXzQRUF9hl6FrhFUFiAA5mSxXNZ1ZoZScImi+SlBuBTuJo99e1Mg==", "requires": { "debug": "^4.1.1", "fast-deep-equal": "^3.1.3" @@ -999,9 +999,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", @@ -1472,8 +1472,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", @@ -1727,11 +1726,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": { @@ -5366,8 +5365,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 +5384,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" @@ -5694,22 +5691,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.0", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.4.0.tgz", + "integrity": "sha512-ofBzoCqf6Nv/PoWb/ByV3VNKy2KJSikamOBxvR3E6eVdIw10GwAXoyvMWXXjZJK2s6S27ZE8fI+JBTnGaovl6Q==", "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 +5714,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 } } }, @@ -5778,9 +5781,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": { diff --git a/package.json b/package.json index 9c071971c..630dd23a9 100644 --- a/package.json +++ b/package.json @@ -49,25 +49,26 @@ "@types" ], "dependencies": { - "@homebridge/ciao": "~1.0.5", + "@homebridge/ciao": "~1.0.10", "fast-srp-hap": "2.0.2", "tweetnacl": "^1.0.3", - "debug": "^4.1.1", + "debug": "^4.3.0", "decimal.js": "^10.2.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" }, "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", "rimraf": "^3.0.2", "semver": "^7.3.2", "simple-plist": "^1.1.0", - "ts-jest": "^26.3.0", + "ts-jest": "^26.4.0", "ts-node": "^9.0.0", - "typescript": "^4.0.2" + "typescript": "^4.0.3" } } diff --git a/src/index.ts b/src/index.ts index 525a53b36..5b6554d0c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ +import "source-map-support/register"; // registering node-source-map-support for typescript stack traces import './lib/gen'; import * as accessoryLoader from './lib/AccessoryLoader'; import * as uuidFunctions from './lib/util/uuid'; From 1fa08d07fa44b8ecfc0444ae8b315aa30092927f Mon Sep 17 00:00:00 2001 From: Supereg Date: Mon, 21 Sep 2020 15:39:47 +0200 Subject: [PATCH 07/70] Add some little deprecation notice --- src/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/index.ts b/src/index.ts index 5b6554d0c..8b7520883 100644 --- a/src/index.ts +++ b/src/index.ts @@ -35,6 +35,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); } From 5eabcf8a4492c5337d9433bb360c81415b5c45cf Mon Sep 17 00:00:00 2001 From: Supereg Date: Mon, 21 Sep 2020 15:47:18 +0200 Subject: [PATCH 08/70] Mitigating warning for memory leak on the UNPAIRED event --- src/lib/Accessory.ts | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index 01438ab43..ab3035d8a 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -641,16 +641,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 @@ -658,6 +648,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 @@ -1131,6 +1134,11 @@ 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(); + } } }; From 0843e2a12bef99cb501501627c373e6061368b05 Mon Sep 17 00:00:00 2001 From: Supereg Date: Wed, 23 Sep 2020 17:53:56 +0200 Subject: [PATCH 09/70] Introducing new "bind" publish option, in oder to bind/advertise the hap-nodejs to a certain interface(s)/ip address(es) --- package-lock.json | 14 +++- package.json | 5 +- src/lib/Accessory.ts | 156 +++++++++++++++++++++++++++++++++--- src/lib/Advertiser.ts | 28 +++---- src/lib/HAPServer.ts | 26 +++--- src/lib/util/eventedhttp.ts | 12 +-- src/types.ts | 20 ++++- tsconfig.json | 2 + 8 files changed, 211 insertions(+), 52 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2e278044a..647f04567 100644 --- a/package-lock.json +++ b/package-lock.json @@ -396,12 +396,13 @@ } }, "@homebridge/ciao": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/@homebridge/ciao/-/ciao-1.0.10.tgz", - "integrity": "sha512-Fe6VdnckLUPsDk8ty0d1YtfeGFEpe3d4x/mXzQRUF9hl6FrhFUFiAA5mSxXNZ1ZoZScImi+SlBuBTuJo99e1Mg==", + "version": "1.1.0-beta.6", + "resolved": "https://registry.npmjs.org/@homebridge/ciao/-/ciao-1.1.0-beta.6.tgz", + "integrity": "sha512-1fsRO1QEp7DQlF44Iyf9Ji+vY7OEHsacpBWskWWoxy/Av6EqKt/uX3PaVZAUHYH5rnf7RBLgeU+Qj11m5L4GkA==", "requires": { "debug": "^4.1.1", - "fast-deep-equal": "^3.1.3" + "fast-deep-equal": "^3.1.3", + "source-map-support": "^0.5.19" }, "dependencies": { "fast-deep-equal": { @@ -5736,6 +5737,11 @@ "yn": "3.1.1" } }, + "tslib": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==" + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", diff --git a/package.json b/package.json index 630dd23a9..de813a01b 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "@types" ], "dependencies": { - "@homebridge/ciao": "~1.0.10", + "@homebridge/ciao": "~1.1.0-beta.6", "fast-srp-hap": "2.0.2", "tweetnacl": "^1.0.3", "debug": "^4.3.0", @@ -57,7 +57,8 @@ "ip": "^1.1.3", "node-persist": "^0.0.11", "futoin-hkdf": "~1.3.2", - "source-map-support": "^0.5.19" + "source-map-support": "^0.5.19", + "tslib": "^2.0.1" }, "devDependencies": { "@types/debug": "^4.1.5", diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index ab3035d8a..46ee12596 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -2,7 +2,8 @@ import { MDNSServerOptions } from "@homebridge/ciao"; import crypto from 'crypto'; import createDebug from 'debug'; import assert from "assert"; - +import net from "net"; +import os from "os"; import * as uuid from './util/uuid'; import { clone } from './util/clone'; import { SerializedService, Service, ServiceConfigurationChange, ServiceEventTypes, ServiceId } from './Service'; @@ -14,7 +15,7 @@ import { IdentifierCache } from './model/IdentifierCache'; import { CharacteristicChange, CharacteristicData, - CharacteristicValue, MacAddress, + CharacteristicValue, HAPPincode, InterfaceName, IPAddress, MacAddress, NodeCallback, Nullable, PairingsCallback, @@ -124,13 +125,14 @@ export const enum AccessoryEventTypes { type Events = { identify: (paired:boolean, cb: VoidCallback) => void; - listening: (port: number) => void; + listening: (port: number, hostname: string) => void; "service-configurationChange": VoidCallback; "service-characteristic-change": (change: ServiceCharacteristicChange) => void; [AccessoryEventTypes.PAIRED]: () => void; [AccessoryEventTypes.UNPAIRED]: () => void; } +// noinspection JSUnusedGlobalSymbols /** * @deprecated Use AccessoryEventTypes instead */ @@ -140,10 +142,74 @@ export type CharacteristicEvents = Record; export interface PublishInfo { username: MacAddress; - pincode: string; + pincode: HAPPincode; + /** + * 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 169.254.104.90. + * The application will throw an error if the address is not available at startup. + * 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, + * the mdns advertisement will result in not advertising a address at all. + * 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. + */ + 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; } @@ -875,6 +941,7 @@ export class Accessory extends EventEmitter { * 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} info.pincode - The 8-digit pincode for clients to use when pairing this Accessory. Must be formatted @@ -885,7 +952,10 @@ export class Accessory extends EventEmitter { * new Accessory. */ publish = (info: PublishInfo, allowInsecureRequest?: boolean) => { - // TODO maybe directly enqueue the method call on nextTick (could solve most out of order constructions) + 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) { @@ -971,7 +1041,13 @@ export class Accessory extends EventEmitter { 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) { @@ -999,7 +1075,7 @@ export class Accessory extends EventEmitter { this._server.on(HAPServerEventTypes.SESSION_CLOSE, this._handleSessionClose.bind(this)); this._server.on(HAPServerEventTypes.REQUEST_RESOURCE, this._handleResource.bind(this)); - this._server.listen(info.port || 0); + this._server.listen(info.port, parsed.serverAddress); } /** @@ -1057,12 +1133,12 @@ export class Accessory extends EventEmitter { } } - _onListening = (port: number) => { + _onListening = (port: number, hostname: string) => { 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); this._advertiser!.startAdvertising(); - this.emit(AccessoryEventTypes.LISTENING, port); + this.emit(AccessoryEventTypes.LISTENING, port, hostname); } // Called when an unpaired client wishes for us to identify ourself @@ -1787,6 +1863,66 @@ 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]); + + 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); + + if (entries.size === 1) { + const entry = entries.values().next().value; // grab the first one + + if (net.isIP(entry)) { + serverAddress = entry; + } else { + serverAddress = "::"; // the interface could have both ipv4 and ipv6 addresses + } + } else { + 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 (bindUnspecifiedIpv6) { + serverAddress = "::"; + } else { + serverAddress = "0.0.0.0"; + } + } + } + } + + return { + advertiserAddress: advertiserAddress, + serviceRestrictedAddress: advertiserAddress, + serviceDisableIpv6: disableIpv6, + serverAddress: serverAddress, + }; + } + } const numberPattern = /^-?\d+$/; diff --git a/src/lib/Advertiser.ts b/src/lib/Advertiser.ts index f4c0d94c8..388d6c4ff 100644 --- a/src/lib/Advertiser.ts +++ b/src/lib/Advertiser.ts @@ -6,6 +6,7 @@ import ciao, { ServiceTxt, ServiceType } from "@homebridge/ciao"; +import { ServiceOptions } from "@homebridge/ciao/lib/CiaoService"; import assert from "assert"; import crypto from 'crypto'; import { EventEmitter } from "events"; @@ -54,33 +55,32 @@ 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 = this.computeSetupHash(); - } - - public initAdvertiser(port: number): void { - assert(!this.advertisedService, "Service was already created!"); + 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 initPort(port: number): void { + this.advertisedService.updatePort(port); + } + public startAdvertising(): Promise { - assert(this.advertisedService, "Cannot create advertisement when the service wasn't created yet!"); return this.advertisedService!.advertise(); } @@ -89,17 +89,15 @@ export class Advertiser extends EventEmitter { } 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(); } diff --git a/src/lib/HAPServer.ts b/src/lib/HAPServer.ts index 8c0e12a3b..c15444671 100644 --- a/src/lib/HAPServer.ts +++ b/src/lib/HAPServer.ts @@ -1,19 +1,17 @@ import crypto from 'crypto'; - import createDebug from 'debug'; +import { SRP, SrpServer } from "fast-srp-hap"; +import { IncomingMessage, ServerResponse } from "http"; import tweetnacl from 'tweetnacl'; import url from 'url'; - -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 } from '../types'; +import { Accessory, CharacteristicEvents, Resource } from './Accessory'; import { EventEmitter } from './EventEmitter'; import { PairingInformation, PermissionTypes } from "./model/AccessoryInfo"; -import { SRP, SrpServer } from "fast-srp-hap"; +import { EventedHTTPServer, EventedHTTPServerEvents, Session } from './util/eventedhttp'; +import * as hapCrypto from './util/hapCrypto'; +import { once } from './util/once'; +import * as tlv from './util/tlv'; const debug = createDebug('HAP-NodeJS:HAPServer'); @@ -109,7 +107,7 @@ export const enum HAPServerEventTypes { export type Events = { [HAPServerEventTypes.IDENTIFY]: (cb: VoidCallback) => void; - [HAPServerEventTypes.LISTENING]: (port: number) => void; + [HAPServerEventTypes.LISTENING]: (port: number, hostname: string) => 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; @@ -232,8 +230,8 @@ export class HAPServer extends EventEmitter { this._keepAliveTimerID = setInterval(this._onKeepAliveTimerTick, 1000 * 60 * 10); // send keepalive every 10 minutes } - listen = (port: number) => { - this._httpServer.listen(port); + listen = (port: number = 0, host?: string) => { + this._httpServer.listen(port, host); } stop = () => { @@ -258,8 +256,8 @@ export class HAPServer extends EventEmitter { this._httpServer.sendEvent(event, JSON.stringify(data), "application/hap+json", excludeEvents); } - _onListening = (port: number) => { - this.emit(HAPServerEventTypes.LISTENING, port); + _onListening = (port: number, hostname: string) => { + this.emit(HAPServerEventTypes.LISTENING, port, hostname); } // Called when an HTTP request was detected. diff --git a/src/lib/util/eventedhttp.ts b/src/lib/util/eventedhttp.ts index 1b5502bed..0b4556138 100644 --- a/src/lib/util/eventedhttp.ts +++ b/src/lib/util/eventedhttp.ts @@ -21,7 +21,7 @@ export const enum EventedHTTPServerEvents { } export type Events = { - [EventedHTTPServerEvents.LISTENING]: (port: number) => void; + [EventedHTTPServerEvents.LISTENING]: (port: number, hostname: string) => 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; @@ -81,16 +81,18 @@ export class EventedHTTPServer extends EventEmitter { this._connections = []; // track all open connections (for sending events) } - listen = (targetPort: number) => { - this._tcpServer.listen(targetPort); + listen = (targetPort: number, hostname?: string) => { + this._tcpServer.listen(targetPort, hostname); this._tcpServer.on('listening', () => { const address = this._tcpServer.address(); if (address && typeof address !== 'string') { + const hostname = address.address; const port = address.port; - debug("Server listening on port %s", port); - this.emit(EventedHTTPServerEvents.LISTENING, port); + + debug("Server listening on %s:%s", address.family === "IPv6"? `[${hostname}]`: hostname, port); + this.emit(EventedHTTPServerEvents.LISTENING, port, hostname); } }); diff --git a/src/types.ts b/src/types.ts index 2a6e1ec10..5ac437a71 100644 --- a/src/types.ts +++ b/src/types.ts @@ -9,8 +9,24 @@ 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; 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", From e858e39576d1b4282d477b78d0e432f417721a03 Mon Sep 17 00:00:00 2001 From: Supereg Date: Wed, 23 Sep 2020 18:26:16 +0200 Subject: [PATCH 10/70] Minor code cleanup --- src/BridgedCore.ts | 6 +- src/Core.ts | 10 +- src/accessories/AirConditioner_accessory.ts | 23 +- src/accessories/Fan_accessory.ts | 13 +- src/accessories/GarageDoorOpener_accessory.ts | 10 +- src/accessories/Light_accessory.ts | 4 +- src/accessories/Lock_accessory.ts | 10 +- src/accessories/MotionSensor_accessory.ts | 8 +- src/accessories/Outlet_accessory.ts | 32 +- src/accessories/Sprinkler_accessory.ts | 37 +-- src/accessories/TV_accessory.ts | 14 +- .../TemperatureSensor_accessory.ts | 12 +- src/accessories/Thermostat_accessory.ts | 148 --------- src/accessories/Wi-FiRouter_accessory.ts | 10 +- src/accessories/Wi-FiSatellite_accessory.ts | 10 +- src/accessories/gstreamer-audioProducer.ts | 2 +- src/lib/Accessory.ts | 218 ++++++------- src/lib/AccessoryLoader.ts | 47 ++- src/lib/Advertiser.spec.ts | 1 - src/lib/Advertiser.ts | 1 - src/lib/Characteristic.spec.ts | 4 +- src/lib/Characteristic.ts | 86 +++--- src/lib/HAPServer.ts | 175 ++++++----- src/lib/Service.ts | 49 +-- src/lib/camera/Camera.ts | 5 + src/lib/camera/RTPStreamManagement.ts | 1 + src/lib/datastream/DataStreamManagement.ts | 2 +- src/lib/datastream/DataStreamServer.ts | 2 + src/lib/gen/HomeKit-Bridge.ts | 42 +-- src/lib/gen/HomeKit-TV.ts | 34 +- src/lib/gen/HomeKit.ts | 292 +++++++++--------- src/lib/gen/importAsClasses.ts | 58 ++-- src/lib/model/AccessoryInfo.ts | 23 +- src/lib/model/ControllerStorage.ts | 4 +- src/lib/model/IdentifierCache.ts | 30 +- src/lib/tv/AccessControlManagement.ts | 1 + src/lib/util/clone.ts | 8 +- src/lib/util/eventedhttp.ts | 2 +- src/lib/util/hapCrypto.ts | 44 +-- src/lib/util/once.ts | 2 +- src/lib/util/tlv.ts | 20 +- src/types.ts | 2 + 42 files changed, 663 insertions(+), 839 deletions(-) delete mode 100644 src/accessories/Thermostat_accessory.ts diff --git a/src/BridgedCore.ts b/src/BridgedCore.ts index 34cb6e6ea..208c5910e 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) => { @@ -38,7 +38,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 493faa463..215d6d9ab 100644 --- a/src/Core.ts +++ b/src/Core.ts @@ -13,11 +13,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) => { @@ -45,10 +45,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..d63dbc015 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 @@ -51,7 +53,7 @@ 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 +70,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 +92,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 +148,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..aa197214d 100644 --- a/src/accessories/Fan_accessory.ts +++ b/src/accessories/Fan_accessory.ts @@ -12,15 +12,14 @@ import { 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 +33,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 @@ -79,7 +78,7 @@ fan // 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 (FAKE_FAN.powerOn) { callback(err, true); 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..6ab9657c1 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 diff --git a/src/accessories/Lock_accessory.ts b/src/accessories/Lock_accessory.ts index de3713ce0..fb5b6d542 100644 --- a/src/accessories/Lock_accessory.ts +++ b/src/accessories/Lock_accessory.ts @@ -11,7 +11,7 @@ import { 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 +24,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 @@ -93,7 +93,7 @@ lock // 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 + const err = null; // in case there were any problems if (FAKE_LOCK.locked) { console.log("Are we locked? Yes."); 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..fd4e682fa 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 @@ -83,7 +87,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/Sprinkler_accessory.ts b/src/accessories/Sprinkler_accessory.ts index b515258f5..5829b518b 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 @@ -46,23 +46,21 @@ 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 +116,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 +131,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 +152,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..28e55abac 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 @@ -30,7 +30,7 @@ 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 +81,7 @@ televisionService // Speaker -var speakerService = tv.addService(Service.TelevisionSpeaker) +const speakerService = tv.addService(Service.TelevisionSpeaker); speakerService .setCharacteristic(Characteristic.Active, Characteristic.Active.ACTIVE) @@ -95,7 +95,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 +105,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 +115,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..1f12a86c3 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 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/lib/Accessory.ts b/src/lib/Accessory.ts index 46ee12596..1a4db3275 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -1,21 +1,16 @@ import { MDNSServerOptions } from "@homebridge/ciao"; +import assert from "assert"; import crypto from 'crypto'; import createDebug from 'debug'; -import assert from "assert"; import net from "net"; -import os from "os"; -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 } from './HAPServer'; -import { AccessoryInfo, PairingInformation, PermissionTypes } from './model/AccessoryInfo'; -import { IdentifierCache } from './model/IdentifierCache'; import { CharacteristicChange, CharacteristicData, - CharacteristicValue, HAPPincode, InterfaceName, IPAddress, MacAddress, + CharacteristicValue, + HAPPincode, + InterfaceName, + IPAddress, + MacAddress, NodeCallback, Nullable, PairingsCallback, @@ -24,24 +19,29 @@ import { VoidCallback, WithUUID, } 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 { 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"; +import { EventEmitter } from './EventEmitter'; +import { CameraEventRecordingManagement, CameraOperatingMode, CameraRTPStreamManagement, } from "./gen/HomeKit"; +import { CharacteristicsWriteRequest, Codes, HAPServer, HAPServerEventTypes, Status } from './HAPServer'; +import { AccessoryInfo, PairingInformation, PermissionTypes } from './model/AccessoryInfo'; import { ControllerStorage } from "./model/ControllerStorage"; +import { IdentifierCache } from './model/IdentifierCache'; +import { SerializedService, Service, ServiceConfigurationChange, ServiceEventTypes, ServiceId } from './Service'; +import { clone } from './util/clone'; +import { Session } from "./util/eventedhttp"; +import * as uuid from './util/uuid'; const debug = createDebug('HAP-NodeJS:Accessory'); const MAX_ACCESSORIES = 149; // Maximum number of bridged accessories per bridge. @@ -339,8 +339,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) @@ -432,8 +432,8 @@ export class Accessory extends EventEmitter { } getService = >(name: string | T) => { - for (var index in this.services) { - var service = this.services[index]; + for (let index in this.services) { + const service = this.services[index]; if (typeof name === 'string' && (service.displayName === name || service.name === name || service.subtype === name)) return service; @@ -479,8 +479,8 @@ export class Accessory extends EventEmitter { throw new Error("Cannot Bridge another Bridge!"); // 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) throw new Error("Cannot add a bridged Accessory with the same UUID as another bridged Accessory: " + existing.UUID); } @@ -514,8 +514,8 @@ export class Accessory extends EventEmitter { } addBridgedAccessories = (accessories: Accessory[]) => { - for (var index in accessories) { - var accessory = accessories[index]; + for (let index in accessories) { + const accessory = accessories[index]; this.addBridgedAccessory(accessory, true); } @@ -526,10 +526,10 @@ export class Accessory extends EventEmitter { 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); @@ -548,8 +548,8 @@ export class Accessory extends EventEmitter { } removeBridgedAccessories = (accessories: Accessory[]) => { - for (var index in accessories) { - var accessory = accessories[index]; + for (let index in accessories) { + const accessory = accessories[index]; this.removeBridgedAccessory(accessory, true); } @@ -557,23 +557,23 @@ export class Accessory extends EventEmitter { } removeAllBridgedAccessories = () => { - for (var i = this.bridgedAccessories.length - 1; i >= 0; i --) { + for (let i = this.bridgedAccessories.length - 1; i >= 0; i --) { this.removeBridgedAccessory(this.bridgedAccessories[i], true); } this._updateConfiguration(); } getCharacteristicByIID = (iid: number) => { - for (var index in this.services) { - var service = this.services[index]; - var characteristic = service.getCharacteristicByIID(iid); + 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]; + for (let index in this.bridgedAccessories) { + const accessory = this.bridgedAccessories[index]; if (accessory.aid === aid) return accessory; } } @@ -582,11 +582,12 @@ export class Accessory extends EventEmitter { // 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); + const accessory = (aid === 1) ? this : this.getBridgedAccessoryByAID(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}. @@ -637,6 +638,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?) @@ -764,11 +766,11 @@ export class Accessory extends EventEmitter { return this._setupURI; } - var buffer = Buffer.alloc(8); - var setupCode = this._accessoryInfo && parseInt(this._accessoryInfo.pincode.replace(/-/g, ''), 10); + const buffer = Buffer.alloc(8); + const setupCode = this._accessoryInfo && parseInt(this._accessoryInfo.pincode.replace(/-/g, ''), 10); - var value_low = setupCode!; - var value_high = this._accessoryInfo && this._accessoryInfo.category >> 1; + let value_low = setupCode!; + const value_high = this._accessoryInfo && this._accessoryInfo.category >> 1; value_low |= 1 << 28; // Supports IP; @@ -780,10 +782,10 @@ export class Accessory extends EventEmitter { buffer.writeUInt32BE(value_high!, 0); - var encodedPayload = (buffer.readUInt32BE(4) + (buffer.readUInt32BE(0) * Math.pow(2, 32))).toString(36).toUpperCase(); + let encodedPayload = (buffer.readUInt32BE(4) + (buffer.readUInt32BE(0) * Math.pow(2, 32))).toString(36).toUpperCase(); if (encodedPayload.length != 9) { - for (var i = 0; i <= 9 - encodedPayload.length; i++) { + for (let i = 0; i <= 9 - encodedPayload.length; i++) { encodedPayload = "0" + encodedPayload; } } @@ -856,8 +858,8 @@ export class Accessory extends EventEmitter { this.aid = 1; } - for (var index in this.services) { - var service = this.services[index]; + for (let index in this.services) { + const service = this.services[index]; if (this._isBridge) { service._assignIDs(identifierCache, this.UUID, 2000000000); } else { @@ -866,8 +868,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); } @@ -896,14 +898,14 @@ 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; } @@ -912,22 +914,22 @@ export class Accessory extends EventEmitter { */ toHAP = (opt?: ToHAPOptions) => { - var servicesHAP = []; + const servicesHAP = []; - for (var index in this.services) { - var service = this.services[index]; + for (let index in this.services) { + const service = this.services[index]; servicesHAP.push(service.toHAP(opt)); } - var accessoriesHAP = [{ + const accessoriesHAP = [{ aid: this.aid, services: servicesHAP }]; // now add any Accessories we are bridging - for (var index in this.bridgedAccessories) { - var accessory = this.bridgedAccessories[index]; - var bridgedAccessoryHAP = accessory.toHAP(opt); + for (let index in this.bridgedAccessories) { + const accessory = this.bridgedAccessories[index]; + const bridgedAccessoryHAP = accessory.toHAP(opt); // bridgedAccessoryHAP is an array of accessories with one item - extract it // and add it to our own array @@ -1010,7 +1012,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(); @@ -1025,12 +1027,12 @@ 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}); + const 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'); + const shasum = crypto.createHash('sha1'); shasum.update(JSON.stringify(config)); - var configHash = shasum.digest('hex'); + const configHash = shasum.digest('hex'); if (configHash !== this._accessoryInfo.configHash) { @@ -1105,6 +1107,7 @@ export class Accessory extends EventEmitter { this._server = undefined; } if (this._advertiser) { + // noinspection JSIgnoredPromiseFromCall this._advertiser.shutdown(); this._advertiser = undefined; } @@ -1115,12 +1118,12 @@ 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}); + const 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'); + const shasum = crypto.createHash('sha1'); shasum.update(JSON.stringify(config)); - var configHash = shasum.digest('hex'); + const configHash = shasum.digest('hex'); if (this._accessoryInfo && configHash !== this._accessoryInfo.configHash) { @@ -1137,6 +1140,7 @@ export class Accessory extends EventEmitter { assert(this._advertiser, "Advertiser wasn't created at onListening!"); // the HAP server is listening, so we can now start advertising our presence. this._advertiser!.initPort(port); + // noinspection JSIgnoredPromiseFromCall this._advertiser!.startAdvertising(); this.emit(AccessoryEventTypes.LISTENING, port, hostname); } @@ -1248,21 +1252,21 @@ export class Accessory extends EventEmitter { _handleGetCharacteristics = (data: CharacteristicData[], events: CharacteristicEvents, callback: HandleGetCharacteristicsCallback, remote: boolean, session: Session) => { // 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 characteristics: CharacteristicData[] = []; + const statusKey = remote ? 's' : 'status'; + const valueKey = remote ? 'v' : 'value'; data.forEach((characteristicData) => { - var aid = characteristicData.aid; - var iid = characteristicData.iid; + const aid = characteristicData.aid; + const iid = characteristicData.iid; - var includeEvent = characteristicData.e; + const includeEvent = characteristicData.e; - var characteristic = this.findCharacteristic(characteristicData.aid, characteristicData.iid); + const characteristic = this.findCharacteristic(characteristicData.aid, characteristicData.iid); 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 = { + let response: any = { aid: aid, iid: iid }; @@ -1321,13 +1325,13 @@ export class Accessory extends EventEmitter { // 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; + const 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 = { + let response: any = { aid: aid, iid: iid }; @@ -1336,14 +1340,14 @@ export class Accessory extends EventEmitter { } else { debug('[%s] Got Characteristic "%s" value: %s', this.displayName, characteristic!.displayName, value); - var response: any = { + let response: any = { aid: aid, iid: iid }; response[valueKey] = value; if (includeEvent) { - var eventName = aid + '.' + iid; + const eventName = aid + '.' + iid; response['e'] = (events[eventName] === true); } @@ -1386,22 +1390,22 @@ export class Accessory extends EventEmitter { } // build up our array of responses to the characteristics requested asynchronously - var characteristics: CharacteristicData[] = []; + const characteristics: CharacteristicData[] = []; 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 aid = characteristicData.aid; + const iid = characteristicData.iid; + const value = remote ? characteristicData.v : characteristicData.value; + const ev = remote ? characteristicData.e : characteristicData.ev; + const includeValue = characteristicData.r || false; - var statusKey = remote ? 's' : 'status'; + const statusKey = remote ? 's' : 'status'; - var characteristic = this.findCharacteristic(aid, iid); + const characteristic = this.findCharacteristic(aid, iid); 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 = { + let response: any = { aid: aid, iid: iid }; @@ -1433,7 +1437,7 @@ export class Accessory extends EventEmitter { // 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; + const context = events; // if "ev" is present, that means we need to register or unregister this client for change events for // this characteristic. @@ -1478,10 +1482,10 @@ export class Accessory extends EventEmitter { // store event registrations in the supplied "events" dict which is associated with the connection making // the request. - var eventName = aid + '.' + iid; + const eventName = aid + '.' + iid; if (ev === true && events[eventName] != true) { - events[eventName] = true; // value is arbitrary, just needs to be non-falsey + events[eventName] = true; // value is arbitrary, just needs to be non-falsy characteristic.subscribe(); } @@ -1551,14 +1555,14 @@ export class Accessory extends EventEmitter { if (err) { debug('[%s] Error setting Characteristic "%s" to value %s: ', this.displayName, characteristic!.displayName, value, err.message); - var response: any = { + let response: any = { aid: aid, iid: iid }; response[statusKey] = hapStatus(err); characteristics.push(response); } else { - var response: any = { + let response: any = { aid: aid, iid: iid }; @@ -1578,7 +1582,7 @@ export class Accessory extends EventEmitter { } else { // no value to set, so we're done (success) - var response: any = { + let response: any = { aid: aid, iid: iid }; @@ -1632,14 +1636,14 @@ export class Accessory extends EventEmitter { } _unsubscribeEvents = (events: CharacteristicEvents) => { - for (var key in events) { + for (let key in events) { if (key.indexOf('.') !== -1) { try { - var id = key.split('.'); - var aid = Number.parseInt(id[0]); - var iid = Number.parseInt(id[1]); + const id = key.split('.'); + const aid = Number.parseInt(id[0]); + const iid = Number.parseInt(id[1]); - var characteristic = this.findCharacteristic(aid, iid); + const characteristic = this.findCharacteristic(aid, iid); if (characteristic) { characteristic.unsubscribe(); } @@ -1654,7 +1658,7 @@ export class Accessory extends EventEmitter { if (!this._server) return; // we're not running a HAPServer, so there's no one to notify about this event - var data = { + const data = { characteristics: [{ aid: change.accessory.aid, iid: change.characteristic.iid, @@ -1663,11 +1667,11 @@ export class Accessory extends EventEmitter { }; // 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; + const eventName = change.accessory.aid + '.' + change.characteristic.iid; // 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; + const excludeEvents = change.context; // pass it along to notifyClients() so that it can omit the connection where events === excludeEvents. this._server.notifyClients(eventName, data, excludeEvents); @@ -1694,8 +1698,8 @@ export class Accessory extends EventEmitter { } _sideloadServices = (targetServices: Service[]) => { - for (var index in targetServices) { - var target = targetServices[index]; + for (let index in targetServices) { + const target = targetServices[index]; this._setupService(target); } @@ -1707,7 +1711,7 @@ export class Accessory extends EventEmitter { .getCharacteristic(Characteristic.Identify)! .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { if (value) { - var paired = true; + const paired = true; this._identificationRequest(paired, callback); } }); @@ -1718,8 +1722,8 @@ export class Accessory extends EventEmitter { const bytes = crypto.randomBytes(4); let setupID = ''; - for (var i = 0; i < 4; i++) { - var index = bytes.readUInt8(i) % 26; + for (let i = 0; i < 4; i++) { + const index = bytes.readUInt8(i) % 26; setupID += chars.charAt(index); } @@ -1760,7 +1764,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), diff --git a/src/lib/AccessoryLoader.ts b/src/lib/AccessoryLoader.ts index 3942cb1b4..ca64b8909 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: Accessory[] = []; 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,9 +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); + const characteristic = new Characteristic(json.manfDescription || characteristicUUID, characteristicUUID); // copy simple properties characteristic.value = json.initialValue; @@ -160,9 +159,9 @@ export function parseCharacteristicJSON(json: any) { // @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 388d6c4ff..dc6920572 100644 --- a/src/lib/Advertiser.ts +++ b/src/lib/Advertiser.ts @@ -7,7 +7,6 @@ import ciao, { ServiceType } from "@homebridge/ciao"; import { ServiceOptions } from "@homebridge/ciao/lib/CiaoService"; -import assert from "assert"; import crypto from 'crypto'; import { EventEmitter } from "events"; import { AccessoryInfo } from './model/AccessoryInfo'; diff --git a/src/lib/Characteristic.spec.ts b/src/lib/Characteristic.spec.ts index 4960a26db..16816cb5c 100644 --- a/src/lib/Characteristic.spec.ts +++ b/src/lib/Characteristic.spec.ts @@ -387,7 +387,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, @@ -430,7 +430,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, diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts index d8ad9bb94..e87e5ba7e 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -1,19 +1,18 @@ import Decimal from 'decimal.js'; - -import { once } from './util/once'; -import { clone } from "./util/clone"; -import { IdentifierCache } from './model/IdentifierCache'; import { CharacteristicChange, CharacteristicValue, HapCharacteristic, - SessionIdentifier, Nullable, + SessionIdentifier, ToHAPOptions, VoidCallback, } from '../types'; import { EventEmitter } from './EventEmitter'; import * as HomeKitTypes from './gen'; +import { IdentifierCache } from './model/IdentifierCache'; +import { clone } from "./util/clone"; +import { once } from './util/once'; import { toShortForm } from './util/uuid'; export const enum Formats { @@ -41,6 +40,7 @@ export const enum Units { // Known HomeKit permission types export const enum Perms { + // noinspection JSUnusedGlobalSymbols /** * @deprecated replaced by {@link PAIRED_READ}. Kept for backwards compatibility. */ @@ -223,14 +223,6 @@ export class Characteristic extends EventEmitter { 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; @@ -283,10 +275,6 @@ export class Characteristic extends EventEmitter { 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; @@ -353,9 +341,6 @@ export class Characteristic extends EventEmitter { 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; @@ -444,7 +429,7 @@ export class Characteristic extends EventEmitter { * } */ setProps = (props: Partial) => { - for (var key in (props || {})) + for (let key in (props || {})) if (Object.prototype.hasOwnProperty.call(props, key)) { // @ts-ignore this.props[key] = props[key]; @@ -469,7 +454,7 @@ export class Characteristic extends EventEmitter { } unsubscribe = () => { - var wasOne = this.subscriptions === 1; + const wasOne = this.subscriptions === 1; this.subscriptions--; this.subscriptions = Math.max(this.subscriptions, 0); if (wasOne) { @@ -479,7 +464,7 @@ export class Characteristic extends EventEmitter { getValue = (callback?: CharacteristicGetCallback, context?: any, connectionID?: SessionIdentifier) => { // Handle special event only characteristics. - if (this.eventOnlyCharacteristic === true) { + if (this.eventOnlyCharacteristic) { if (callback) { callback(null, null); } @@ -494,11 +479,11 @@ export class Characteristic extends EventEmitter { if (callback) callback(err); } else { - newValue = this.validateValue(newValue); //validateValue returns a value that has be cooerced into a valid value. + newValue = this.validateValue(newValue); //validateValue returns a value that has be coerced 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; + const oldValue = this.value; this.value = newValue; if (callback) callback(null, newValue); @@ -557,35 +542,34 @@ export class Characteristic extends EventEmitter { maxValue_resolved = 18446744073709551615; isNumericType = true; break; - //All of the following datatypes return from this switch. + //All of the following data types return from this switch. case Formats.BOOL: // @ts-ignore return (newValue == true); //We don't need to make sure this returns true or false - break; - case Formats.STRING: + 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; + let 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 - break; - case Formats.DATA: - var maxLength = this.props.maxDataLen; + } + case Formats.DATA: { + let 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; - break; + } case Formats.TLV8: //Should we parse this to make sure the tlv8 is valid? 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) { @@ -613,10 +597,10 @@ export class Characteristic extends EventEmitter { stepDecimals = 0; else stepDecimals = minStep_resolved.toString().split(".")[1].length || 0; - //Use Decimal to detemine the lowest value within the step. + //Use Decimal to determine the lowest value within the step. try { - var decimalVal = new Decimal(parseFloat(newValue as string)); - var decimalDiff = decimalVal.mod(minStep_resolved); + let decimalVal = new Decimal(parseFloat(newValue as string)); + const decimalDiff = decimalVal.mod(minStep_resolved); decimalVal = decimalVal.minus(decimalDiff); if (stepDecimals === 0) { newValue = parseInt(decimalVal.toFixed(0)); @@ -646,8 +630,8 @@ export class Characteristic extends EventEmitter { } 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; + newValue = this.validateValue(newValue as Nullable); //validateValue returns a value that has be coerced into a valid value. + const 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) => { @@ -666,7 +650,7 @@ export class Characteristic extends EventEmitter { this.value = newValue as CharacteristicValue; if (callback) callback(); - if (this.eventOnlyCharacteristic === true || oldValue !== newValue) + if (this.eventOnlyCharacteristic || oldValue !== newValue) this.emit(CharacteristicEventTypes.CHANGE, {oldValue: oldValue, newValue: newValue, context: context}); } }), context, connectionID); @@ -677,7 +661,7 @@ export class Characteristic extends EventEmitter { this.value = newValue as string | number; if (callback) callback(); - if (this.eventOnlyCharacteristic === true || oldValue !== newValue) + if (this.eventOnlyCharacteristic || oldValue !== newValue) this.emit(CharacteristicEventTypes.CHANGE, {oldValue: oldValue, newValue: newValue, context: context}); } return this; // for chaining @@ -689,15 +673,15 @@ export class Characteristic extends EventEmitter { } else { this.status = null; } - newValue = this.validateValue(newValue as Nullable); //validateValue returns a value that has be cooerced into a valid value. + newValue = this.validateValue(newValue as Nullable); //validateValue returns a value that has be coerced 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; + const oldValue = this.value; this.value = newValue; if (callback) callback(); - if (this.eventOnlyCharacteristic === true || oldValue !== newValue) + if (this.eventOnlyCharacteristic || oldValue !== newValue) this.emit(CharacteristicEventTypes.CHANGE, {oldValue: oldValue, newValue: newValue, context: context}); return this; // for chaining } @@ -731,7 +715,7 @@ export class Characteristic extends EventEmitter { */ toHAP = (opt?: ToHAPOptions) => { // ensure our value fits within our constraints if present - var value = this.value; + let value = this.value; if (this.props.minValue != null && value! < this.props.minValue) value = this.props.minValue; @@ -751,12 +735,12 @@ export class Characteristic extends EventEmitter { 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)); + const pow = Math.pow(10, decimalPlaces(this.props.minStep)); value = Math.round(value * pow) / pow; } } } - if (this.eventOnlyCharacteristic === true) { + if (this.eventOnlyCharacteristic) { // @ts-ignore value = null; } @@ -790,16 +774,16 @@ export class Characteristic extends EventEmitter { 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; + const 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 + } else if (len > 64) { // values below can be omitted 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) + if (this.props.perms.indexOf(Perms.PAIRED_READ) == -1) delete hap.value; // delete the "value" property anyway if we were asked to if (opt && opt.omitValues) @@ -831,7 +815,7 @@ export class Characteristic extends EventEmitter { // 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+))?$/); + const match = ('' + num).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/); if (!match) { return 0; } return Math.max( 0, diff --git a/src/lib/HAPServer.ts b/src/lib/HAPServer.ts index c15444671..ec450ba6e 100644 --- a/src/lib/HAPServer.ts +++ b/src/lib/HAPServer.ts @@ -16,6 +16,7 @@ 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, @@ -38,6 +39,7 @@ const enum TLVValues { } const enum Methods { + // noinspection JSUnusedGlobalSymbols PAIR_SETUP = 0x00, PAIR_SETUP_WITH_AUTH = 0x01, PAIR_VERIFY = 0x02, @@ -56,6 +58,7 @@ const enum States { } export const enum Codes { + // noinspection JSUnusedGlobalSymbols UNKNOWN = 0x01, INVALID_REQUEST = 0x02, AUTHENTICATION = 0x02, // setup code or signature verification failed @@ -67,6 +70,7 @@ export const enum Codes { } export const enum Status { + // noinspection JSUnusedGlobalSymbols SUCCESS = 0, INSUFFICIENT_PRIVILEGES = -70401, SERVICE_COMMUNICATION_FAILURE = -70402, @@ -224,7 +228,7 @@ export class HAPServer extends EventEmitter { // 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 + // invented a manual "keepalive" mechanism where we send "empty" events periodically, 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 @@ -264,15 +268,15 @@ export class HAPServer extends EventEmitter { _onRequest = (request: IncomingMessage, response: ServerResponse, session: Session, events: any) => { debug("[%s] HAP Request: %s %s", this.accessoryInfo.username, request.method, request.url); // collect request data, if any - var requestData = Buffer.alloc(0); + let requestData = Buffer.alloc(0); request.on('data', (data) => { requestData = Buffer.concat([requestData, 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!; + const pathname = url.parse(request.url!).pathname!; // all request data received; now process this request - for (var path in HAPServer.handlers) + for (let path in HAPServer.handlers) if (new RegExp('^' + path + '/?$').test(pathname)) { // match exact string and allow trailing slash const handler = HAPServer.handlers[path] as keyof HAPServer; this[handler](request, response, session, events, requestData); @@ -287,7 +291,7 @@ export class HAPServer extends EventEmitter { _onEncrypt = (data: Buffer, encrypted: { data: Buffer; }, session: Session) => { // instance of HAPEncryption (created in handlePairVerifyStepOne) - var enc = session.encryption; + const 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. @@ -300,7 +304,7 @@ export class HAPServer extends EventEmitter { _onDecrypt = (data: Buffer, decrypted: { data: number | Buffer; error: Error | null }, session: Session) => { // possibly an instance of HAPEncryption (created in handlePairVerifyStepOne) - var enc = session.encryption; + const enc = session.encryption; // if controllerToAccessoryKey is not empty, then encryption is enabled for this connection. if (enc && enc.controllerToAccessoryKey.length > 0) { try { @@ -319,7 +323,7 @@ export class HAPServer extends EventEmitter { * Unpaired Accessory identification. */ _handleIdentify = (request: IncomingMessage, response: ServerResponse, session: Session, events: any, requestData: any) => { - // /identify only works if the accesory is not paired + // /identify only works if the accessory is not paired if (!this.allowInsecureRequest && this.accessoryInfo.paired()) { response.writeHead(400, {"Content-Type": "application/hap+json"}); response.end(JSON.stringify({status: Status.INSUFFICIENT_PRIVILEGES})); @@ -354,8 +358,8 @@ export class HAPServer extends EventEmitter { return; } - var objects = tlv.decode(requestData); - var sequence = objects[TLVValues.SEQUENCE_NUM][0]; // value is single byte with sequence number + const objects = tlv.decode(requestData); + const sequence = objects[TLVValues.SEQUENCE_NUM][0]; // value is single byte with sequence number if (sequence == States.M1) this._handlePairStepOne(request, response, session); else if (sequence == States.M3 && session._pairSetupState === States.M2) @@ -396,10 +400,10 @@ export class HAPServer extends EventEmitter { // M3 + M4 _handlePairStepTwo = (request: IncomingMessage, response: ServerResponse, session: Session, objects: Record) => { 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 = objects[TLVValues.PUBLIC_KEY]; // "A is a public key that exists only for a single login session." + const M1 = objects[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 = session.srpServer!; srpServer.setA(A); try { srpServer.checkM1(M1); @@ -413,7 +417,7 @@ export class HAPServer extends EventEmitter { return; } // "M2 is the proof that the server actually knows your password." - var M2 = srpServer.computeM2(); + const 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; @@ -423,16 +427,16 @@ export class HAPServer extends EventEmitter { _handlePairStepThree = (request: IncomingMessage, response: ServerResponse, session: Session, objects: Record) => { 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 = session.srpServer!; + 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); - 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 { @@ -445,22 +449,21 @@ export class HAPServer extends EventEmitter { 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._handlePairStepFour(request, response, session, clientUsername, clientLTPK, clientProof, outputKey); } // M5-2 _handlePairStepFour = (request: IncomingMessage, response: ServerResponse, session: Session, clientUsername: Buffer, clientLTPK: Buffer, clientProof: Buffer, hkdfEncKey: Buffer) => { 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 = session.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"}); @@ -474,16 +477,16 @@ export class HAPServer extends EventEmitter { // M5 - F + M6 _handlePairStepFive = (request: IncomingMessage, response: ServerResponse, session: Session, clientUsername: Buffer, clientLTPK: Buffer, hkdfEncKey: Buffer) => { 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 = session.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); @@ -507,8 +510,8 @@ export class HAPServer extends EventEmitter { * 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 + const objects = tlv.decode(requestData); + const 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) @@ -523,21 +526,21 @@ export class HAPServer extends EventEmitter { _handlePairVerifyStepOne = (request: IncomingMessage, response: ServerResponse, session: Session, objects: Record) => { debug("[%s] Pair verify step 1/2", this.accessoryInfo.username); - var clientPublicKey = objects[TLVValues.PUBLIC_KEY]; // Buffer + const clientPublicKey = objects[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); + 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); // store keys in a new instance of HAPEncryption - var enc = new HAPEncryption(); + const enc = new HAPEncryption(); enc.clientPublicKey = clientPublicKey; enc.secretKey = secretKey; enc.publicKey = publicKey; @@ -546,7 +549,7 @@ export class HAPServer extends EventEmitter { // store this in the current TCP session session.encryption = enc; // 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); @@ -557,14 +560,14 @@ export class HAPServer extends EventEmitter { _handlePairVerifyStepTwo = (request: IncomingMessage, response: ServerResponse, session: Session, events: any, objects: Record) => { 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 = session.encryption!; let plaintext; try { @@ -577,12 +580,12 @@ export class HAPServer extends EventEmitter { 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) { @@ -602,12 +605,12 @@ export class HAPServer extends EventEmitter { 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 + // 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"); + 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.sharedSec, infoRead, 32); enc.controllerToAccessoryKey = hapCrypto.HKDF("sha512", encSalt, enc.sharedSec, infoWrite, 32); // Our connection is now completely setup. We now want to subscribe this connection to special @@ -730,25 +733,21 @@ export class HAPServer extends EventEmitter { return; } - type Characteristics = Pick & { - status?: any; - }; - 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' } + const parseQueryString = true; + const query = url.parse(request.url!, parseQueryString).query; // { id: '1.9,2.14' } if (query == undefined || query.id == undefined) { response.writeHead(500); response.end(); 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) + const sets = (query.id as string).split(','); // ["1.9","2.14"] + const data: CharacteristicData[] = []; // [{aid:1,iid:9},{aid:2,iid:14}] + for (let i in sets) { + const ids = sets[i].split('.'); // ["1","9"] + const aid = parseInt(ids[0]); // accessory ID + const iid = parseInt(ids[1]); // instance ID (for characteristic) data.push({aid: aid, iid: iid}); } this.emit(HAPServerEventTypes.GET_CHARACTERISTICS, data, events, once((err: Error, characteristics: CharacteristicData[]) => { @@ -758,7 +757,7 @@ export class HAPServer extends EventEmitter { 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) { + for (let i in data) { characteristics.push({ aid: data[i].aid, iid: data[i].iid, @@ -804,15 +803,15 @@ export class HAPServer extends EventEmitter { 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 + const writeRequest = JSON.parse(requestData.toString()) as CharacteristicsWriteRequest; + const 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) { + for (let i in data) { characteristics.push({ aid: data[i].aid, iid: data[i].iid, @@ -911,7 +910,7 @@ export class HAPServer extends EventEmitter { return; } // requestData is a JSON payload - var data = JSON.parse(requestData.toString()); + const data = JSON.parse(requestData.toString()); // 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) { diff --git a/src/lib/Service.ts b/src/lib/Service.ts index b59f3287c..e6965e298 100644 --- a/src/lib/Service.ts +++ b/src/lib/Service.ts @@ -1,9 +1,9 @@ +import { CharacteristicChange, CharacteristicValue, HapService, Nullable, ToHAPOptions, WithUUID, } from '../types'; import { Characteristic, CharacteristicEventTypes, SerializedCharacteristic } from './Characteristic'; -import { clone } from './util/clone'; import { EventEmitter } from './EventEmitter'; -import { IdentifierCache } from './model/IdentifierCache'; -import { CharacteristicChange, CharacteristicValue, HapService, Nullable, ToHAPOptions, WithUUID, } from '../types'; import * as HomeKitTypes from './gen'; +import { IdentifierCache } from './model/IdentifierCache'; +import { clone } from './util/clone'; import { toShortForm } from './util/uuid'; const MAX_CHARACTERISTICS = 100; @@ -36,6 +36,7 @@ type Events = { [ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE]: (change: ServiceConfigurationChange) => void; } +// noinspection JSUnusedGlobalSymbols /** * @deprecated Use ServiceEventTypes instead */ @@ -158,7 +159,7 @@ export class Service extends EventEmitter { // 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); @@ -184,8 +185,8 @@ export class Service extends EventEmitter { characteristic = new characteristic(...constructorArgs) as Characteristic; } // 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. @@ -251,10 +252,10 @@ export class Service extends EventEmitter { } removeCharacteristic = (characteristic: Characteristic) => { - var targetCharacteristicIndex; + 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; @@ -280,7 +281,7 @@ export class Service extends EventEmitter { // 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) { @@ -306,7 +307,7 @@ export class Service extends EventEmitter { testCharacteristic = >(name: string | T) => { // 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) { @@ -346,7 +347,7 @@ export class Service extends EventEmitter { * 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 */ replaceCharacteristicsFromService(service: Service) { @@ -363,7 +364,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; @@ -390,8 +391,8 @@ export class Service extends EventEmitter { } getCharacteristicByIID = (iid: number) => { - for (var index in this.characteristics) { - var characteristic = this.characteristics[index]; + for (let index in this.characteristics) { + const characteristic = this.characteristics[index]; if (characteristic.iid === iid) return characteristic; } @@ -407,8 +408,8 @@ 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); } } @@ -417,10 +418,10 @@ export class Service extends EventEmitter { * Returns a JSON representation of this Accessory suitable for delivering to HAP clients. */ toHAP = (opt?: ToHAPOptions) => { - var characteristicsHAP = []; + const characteristicsHAP = []; - for (var index in this.characteristics) { - var characteristic = this.characteristics[index]; + for (let index in this.characteristics) { + const characteristic = this.characteristics[index]; characteristicsHAP.push(characteristic.toHAP(opt)); } @@ -440,8 +441,8 @@ export class Service extends EventEmitter { if (this.linkedServices.length > 0) { hap['linked'] = []; - for (var index in this.linkedServices) { - var otherService = this.linkedServices[index]; + for (let index in this.linkedServices) { + const otherService = this.linkedServices[index]; hap['linked'].push(otherService.iid!); } } @@ -458,8 +459,8 @@ export class Service extends EventEmitter { } _sideloadCharacteristics = (targetCharacteristics: Characteristic[]) => { - for (var index in targetCharacteristics) { - var target = targetCharacteristics[index]; + for (let index in targetCharacteristics) { + const target = targetCharacteristics[index]; this._setupCharacteristic(target); } 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 46216c5e3..f60085efc 100644 --- a/src/lib/camera/RTPStreamManagement.ts +++ b/src/lib/camera/RTPStreamManagement.ts @@ -1,6 +1,7 @@ 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 { diff --git a/src/lib/datastream/DataStreamManagement.ts b/src/lib/datastream/DataStreamManagement.ts index f00b3a5c9..e3f73fdf0 100644 --- a/src/lib/datastream/DataStreamManagement.ts +++ b/src/lib/datastream/DataStreamManagement.ts @@ -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 diff --git a/src/lib/datastream/DataStreamServer.ts b/src/lib/datastream/DataStreamServer.ts index 0efdca5c6..912a3788b 100644 --- a/src/lib/datastream/DataStreamServer.ts +++ b/src/lib/datastream/DataStreamServer.ts @@ -60,6 +60,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 +71,7 @@ export enum HDSStatus { } export enum DataSendCloseReason { // close reason used in the dataSend protocol + // noinspection JSUnusedGlobalSymbols NORMAL = 0, NOT_ALLOWED = 1, BUSY = 2, diff --git a/src/lib/gen/HomeKit-Bridge.ts b/src/lib/gen/HomeKit-Bridge.ts index 737c2ef25..9e8e59029 100644 --- a/src/lib/gen/HomeKit-Bridge.ts +++ b/src/lib/gen/HomeKit-Bridge.ts @@ -19,7 +19,7 @@ export class AppMatchingIdentifier extends Characteristic { super('App Matching Identifier', AppMatchingIdentifier.UUID); this.setProps({ format: Formats.TLV8, - perms: [Perms.READ] + perms: [Perms.PAIRED_READ] }); this.value = this.getDefaultValue(); } @@ -42,7 +42,7 @@ export class ProgrammableSwitchOutputState extends Characteristic { maxValue: 1, minValue: 0, minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -62,7 +62,7 @@ export class SoftwareRevision extends Characteristic { super('Software Revision', SoftwareRevision.UUID); this.setProps({ format: Formats.STRING, - perms: [Perms.READ] + perms: [Perms.PAIRED_READ] }); this.value = this.getDefaultValue(); } @@ -140,7 +140,7 @@ export class AccessoryIdentifier extends Characteristic { super('Accessory Identifier', AccessoryIdentifier.UUID); this.setProps({ format: Formats.STRING, - perms: [Perms.READ] + perms: [Perms.PAIRED_READ] }); this.value = this.getDefaultValue(); } @@ -163,7 +163,7 @@ export class Category extends Characteristic { maxValue: 16, minValue: 1, minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -183,7 +183,7 @@ export class ConfigureBridgedAccessory extends Characteristic { super('Configure Bridged Accessory', ConfigureBridgedAccessory.UUID); this.setProps({ format: Formats.TLV8, - perms: [Perms.WRITE] + perms: [Perms.PAIRED_WRITE] }); this.value = this.getDefaultValue(); } @@ -203,7 +203,7 @@ export class ConfigureBridgedAccessoryStatus extends Characteristic { super('Configure Bridged Accessory Status', ConfigureBridgedAccessoryStatus.UUID); this.setProps({ format: Formats.TLV8, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -223,7 +223,7 @@ export class CurrentTime extends Characteristic { super('Current Time', CurrentTime.UUID); this.setProps({ format: Formats.STRING, - perms: [Perms.READ, Perms.WRITE] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE] }); this.value = this.getDefaultValue(); } @@ -246,7 +246,7 @@ export class DayoftheWeek extends Characteristic { maxValue: 7, minValue: 1, minStep: 1, - perms: [Perms.READ, Perms.WRITE] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE] }); this.value = this.getDefaultValue(); } @@ -270,7 +270,7 @@ export class DiscoverBridgedAccessories extends Characteristic { super('Discover Bridged Accessories', DiscoverBridgedAccessories.UUID); this.setProps({ format: Formats.UINT8, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -290,7 +290,7 @@ export class DiscoveredBridgedAccessories extends Characteristic { super('Discovered Bridged Accessories', DiscoveredBridgedAccessories.UUID); this.setProps({ format: Formats.UINT16, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -313,7 +313,7 @@ export class LinkQuality extends Characteristic { maxValue: 4, minValue: 1, minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -333,7 +333,7 @@ export class Reachable extends Characteristic { super('Reachable', Reachable.UUID); this.setProps({ format: Formats.BOOL, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -353,7 +353,7 @@ export class RelayControlPoint extends Characteristic { super('Relay Control Point', RelayControlPoint.UUID); this.setProps({ format: Formats.TLV8, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -373,7 +373,7 @@ export class RelayEnabled extends Characteristic { super('Relay Enabled', RelayEnabled.UUID); this.setProps({ format: Formats.BOOL, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -393,7 +393,7 @@ export class RelayState extends Characteristic { super('Relay State', RelayState.UUID); this.setProps({ format: Formats.UINT8, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -413,7 +413,7 @@ export class TimeUpdate extends Characteristic { super('Time Update', TimeUpdate.UUID); this.setProps({ format: Formats.BOOL, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -433,7 +433,7 @@ export class TunnelConnectionTimeout extends Characteristic { super('Tunnel Connection Timeout ', TunnelConnectionTimeout.UUID); this.setProps({ format: Formats.UINT32, - perms: [Perms.WRITE, Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_WRITE, Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -453,7 +453,7 @@ export class TunneledAccessoryAdvertising extends Characteristic { super('Tunneled Accessory Advertising', TunneledAccessoryAdvertising.UUID); this.setProps({ format: Formats.BOOL, - perms: [Perms.WRITE, Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_WRITE, Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -473,7 +473,7 @@ export class TunneledAccessoryConnected extends Characteristic { super('Tunneled Accessory Connected', TunneledAccessoryConnected.UUID); this.setProps({ format: Formats.BOOL, - perms: [Perms.WRITE, Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_WRITE, Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -493,7 +493,7 @@ export class TunneledAccessoryStateNumber extends Characteristic { super('Tunneled Accessory State Number', TunneledAccessoryStateNumber.UUID); this.setProps({ format: Formats.FLOAT, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } diff --git a/src/lib/gen/HomeKit-TV.ts b/src/lib/gen/HomeKit-TV.ts index c3b72482b..c34f333db 100644 --- a/src/lib/gen/HomeKit-TV.ts +++ b/src/lib/gen/HomeKit-TV.ts @@ -15,7 +15,7 @@ export class ActiveIdentifier extends Characteristic { super('Active Identifier', ActiveIdentifier.UUID); this.setProps({ format: Formats.UINT32, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -35,7 +35,7 @@ export class ConfiguredName extends Characteristic { super('Configured Name', ConfiguredName.UUID); this.setProps({ format: Formats.STRING, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -62,7 +62,7 @@ export class SleepDiscoveryMode extends Characteristic { maxValue: 1, minValue: 0, validValues: [0,1], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -89,7 +89,7 @@ export class ClosedCaptions extends Characteristic { maxValue: 1, minValue: 0, validValues: [0,1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -109,7 +109,7 @@ export class DisplayOrder extends Characteristic { super('Display Order', DisplayOrder.UUID); this.setProps({ format: Formats.TLV8, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -139,7 +139,7 @@ export class CurrentMediaState extends Characteristic { maxValue: 5, minValue: 0, validValues: [0,1,2,3,4,5], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -167,7 +167,7 @@ export class TargetMediaState extends Characteristic { maxValue: 2, minValue: 0, validValues: [0,1,2,3], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -200,7 +200,7 @@ export class PictureMode extends Characteristic { 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] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -227,7 +227,7 @@ export class PowerModeSelection extends Characteristic { maxValue: 1, minValue: 0, validValues: [0,1], - perms: [Perms.WRITE] + perms: [Perms.PAIRED_WRITE] }); this.value = this.getDefaultValue(); } @@ -265,7 +265,7 @@ export class RemoteKey extends Characteristic { 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] + perms: [Perms.PAIRED_WRITE] }); this.value = this.getDefaultValue(); } @@ -301,7 +301,7 @@ export class InputSourceType extends Characteristic { maxValue: 10, minValue: 0, validValues: [0,1,2,3,4,5,6,7,8,9,10], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -333,7 +333,7 @@ export class InputDeviceType extends Characteristic { maxValue: 6, minValue: 0, validValues: [0,1,2,3,4,5], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -355,7 +355,7 @@ export class Identifier extends Characteristic { format: Formats.UINT32, minValue: 0, minStep: 1, - perms: [Perms.READ] + perms: [Perms.PAIRED_READ] }); this.value = this.getDefaultValue(); } @@ -382,7 +382,7 @@ export class CurrentVisibilityState extends Characteristic { maxValue: 3, minValue: 0, validValues: [0,1,2,3], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -409,7 +409,7 @@ export class TargetVisibilityState extends Characteristic { maxValue: 1, minValue: 0, validValues: [0,1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -438,7 +438,7 @@ export class VolumeControlType extends Characteristic { maxValue: 3, minValue: 0, validValues: [0,1,2,3], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -465,7 +465,7 @@ export class VolumeSelector extends Characteristic { maxValue: 1, minValue: 0, validValues: [0,1], - perms: [Perms.WRITE] + perms: [Perms.PAIRED_WRITE] }); this.value = this.getDefaultValue(); } diff --git a/src/lib/gen/HomeKit.ts b/src/lib/gen/HomeKit.ts index cc262d0b2..f6ab398d7 100644 --- a/src/lib/gen/HomeKit.ts +++ b/src/lib/gen/HomeKit.ts @@ -38,7 +38,7 @@ export class AccessoryFlags extends Characteristic { super('Accessory Flags', AccessoryFlags.UUID); this.setProps({ format: Formats.UINT32, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -58,7 +58,7 @@ export class ProductData extends Characteristic { super('Product Data', ProductData.UUID); this.setProps({ format: Formats.DATA, - perms: [Perms.READ] + perms: [Perms.PAIRED_READ] }); this.value = this.getDefaultValue(); } @@ -85,7 +85,7 @@ export class Active extends Characteristic { maxValue: 1, minValue: 0, validValues: [0, 1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -105,7 +105,7 @@ export class AdministratorOnlyAccess extends Characteristic { super('Administrator Only Access', AdministratorOnlyAccess.UUID); this.setProps({ format: Formats.BOOL, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -128,7 +128,7 @@ export class AirParticulateDensity extends Characteristic { maxValue: 1000, minValue: 0, minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -155,7 +155,7 @@ export class AirParticulateSize extends Characteristic { maxValue: 1, minValue: 0, validValues: [0, 1], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -187,7 +187,7 @@ export class AirQuality extends Characteristic { maxValue: 5, minValue: 0, validValues: [0, 1, 2, 3, 4, 5], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -208,7 +208,7 @@ export class AudioFeedback extends Characteristic { super('Audio Feedback', AudioFeedback.UUID); this.setProps({ format: Formats.BOOL, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -232,7 +232,7 @@ export class BatteryLevel extends Characteristic { maxValue: 100, minValue: 0, minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -256,7 +256,7 @@ export class Brightness extends Characteristic { maxValue: 100, minValue: 0, minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -283,7 +283,7 @@ export class CarbonDioxideDetected extends Characteristic { maxValue: 1, minValue: 0, validValues: [0, 1], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -305,7 +305,7 @@ export class CarbonDioxideLevel extends Characteristic { format: Formats.FLOAT, maxValue: 100000, minValue: 0, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -327,7 +327,7 @@ export class CarbonDioxidePeakLevel extends Characteristic { format: Formats.FLOAT, maxValue: 100000, minValue: 0, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -354,7 +354,7 @@ export class CarbonMonoxideDetected extends Characteristic { maxValue: 1, minValue: 0, validValues: [0, 1], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -376,7 +376,7 @@ export class CarbonMonoxideLevel extends Characteristic { format: Formats.FLOAT, maxValue: 100, minValue: 0, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -398,7 +398,7 @@ export class CarbonMonoxidePeakLevel extends Characteristic { format: Formats.FLOAT, maxValue: 100, minValue: 0, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -426,7 +426,7 @@ export class ChargingState extends Characteristic { maxValue: 2, minValue: 0, validValues: [0, 1, 2], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -449,7 +449,7 @@ export class ColorTemperature extends Characteristic { maxValue: 500, minValue: 140, minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -476,7 +476,7 @@ export class ContactSensorState extends Characteristic { maxValue: 1, minValue: 0, validValues: [0, 1], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -500,7 +500,7 @@ export class CoolingThresholdTemperature extends Characteristic { maxValue: 35, minValue: 10, minStep: 0.1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -528,7 +528,7 @@ export class CurrentAirPurifierState extends Characteristic { maxValue: 2, minValue: 0, validValues: [0, 1, 2], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -552,7 +552,7 @@ export class CurrentAmbientLightLevel extends Characteristic { unit: Units.LUX, maxValue: 100000, minValue: 0.0001, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -582,7 +582,7 @@ export class CurrentDoorState extends Characteristic { maxValue: 4, minValue: 0, validValues: [0, 1, 2, 3, 4], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -610,7 +610,7 @@ export class CurrentFanState extends Characteristic { maxValue: 2, minValue: 0, validValues: [0, 1, 2], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -639,7 +639,7 @@ export class CurrentHeaterCoolerState extends Characteristic { maxValue: 3, minValue: 0, validValues: [0, 1, 2, 3], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -667,7 +667,7 @@ export class CurrentHeatingCoolingState extends Characteristic { maxValue: 2, minValue: 0, validValues: [0, 1, 2], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -691,7 +691,7 @@ export class CurrentHorizontalTiltAngle extends Characteristic { maxValue: 90, minValue: -90, minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -720,7 +720,7 @@ export class CurrentHumidifierDehumidifierState extends Characteristic { maxValue: 3, minValue: 0, validValues: [0, 1, 2, 3], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -744,7 +744,7 @@ export class CurrentPosition extends Characteristic { maxValue: 100, minValue: 0, minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -768,7 +768,7 @@ export class CurrentRelativeHumidity extends Characteristic { maxValue: 100, minValue: 0, minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -796,7 +796,7 @@ export class CurrentSlatState extends Characteristic { maxValue: 2, minValue: 0, validValues: [0, 1, 2], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -820,7 +820,7 @@ export class CurrentTemperature extends Characteristic { maxValue: 100, minValue: 0, minStep: 0.1, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -844,7 +844,7 @@ export class CurrentTiltAngle extends Characteristic { maxValue: 90, minValue: -90, minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -868,7 +868,7 @@ export class CurrentVerticalTiltAngle extends Characteristic { maxValue: 90, minValue: -90, minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -888,7 +888,7 @@ export class DigitalZoom extends Characteristic { super('Digital Zoom', DigitalZoom.UUID); this.setProps({ format: Formats.FLOAT, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -915,7 +915,7 @@ export class FilterChangeIndication extends Characteristic { maxValue: 1, minValue: 0, validValues: [0, 1], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -937,7 +937,7 @@ export class FilterLifeLevel extends Characteristic { format: Formats.FLOAT, maxValue: 100, minValue: 0, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -957,7 +957,7 @@ export class FirmwareRevision extends Characteristic { super('Firmware Revision', FirmwareRevision.UUID); this.setProps({ format: Formats.STRING, - perms: [Perms.READ] + perms: [Perms.PAIRED_READ] }); this.value = this.getDefaultValue(); } @@ -977,7 +977,7 @@ export class HardwareRevision extends Characteristic { super('Hardware Revision', HardwareRevision.UUID); this.setProps({ format: Formats.STRING, - perms: [Perms.READ] + perms: [Perms.PAIRED_READ] }); this.value = this.getDefaultValue(); } @@ -1001,7 +1001,7 @@ export class HeatingThresholdTemperature extends Characteristic { maxValue: 25, minValue: 0, minStep: 0.1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1021,7 +1021,7 @@ export class HoldPosition extends Characteristic { super('Hold Position', HoldPosition.UUID); this.setProps({ format: Formats.BOOL, - perms: [Perms.WRITE] + perms: [Perms.PAIRED_WRITE] }); this.value = this.getDefaultValue(); } @@ -1045,7 +1045,7 @@ export class Hue extends Characteristic { maxValue: 360, minValue: 0, minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1065,7 +1065,7 @@ export class Identify extends Characteristic { super('Identify', Identify.UUID); this.setProps({ format: Formats.BOOL, - perms: [Perms.WRITE] + perms: [Perms.PAIRED_WRITE] }); this.value = this.getDefaultValue(); } @@ -1085,7 +1085,7 @@ export class ImageMirroring extends Characteristic { super('Image Mirroring', ImageMirroring.UUID); this.setProps({ format: Formats.BOOL, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1109,7 +1109,7 @@ export class ImageRotation extends Characteristic { maxValue: 270, minValue: 0, minStep: 90, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1136,7 +1136,7 @@ export class InUse extends Characteristic { maxValue: 1, minValue: 0, validValues: [0, 1], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1163,7 +1163,7 @@ export class IsConfigured extends Characteristic { maxValue: 1, minValue: 0, validValues: [0, 1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1190,7 +1190,7 @@ export class LeakDetected extends Characteristic { maxValue: 1, minValue: 0, validValues: [0, 1], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1210,7 +1210,7 @@ export class LockControlPoint extends Characteristic { super('Lock Control Point', LockControlPoint.UUID); this.setProps({ format: Formats.TLV8, - perms: [Perms.WRITE] + perms: [Perms.PAIRED_WRITE] }); this.value = this.getDefaultValue(); } @@ -1239,7 +1239,7 @@ export class LockCurrentState extends Characteristic { maxValue: 3, minValue: 0, validValues: [0, 1, 2, 3], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1273,7 +1273,7 @@ export class LockLastKnownAction extends Characteristic { maxValue: 8, minValue: 0, validValues: [0, 1, 2, 3, 4, 5, 6, 7, 8], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1294,7 +1294,7 @@ export class LockManagementAutoSecurityTimeout extends Characteristic { this.setProps({ format: Formats.UINT32, unit: Units.SECONDS, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1321,7 +1321,7 @@ export class LockPhysicalControls extends Characteristic { maxValue: 1, minValue: 0, validValues: [0, 1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1348,7 +1348,7 @@ export class LockTargetState extends Characteristic { maxValue: 1, minValue: 0, validValues: [0, 1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1368,7 +1368,7 @@ export class Logs extends Characteristic { super('Logs', Logs.UUID); this.setProps({ format: Formats.TLV8, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1388,7 +1388,7 @@ export class Manufacturer extends Characteristic { super('Manufacturer', Manufacturer.UUID); this.setProps({ format: Formats.STRING, - perms: [Perms.READ] + perms: [Perms.PAIRED_READ] }); this.value = this.getDefaultValue(); } @@ -1408,7 +1408,7 @@ export class Model extends Characteristic { super('Model', Model.UUID); this.setProps({ format: Formats.STRING, - perms: [Perms.READ] + perms: [Perms.PAIRED_READ] }); this.value = this.getDefaultValue(); } @@ -1428,7 +1428,7 @@ export class MotionDetected extends Characteristic { super('Motion Detected', MotionDetected.UUID); this.setProps({ format: Formats.BOOL, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1448,7 +1448,7 @@ export class Mute extends Characteristic { super('Mute', Mute.UUID); this.setProps({ format: Formats.BOOL, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1468,7 +1468,7 @@ export class Name extends Characteristic { super('Name', Name.UUID); this.setProps({ format: Formats.STRING, - perms: [Perms.READ] + perms: [Perms.PAIRED_READ] }); this.value = this.getDefaultValue(); } @@ -1488,7 +1488,7 @@ export class NightVision extends Characteristic { super('Night Vision', NightVision.UUID); this.setProps({ format: Formats.BOOL, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY, Perms.TIMED_WRITE] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY, Perms.TIMED_WRITE] }); this.value = this.getDefaultValue(); } @@ -1511,7 +1511,7 @@ export class NitrogenDioxideDensity extends Characteristic { maxValue: 1000, minValue: 0, minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1531,7 +1531,7 @@ export class ObstructionDetected extends Characteristic { super('Obstruction Detected', ObstructionDetected.UUID); this.setProps({ format: Formats.BOOL, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1558,7 +1558,7 @@ export class OccupancyDetected extends Characteristic { maxValue: 1, minValue: 0, validValues: [0, 1], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1578,7 +1578,7 @@ export class On extends Characteristic { super('On', On.UUID); this.setProps({ format: Formats.BOOL, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1598,7 +1598,7 @@ export class OpticalZoom extends Characteristic { super('Optical Zoom', OpticalZoom.UUID); this.setProps({ format: Formats.FLOAT, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1618,7 +1618,7 @@ export class OutletInUse extends Characteristic { super('Outlet In Use', OutletInUse.UUID); this.setProps({ format: Formats.BOOL, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1641,7 +1641,7 @@ export class OzoneDensity extends Characteristic { maxValue: 1000, minValue: 0, minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1661,7 +1661,7 @@ export class PairSetup extends Characteristic { super('Pair Setup', PairSetup.UUID); this.setProps({ format: Formats.TLV8, - perms: [Perms.READ, Perms.WRITE] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE] }); this.value = this.getDefaultValue(); } @@ -1681,7 +1681,7 @@ export class PairVerify extends Characteristic { super('Pair Verify', PairVerify.UUID); this.setProps({ format: Formats.TLV8, - perms: [Perms.READ, Perms.WRITE] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE] }); this.value = this.getDefaultValue(); } @@ -1701,7 +1701,7 @@ export class PairingFeatures extends Characteristic { super('Pairing Features', PairingFeatures.UUID); this.setProps({ format: Formats.UINT8, - perms: [Perms.READ] + perms: [Perms.PAIRED_READ] }); this.value = this.getDefaultValue(); } @@ -1721,7 +1721,7 @@ export class PairingPairings extends Characteristic { super('Pairing Pairings', PairingPairings.UUID); this.setProps({ format: Formats.TLV8, - perms: [Perms.READ, Perms.WRITE] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE] }); this.value = this.getDefaultValue(); } @@ -1764,7 +1764,7 @@ export class PM10Density extends Characteristic { maxValue: 1000, minValue: 0, minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1787,7 +1787,7 @@ export class PM2_5Density extends Characteristic { maxValue: 1000, minValue: 0, minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1815,7 +1815,7 @@ export class PositionState extends Characteristic { maxValue: 2, minValue: 0, validValues: [0, 1, 2], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1843,7 +1843,7 @@ export class ProgramMode extends Characteristic { maxValue: 2, minValue: 0, validValues: [0, 1, 2], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1871,7 +1871,7 @@ export class ProgrammableSwitchEvent extends Characteristic { maxValue: 2, minValue: 0, validValues: [0, 1, 2], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.eventOnlyCharacteristic = true; //Manual addition. this.value = this.getDefaultValue(); @@ -1895,7 +1895,7 @@ export class RelativeHumidityDehumidifierThreshold extends Characteristic { maxValue: 100, minValue: 0, minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1919,7 +1919,7 @@ export class RelativeHumidityHumidifierThreshold extends Characteristic { maxValue: 100, minValue: 0, minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1942,7 +1942,7 @@ export class RemainingDuration extends Characteristic { maxValue: 3600, minValue: 0, minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -1965,7 +1965,7 @@ export class ResetFilterIndication extends Characteristic { maxValue: 1, minValue: 1, minStep: 1, - perms: [Perms.WRITE] + perms: [Perms.PAIRED_WRITE] }); this.value = this.getDefaultValue(); } @@ -1992,7 +1992,7 @@ export class RotationDirection extends Characteristic { maxValue: 1, minValue: 0, validValues: [0, 1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2016,7 +2016,7 @@ export class RotationSpeed extends Characteristic { maxValue: 100, minValue: 0, minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2040,7 +2040,7 @@ export class Saturation extends Characteristic { maxValue: 100, minValue: 0, minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2063,7 +2063,7 @@ export class SecuritySystemAlarmType extends Characteristic { maxValue: 1, minValue: 0, minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2093,7 +2093,7 @@ export class SecuritySystemCurrentState extends Characteristic { maxValue: 4, minValue: 0, validValues: [0, 1, 2, 3, 4], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2122,7 +2122,7 @@ export class SecuritySystemTargetState extends Characteristic { maxValue: 3, minValue: 0, validValues: [0, 1, 2, 3], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2142,7 +2142,7 @@ export class SelectedRTPStreamConfiguration extends Characteristic { super('Selected RTP Stream Configuration', SelectedRTPStreamConfiguration.UUID); this.setProps({ format: Formats.TLV8, - perms: [Perms.READ, Perms.WRITE] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE] }); this.value = this.getDefaultValue(); } @@ -2162,7 +2162,7 @@ export class SerialNumber extends Characteristic { super('Serial Number', SerialNumber.UUID); this.setProps({ format: Formats.STRING, - perms: [Perms.READ] + perms: [Perms.PAIRED_READ] }); this.value = this.getDefaultValue(); } @@ -2185,7 +2185,7 @@ export class ServiceLabelIndex extends Characteristic { maxValue: 255, minValue: 1, minStep: 1, - perms: [Perms.READ] + perms: [Perms.PAIRED_READ] }); this.value = this.getDefaultValue(); } @@ -2212,7 +2212,7 @@ export class ServiceLabelNamespace extends Characteristic { maxValue: 1, minValue: 0, validValues: [0, 1], - perms: [Perms.READ] + perms: [Perms.PAIRED_READ] }); this.value = this.getDefaultValue(); } @@ -2235,7 +2235,7 @@ export class SetDuration extends Characteristic { maxValue: 3600, minValue: 0, minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2255,7 +2255,7 @@ export class SetupEndpoints extends Characteristic { super('Setup Endpoints', SetupEndpoints.UUID); this.setProps({ format: Formats.TLV8, - perms: [Perms.READ, Perms.WRITE] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE] }); this.value = this.getDefaultValue(); } @@ -2282,7 +2282,7 @@ export class SlatType extends Characteristic { maxValue: 1, minValue: 0, validValues: [0, 1], - perms: [Perms.READ] + perms: [Perms.PAIRED_READ] }); this.value = this.getDefaultValue(); } @@ -2309,7 +2309,7 @@ export class SmokeDetected extends Characteristic { maxValue: 1, minValue: 0, validValues: [0, 1], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2329,7 +2329,7 @@ export class StatusActive extends Characteristic { super('Status Active', StatusActive.UUID); this.setProps({ format: Formats.BOOL, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2356,7 +2356,7 @@ export class StatusFault extends Characteristic { maxValue: 1, minValue: 0, validValues: [0, 1], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2383,7 +2383,7 @@ export class StatusJammed extends Characteristic { maxValue: 1, minValue: 0, validValues: [0, 1], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2410,7 +2410,7 @@ export class StatusLowBattery extends Characteristic { maxValue: 1, minValue: 0, validValues: [0, 1], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2438,7 +2438,7 @@ export class StatusTampered extends Characteristic { maxValue: 1, minValue: 0, validValues: [0, 1], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2458,7 +2458,7 @@ export class StreamingStatus extends Characteristic { super('Streaming Status', StreamingStatus.UUID); this.setProps({ format: Formats.TLV8, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2481,7 +2481,7 @@ export class SulphurDioxideDensity extends Characteristic { maxValue: 1000, minValue: 0, minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2501,7 +2501,7 @@ export class SupportedAudioStreamConfiguration extends Characteristic { super('Supported Audio Stream Configuration', SupportedAudioStreamConfiguration.UUID); this.setProps({ format: Formats.TLV8, - perms: [Perms.READ] + perms: [Perms.PAIRED_READ] }); this.value = this.getDefaultValue(); } @@ -2521,7 +2521,7 @@ export class SupportedRTPConfiguration extends Characteristic { super('Supported RTP Configuration', SupportedRTPConfiguration.UUID); this.setProps({ format: Formats.TLV8, - perms: [Perms.READ] + perms: [Perms.PAIRED_READ] }); this.value = this.getDefaultValue(); } @@ -2541,7 +2541,7 @@ export class SupportedVideoStreamConfiguration extends Characteristic { super('Supported Video Stream Configuration', SupportedVideoStreamConfiguration.UUID); this.setProps({ format: Formats.TLV8, - perms: [Perms.READ] + perms: [Perms.PAIRED_READ] }); this.value = this.getDefaultValue(); } @@ -2568,7 +2568,7 @@ export class SwingMode extends Characteristic { maxValue: 1, minValue: 0, validValues: [0, 1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2595,7 +2595,7 @@ export class TargetAirPurifierState extends Characteristic { maxValue: 1, minValue: 0, validValues: [0, 1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2623,7 +2623,7 @@ export class TargetAirQuality extends Characteristic { maxValue: 2, minValue: 0, validValues: [0, 1, 2], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2650,7 +2650,7 @@ export class TargetDoorState extends Characteristic { maxValue: 1, minValue: 0, validValues: [0, 1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2677,7 +2677,7 @@ export class TargetFanState extends Characteristic { maxValue: 1, minValue: 0, validValues: [0, 1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2705,7 +2705,7 @@ export class TargetHeaterCoolerState extends Characteristic { maxValue: 2, minValue: 0, validValues: [0, 1, 2], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2734,7 +2734,7 @@ export class TargetHeatingCoolingState extends Characteristic { maxValue: 3, minValue: 0, validValues: [0, 1, 2, 3], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2758,7 +2758,7 @@ export class TargetHorizontalTiltAngle extends Characteristic { maxValue: 90, minValue: -90, minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2791,7 +2791,7 @@ export class TargetHumidifierDehumidifierState extends Characteristic { maxValue: 2, minValue: 0, validValues: [0, 1, 2], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2815,7 +2815,7 @@ export class TargetPosition extends Characteristic { maxValue: 100, minValue: 0, minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2839,7 +2839,7 @@ export class TargetRelativeHumidity extends Characteristic { maxValue: 100, minValue: 0, minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2866,7 +2866,7 @@ export class TargetSlatState extends Characteristic { maxValue: 1, minValue: 0, validValues: [0, 1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2890,7 +2890,7 @@ export class TargetTemperature extends Characteristic { maxValue: 38, minValue: 10, minStep: 0.1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2914,7 +2914,7 @@ export class TargetTiltAngle extends Characteristic { maxValue: 90, minValue: -90, minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2938,7 +2938,7 @@ export class TargetVerticalTiltAngle extends Characteristic { maxValue: 90, minValue: -90, minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2965,7 +2965,7 @@ export class TemperatureDisplayUnits extends Characteristic { maxValue: 1, minValue: 0, validValues: [0, 1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -2994,7 +2994,7 @@ export class ValveType extends Characteristic { maxValue: 3, minValue: 0, validValues: [0, 1, 2, 3], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -3037,7 +3037,7 @@ export class VOCDensity extends Characteristic { maxValue: 1000, minValue: 0, minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -3061,7 +3061,7 @@ export class Volume extends Characteristic { maxValue: 100, minValue: 0, minStep: 1, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -3083,7 +3083,7 @@ export class WaterLevel extends Characteristic { format: Formats.FLOAT, maxValue: 100, minValue: 0, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -3106,7 +3106,7 @@ export class RecordingAudioActive extends Characteristic { super('Recording Audio Active', RecordingAudioActive.UUID); this.setProps({ format: Formats.UINT8, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -3126,7 +3126,7 @@ export class SupportedCameraRecordingConfiguration extends Characteristic { super('Supported Camera Recording Configuration', SupportedCameraRecordingConfiguration.UUID); this.setProps({ format: Formats.TLV8, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -3146,7 +3146,7 @@ export class SupportedVideoRecordingConfiguration extends Characteristic { super('Supported Video Recording Configuration', SupportedVideoRecordingConfiguration.UUID); this.setProps({ format: Formats.TLV8, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -3166,7 +3166,7 @@ export class SupportedAudioRecordingConfiguration extends Characteristic { super('Supported Audio Recording Configuration', SupportedAudioRecordingConfiguration.UUID); this.setProps({ format: Formats.TLV8, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -3186,7 +3186,7 @@ export class SelectedCameraRecordingConfiguration extends Characteristic { super('Selected Camera Recording Configuration', SelectedCameraRecordingConfiguration.UUID); this.setProps({ format: Formats.TLV8, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -3209,7 +3209,7 @@ export class CameraOperatingModeIndicator extends Characteristic { super('Camera Operating Mode Indicator', CameraOperatingModeIndicator.UUID); this.setProps({ format: Formats.UINT8, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY, Perms.TIMED_WRITE] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY, Perms.TIMED_WRITE] }); this.value = this.getDefaultValue(); } @@ -3232,7 +3232,7 @@ export class EventSnapshotsActive extends Characteristic { super('Event Snapshots Active', EventSnapshotsActive.UUID); this.setProps({ format: Formats.UINT8, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -3242,8 +3242,6 @@ Characteristic.EventSnapshotsActive = EventSnapshotsActive; /** * Characteristic "Diagonal Field Of View" - * - * @deprecated was removed again */ export class DiagonalFieldOfView extends Characteristic { @@ -3258,7 +3256,7 @@ export class DiagonalFieldOfView extends Characteristic { maxValue: 360, minValue: 0, minStep: 1, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -3281,7 +3279,7 @@ export class HomeKitCameraActive extends Characteristic { super('HomeKit Camera Active', HomeKitCameraActive.UUID); this.setProps({ format: Formats.UINT8, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY, Perms.TIMED_WRITE] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY, Perms.TIMED_WRITE] }); this.value = this.getDefaultValue(); } @@ -3304,7 +3302,7 @@ export class ManuallyDisabled extends Characteristic { super('Manually disabled', ManuallyDisabled.UUID); this.setProps({ format: Formats.BOOL, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -3327,7 +3325,7 @@ export class ThirdPartyCameraActive extends Characteristic { super('Third Party Camera Active', ThirdPartyCameraActive.UUID); this.setProps({ format: Formats.UINT8, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -3350,7 +3348,7 @@ export class PeriodicSnapshotsActive extends Characteristic { super('Periodic Snapshots Active', PeriodicSnapshotsActive.UUID); this.setProps({ format: Formats.UINT8, - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -3370,7 +3368,7 @@ export class NetworkClientProfileControl extends Characteristic { 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] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY, Perms.TIMED_WRITE, Perms.WRITE_RESPONSE] }); this.value = this.getDefaultValue(); } @@ -3390,7 +3388,7 @@ export class NetworkClientStatusControl extends Characteristic { super('Network Client Status Control', NetworkClientStatusControl.UUID); this.setProps({ format: Formats.TLV8, - perms: [Perms.READ, Perms.WRITE, Perms.WRITE_RESPONSE] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.WRITE_RESPONSE] }); this.value = this.getDefaultValue(); } @@ -3416,7 +3414,7 @@ export class RouterStatus extends Characteristic { maxValue: 1, minValue: 0, validValues: [0,1], - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -3436,7 +3434,7 @@ export class SupportedRouterConfiguration extends Characteristic { super('Supported Router Configuration', SupportedRouterConfiguration.UUID); this.setProps({ format: Formats.TLV8, - perms: [Perms.READ] + perms: [Perms.PAIRED_READ] }); this.value = this.getDefaultValue(); } @@ -3456,7 +3454,7 @@ export class WANConfigurationList extends Characteristic { super('WAN Configuration List', WANConfigurationList.UUID); this.setProps({ format: Formats.TLV8, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -3476,7 +3474,7 @@ export class WANStatusList extends Characteristic { super('WAN Status List', WANStatusList.UUID); this.setProps({ format: Formats.TLV8, - perms: [Perms.READ, Perms.NOTIFY] + perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); this.value = this.getDefaultValue(); } @@ -3502,7 +3500,7 @@ export class ManagedNetworkEnable extends Characteristic { maxValue: 1, minValue: 0, validValues: [0,1], - perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY, Perms.TIMED_WRITE] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY, Perms.TIMED_WRITE] }); this.value = this.getDefaultValue(); } @@ -3522,7 +3520,7 @@ export class NetworkAccessViolationControl extends Characteristic { 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] + perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY, Perms.TIMED_WRITE, Perms.WRITE_RESPONSE] }); this.value = this.getDefaultValue(); } diff --git a/src/lib/gen/importAsClasses.ts b/src/lib/gen/importAsClasses.ts index d00b215a2..c5dcd50dc 100644 --- a/src/lib/gen/importAsClasses.ts +++ b/src/lib/gen/importAsClasses.ts @@ -1,23 +1,19 @@ /// import fs from 'fs'; import path from 'path'; - import plist from 'simple-plist'; - -import { Characteristic, Formats, Units } from "../Characteristic"; +import { 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); +const plistPath = '/Applications/HomeKit Accessory Simulator.app/Contents/Frameworks/HAPAccessoryKit.framework/Versions/A/Resources/default.metadata.plist'; +const 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); +const outputPath = path.join(__dirname, '..', '..', '..', 'src', 'lib', 'gen', 'HomeKitTypes.generated.ts'); +const output = fs.createWriteStream(outputPath); output.write("// THIS FILE IS AUTO-GENERATED - DO NOT MODIFY\n"); output.write("\n"); @@ -39,9 +35,9 @@ output.write("\n"); // 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" +for (let index in metadata.Characteristics) { + const characteristic = metadata.Characteristics[index]; + let 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 @@ -56,10 +52,10 @@ for (var index in metadata.Characteristics) { // 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]; + for (let value in characteristic.Constraints.ValidValues) { + const name = characteristic.Constraints.ValidValues[value]; - var constName = name.toUpperCase().replace(/[^\w]+/g, '_'); + let 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`); } @@ -92,9 +88,9 @@ for (var index in metadata.Characteristics) { 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]); + let sep = ""; + for (let i in characteristic.Properties) { + const perms = getCharacteristicPermsKey(characteristic.Properties[i]); if (perms) { output.write(sep + "Perms." + getCharacteristicPermsKey(characteristic.Properties[i])); sep = ", " @@ -118,9 +114,9 @@ for (var index in metadata.Characteristics) { * Services */ -for (var index in metadata.Services) { - var service = metadata.Services[index]; - var classyName = service.Name.replace(/[\s\-]/g, ""); // "Smoke Sensor" -> "SmokeSensor" +for (let index in metadata.Services) { + const service = metadata.Services[index]; + const 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`); @@ -134,11 +130,11 @@ for (var index in metadata.Services) { if (service.RequiredCharacteristics) { output.write("\n // Required Characteristics\n"); - for (var index in service.RequiredCharacteristics) { - var characteristicUUID = service.RequiredCharacteristics[index]; + for (let index in service.RequiredCharacteristics) { + let characteristicUUID = service.RequiredCharacteristics[index]; // look up the classyName from the hash we built above - var characteristicClassyName = characteristics[characteristicUUID]; + let characteristicClassyName = characteristics[characteristicUUID]; output.write(" this.addCharacteristic(Characteristic." + characteristicClassyName + ");\n"); } @@ -148,11 +144,11 @@ for (var index in metadata.Services) { if (service.OptionalCharacteristics) { output.write("\n // Optional Characteristics\n"); - for (var index in service.OptionalCharacteristics) { - var characteristicUUID = service.OptionalCharacteristics[index]; + for (let index in service.OptionalCharacteristics) { + let characteristicUUID = service.OptionalCharacteristics[index]; // look up the classyName from the hash we built above - var characteristicClassyName = characteristics[characteristicUUID]; + let characteristicClassyName = characteristics[characteristicUUID]; output.write(" this.addOptionalCharacteristic(Characteristic." + characteristicClassyName + ");\n"); } @@ -180,9 +176,9 @@ function getCharacteristicFormatsKey(format: string) { // look up the key in our known-formats dict // @ts-ignore - for (var key in Characteristic.Formats) { + for (let key in Formats) { // @ts-ignore - if (Characteristic.Formats[key as keyof typeof Characteristic.Formats] == format) { + if (Formats[key as keyof typeof Formats] == format) { return key; } } @@ -193,9 +189,9 @@ function getCharacteristicFormatsKey(format: string) { function getCharacteristicUnitsKey(units: string) { // look up the key in our known-units dict // @ts-ignore - for (var key in Characteristic.Units) { + for (let key in Units) { // @ts-ignore - if (Characteristic.Units[key as keyof typeof Characteristic.Units] == units) { + if (Units[key as keyof typeof Units] == units) { return key; } } diff --git a/src/lib/model/AccessoryInfo.ts b/src/lib/model/AccessoryInfo.ts index 6c74893f5..0781d8b7d 100644 --- a/src/lib/model/AccessoryInfo.ts +++ b/src/lib/model/AccessoryInfo.ts @@ -8,6 +8,7 @@ import { MacAddress } from "../../types"; import { HAPStorage } from "./HAPStorage"; 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) } @@ -135,7 +136,7 @@ export class AccessoryInfo { return !!pairingInformation && pairingInformation.permission === PermissionTypes.ADMIN; }; - // Gets the public key for a paired client as a Buffer, or falsey value if not paired. + // Gets the public key for a paired client as a Buffer, or falsy value if not paired. getClientPublicKey = (username: string) => { const pairingInformation = this.pairedClients[username]; if (pairingInformation) { @@ -172,7 +173,7 @@ export class AccessoryInfo { } save = () => { - var saved = { + const saved = { displayName: this.displayName, category: this.category, pincode: this.pincode, @@ -188,7 +189,7 @@ export class AccessoryInfo { setupID: this.setupID, }; - for (var username in this.pairedClients) { + for (let username in this.pairedClients) { const pairingInformation = this.pairedClients[username]; //@ts-ignore saved.pairedClients[username] = pairingInformation.publicKey.toString('hex'); @@ -196,7 +197,7 @@ export class AccessoryInfo { saved.pairedClientsPermission[username] = pairingInformation.permission; } - var key = AccessoryInfo.persistKey(this.username); + const key = AccessoryInfo.persistKey(this.username); HAPStorage.storage().setItemSync(key, saved); } @@ -208,10 +209,10 @@ export class AccessoryInfo { static create = (username: MacAddress) => { AccessoryInfo.assertValidUsername(username); - var accessoryInfo = new AccessoryInfo(username); + const accessoryInfo = new AccessoryInfo(username); // 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); @@ -222,11 +223,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 || ""; @@ -234,8 +235,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 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..07ce8861c 100644 --- a/src/lib/tv/AccessControlManagement.ts +++ b/src/lib/tv/AccessControlManagement.ts @@ -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. 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 0b4556138..91a143320 100644 --- a/src/lib/util/eventedhttp.ts +++ b/src/lib/util/eventedhttp.ts @@ -56,7 +56,7 @@ export type Events = { * 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 + * Fired when we receive data from the client device. You may determine 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. * diff --git a/src/lib/util/hapCrypto.ts b/src/lib/util/hapCrypto.ts index 04ff0c39c..7adaaedc9 100644 --- a/src/lib/util/hapCrypto.ts +++ b/src/lib/util/hapCrypto.ts @@ -22,14 +22,14 @@ type Count = { } 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); + 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); + const nonce = Buffer.alloc(8); writeUInt64LE(count.value++, nonce, 0); const encrypted = chacha20_poly1305_encryptAndSeal(key, nonce, leLength, data.slice(offset, offset + length)); @@ -46,13 +46,13 @@ export function layerDecrypt(packet: Buffer, count: Count, key: Buffer, extraInf packet = Buffer.concat([extraInfo.leftoverData, packet]); } - 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; + const availableDataLength = total - offset - 2 - 16; if (realDataLength > availableDataLength) { // Fragmented packet extraInfo.leftoverData = packet.slice(offset); @@ -61,7 +61,7 @@ export function layerDecrypt(packet: Buffer, count: Count, key: Buffer, extraInf extraInfo.leftoverData = undefined; } - var nonce = Buffer.alloc(8); + const nonce = Buffer.alloc(8); writeUInt64LE(count.value++, 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)); @@ -103,8 +103,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 +117,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 +130,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 +144,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/once.ts b/src/lib/util/once.ts index 8d42c2fad..630fc14ca 100644 --- a/src/lib/util/once.ts +++ b/src/lib/util/once.ts @@ -1,5 +1,5 @@ export function once(func: Function) { - var called = false; + let called = false; return (...args: any[]) => { if (called) { 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/types.ts b/src/types.ts index 5ac437a71..4a1740548 100644 --- a/src/types.ts +++ b/src/types.ts @@ -89,12 +89,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 }; From 72baab0ec1332dd11a14892a6702d06861be36bf Mon Sep 17 00:00:00 2001 From: Supereg Date: Wed, 23 Sep 2020 18:59:22 +0200 Subject: [PATCH 11/70] Throw a proper exception if chacha20-poly1305 cipher isn't found --- src/lib/util/hapCrypto.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/lib/util/hapCrypto.ts b/src/lib/util/hapCrypto.ts index 7adaaedc9..eb55df041 100644 --- a/src/lib/util/hapCrypto.ts +++ b/src/lib/util/hapCrypto.ts @@ -1,7 +1,12 @@ -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'; + +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(); From b9b95d52fd845f168c22a0eea0cf19afeccdd561 Mon Sep 17 00:00:00 2001 From: Supereg Date: Wed, 23 Sep 2020 19:14:07 +0200 Subject: [PATCH 12/70] Fixed: if a service is removed it is now ensured that no existing services still link to that removed service --- src/lib/Accessory.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index 1a4db3275..d63a40129 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -420,17 +420,28 @@ 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(); } else { this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({accessory:this, service:service})); + + for (const accessory of this.bridgedAccessories) { + accessory.removeLinkedService(service); + } } service.removeAllListeners(); } } + private removeLinkedService(service: Service) { + for (const service of this.services) { + service.removeLinkedService(service); + } + } + getService = >(name: string | T) => { for (let index in this.services) { const service = this.services[index]; From c06dbb896edfa932de10b31713c5e879d8c135a9 Mon Sep 17 00:00:00 2001 From: Supereg Date: Wed, 23 Sep 2020 19:44:40 +0200 Subject: [PATCH 13/70] Listen on the loopback interface only for the internal http server --- src/lib/util/eventedhttp.ts | 60 ++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/src/lib/util/eventedhttp.ts b/src/lib/util/eventedhttp.ts index 91a143320..7aebecd83 100644 --- a/src/lib/util/eventedhttp.ts +++ b/src/lib/util/eventedhttp.ts @@ -29,6 +29,47 @@ export type Events = { [EventedHTTPServerEvents.SESSION_CLOSE]: (sessionID: SessionIdentifier, events: any) => void; }; +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; +} +const loopbackAddress = findLoopbackAddress(); // loopback addressed used for the internal http server (::1 or 127.0.0.1) + /** * EventedHTTPServer provides an HTTP-like server that supports HAP "extensions" for security and events. * @@ -66,8 +107,8 @@ export type Events = { */ export class EventedHTTPServer extends EventEmitter { - _tcpServer: net.Server; - _connections: EventedHTTPServerConnection[]; + private readonly _tcpServer: net.Server; + private readonly _connections: EventedHTTPServerConnection[] = []; /** * Session dictionary indexed by username/identifier. The username uniquely identifies every person added to the home. @@ -78,7 +119,6 @@ export class EventedHTTPServer extends EventEmitter { constructor() { super(); this._tcpServer = net.createServer(); - this._connections = []; // track all open connections (for sending events) } listen = (targetPort: number, hostname?: string) => { @@ -105,7 +145,7 @@ export class EventedHTTPServer extends EventEmitter { this._connections.forEach((connection) => { connection.close(); }); - this._connections = []; + this._connections.splice(0, this._connections.length); } sendEvent = ( @@ -141,6 +181,7 @@ export class EventedHTTPServer extends EventEmitter { // remove it from our array of connections for events this._connections.splice(this._connections.indexOf(connection), 1); } + } export const enum HAPSessionEvents { @@ -333,7 +374,7 @@ class EventedHTTPServerConnection extends EventEmitter { this._httpServer.on('listening', this._onHttpServerListening); this._httpServer.on('request', this._onHttpServerRequest); this._httpServer.on('error', this._onHttpServerError); - this._httpServer.listen(0); + this._httpServer.listen(0, loopbackAddress); // 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 @@ -411,12 +452,15 @@ class EventedHTTPServerConnection extends EventEmitter { // 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); + const addressInfo = this._httpServer.address() as AddressInfo; // address() is only a string when listening to unix domain sockets + this._httpPort = addressInfo.port; + const addressString = addressInfo.family === "IPv6"? `[${addressInfo.address}]`: addressInfo.address; + + debug("[%s] HTTP server listening on %s:%s", this._remoteAddress, addressString, addressInfo.port); // 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 = net.createConnection(this._httpPort, addressInfo.address); this._serverSocket.on('connect', this._onServerSocketConnect); this._serverSocket.on('data', this._onServerSocketData); this._serverSocket.on('close', this._onServerSocketClose); From 62348d30a3684261fa32621ae89c94320dea5f32 Mon Sep 17 00:00:00 2001 From: Supereg Date: Thu, 24 Sep 2020 00:54:07 +0200 Subject: [PATCH 14/70] Fixed removeLinkedService --- src/lib/Accessory.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index d63a40129..0fdc3fe2a 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -426,7 +426,7 @@ export class Accessory extends EventEmitter { this._updateConfiguration(); } else { this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({accessory:this, service:service})); - + for (const accessory of this.bridgedAccessories) { accessory.removeLinkedService(service); } @@ -436,9 +436,9 @@ export class Accessory extends EventEmitter { } } - private removeLinkedService(service: Service) { + private removeLinkedService(removed: Service) { for (const service of this.services) { - service.removeLinkedService(service); + service.removeLinkedService(removed); } } From 1a6d30bb1e7f445a8ea80e6f2ca503a6cca46ffa Mon Sep 17 00:00:00 2001 From: Supereg Date: Thu, 24 Sep 2020 00:54:19 +0200 Subject: [PATCH 15/70] Minor improvment of removeLinkedService from Service class --- src/lib/Service.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib/Service.ts b/src/lib/Service.ts index e6965e298..90bb79be8 100644 --- a/src/lib/Service.ts +++ b/src/lib/Service.ts @@ -246,8 +246,9 @@ export class Service extends EventEmitter { removeLinkedService = (oldLinkedService: Service) => { //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); + const index = this.linkedServices.indexOf(oldLinkedService); + if (index !== -1) + this.linkedServices.splice(index, 1); this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({ service: this })); } From e2ffbd8b87e9ee8b43317f964100b629ef204cfe Mon Sep 17 00:00:00 2001 From: Supereg Date: Wed, 30 Sep 2020 01:35:11 +0200 Subject: [PATCH 16/70] Completely reworked /characteristics * Adding support for metadata, events, type, perms query parameters * Moving to promises * Reworked Characteristic code --- package-lock.json | 9 +- package.json | 2 +- src/accessories/SmartSpeaker_accessory.ts | 54 ++- src/internal-types.ts | 108 +++++ src/lib/Accessory.ts | 521 +++++++++------------ src/lib/Characteristic.ts | 393 ++++++++++------ src/lib/HAPServer.ts | 213 ++++----- src/lib/Service.ts | 21 +- src/lib/camera/RTPStreamManagement.ts | 20 +- src/lib/controller/RemoteController.ts | 50 +- src/lib/datastream/DataStreamManagement.ts | 49 +- src/lib/datastream/DataStreamServer.ts | 8 +- src/lib/model/AccessoryInfo.ts | 8 +- src/lib/util/eventedhttp.ts | 37 +- src/lib/util/once.ts | 7 +- src/types.ts | 27 +- 16 files changed, 819 insertions(+), 708 deletions(-) create mode 100644 src/internal-types.ts diff --git a/package-lock.json b/package-lock.json index 647f04567..66758ede3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -396,13 +396,14 @@ } }, "@homebridge/ciao": { - "version": "1.1.0-beta.6", - "resolved": "https://registry.npmjs.org/@homebridge/ciao/-/ciao-1.1.0-beta.6.tgz", - "integrity": "sha512-1fsRO1QEp7DQlF44Iyf9Ji+vY7OEHsacpBWskWWoxy/Av6EqKt/uX3PaVZAUHYH5rnf7RBLgeU+Qj11m5L4GkA==", + "version": "1.1.0-beta.12", + "resolved": "https://registry.npmjs.org/@homebridge/ciao/-/ciao-1.1.0-beta.12.tgz", + "integrity": "sha512-ce5nW/xI/xc39bQjhdPy9OiO4aKc3HJpbqftKlEQ7Rs69WouFbSbjWyvAj+ItfZCge7tDC+UHl08slqSLbQ0IQ==", "requires": { "debug": "^4.1.1", "fast-deep-equal": "^3.1.3", - "source-map-support": "^0.5.19" + "source-map-support": "^0.5.19", + "tslib": "^2.0.1" }, "dependencies": { "fast-deep-equal": { diff --git a/package.json b/package.json index de813a01b..a7ab4f76a 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "@types" ], "dependencies": { - "@homebridge/ciao": "~1.1.0-beta.6", + "@homebridge/ciao": "~1.1.0-beta.12", "fast-srp-hap": "2.0.2", "tweetnacl": "^1.0.3", "debug": "^4.3.0", diff --git a/src/accessories/SmartSpeaker_accessory.ts b/src/accessories/SmartSpeaker_accessory.ts index edee0c4e2..70ee33530 100644 --- a/src/accessories/SmartSpeaker_accessory.ts +++ b/src/accessories/SmartSpeaker_accessory.ts @@ -1,11 +1,11 @@ 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"; @@ -30,31 +30,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/internal-types.ts b/src/internal-types.ts new file mode 100644 index 000000000..460a3eff9 --- /dev/null +++ b/src/internal-types.ts @@ -0,0 +1,108 @@ +import { Formats, Perms, Units } from "./lib/Characteristic"; +import { Status } from "./lib/HAPServer"; +import { CharacteristicValue } from "./types"; + +export interface CharacteristicId { + aid: number, + iid: number, +} + +export interface CharacteristicsReadRequest { + ids: CharacteristicId[], + includeMeta: boolean; + includePerms: boolean, + includeType: boolean, + includeEvent: boolean, +} + +export interface CharacteristicReadDataValue { + aid: number, + iid: number, + value: CharacteristicValue | null, + + status?: Status.SUCCESS, + + // type + type?: string, // characteristics uuid + + // metadata + format?: Formats, + unit?: Units, + minValue?: number, + maxValue?: number, + minStep?: number, + maxLen?: number, + + // perms + perms?: Perms[], + + // event + ev?: boolean, +} + +export interface CharacteristicReadError { + aid: number, + iid: number, + status: Status, +} + +export type CharacteristicsReadData = CharacteristicReadDataValue | CharacteristicReadError; + +export interface CharacteristicsReadResponse { + characteristics: CharacteristicsReadData[], +} + +export interface CharacteristicWrite { + aid: number, + iid: number, + + value?: CharacteristicValue, + ev?: boolean, // enable/disable event notifications for the accessory + + authData?: string, // base64 encoded + remote?: boolean, // remote access used + r?: boolean, // write response +} + +export interface CharacteristicsWriteRequest { + characteristics: CharacteristicWrite[], + pid?: number +} + +export interface CharacteristicWriteDataValue { + aid: number, + iid: number, + value?: CharacteristicValue | null, + + // event + ev?: boolean, + + status?: Status.SUCCESS, +} + +export interface CharacteristicWriteError { + aid: number, + iid: number, + status: Status, + + value?: undefined, // defined to make things easier +} + +export type CharacteristicsWriteData = CharacteristicWriteDataValue | CharacteristicWriteError; + +export interface CharacteristicsWriteResponse { + characteristics: CharacteristicsWriteData[], +} + +export type PrepareWriteRequest = { + ttl: number, + pid: number +} + +export function consideredTrue(input: string | null): boolean { + if (!input) { + return false; + } + + return input === "true" || input === "1"; +} diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index 0fdc3fe2a..8e38e2cda 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -4,8 +4,12 @@ import crypto from 'crypto'; import createDebug from 'debug'; import net from "net"; import { - CharacteristicChange, - CharacteristicData, + CharacteristicsReadData, + CharacteristicsReadRequest, + CharacteristicsReadResponse, CharacteristicsWriteData, CharacteristicsWriteRequest, + CharacteristicsWriteResponse +} from "../internal-types"; +import { CharacteristicValue, HAPPincode, InterfaceName, @@ -22,7 +26,13 @@ import { import { Advertiser, AdvertiserEvent } from './Advertiser'; // noinspection JSDeprecatedSymbols import { LegacyCameraSource, LegacyCameraSourceAdapter, StreamController } from './camera'; -import { Access, Characteristic, CharacteristicEventTypes, CharacteristicSetCallback, Perms } from './Characteristic'; +import { + Access, + Characteristic, + CharacteristicEventTypes, + CharacteristicSetCallback, + Perms +} from './Characteristic'; import { CameraController, CameraControllerOptions, @@ -33,14 +43,23 @@ import { isSerializableController, } from "./controller"; import { EventEmitter } from './EventEmitter'; +import * as HomeKitTypes from "./gen"; import { CameraEventRecordingManagement, CameraOperatingMode, CameraRTPStreamManagement, } from "./gen/HomeKit"; -import { CharacteristicsWriteRequest, Codes, HAPServer, HAPServerEventTypes, Status } from './HAPServer'; +import { Codes, HAPServer, HAPServerEventTypes, Status } from './HAPServer'; import { AccessoryInfo, PairingInformation, PermissionTypes } from './model/AccessoryInfo'; import { ControllerStorage } from "./model/ControllerStorage"; import { IdentifierCache } from './model/IdentifierCache'; -import { SerializedService, Service, ServiceConfigurationChange, ServiceEventTypes, ServiceId } from './Service'; +import { + SerializedService, + Service, + ServiceCharacteristicChange, + ServiceConfigurationChange, + ServiceEventTypes, + ServiceId +} from './Service'; import { clone } from './util/clone'; -import { Session } from "./util/eventedhttp"; +import { HAPSession } from "./util/eventedhttp"; +import { toShortForm } from "./util/uuid"; import * as uuid from './util/uuid'; const debug = createDebug('HAP-NodeJS:Accessory'); @@ -127,7 +146,7 @@ type Events = { identify: (paired:boolean, cb: VoidCallback) => void; listening: (port: number, hostname: string) => void; "service-configurationChange": VoidCallback; - "service-characteristic-change": (change: ServiceCharacteristicChange) => void; + "service-characteristic-change": (change: AccessoryCharacteristicChange) => void; [AccessoryEventTypes.PAIRED]: () => void; [AccessoryEventTypes.UNPAIRED]: () => void; } @@ -213,8 +232,7 @@ export interface PublishInfo { mdns?: MDNSServerOptions; } -export type ServiceCharacteristicChange = CharacteristicChange & { - accessory: Accessory; +export type AccessoryCharacteristicChange = ServiceCharacteristicChange & { service: Service; }; @@ -241,8 +259,6 @@ type AddPairingCallback = PairingsCallback; type RemovePairingCallback = PairingsCallback; type ListPairingsCallback = PairingsCallback; type HandleAccessoriesCallback = NodeCallback<{ accessories: any[] }>; -type HandleGetCharacteristicsCallback = NodeCallback; -type HandleSetCharacteristicsCallback = NodeCallback; /** * Accessory is a virtual HomeKit device. It can publish an associated HAP server for iOS devices to communicate @@ -318,7 +334,7 @@ export class Accessory extends EventEmitter { }); } - _identificationRequest = (paired: boolean, callback: CharacteristicSetCallback) => { + private _identificationRequest(paired: boolean, callback: VoidCallback) { debug("[%s] Identification request", this.displayName); if (this.listeners(AccessoryEventTypes.IDENTIFY).length > 0) { @@ -392,13 +408,16 @@ export class Accessory extends EventEmitter { }); // 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})); + service.on(ServiceEventTypes.CHARACTERISTIC_CHANGE, (change: ServiceCharacteristicChange) => { + this.emit(AccessoryEventTypes.SERVICE_CHARACTERISTIC_CHANGE, { + ...change, + service: 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})); - + if (!this.bridged) { + this._handleCharacteristicChange({ ...change, service: service, accessory: this }); + } }); return service; @@ -502,8 +521,8 @@ export class Accessory extends EventEmitter { } // 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_CHARACTERISTIC_CHANGE, (change: AccessoryCharacteristicChange) => { + this._handleCharacteristicChange({ ...change, accessory: accessory }); }); accessory.on(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, () => { @@ -574,11 +593,14 @@ export class Accessory extends EventEmitter { this._updateConfiguration(); } - getCharacteristicByIID = (iid: number) => { + 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; + + if (characteristic) { + return characteristic; + } } } @@ -589,8 +611,7 @@ export class Accessory extends EventEmitter { } } - findCharacteristic = (aid: number, iid: number) => { - + private findCharacteristic(aid: number, iid: number): Characteristic | undefined { // if aid === 1, the accessory is us (because we are the server), otherwise find it among our bridged // accessories (if any) const accessory = (aid === 1) ? this : this.getBridgedAccessoryByAID(aid); @@ -1156,8 +1177,7 @@ export class Accessory extends EventEmitter { this.emit(AccessoryEventTypes.LISTENING, port, hostname); } -// Called when an unpaired client wishes for us to identify ourself - _handleIdentify = (callback: IdentifyCallback) => { + private _handleIdentify(callback: IdentifyCallback) { this._identificationRequest(false, callback); } @@ -1178,7 +1198,7 @@ export class Accessory extends EventEmitter { } // called when a controller adds an additional pairing - _handleAddPairing = (controller: Session, username: string, publicKey: Buffer, permission: PermissionTypes, callback: AddPairingCallback) => { + _handleAddPairing = (controller: HAPSession, username: string, publicKey: Buffer, permission: PermissionTypes, callback: AddPairingCallback) => { if (!this._accessoryInfo) { callback(Codes.UNAVAILABLE); return; @@ -1206,7 +1226,7 @@ export class Accessory extends EventEmitter { callback(0); }; - _handleRemovePairing = (controller: Session, username: string, callback: RemovePairingCallback) => { + _handleRemovePairing = (controller: HAPSession, username: string, callback: RemovePairingCallback) => { if (!this._accessoryInfo) { callback(Codes.UNAVAILABLE); return; @@ -1233,7 +1253,7 @@ export class Accessory extends EventEmitter { } }; - _handleListPairings = (controller: Session, callback: ListPairingsCallback) => { + _handleListPairings = (controller: HAPSession, callback: ListPairingsCallback) => { if (!this._accessoryInfo) { callback(Codes.UNAVAILABLE); return; @@ -1247,8 +1267,7 @@ export class Accessory extends EventEmitter { 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(callback: HandleAccessoriesCallback): void { // make sure our aid/iid's are all assigned this._assignIDs(this._identifierCache!); @@ -1259,131 +1278,95 @@ export class Accessory extends EventEmitter { }); } -// 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) => { - - // build up our array of responses to the characteristics requested asynchronously - const characteristics: CharacteristicData[] = []; - const statusKey = remote ? 's' : 'status'; - const valueKey = remote ? 'v' : 'value'; - - data.forEach((characteristicData) => { - const aid = characteristicData.aid; - const iid = characteristicData.iid; + private _handleGetCharacteristics(request: CharacteristicsReadRequest, session: HAPSession, events: CharacteristicEvents, callback: (response: CharacteristicsReadResponse) => void): void { + const promises: Promise[] = []; - const includeEvent = characteristicData.e; - - const characteristic = this.findCharacteristic(characteristicData.aid, characteristicData.iid); + for (const id of request.ids) { + const characteristic = this.findCharacteristic(id.aid, id.iid); if (!characteristic) { - debug('[%s] Could not find a Characteristic with aid of %s and iid of %s', this.displayName, characteristicData.aid, characteristicData.iid); - let response: any = { - aid: aid, - iid: iid - }; - response[statusKey] = Status.SERVICE_COMMUNICATION_FAILURE; // generic error status - characteristics.push(response); - - // have we collected all responses yet? - if (characteristics.length === data.length) - callback(null, characteristics); - - 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 - }; - response[statusKey] = Status.WRITE_ONLY_CHARACTERISTIC; - characteristics.push(response); - - if (characteristics.length === data.length) { - callback(null, characteristics); - } - return; + debug('[%s] Could not find a Characteristic with aid of %s and iid of %s', this.displayName, id.aid, id.iid); + + promises.push(Promise.resolve({ + aid: id.aid, + iid: id.iid, + status: Status.INVALID_VALUE_IN_REQUEST, + })); + continue; } 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) + 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) } 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; + + promises.push(Promise.resolve({ + aid: id.aid, + iid: id.iid, + status: Status.INSUFFICIENT_PRIVILEGES, + })); + continue; } } - // Found the Characteristic! Get the value! - debug('[%s] Getting value for Characteristic "%s"', this.displayName, characteristic.displayName); - + // Explanation for "events" parameter // 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. - const 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); - let 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); - let response: any = { - aid: aid, - iid: iid - }; - response[valueKey] = value; + // TODO introduce a timeout on those values? + const promise = characteristic.handleGetRequest(session, events).then(value => { + debug('[%s] Got Characteristic "%s" value: %s', this.displayName, characteristic!.displayName, value); - if (includeEvent) { - const eventName = aid + '.' + iid; - response['e'] = (events[eventName] === true); - } + const data: CharacteristicsReadData = { + aid: id.aid, + iid: id.iid, + value: value == undefined? null: value, + }; - // compose the response and add it to the list - characteristics.push(response); + 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, HomeKitTypes.BASE_UUID); + } + if (request.includeEvent) { + data.ev = events[id.aid + "." + id.iid]; } - // have we collected all responses yet? - if (characteristics.length === data.length) - callback(null, characteristics); - - }, context, session? session.sessionID: undefined); + return data; + }, (reason: Status) => { + // @ts-expect-error + debug('[%s] Error getting value for characteristic "%s": %s', this.displayName, characteristic.displayName, Status[reason]); + return { + aid: id.aid, + iid: id.iid, + status: reason, + }; + }); + promises.push(promise); + } - }); + Promise.all(promises).then(value => callback({ characteristics: 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; - - // data is an array of characteristics and values like this: - // [ { aid: 1, iid: 8, value: true, ev: true } ] - - debug("[%s] Processing characteristic set: %s", this.displayName, JSON.stringify(data)); + private _handleSetCharacteristics(writeRequest: CharacteristicsWriteRequest, session: HAPSession, events: CharacteristicEvents, callback: (response: CharacteristicsWriteResponse) => void): 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 @@ -1400,212 +1383,186 @@ export class Accessory extends EventEmitter { } } - // build up our array of responses to the characteristics requested asynchronously - const characteristics: CharacteristicData[] = []; - - data.forEach((characteristicData) => { - const aid = characteristicData.aid; - const iid = characteristicData.iid; - const value = remote ? characteristicData.v : characteristicData.value; - const ev = remote ? characteristicData.e : characteristicData.ev; - const includeValue = characteristicData.r || false; + const characteristics: CharacteristicsWriteData[] = []; + const response: CharacteristicsWriteResponse = { characteristics: characteristics }; - const statusKey = remote ? 's' : 'status'; - - const characteristic = this.findCharacteristic(aid, iid); + for (const data of writeRequest.characteristics) { + const characteristic = this.findCharacteristic(data.aid, data.iid); + let evResponse: boolean | undefined = undefined; if (!characteristic) { - debug('[%s] Could not find a Characteristic with iid of %s and aid of %s', this.displayName, characteristicData.aid, characteristicData.iid); - let response: any = { - aid: aid, - iid: iid - }; - response[statusKey] = Status.SERVICE_COMMUNICATION_FAILURE; // generic error status - characteristics.push(response); + debug('[%s] Could not find a Characteristic with aid of %s and iid of %s', this.displayName, data.aid, data.iid); - // have we collected all responses yet? - if (characteristics.length === data.length) - callback(null, characteristics); + characteristics.push({ + aid: data.aid, + iid: data.iid, + status: Status.INVALID_VALUE_IN_REQUEST, + }); - return; + if (characteristics.length === writeRequest.characteristics.length) { + callback(response); + } + continue; } if (writeState === WriteRequestState.TIMED_WRITE_REJECTED) { - const response: any = { - aid: aid, - iid: iid - }; - response[statusKey] = Status.INVALID_VALUE_IN_REQUEST; - characteristics.push(response); + characteristics.push({ + aid: data.aid, + iid: data.iid, + status: Status.INVALID_VALUE_IN_REQUEST, + }); - if (characteristics.length === data.length) - callback(null, characteristics); - return; + if (characteristics.length === writeRequest.characteristics.length) { + callback(response); + } + continue; } - // 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. - const 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 (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 (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); - - if (characteristics.length === data.length) { - callback(null, characteristics); + 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); + characteristics.push({ + aid: data.aid, + iid: data.iid, + status: Status.NOTIFICATION_NOT_SUPPORTED, + }); + + if (characteristics.length === writeRequest.characteristics.length) { + callback(response); } - return; + continue; } 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) + 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 (!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; + characteristics.push({ + aid: data.aid, + iid: data.iid, + status: Status.INSUFFICIENT_PRIVILEGES, + }); + + if (characteristics.length === writeRequest.characteristics.length) { + callback(response); + } + continue; } } - debug('[%s] %s Characteristic "%s" for events', this.displayName, ev ? "Registering" : "Unregistering", characteristic.displayName); + debug('[%s] %s Characteristic "%s" for events', this.displayName, data.ev ? "Registering" : "Unregistering", characteristic.displayName); - // store event registrations in the supplied "events" dict which is associated with the connection making - // the request. - const eventName = aid + '.' + iid; + const eventName = data.aid + "." + data.iid; - if (ev === true && events[eventName] != true) { - events[eventName] = true; // value is arbitrary, just needs to be non-falsy + if (data.ev && !events[eventName]) { + events[eventName] = true; characteristic.subscribe(); + evResponse = true; } - if (ev === false && events[eventName] != undefined) { + if (!data.ev && events[eventName]) { characteristic.unsubscribe(); - delete events[eventName]; // unsubscribe by deleting name from dict + delete events[eventName]; + evResponse = false; } } - // Found the characteristic - set the value if there is one - if (typeof value !== 'undefined') { + if (data.value != undefined) { + // TODO move from here 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 (characteristics.length === data.length) { - callback(null, characteristics); + debug('[%s] Tried writing to Characteristic which does not allow writing (aid of %s and iid of %s)', this.displayName, data.aid, data.iid); + characteristics.push({ + aid: data.aid, + iid: data.iid, + status: Status.READ_ONLY_CHARACTERISTIC, + }); + + if (characteristics.length === writeRequest.characteristics.length) { + callback(response); } - return; + continue; } 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) + 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 (!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; + characteristics.push({ + aid: data.aid, + iid: data.iid, + status: Status.INSUFFICIENT_PRIVILEGES, + }); + + if (characteristics.length === writeRequest.characteristics.length) { + callback(response); + } + continue; } } 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; + 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); + characteristics.push({ + aid: data.aid, + iid: data.iid, + status: Status.INVALID_VALUE_IN_REQUEST, + }); + + if (characteristics.length === writeRequest.characteristics.length) { + callback(response); + } + continue; } - debug('[%s] Setting Characteristic "%s" to value %s', this.displayName, characteristic.displayName, value); - - // set the value and wait for success - characteristic.setValue(value, (err) => { + debug('[%s] Setting Characteristic "%s" to value %s', this.displayName, characteristic.displayName, data.value); - if (err) { - debug('[%s] Error setting Characteristic "%s" to value %s: ', this.displayName, characteristic!.displayName, value, err.message); + characteristic.handleSetRequest(data.value, session, events).then(value => { + characteristics.push({ + aid: data.aid, + iid: data.iid, - let response: any = { - aid: aid, - iid: iid - }; - response[statusKey] = hapStatus(err); - characteristics.push(response); - } else { - let response: any = { - aid: aid, - iid: iid - }; - response[statusKey] = 0; + value: data.r && value? value: undefined, // if write response is requests and value is provided, return that - if (includeValue) - response['value'] = characteristic!.value; + ev: evResponse, + }) - characteristics.push(response); + if (characteristics.length === writeRequest.characteristics.length) { + callback(response); } - - // have we collected all responses yet? - if (characteristics.length === data.length) - callback(null, characteristics); - - }, context, session? session.sessionID: undefined); - + }, (status: Status) => { + // @ts-expect-error + debug('[%s] Error setting Characteristic "%s" to value %s: ', this.displayName, characteristic.displayName, data.value, Status[status]); + + characteristics.push({ + aid: data.aid, + iid: data.iid, + status: status, + }); + + if (characteristics.length === writeRequest.characteristics.length) { + callback(response); + } + }); } else { - // no value to set, so we're done (success) - let response: any = { - aid: aid, - iid: iid - }; - response[statusKey] = 0; - characteristics.push(response); + characteristics.push({ + aid: data.aid, + iid: data.iid, + ev: evResponse, + }); - // have we collected all responses yet? - if (characteristics.length === data.length) - callback(null, characteristics); + if (characteristics.length === writeRequest.characteristics.length) { + callback(response); + } } - - }); + } } _handleResource(data: Resource, callback: NodeCallback): void { @@ -1665,7 +1622,7 @@ export class Accessory extends EventEmitter { } // Called internally above when a change was detected in one of our hosted Characteristics somewhere in our hierarchy. - _handleCharacteristicChange = (change: ServiceCharacteristicChange) => { + _handleCharacteristicChange = (change: AccessoryCharacteristicChange & { accessory: Accessory }) => { if (!this._server) return; // we're not running a HAPServer, so there's no one to notify about this event @@ -1939,19 +1896,3 @@ export class Accessory extends EventEmitter { } } - -const numberPattern = /^-?\d+$/; - -function hapStatus(err: Error) { - let errorValue = Status.SERVICE_COMMUNICATION_FAILURE; - - if (numberPattern.test(err.message)) { - const value = parseInt(err.message); - - if (value >= Status.INSUFFICIENT_PRIVILEGES && value <= Status.INSUFFICIENT_AUTHORIZATION) { - errorValue = value; - } - } - - return errorValue; -} diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts index e87e5ba7e..f8ee25c58 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -1,6 +1,6 @@ import Decimal from 'decimal.js'; +import { EventEmitter } from "events"; import { - CharacteristicChange, CharacteristicValue, HapCharacteristic, Nullable, @@ -8,10 +8,12 @@ import { ToHAPOptions, VoidCallback, } from '../types'; -import { EventEmitter } from './EventEmitter'; +import { CharacteristicEvents } from "./Accessory"; import * as HomeKitTypes from './gen'; +import { Status } from "./HAPServer"; import { IdentifierCache } from './model/IdentifierCache'; import { clone } from "./util/clone"; +import { HAPSession } from "./util/eventedhttp"; import { once } from './util/once'; import { toShortForm } from './util/uuid'; @@ -61,9 +63,8 @@ export const enum Perms { export interface CharacteristicProps { format: Formats; - unit?: Units; perms: Perms[]; - ev?: boolean; + unit?: Units; description?: string; minValue?: number; maxValue?: number; @@ -81,6 +82,12 @@ export const enum Access { NOTIFY = 0x02 } +export type CharacteristicChange = { + newValue: Nullable; + oldValue: Nullable; + context?: CharacteristicEvents; +}; + export interface SerializedCharacteristic { displayName: string, UUID: string, @@ -92,20 +99,28 @@ export interface SerializedCharacteristic { export const enum CharacteristicEventTypes { GET = "get", SET = "set", + CHANGE = "change", SUBSCRIBE = "subscribe", UNSUBSCRIBE = "unsubscribe", - CHANGE = "change", } -export type CharacteristicGetCallback> = (error?: Error | null , value?: T) => void -export type CharacteristicSetCallback = (error?: Error | null, value?: CharacteristicValue) => void +export type CharacteristicGetCallback = (status?: Status | null | Error, value?: Nullable) => void; +export type CharacteristicSetCallback = (error?: Status | null | Error, writeResponse?: Nullable) => void; + +export declare interface Characteristic { + + on(event: "get", listener: (callback: CharacteristicGetCallback, context: CharacteristicEvents, session: HAPSession) => void): this; + on(event: "set", listener: (value: CharacteristicValue, callback: CharacteristicSetCallback, context: CharacteristicEvents, session: HAPSession) => void): this + on(event: "change", listener: (change: CharacteristicChange) => void): this; + on(event: "subscribe", listener: VoidCallback): this; + on(event: "unsubscribe", listener: VoidCallback): this; + + emit(event: "get", callback: CharacteristicGetCallback, context: CharacteristicEvents, session: HAPSession): boolean; + emit(event: "set", value: CharacteristicValue, callback: CharacteristicSetCallback, context: CharacteristicEvents, session: HAPSession): boolean; + emit(event: "change", change: CharacteristicChange): boolean; + emit(event: "subscribe"): boolean; + emit(event: "unsubscribe"): 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; } /** @@ -139,7 +154,7 @@ type Events = { * 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. @@ -386,13 +401,10 @@ export class Characteristic extends EventEmitter { // NOTICE: when adding/changing properties, remember to possibly adjust the serialize/deserialize functions iid: Nullable = null; value: Nullable = null; - status: Nullable = null; + status: Status = Status.SUCCESS; eventOnlyCharacteristic: boolean = false; props: CharacteristicProps; - subscriptions: number = 0; - - 'valid-values': number[]; - 'valid-values-range': [number, number]; + private subscriptions: number = 0; constructor(public displayName: string, public UUID: string, props?: CharacteristicProps) { super(); @@ -446,14 +458,20 @@ export class Characteristic extends EventEmitter { return this; } - subscribe = () => { + /** + * @internal + */ + subscribe(): void { if (this.subscriptions === 0) { this.emit(CharacteristicEventTypes.SUBSCRIBE); } this.subscriptions++; } - unsubscribe = () => { + /** + * @internal + */ + unsubscribe(): void { const wasOne = this.subscriptions === 1; this.subscriptions--; this.subscriptions = Math.max(this.subscriptions, 0); @@ -462,44 +480,183 @@ export class Characteristic extends EventEmitter { } } - getValue = (callback?: CharacteristicGetCallback, context?: any, connectionID?: SessionIdentifier) => { - // Handle special event only characteristics. - if (this.eventOnlyCharacteristic) { + /** + * Updates the current value of the characteristic. + * + * @param callback + * @param context + * @param sessionId + * @internal use to return the current value on HAP requests + * + * @deprecated + */ + getValue(callback?: CharacteristicGetCallback, context?: CharacteristicEvents, sessionId?: SessionIdentifier): void { + let session: HAPSession | undefined = undefined; + if (sessionId) { + session = HAPSession.getSession(sessionId) + } + + this.handleGetRequest(session!, context!).then(value => { if (callback) { - callback(null, null); + callback(null, value); } - return; + }, reason => { + if (callback) { + callback(reason); + } + }); + } + + setValue(newValue: Nullable, callback?: () => void, context?: CharacteristicEvents): Characteristic { + return this.updateValue(newValue, callback, context) + } + + updateValue(value: Nullable, callback?: () => void, context?: CharacteristicEvents): Characteristic { + this.status = Status.SUCCESS; + value = this.validateValue(value); //validateValue returns a value that has be coerced into a valid value. + + if (value == undefined) { + value = this.getDefaultValue(); } - 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 coerced 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 - const 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}); + + const oldValue = this.value; + this.value = value; + + if (callback) { + callback(); + } + + if (this.eventOnlyCharacteristic || oldValue !== value) { + this.emit(CharacteristicEventTypes.CHANGE, { 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 session - The HAP session from which the request originated from + * @param context - events context + * @internal Used by the Accessory to load the characteristic value + */ + handleGetRequest(session: HAPSession, context: CharacteristicEvents): Promise> { + if (!this.props.perms.includes(Perms.PAIRED_READ)) { // check if we are allowed to read from this characteristic + return Promise.reject(Status.WRITE_ONLY_CHARACTERISTIC); + } + + // TODO remove this thing (maybe? doorbell people should setup event handler with null returned?) + if (this.eventOnlyCharacteristic) { + return Promise.resolve(null); + } + if (this.listeners(CharacteristicEventTypes.GET).length === 0) { + return this.status? Promise.reject(this.status): Promise.resolve(this.value); + } + + return new Promise((resolve, reject) => { + this.emit(CharacteristicEventTypes.GET, once((status?: Error | Status | null, value?: Nullable) => { + if (status) { + this.status = typeof status === "number"? status: extractHAPStatusFromError(status); + reject(this.status); + return; + } + + this.status = Status.SUCCESS; + + value = this.validateValue(value); // validateValue returns a value that has be coerced into a valid value. + if (value == null) { // null or undefined + value = this.getDefaultValue(); + } + + const oldValue = this.value; + this.value = value; + + resolve(value); + + if (oldValue !== value) { // emit a change event if necessary + this.emit(CharacteristicEventTypes.CHANGE, { oldValue: oldValue, newValue: value, context: context }); } - }), context, connectionID); + }), context, session); + }); + } + + /** + * Called when a HAP requests update the current value of the characteristic. + * + * @param value - The update value + * @param session - The session from which the request originated from + * @param context + * @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 + */ + handleSetRequest(value: CharacteristicValue, session: HAPSession, context: CharacteristicEvents): Promise { + this.status = Status.SUCCESS; + + // TODO return proper error code if incoming value is not valid! + value = this.validateValue(value)!; // validateValue returns a value that has be coerced into a valid value. + const oldValue = this.value; + + if (this.listeners(CharacteristicEventTypes.SET).length === 0) { + this.value = value; + if (this.eventOnlyCharacteristic || oldValue !== value) { + const change: CharacteristicChange = { + oldValue: oldValue as CharacteristicValue, + newValue: value as CharacteristicValue, + context: context, + } + this.emit(CharacteristicEventTypes.CHANGE, change); + } + return Promise.resolve(); } else { - // no one is listening to the 'get' event, so just return the cached value - if (callback) - callback(this.status, this.value); + return new Promise((resolve, reject) => { + this.emit(CharacteristicEventTypes.SET, value, once((status?: Error | Status | null, writeResponse?: Nullable) => { + if (status) { + this.status = typeof status === "number"? status: extractHAPStatusFromError(status); + reject(this.status); + return; + } + + this.status = Status.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 { + this.value = value; + resolve(); + } + + if (this.eventOnlyCharacteristic || oldValue !== value) { + this.emit(CharacteristicEventTypes.CHANGE, { oldValue: oldValue, newValue: value, context: context }); + } + }), context, session); + }); + } + } + + protected getDefaultValue(): Nullable { + switch (this.props.format) { + case Formats.BOOL: + return false; + case Formats.STRING: + 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; } } - validateValue = (newValue: Nullable): Nullable => { + private validateValue(newValue?: Nullable): Nullable { let isNumericType = false; let minValue_resolved: number | undefined = 0; let maxValue_resolved: number | undefined = 0; @@ -562,13 +719,13 @@ export class Characteristic extends EventEmitter { 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; + return newValue == undefined? null: newValue; } case Formats.TLV8: //Should we parse this to make sure the tlv8 is valid? break; default: //Datatype out of HAP Spec encountered. We'll assume the developer knows what they're doing. - return newValue; + return newValue == undefined? null: newValue; } if (isNumericType) { @@ -611,98 +768,17 @@ export class Characteristic extends EventEmitter { return this.value!; //If we had an error, return the current value. } } - if (this['valid-values'] !== undefined) - if (!this['valid-values'].includes(newValue as number)) + if (this.props.validValues !== undefined) + if (!this.props.validValues.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 (this.props.validValueRanges !== undefined) { //This is another way Apple has to handle min/max + if (newValue! < this.props.validValueRanges[0]) + newValue = this.props.validValueRanges[0]; + if (newValue! > this.props.validValueRanges[1]) + newValue = this.props.validValueRanges[1]; } } - return newValue; - } - - 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 coerced into a valid value. - const 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 || 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 || oldValue !== newValue) - this.emit(CharacteristicEventTypes.CHANGE, {oldValue: oldValue, newValue: newValue, context: context}); - } - 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 coerced 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 - const oldValue = this.value; - this.value = newValue; - if (callback) - callback(); - if (this.eventOnlyCharacteristic || oldValue !== newValue) - this.emit(CharacteristicEventTypes.CHANGE, {oldValue: oldValue, newValue: newValue, context: context}); - return this; // for chaining - } - - getDefaultValue = (): Nullable => { - switch (this.props.format) { - case Formats.BOOL: - return false; - case Formats.STRING: - 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; - } + return newValue == undefined? null: newValue; } _assignID = (identifierCache: IdentifierCache, accessoryName: string, serviceUUID: string, serviceSubtype?: string) => { @@ -712,8 +788,9 @@ export class Characteristic extends EventEmitter { /** * Returns a JSON representation of this Accessory suitable for delivering to HAP clients. + * @internal */ - toHAP = (opt?: ToHAPOptions) => { + toHAP(opt?: ToHAPOptions): HapCharacteristic { // ensure our value fits within our constraints if present let value = this.value; @@ -791,7 +868,12 @@ export class Characteristic extends EventEmitter { return hap as HapCharacteristic; } - static serialize = (characteristic: Characteristic): SerializedCharacteristic => { + /** + * + * @param characteristic + * @internal + */ + static serialize(characteristic: Characteristic): SerializedCharacteristic { return { displayName: characteristic.displayName, UUID: characteristic.UUID, @@ -801,7 +883,12 @@ export class Characteristic extends EventEmitter { } }; - static deserialize = (json: SerializedCharacteristic): Characteristic => { + /** + * + * @param json + * @internal + */ + static deserialize(json: SerializedCharacteristic): Characteristic { const characteristic = new Characteristic(json.displayName, json.UUID, json.props); characteristic.value = json.value; @@ -824,3 +911,19 @@ function decimalPlaces(num: number) { // Adjust for scientific notation. - (match[2] ? +match[2] : 0)); } + +const numberPattern = /^-?\d+$/; + +function extractHAPStatusFromError(error: Error) { + let errorValue = Status.SERVICE_COMMUNICATION_FAILURE; + + if (numberPattern.test(error.message)) { + const value = parseInt(error.message); + + if (value >= Status.INSUFFICIENT_PRIVILEGES && value <= Status.NOT_ALLOWED_IN_CURRENT_STATE) { + errorValue = value; + } + } + + return errorValue; +} diff --git a/src/lib/HAPServer.ts b/src/lib/HAPServer.ts index ec450ba6e..3dcc9332d 100644 --- a/src/lib/HAPServer.ts +++ b/src/lib/HAPServer.ts @@ -3,12 +3,21 @@ import createDebug from 'debug'; import { SRP, SrpServer } from "fast-srp-hap"; import { IncomingMessage, ServerResponse } from "http"; import tweetnacl from 'tweetnacl'; -import url from 'url'; -import { CharacteristicData, NodeCallback, PairingsCallback, SessionIdentifier, VoidCallback } from '../types'; +import { URL } from 'url'; +import { + CharacteristicId, + CharacteristicsReadRequest, + CharacteristicsReadResponse, + CharacteristicsWriteRequest, + CharacteristicsWriteResponse, + consideredTrue, + PrepareWriteRequest +} from "../internal-types"; +import { NodeCallback, PairingsCallback, SessionIdentifier, VoidCallback } from '../types'; import { Accessory, CharacteristicEvents, Resource } from './Accessory'; import { EventEmitter } from './EventEmitter'; import { PairingInformation, PermissionTypes } from "./model/AccessoryInfo"; -import { EventedHTTPServer, EventedHTTPServerEvents, Session } from './util/eventedhttp'; +import { EventedHTTPServer, EventedHTTPServerEvents, HAPSession } from './util/eventedhttp'; import * as hapCrypto from './util/hapCrypto'; import { once } from './util/once'; import * as tlv from './util/tlv'; @@ -75,24 +84,17 @@ export const enum Status { 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 -} - -export type CharacteristicsWriteRequest = { - characteristics: CharacteristicData[], - pid?: number -} + INSUFFICIENT_AUTHORIZATION = -70411, + NOT_ALLOWED_IN_CURRENT_STATE = -70412, -export type PrepareWriteRequest = { - ttl: number, - pid: number + // when adding new status codes, remember to change upper bound in extractHAPStatusFromError } export const enum HAPServerEventTypes { @@ -113,23 +115,21 @@ export type Events = { [HAPServerEventTypes.IDENTIFY]: (cb: VoidCallback) => void; [HAPServerEventTypes.LISTENING]: (port: number, hostname: string) => 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.ADD_PAIRING]: (controller: HAPSession, username: string, publicKey: Buffer, permission: number, callback: PairingsCallback) => void; + [HAPServerEventTypes.REMOVE_PAIRING]: (controller: HAPSession, username: string, callback: PairingsCallback) => void; + [HAPServerEventTypes.LIST_PAIRINGS]: (controller: HAPSession, callback: PairingsCallback) => void; [HAPServerEventTypes.ACCESSORIES]: (cb: NodeCallback) => void; [HAPServerEventTypes.GET_CHARACTERISTICS]: ( - data: CharacteristicData[], + request: CharacteristicsReadRequest, + session: HAPSession, events: CharacteristicEvents, - cb: NodeCallback, - remote: boolean, - session: Session, + callback: (response: CharacteristicsReadResponse) => void, ) => void; [HAPServerEventTypes.SET_CHARACTERISTICS]: ( writeRequest: CharacteristicsWriteRequest, + session: HAPSession, events: CharacteristicEvents, - cb: NodeCallback, - remote: boolean, - session: Session, + callback: (response: CharacteristicsWriteResponse) => void, ) => void; [HAPServerEventTypes.SESSION_CLOSE]: (sessionID: SessionIdentifier, events: CharacteristicEvents) => void; [HAPServerEventTypes.REQUEST_RESOURCE]: (data: Resource, cb: NodeCallback) => void; @@ -194,7 +194,7 @@ export type Events = { */ export class HAPServer extends EventEmitter { - static handlers: Record = { + static handlers: Record = { '/identify': '_handleIdentify', '/pair-setup': '_handlePair', '/pair-verify': '_handlePairVerify', @@ -265,7 +265,7 @@ export class HAPServer extends EventEmitter { } // Called when an HTTP request was detected. - _onRequest = (request: IncomingMessage, response: ServerResponse, session: Session, events: any) => { + _onRequest = (request: IncomingMessage, response: ServerResponse, session: HAPSession, events: CharacteristicEvents) => { debug("[%s] HAP Request: %s %s", this.accessoryInfo.username, request.method, request.url); // collect request data, if any let requestData = Buffer.alloc(0); @@ -274,11 +274,11 @@ export class HAPServer extends EventEmitter { }); request.on('end', () => { // parse request.url (which can contain querystring, etc.) into components, then extract just the path - const pathname = url.parse(request.url!).pathname!; + const url = new URL(request.url!, "http://hap-nodejs.local"); // all request data received; now process this request for (let path in HAPServer.handlers) - if (new RegExp('^' + path + '/?$').test(pathname)) { // match exact string and allow trailing slash - const handler = HAPServer.handlers[path] as keyof HAPServer; + if (new RegExp('^' + path + '/?$').test(url.pathname)) { // match exact string and allow trailing slash + const handler = HAPServer.handlers[path]; this[handler](request, response, session, events, requestData); return; } @@ -289,7 +289,7 @@ export class HAPServer extends EventEmitter { }); } - _onEncrypt = (data: Buffer, encrypted: { data: Buffer; }, session: Session) => { + _onEncrypt = (data: Buffer, encrypted: { data: Buffer; }, session: HAPSession) => { // instance of HAPEncryption (created in handlePairVerifyStepOne) const enc = session.encryption; // if accessoryToControllerKey is not empty, then encryption is enabled for this connection. However, we'll @@ -302,7 +302,7 @@ export class HAPServer extends EventEmitter { } } - _onDecrypt = (data: Buffer, decrypted: { data: number | Buffer; error: Error | null }, session: Session) => { + _onDecrypt = (data: Buffer, decrypted: { data: number | Buffer; error: Error | null }, session: HAPSession) => { // possibly an instance of HAPEncryption (created in handlePairVerifyStepOne) const enc = session.encryption; // if controllerToAccessoryKey is not empty, then encryption is enabled for this connection. @@ -322,7 +322,7 @@ export class HAPServer extends EventEmitter { /** * Unpaired Accessory identification. */ - _handleIdentify = (request: IncomingMessage, response: ServerResponse, session: Session, events: any, requestData: any) => { + _handleIdentify = (request: IncomingMessage, response: ServerResponse, session: HAPSession, events: any, requestData: any) => { // /identify only works if the accessory is not paired if (!this.allowInsecureRequest && this.accessoryInfo.paired()) { response.writeHead(400, {"Content-Type": "application/hap+json"}); @@ -345,7 +345,7 @@ export class HAPServer extends EventEmitter { /** * iOS <-> Accessory pairing process. */ - _handlePair = (request: IncomingMessage, response: ServerResponse, session: Session, events: any, requestData: Buffer) => { + _handlePair = (request: IncomingMessage, response: ServerResponse, session: HAPSession, events: any, requestData: Buffer) => { // Can only be directly paired with one iOS device if (!this.allowInsecureRequest && this.accessoryInfo.paired()) { response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); @@ -375,7 +375,7 @@ export class HAPServer extends EventEmitter { } // M1 + M2 - _handlePairStepOne = (request: IncomingMessage, response: ServerResponse, session: Session) => { + _handlePairStepOne = (request: IncomingMessage, response: ServerResponse, session: HAPSession) => { debug("[%s] Pair step 1/5", this.accessoryInfo.username); const salt = crypto.randomBytes(16, ); @@ -398,7 +398,7 @@ export class HAPServer extends EventEmitter { } // M3 + M4 - _handlePairStepTwo = (request: IncomingMessage, response: ServerResponse, session: Session, objects: Record) => { + _handlePairStepTwo = (request: IncomingMessage, response: ServerResponse, session: HAPSession, objects: Record) => { debug("[%s] Pair step 2/5", this.accessoryInfo.username); const A = objects[TLVValues.PUBLIC_KEY]; // "A is a public key that exists only for a single login session." const M1 = objects[TLVValues.PASSWORD_PROOF]; // "M1 is the proof that you actually know your own password." @@ -424,7 +424,7 @@ export class HAPServer extends EventEmitter { } // M5-1 - _handlePairStepThree = (request: IncomingMessage, response: ServerResponse, session: Session, objects: Record) => { + _handlePairStepThree = (request: IncomingMessage, response: ServerResponse, session: HAPSession, objects: Record) => { debug("[%s] Pair step 3/5", this.accessoryInfo.username); // pull the SRP server we created in stepOne out of the current session const srpServer = session.srpServer!; @@ -457,7 +457,7 @@ export class HAPServer extends EventEmitter { } // M5-2 - _handlePairStepFour = (request: IncomingMessage, response: ServerResponse, session: Session, clientUsername: Buffer, clientLTPK: Buffer, clientProof: Buffer, hkdfEncKey: Buffer) => { + _handlePairStepFour = (request: IncomingMessage, response: ServerResponse, session: HAPSession, clientUsername: Buffer, clientLTPK: Buffer, clientProof: Buffer, hkdfEncKey: Buffer) => { debug("[%s] Pair step 4/5", this.accessoryInfo.username); const S_private = session.srpServer!.computeK(); const controllerSalt = Buffer.from("Pair-Setup-Controller-Sign-Salt"); @@ -475,7 +475,7 @@ export class HAPServer extends EventEmitter { } // M5 - F + M6 - _handlePairStepFive = (request: IncomingMessage, response: ServerResponse, session: Session, clientUsername: Buffer, clientLTPK: Buffer, hkdfEncKey: Buffer) => { + _handlePairStepFive = (request: IncomingMessage, response: ServerResponse, session: HAPSession, clientUsername: Buffer, clientLTPK: Buffer, hkdfEncKey: Buffer) => { debug("[%s] Pair step 5/5", this.accessoryInfo.username); const S_private = session.srpServer!.computeK(); const accessorySalt = Buffer.from("Pair-Setup-Accessory-Sign-Salt"); @@ -509,7 +509,7 @@ export class HAPServer extends EventEmitter { /** * iOS <-> Accessory pairing verification. */ - _handlePairVerify = (request: IncomingMessage, response: ServerResponse, session: Session, events: any, requestData: Buffer) => { + _handlePairVerify = (request: IncomingMessage, response: ServerResponse, session: HAPSession, events: any, requestData: Buffer) => { const objects = tlv.decode(requestData); const sequence = objects[TLVValues.SEQUENCE_NUM][0]; // value is single byte with sequence number if (sequence == States.M1) @@ -524,7 +524,7 @@ export class HAPServer extends EventEmitter { } } - _handlePairVerifyStepOne = (request: IncomingMessage, response: ServerResponse, session: Session, objects: Record) => { + _handlePairVerifyStepOne = (request: IncomingMessage, response: ServerResponse, session: HAPSession, objects: Record) => { debug("[%s] Pair verify step 1/2", this.accessoryInfo.username); const clientPublicKey = objects[TLVValues.PUBLIC_KEY]; // Buffer // generate new encryption keys for this session @@ -558,7 +558,7 @@ export class HAPServer extends EventEmitter { session._pairVerifyState = States.M2; } - _handlePairVerifyStepTwo = (request: IncomingMessage, response: ServerResponse, session: Session, events: any, objects: Record) => { + _handlePairVerifyStepTwo = (request: IncomingMessage, response: ServerResponse, session: HAPSession, events: any, objects: Record) => { debug("[%s] Pair verify step 2/2", this.accessoryInfo.username); const encryptedData = objects[TLVValues.ENCRYPTED_DATA]; const messageData = Buffer.alloc(encryptedData.length - 16); @@ -623,7 +623,7 @@ export class HAPServer extends EventEmitter { /** * Pair add/remove/list */ - _handlePairings = (request: IncomingMessage, response: ServerResponse, session: Session, events: any, requestData: Buffer) => { + _handlePairings = (request: IncomingMessage, response: ServerResponse, session: HAPSession, events: any, requestData: Buffer) => { // Only accept /pairing request if there is a secure session if (!this.allowInsecureRequest && !session.authenticated) { response.writeHead(470, {"Content-Type": "application/hap+json"}); @@ -706,7 +706,7 @@ export class HAPServer extends EventEmitter { */ // Called when the client wishes to fetch all data regarding our published Accessories. - _handleAccessories = (request: IncomingMessage, response: ServerResponse, session: Session, events: any, requestData: any) => { + _handleAccessories = (request: IncomingMessage, response: ServerResponse, session: HAPSession, 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})); @@ -726,69 +726,65 @@ export class HAPServer extends EventEmitter { } // 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; }) => { + _handleCharacteristics(request: IncomingMessage, response: ServerResponse, session: HAPSession, events: CharacteristicEvents, requestData: { length: number; toString: () => string; }): void { 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 == "GET") { - // Extract the query params from the URL which looks like: /characteristics?id=1.9,2.14,... - const parseQueryString = true; - const query = url.parse(request.url!, parseQueryString).query; // { id: '1.9,2.14' } - if (query == undefined || query.id == undefined) { - response.writeHead(500); - response.end(); + if (request.method === "GET") { + const url = new URL(request.url!, "http://hap-nodejs.local"); + const searchParams = url.searchParams; + + const idParam = searchParams.get("id"); + if (!idParam) { + // TODO proper error code + response.writeHead(500, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify({ status: Status.INVALID_VALUE_IN_REQUEST })); return; } - const sets = (query.id as string).split(','); // ["1.9","2.14"] - const data: CharacteristicData[] = []; // [{aid:1,iid:9},{aid:2,iid:14}] - for (let i in sets) { - const ids = sets[i].split('.'); // ["1","9"] - const aid = parseInt(ids[0]); // accessory ID - const 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 (let 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, readRequest, session, events, once((readResponse: CharacteristicsReadResponse) => { + 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 = Status.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); + response.end(JSON.stringify({ characteristics: characteristics })); + })); } else if (request.method == "PUT") { if (!session.authenticated) { if (!request.headers || (request.headers && request.headers["authorization"] !== this.accessoryInfo.pincode)) { @@ -797,63 +793,50 @@ export class HAPServer extends EventEmitter { return; } } - if (requestData.length == 0) { + if (requestData.length === 0) { response.writeHead(400, {"Content-Type": "application/hap+json"}); response.end(JSON.stringify({status: Status.INVALID_VALUE_IN_REQUEST})); return; } - // requestData is a JSON payload like { characteristics: [ { aid: 1, iid: 8, value: true, ev: true } ] } + const writeRequest = JSON.parse(requestData.toString()) as CharacteristicsWriteRequest; - const 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 (let i in data) { - characteristics.push({ - aid: data[i].aid, - iid: data[i].iid, - // @ts-ignore - status: Status.SERVICE_COMMUNICATION_FAILURE - }); - } - } + const data = writeRequest.characteristics; + + this.emit(HAPServerEventTypes.SET_CHARACTERISTICS, writeRequest, session, events, once((writeResponse: CharacteristicsWriteResponse) => { + 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 = Status.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.end(JSON.stringify({ characteristics: characteristics })); } else { // if everything went fine send 204 no content response response.writeHead(204); // 204 "No content" response.end(); } - }), false, session); + })); + } else { + // TODO handle that at least for debugging purposes } } // Called when controller requests a timed write - _prepareWrite = (request: IncomingMessage, response: ServerResponse, session: Session, events: any, requestData: { length: number; toString: () => string; }) => { + _prepareWrite = (request: IncomingMessage, response: ServerResponse, session: HAPSession, 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})); @@ -890,7 +873,7 @@ export class HAPServer extends EventEmitter { }; // Called when controller request snapshot - _handleResource = (request: IncomingMessage, response: ServerResponse, session: Session, events: any, requestData: { length: number; toString: () => string; }) => { + _handleResource = (request: IncomingMessage, response: ServerResponse, session: HAPSession, 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})); diff --git a/src/lib/Service.ts b/src/lib/Service.ts index 90bb79be8..d020804af 100644 --- a/src/lib/Service.ts +++ b/src/lib/Service.ts @@ -1,5 +1,10 @@ -import { CharacteristicChange, CharacteristicValue, HapService, Nullable, ToHAPOptions, WithUUID, } from '../types'; -import { Characteristic, CharacteristicEventTypes, SerializedCharacteristic } from './Characteristic'; +import { CharacteristicValue, HapService, Nullable, ToHAPOptions, WithUUID, } from '../types'; +import { + Characteristic, + CharacteristicChange, + CharacteristicEventTypes, + SerializedCharacteristic +} from './Characteristic'; import { EventEmitter } from './EventEmitter'; import * as HomeKitTypes from './gen'; import { IdentifierCache } from './model/IdentifierCache'; @@ -31,8 +36,10 @@ export type ServiceConfigurationChange = { service: Service; }; +export type ServiceCharacteristicChange = CharacteristicChange & { characteristic: Characteristic }; + type Events = { - [ServiceEventTypes.CHARACTERISTIC_CHANGE]: (change: CharacteristicChange) => void; + [ServiceEventTypes.CHARACTERISTIC_CHANGE]: (change: ServiceCharacteristicChange) => void; [ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE]: (change: ServiceConfigurationChange) => void; } @@ -202,8 +209,7 @@ export class Service extends EventEmitter { // 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 }); }); this.characteristics.push(characteristic); @@ -375,6 +381,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)); } @@ -382,6 +389,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)); } } @@ -454,8 +462,7 @@ export class Service extends EventEmitter { _setupCharacteristic = (characteristic: Characteristic) => { // 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 }); }); } diff --git a/src/lib/camera/RTPStreamManagement.ts b/src/lib/camera/RTPStreamManagement.ts index f60085efc..1c8d4ff5e 100644 --- a/src/lib/camera/RTPStreamManagement.ts +++ b/src/lib/camera/RTPStreamManagement.ts @@ -2,7 +2,7 @@ import crypto from 'crypto'; import createDebug from 'debug'; import net from "net"; // noinspection JSDeprecatedSymbols -import { LegacyCameraSource, LegacyCameraSourceAdapter, once, uuid } from "../../index"; +import { CharacteristicEvents, LegacyCameraSource, LegacyCameraSourceAdapter, once, uuid } from "../../index"; import { CharacteristicValue, Nullable, SessionIdentifier } from '../../types'; import { Characteristic, @@ -14,7 +14,7 @@ import { CameraController, CameraStreamingDelegate } from "../controller"; import { CameraRTPStreamManagement } from "../gen/HomeKit"; import { Status } from "../HAPServer"; import { Service } from '../Service'; -import { EventedHTTPServer, Session } from "../util/eventedhttp"; +import { HAPSession } from "../util/eventedhttp"; import * as tlv from '../util/tlv'; import RTPProxy from './RTPProxy'; @@ -575,8 +575,8 @@ export class RTPStreamManagement { .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { callback(null, this.setupEndpointsResponse); }) - .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback, context?: any, connectionID?: SessionIdentifier) => { - this.handleSetupEndpoints(value, callback, connectionID!); + .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback, context: CharacteristicEvents, session: HAPSession) => { + this.handleSetupEndpoints(value, callback, session); }); } @@ -615,7 +615,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(Status.INVALID_VALUE_IN_REQUEST); return; } @@ -648,7 +648,7 @@ export class RTPStreamManagement { case SessionControlCommand.SUSPEND_SESSION: default: debug(`Unhandled request type ${SessionControlCommand[requestType]}`); - callback(new Error(Status.INVALID_VALUE_IN_REQUEST + "")); + callback(Status.INVALID_VALUE_IN_REQUEST); return; } } @@ -851,7 +851,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, session: HAPSession): void { const data = Buffer.from(value as string, 'base64'); const objects = tlv.decode(data); @@ -866,12 +866,10 @@ export class RTPStreamManagement { return; } - this.connectionID = connectionID; + this.connectionID = session.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); @@ -979,7 +977,7 @@ export class RTPStreamManagement { }); } - private generateSetupEndpointResponse(session: Session, identifier: StreamSessionIdentifier, request: PrepareStreamRequest, response: PrepareStreamResponse, callback: CharacteristicSetCallback): void { + private generateSetupEndpointResponse(session: HAPSession, identifier: StreamSessionIdentifier, request: PrepareStreamRequest, response: PrepareStreamResponse, callback: CharacteristicSetCallback): void { let address: string; let addressVersion = request.addressVersion; diff --git a/src/lib/controller/RemoteController.ts b/src/lib/controller/RemoteController.ts index ac5ba68e8..e2fd1a25a 100644 --- a/src/lib/controller/RemoteController.ts +++ b/src/lib/controller/RemoteController.ts @@ -1,17 +1,13 @@ -import * as tlv from '../util/tlv'; -import createDebug from 'debug'; import assert from 'assert'; - -import {Service} from "../Service"; +import createDebug from 'debug'; +import { CharacteristicValue } from "../../types"; +import { Accessory, CharacteristicEvents } 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 +23,14 @@ 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 { EventEmitter } from "../EventEmitter"; +import { DataStreamTransportManagement } from "../gen/HomeKit-DataStream"; +import { AudioStreamManagement, Siri, TargetControl, TargetControlManagement } from "../gen/HomeKit-Remote"; +import { Status } from "../HAPServer"; +import { Service } from "../Service"; +import { HAPSession, HAPSessionEvents } 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'); @@ -360,7 +359,7 @@ 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 activeSession?: HAPSession; // session which marked this remote as active and listens for events and siri private activeSessionDisconnectionListener?: () => void; supportedAudioConfiguration: string; @@ -587,7 +586,7 @@ export class RemoteController extends EventEmitter handler = this.handleListTargets; break; default: - callback(new Error(Status.INVALID_VALUE_IN_REQUEST+""), undefined); + callback(Status.INVALID_VALUE_IN_REQUEST, undefined); return; } @@ -719,20 +718,10 @@ export class RemoteController extends EventEmitter return Status.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, session: HAPSession): 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(Status.INVALID_VALUE_IN_REQUEST); return; } @@ -771,7 +760,7 @@ export class RemoteController extends EventEmitter setTimeout(() => this.emit(RemoteControllerEvents.ACTIVE_CHANGE, false), 0); }; - private handleActiveSessionDisconnected = (session: Session) => { + private handleActiveSessionDisconnected = (session: HAPSession) => { if (session !== this.activeSession) { return; } @@ -1198,8 +1187,8 @@ export class RemoteController extends EventEmitter .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { callback(undefined, this.isActive()); }) - .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback, context?: any, connectionID?: SessionIdentifier) => { - this.handleActiveWrite(value, callback, connectionID); + .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback, context: CharacteristicEvents, session: HAPSession) => { + this.handleActiveWrite(value, callback, session); }); this.targetControlService.getCharacteristic(Characteristic.ButtonEvent)! .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { @@ -1213,7 +1202,8 @@ export class RemoteController extends EventEmitter }) .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { this.handleSelectedAudioConfigurationWrite(value, callback); - }).getValue(); + }) + .updateValue(this.selectedAudioConfigurationString); this.dataStreamManagement! .onEventMessage(Protocols.TARGET_CONTROL, Topics.WHOAMI, this.handleTargetControlWhoAmI.bind(this)) diff --git a/src/lib/datastream/DataStreamManagement.ts b/src/lib/datastream/DataStreamManagement.ts index e3f73fdf0..177fbeba8 100644 --- a/src/lib/datastream/DataStreamManagement.ts +++ b/src/lib/datastream/DataStreamManagement.ts @@ -1,17 +1,24 @@ -import * as tlv from '../util/tlv'; import createDebug from "debug"; -import {Service} from "../Service"; +import { CharacteristicValue } from "../../types"; +import { CharacteristicEvents } from "../Accessory"; 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"; +import { Event } from "../EventEmitter"; +import { DataStreamTransportManagement } from "../gen/HomeKit-DataStream"; +import { Status } from "../HAPServer"; +import { Service } from "../Service"; +import { HAPSession } from "../util/eventedhttp"; +import * as tlv from '../util/tlv'; +import { + DataStreamServer, + DataStreamServerEventMap, + GlobalEventHandler, + GlobalRequestHandler +} from "./DataStreamServer"; const debug = createDebug('HAP-NodeJS:DataStream:Management'); @@ -144,7 +151,7 @@ export class DataStreamManagement { return this; } - private handleSetupDataStreamTransportWrite(value: any, callback: CharacteristicSetCallback, connectionID?: string) { + private handleSetupDataStreamTransportWrite(value: any, callback: CharacteristicSetCallback, session: HAPSession) { const data = Buffer.from(value, 'base64'); const objects = tlv.decode(data); @@ -156,18 +163,7 @@ export class DataStreamManagement { 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)); - 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)); + callback(Status.INVALID_VALUE_IN_REQUEST); return; } @@ -187,15 +183,11 @@ export class DataStreamManagement { callback(null, response.toString('base64')); }); } else { - callback(null, DataStreamManagement.buildSetupStatusResponse(DataStreamStatus.GENERIC_ERROR)); + callback(Status.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 => { @@ -222,9 +214,10 @@ export class DataStreamManagement { .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { 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: CharacteristicValue, callback: CharacteristicSetCallback, context: CharacteristicEvents, session: HAPSession) => { + this.handleSetupDataStreamTransportWrite(value, callback, session); + }) + .updateValue(this.lastSetupDataStreamTransportResponse); } } diff --git a/src/lib/datastream/DataStreamServer.ts b/src/lib/datastream/DataStreamServer.ts index 912a3788b..118345bc8 100644 --- a/src/lib/datastream/DataStreamServer.ts +++ b/src/lib/datastream/DataStreamServer.ts @@ -5,7 +5,7 @@ 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 {HAPSessionEvents, HAPSession} from "../util/eventedhttp"; import {EventEmitter as NodeEventEmitter} from "events"; import {EventEmitter} from "../EventEmitter"; import Timeout = NodeJS.Timeout; @@ -14,7 +14,7 @@ const debug = createDebug('HAP-NodeJS:DataStream:Server'); export type PreparedDataStreamSession = { - session: Session, // reference to the hap session which created the request + session: HAPSession, // reference to the hap session which created the request accessoryToControllerEncryptionKey: Buffer, controllerToAccessoryEncryptionKey: Buffer, @@ -218,7 +218,7 @@ export class DataStreamServer extends EventEmitter { return this; } - prepareSession(session: Session, controllerKeySalt: Buffer, callback: (preparedSession: PreparedDataStreamSession) => void) { + prepareSession(session: HAPSession, controllerKeySalt: Buffer, callback: (preparedSession: PreparedDataStreamSession) => void) { debug("Preparing for incoming HDS connection from session %s", session.sessionID); const accessoryKeySalt = crypto.randomBytes(32); const salt = Buffer.concat([controllerKeySalt, accessoryKeySalt]); @@ -426,7 +426,7 @@ export class DataStreamConnection extends EventEmitter UNIDENTIFIED + private session?: HAPSession; // reference to the hap session. 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 diff --git a/src/lib/model/AccessoryInfo.ts b/src/lib/model/AccessoryInfo.ts index 0781d8b7d..e112ad50c 100644 --- a/src/lib/model/AccessoryInfo.ts +++ b/src/lib/model/AccessoryInfo.ts @@ -3,7 +3,7 @@ import assert from 'assert'; import tweetnacl from 'tweetnacl'; import { Categories } from '../Accessory'; -import { Session } from "../util/eventedhttp"; +import { HAPSession } from "../util/eventedhttp"; import { MacAddress } from "../../types"; import { HAPStorage } from "./HAPStorage"; @@ -104,7 +104,7 @@ export class AccessoryInfo { * @param controller - the session of the controller initiated the removal of the pairing * @param {string} username */ - removePairedClient = (controller: Session, username: string) => { + removePairedClient = (controller: HAPSession, username: string) => { this._removePairedClient0(controller, username); if (this.pairedAdminClients === 0) { // if we don't have any admin clients left paired it is required to kill all normal clients @@ -114,12 +114,12 @@ export class AccessoryInfo { } }; - _removePairedClient0 = (controller: Session, username: string) => { + _removePairedClient0 = (controller: HAPSession, username: string) => { if (this.pairedClients[username] && this.pairedClients[username].permission === PermissionTypes.ADMIN) this.pairedAdminClients--; delete this.pairedClients[username]; - Session.destroyExistingConnectionsAfterUnpair(controller, username); + HAPSession.destroyExistingConnectionsAfterUnpair(controller, username); }; /** diff --git a/src/lib/util/eventedhttp.ts b/src/lib/util/eventedhttp.ts index 7aebecd83..4146fbcb0 100644 --- a/src/lib/util/eventedhttp.ts +++ b/src/lib/util/eventedhttp.ts @@ -5,6 +5,7 @@ import http, { IncomingMessage, OutgoingMessage, ServerResponse } from 'http'; import net, { AddressInfo, Socket } from 'net'; import os from "os"; import { Nullable, SessionIdentifier } from '../../types'; +import { CharacteristicEvents } from "../Accessory"; import { EventEmitter } from '../EventEmitter'; import { HAPEncryption } from '../HAPServer'; import * as uuid from './uuid'; @@ -22,9 +23,9 @@ export const enum EventedHTTPServerEvents { export type Events = { [EventedHTTPServerEvents.LISTENING]: (port: number, hostname: string) => 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.REQUEST]: (request: IncomingMessage, response: ServerResponse, session: HAPSession, events: CharacteristicEvents) => void; + [EventedHTTPServerEvents.DECRYPT]: (data: Buffer, decrypted: { data: Buffer; error: Error | null }, session: HAPSession) => void; + [EventedHTTPServerEvents.ENCRYPT]: (data: Buffer, encrypted: { data: number | Buffer; }, session: HAPSession) => void; [EventedHTTPServerEvents.CLOSE]: (events: any) => void; [EventedHTTPServerEvents.SESSION_CLOSE]: (sessionID: SessionIdentifier, events: any) => void; }; @@ -114,7 +115,7 @@ export class EventedHTTPServer extends EventEmitter { * 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 = {}; + sessions: Record = {}; constructor() { super(); @@ -165,9 +166,9 @@ export class EventedHTTPServer extends EventEmitter { const connection = new EventedHTTPServerConnection(this, socket); // 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.REQUEST, (request: IncomingMessage, response: ServerResponse, session: HAPSession, events: any) => { this.emit(EventedHTTPServerEvents.REQUEST, request, response, session, events); }); + connection.on(EventedHTTPServerEvents.ENCRYPT, (data: Buffer, encrypted: { data: Buffer; }, session: HAPSession) => { this.emit(EventedHTTPServerEvents.ENCRYPT, data, encrypted, session); }); + connection.on(EventedHTTPServerEvents.DECRYPT, (data: Buffer, decrypted: { data: number | Buffer; }, session: HAPSession) => { this.emit(EventedHTTPServerEvents.DECRYPT, data, decrypted, session); }); connection.on(EventedHTTPServerEvents.CLOSE, (events: any) => { this._handleConnectionClose(connection, events); }); this._connections.push(connection); } @@ -192,7 +193,7 @@ export type HAPSessionEventMap = { [HAPSessionEvents.CLOSED]: () => void; } -export class Session extends EventEmitter { +export class HAPSession extends EventEmitter { readonly _server: EventedHTTPServer; readonly _connection: EventedHTTPServerConnection; @@ -201,7 +202,7 @@ export class Session extends EventEmitter { 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 = {}; + private static sessionsBySessionID: Record = {}; sessionID: SessionIdentifier; // uuid unique to every HAP connection _pairSetupState?: number; @@ -220,7 +221,7 @@ export class Session extends EventEmitter { this._connection = connection; this.sessionID = connection.sessionID; - Session.sessionsBySessionID[this.sessionID] = this; + HAPSession.sessionsBySessionID[this.sessionID] = this; } public getLocalAddress(ipVersion: "ipv4" | "ipv6"): string { @@ -263,7 +264,7 @@ export class Session extends EventEmitter { this.authenticated = true; this.username = username; - let sessions: Session[] = this._server.sessions[username]; + let sessions: HAPSession[] = this._server.sessions[username]; if (!sessions) { sessions = []; this._server.sessions[username] = sessions; @@ -278,10 +279,10 @@ export class Session extends EventEmitter { // called when socket of this session is destroyed _connectionDestroyed = () => { - delete Session.sessionsBySessionID[this.sessionID]; + delete HAPSession.sessionsBySessionID[this.sessionID]; if (this.username) { - const sessions: Session[] = this._server.sessions[this.username]; + const sessions: HAPSession[] = this._server.sessions[this.username]; if (sessions) { const index = sessions.indexOf(this); if (index >= 0) { @@ -295,8 +296,8 @@ export class Session extends EventEmitter { this.emit(HAPSessionEvents.CLOSED); }; - static destroyExistingConnectionsAfterUnpair = (initiator: Session, username: string) => { - const sessions: Session[] = initiator._server.sessions[username]; + static destroyExistingConnectionsAfterUnpair = (initiator: HAPSession, username: string) => { + const sessions: HAPSession[] = initiator._server.sessions[username]; if (sessions) { sessions.forEach(session => { @@ -341,8 +342,8 @@ class EventedHTTPServerConnection extends EventEmitter { _clientSocket: Socket; _httpServer: http.Server; _serverSocket: Nullable; - _session: Session; - _events: Record; + _session: HAPSession; + _events: CharacteristicEvents; _httpPort?: number; constructor(server: EventedHTTPServer, clientSocket: Socket) { @@ -376,7 +377,7 @@ class EventedHTTPServerConnection extends EventEmitter { this._httpServer.on('error', this._onHttpServerError); this._httpServer.listen(0, loopbackAddress); // an arbitrary dict that users of this class can store values in to associate with this particular connection - this._session = new Session(this); + this._session = new HAPSession(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); diff --git a/src/lib/util/once.ts b/src/lib/util/once.ts index 630fc14ca..29dfb0dca 100644 --- a/src/lib/util/once.ts +++ b/src/lib/util/once.ts @@ -1,11 +1,10 @@ -export function once(func: Function) { +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/types.ts b/src/types.ts index 4a1740548..50910b4d2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,3 +1,4 @@ +import { CharacteristicEvents } from "./lib/Accessory"; import { Status } from './lib/HAPServer'; import { Characteristic, CharacteristicProps } from './lib/Characteristic'; @@ -34,21 +35,17 @@ export type VoidCallback = (err?: Nullable) => void; export type PairingsCallback = (err: number, data?: T) => void; export type PrimitiveTypes = string | number | boolean; -type HAPProps = - Pick - & Pick +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 CharacteristicValue = PrimitiveTypes | PrimitiveTypes[] | { [key: string]: PrimitiveTypes }; -export type CharacteristicChange = { - newValue: CharacteristicValue; - oldValue: CharacteristicValue; - context?: any; - characteristic: Characteristic; -}; export type HapService = { iid: number; type: string; @@ -58,18 +55,6 @@ export type HapService = { 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} */ From bda16dbca6f160c596cdef146a76990d36ba0f4f Mon Sep 17 00:00:00 2001 From: Supereg Date: Wed, 30 Sep 2020 01:54:05 +0200 Subject: [PATCH 17/70] Fixed some tests --- package-lock.json | 12 +++--- package.json | 4 +- src/lib/Characteristic.spec.ts | 77 ++++++++++++++++++---------------- src/lib/Characteristic.ts | 2 +- 4 files changed, 51 insertions(+), 44 deletions(-) diff --git a/package-lock.json b/package-lock.json index 66758ede3..ee9ed627d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1742,9 +1742,9 @@ "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==" }, "decode-uri-component": { "version": "0.2.0", @@ -5693,9 +5693,9 @@ } }, "ts-jest": { - "version": "26.4.0", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.4.0.tgz", - "integrity": "sha512-ofBzoCqf6Nv/PoWb/ByV3VNKy2KJSikamOBxvR3E6eVdIw10GwAXoyvMWXXjZJK2s6S27ZE8fI+JBTnGaovl6Q==", + "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", diff --git a/package.json b/package.json index a7ab4f76a..d80d05414 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "fast-srp-hap": "2.0.2", "tweetnacl": "^1.0.3", "debug": "^4.3.0", - "decimal.js": "^10.2.0", + "decimal.js": "^10.2.1", "ip": "^1.1.3", "node-persist": "^0.0.11", "futoin-hkdf": "~1.3.2", @@ -68,7 +68,7 @@ "rimraf": "^3.0.2", "semver": "^7.3.2", "simple-plist": "^1.1.0", - "ts-jest": "^26.4.0", + "ts-jest": "^26.4.1", "ts-node": "^9.0.0", "typescript": "^4.0.3" } diff --git a/src/lib/Characteristic.spec.ts b/src/lib/Characteristic.spec.ts index 16816cb5c..2a584e129 100644 --- a/src/lib/Characteristic.spec.ts +++ b/src/lib/Characteristic.spec.ts @@ -6,13 +6,13 @@ import { CharacteristicProps, Formats, Perms, - SerializedCharacteristic, + SerializedCharacteristic, Status, Units, uuid } from '..'; const createCharacteristic = (type: Formats) => { - return new Characteristic('Test', uuid.generate('Foo'), { format: type, perms: [] }); + return new Characteristic('Test', uuid.generate('Foo'), { format: type, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE] }); }; const createCharacteristicWithProps = (props: CharacteristicProps) => { @@ -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,27 +107,30 @@ describe('Characteristic', () => { expect(subscribeSpy).toHaveBeenCalledTimes(1); expect(unsubscribeSpy).toHaveBeenCalledTimes(1); + // @ts-expect-error expect(characteristic.subscriptions).toEqual(0); }); }); - describe('#getValue()', () => { + describe('#handleGetRequest()', () => { it('should handle special event only characteristics', () => { const characteristic = createCharacteristic(Formats.BOOL); characteristic.eventOnlyCharacteristic = true; - characteristic.getValue((error, value) => { - expect(error).toEqual(null); - expect(value).toEqual(null); + // @ts-expect-error + characteristic.handleGetRequest().then(() => { + expect(characteristic.status).toEqual(Status.SUCCESS); + expect(characteristic.value).toEqual(null); }); }); it('should return cached values if no listeners are registered', () => { const characteristic = createCharacteristic(Formats.BOOL); - characteristic.getValue((status, value) => { - expect(status).toEqual(null); - expect(value).toEqual(null); + // @ts-expect-error + characteristic.handleGetRequest().then(() => { + expect(characteristic.status).toEqual(Status.SUCCESS); + expect(characteristic.value).toEqual(null); }); }); }); @@ -134,6 +140,7 @@ describe('Characteristic', () => { it('should validate an integer property', () => { const VALUE = 1024; const characteristic = createCharacteristic(Formats.INT); + // @ts-expect-error expect(characteristic.validateValue(VALUE)).toEqual(VALUE); }); @@ -144,6 +151,7 @@ describe('Characteristic', () => { minStep: 0.001, perms: [], }); + // @ts-expect-error expect(characteristic.validateValue(VALUE)).toEqual(VALUE); }); @@ -154,117 +162,116 @@ describe('Characteristic', () => { minStep: 0.1, perms: [], }); + // @ts-expect-error expect(characteristic.validateValue(VALUE)).toEqual(1.5); }); it('should validate a UINT8 property', () => { const VALUE = 10; const characteristic = createCharacteristic(Formats.UINT8); + // @ts-expect-error expect(characteristic.validateValue(VALUE)).toEqual(VALUE); }); it('should validate a UINT16 property', () => { const VALUE = 10; const characteristic = createCharacteristic(Formats.UINT16); + // @ts-expect-error expect(characteristic.validateValue(VALUE)).toEqual(VALUE); }); it('should validate a UINT32 property', () => { const VALUE = 10; const characteristic = createCharacteristic(Formats.UINT32); + // @ts-expect-error expect(characteristic.validateValue(VALUE)).toEqual(VALUE); }); it('should validate a UINT64 property', () => { const VALUE = 10; const characteristic = createCharacteristic(Formats.UINT64); + // @ts-expect-error expect(characteristic.validateValue(VALUE)).toEqual(VALUE); }); it('should validate a boolean property', () => { const VALUE = true; const characteristic = createCharacteristic(Formats.BOOL); + // @ts-expect-error expect(characteristic.validateValue(VALUE)).toEqual(VALUE); }); it('should validate a string property', () => { const VALUE = 'Test'; const characteristic = createCharacteristic(Formats.STRING); + // @ts-expect-error expect(characteristic.validateValue(VALUE)).toEqual(VALUE); }); it('should validate a data property', () => { const VALUE = {}; const characteristic = createCharacteristic(Formats.DATA); + // @ts-expect-error expect(characteristic.validateValue(VALUE)).toEqual(VALUE); }); it('should validate a TLV8 property', () => { const VALUE = ''; const characteristic = createCharacteristic(Formats.TLV8); + // @ts-expect-error expect(characteristic.validateValue(VALUE)).toEqual(VALUE); }); it('should validate a dictionary property', () => { const VALUE = {}; const characteristic = createCharacteristic(Formats.DICTIONARY); + // @ts-expect-error expect(characteristic.validateValue(VALUE)).toEqual(VALUE); }); it('should validate an array property', () => { const VALUE = ['asd']; const characteristic = createCharacteristic(Formats.ARRAY); + // @ts-expect-error 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); - }); - }); - describe('#getDefaultValue()', () => { 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([]); }); @@ -280,14 +287,14 @@ describe('Characteristic', () => { const characteristic = createCharacteristic(Formats.STRING); const listenerCallback = jest.fn(); - const getValueCallback = jest.fn(); - characteristic.getValue(getValueCallback); + // @ts-expect-error + characteristic.handleGetRequest(); characteristic.on(CharacteristicEventTypes.GET, listenerCallback); - characteristic.getValue(getValueCallback); + // @ts-expect-error + characteristic.handleGetRequest(); expect(listenerCallback).toHaveBeenCalledTimes(1); - expect(getValueCallback).toHaveBeenCalledTimes(1); }); }); @@ -298,14 +305,14 @@ describe('Characteristic', () => { const VALUE = 'NewValue'; const listenerCallback = jest.fn(); - const setValueCallback = jest.fn(); - characteristic.setValue(VALUE, setValueCallback) + // @ts-expect-error + characteristic.handleSetRequest(VALUE); characteristic.on(CharacteristicEventTypes.SET, listenerCallback); - characteristic.setValue(VALUE, setValueCallback); + // @ts-expect-error + characteristic.handleSetRequest(VALUE); expect(listenerCallback).toHaveBeenCalledTimes(1); - expect(setValueCallback).toHaveBeenCalledTimes(1); }); }); diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts index f8ee25c58..ab16c5ab2 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -792,7 +792,7 @@ export class Characteristic extends EventEmitter { */ toHAP(opt?: ToHAPOptions): HapCharacteristic { // ensure our value fits within our constraints if present - let value = this.value; + let value = this.value; // TODO query value for characteristics which support it if (this.props.minValue != null && value! < this.props.minValue) value = this.props.minValue; From 8521c695e88200af965a809c7de1ffd9717075f7 Mon Sep 17 00:00:00 2001 From: Supereg Date: Sat, 3 Oct 2020 19:02:46 +0200 Subject: [PATCH 18/70] Validate controllerKeySalt length before passing it to the DataStream Server --- src/lib/datastream/DataStreamManagement.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/datastream/DataStreamManagement.ts b/src/lib/datastream/DataStreamManagement.ts index 177fbeba8..3be84681c 100644 --- a/src/lib/datastream/DataStreamManagement.ts +++ b/src/lib/datastream/DataStreamManagement.ts @@ -162,7 +162,7 @@ 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) { + if (transportType !== TransportType.HOMEKIT_DATA_STREAM || controllerKeySalt.length !== 32) { callback(Status.INVALID_VALUE_IN_REQUEST); return; } From 2b570178abe2c4190003df6016ad52bfb502b264 Mon Sep 17 00:00:00 2001 From: Supereg Date: Sat, 3 Oct 2020 19:03:08 +0200 Subject: [PATCH 19/70] Set the tcp_keepalive_time to 5 seconds --- src/lib/util/eventedhttp.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/util/eventedhttp.ts b/src/lib/util/eventedhttp.ts index 4146fbcb0..b770edb1f 100644 --- a/src/lib/util/eventedhttp.ts +++ b/src/lib/util/eventedhttp.ts @@ -364,7 +364,7 @@ class EventedHTTPServerConnection extends EventEmitter { 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); + this._clientSocket.setKeepAlive(true, 5000); // serverSocket is our connection to our own internal httpServer this._serverSocket = null; // created after httpServer 'listening' event @@ -468,7 +468,6 @@ class EventedHTTPServerConnection extends EventEmitter { 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 From 576271bec20f75f2519344035d2979e439783c54 Mon Sep 17 00:00:00 2001 From: Supereg Date: Sat, 3 Oct 2020 19:05:35 +0200 Subject: [PATCH 20/70] Reduce daysUntilStale to 23 --- .github/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 2ef51cf4757cf3ab1101835fa1f318e389e7416d Mon Sep 17 00:00:00 2001 From: Supereg Date: Mon, 5 Oct 2020 01:31:47 +0200 Subject: [PATCH 21/70] Major code cleanup in the EventedHTTPServer and HAPServer classes Various fixes, improved typing and more robustness --- package-lock.json | 6 +- package.json | 2 +- src/internal-types.ts | 11 + src/lib/Accessory.ts | 264 +++---- src/lib/Advertiser.ts | 1 + src/lib/Characteristic.ts | 64 +- src/lib/HAPServer.ts | 734 +++++++++--------- src/lib/camera/RTPStreamManagement.ts | 55 +- src/lib/controller/CameraController.ts | 6 - src/lib/controller/RemoteController.ts | 140 ++-- src/lib/datastream/DataStreamManagement.ts | 11 +- src/lib/datastream/DataStreamParser.ts | 80 +- src/lib/datastream/DataStreamServer.ts | 83 +- src/lib/model/AccessoryInfo.ts | 48 +- src/lib/util/eventedhttp.ts | 851 ++++++++++----------- src/lib/util/net-utils.ts | 46 ++ src/types.ts | 5 +- 17 files changed, 1198 insertions(+), 1209 deletions(-) create mode 100644 src/lib/util/net-utils.ts diff --git a/package-lock.json b/package-lock.json index ee9ed627d..8cd2f6c7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -396,9 +396,9 @@ } }, "@homebridge/ciao": { - "version": "1.1.0-beta.12", - "resolved": "https://registry.npmjs.org/@homebridge/ciao/-/ciao-1.1.0-beta.12.tgz", - "integrity": "sha512-ce5nW/xI/xc39bQjhdPy9OiO4aKc3HJpbqftKlEQ7Rs69WouFbSbjWyvAj+ItfZCge7tDC+UHl08slqSLbQ0IQ==", + "version": "1.1.0-beta.17", + "resolved": "https://registry.npmjs.org/@homebridge/ciao/-/ciao-1.1.0-beta.17.tgz", + "integrity": "sha512-zl4VTFrjX+mmJXnA/0pGl/Ej4Hy6iLMefBEyZGRSgk+1mygHcygQaGvk+bAuYm5VJ72XJVLCoFVxzG9CDc8g2A==", "requires": { "debug": "^4.1.1", "fast-deep-equal": "^3.1.3", diff --git a/package.json b/package.json index d80d05414..5a62553f4 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "@types" ], "dependencies": { - "@homebridge/ciao": "~1.1.0-beta.12", + "@homebridge/ciao": "~1.1.0-beta.17", "fast-srp-hap": "2.0.2", "tweetnacl": "^1.0.3", "debug": "^4.3.0", diff --git a/src/internal-types.ts b/src/internal-types.ts index 460a3eff9..026b0dee2 100644 --- a/src/internal-types.ts +++ b/src/internal-types.ts @@ -99,6 +99,17 @@ export type PrepareWriteRequest = { pid: number } +export const enum ResourceRequestType { + IMAGE = "image", +} + +export interface ResourceRequest { + aid?: number; + "image-height": number; + "image-width": number; + "resource-type": ResourceRequestType; +} + export function consideredTrue(input: string | null): boolean { if (!input) { return false; diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index 8e38e2cda..0e606acfb 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -6,8 +6,11 @@ import net from "net"; import { CharacteristicsReadData, CharacteristicsReadRequest, - CharacteristicsReadResponse, CharacteristicsWriteData, CharacteristicsWriteRequest, - CharacteristicsWriteResponse + CharacteristicsWriteData, + CharacteristicsWriteRequest, + CharacteristicsWriteResponse, + ResourceRequest, + ResourceRequestType } from "../internal-types"; import { CharacteristicValue, @@ -15,10 +18,7 @@ import { InterfaceName, IPAddress, MacAddress, - NodeCallback, Nullable, - PairingsCallback, - SessionIdentifier, ToHAPOptions, VoidCallback, WithUUID, @@ -26,13 +26,7 @@ import { import { Advertiser, AdvertiserEvent } from './Advertiser'; // noinspection JSDeprecatedSymbols import { LegacyCameraSource, LegacyCameraSourceAdapter, StreamController } from './camera'; -import { - Access, - Characteristic, - CharacteristicEventTypes, - CharacteristicSetCallback, - Perms -} from './Characteristic'; +import { Access, Characteristic, CharacteristicEventTypes, CharacteristicSetCallback, Perms } from './Characteristic'; import { CameraController, CameraControllerOptions, @@ -45,8 +39,23 @@ import { import { EventEmitter } from './EventEmitter'; import * as HomeKitTypes from "./gen"; import { CameraEventRecordingManagement, CameraOperatingMode, CameraRTPStreamManagement, } from "./gen/HomeKit"; -import { Codes, HAPServer, HAPServerEventTypes, Status } from './HAPServer'; -import { AccessoryInfo, PairingInformation, PermissionTypes } from './model/AccessoryInfo'; +import { + AccessoriesCallback, + AddPairingCallback, + Codes, + HAPHTTPCode, + HAPServer, + HAPServerEventTypes, + IdentifyCallback, + ListPairingsCallback, + PairCallback, + ReadCharacteristicsCallback, + RemovePairingCallback, + ResourceRequestCallback, + Status, + WriteCharacteristicsCallback +} from './HAPServer'; +import { AccessoryInfo, PermissionTypes } from './model/AccessoryInfo'; import { ControllerStorage } from "./model/ControllerStorage"; import { IdentifierCache } from './model/IdentifierCache'; import { @@ -58,9 +67,9 @@ import { ServiceId } from './Service'; import { clone } from './util/clone'; -import { HAPSession } from "./util/eventedhttp"; +import { HAPConnection, HAPUsername } from "./util/eventedhttp"; +import * as uuid from "./util/uuid"; import { toShortForm } from "./util/uuid"; -import * as uuid from './util/uuid'; const debug = createDebug('HAP-NodeJS:Accessory'); const MAX_ACCESSORIES = 149; // Maximum number of bridged accessories per bridge. @@ -157,6 +166,9 @@ type Events = { */ export type EventAccessory = "identify" | "listening" | "service-configurationChange" | "service-characteristic-change"; +/** + * @deprecated + */ export type CharacteristicEvents = Record; export interface PublishInfo { @@ -236,30 +248,12 @@ 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; -} - const enum WriteRequestState { REGULAR_REQUEST, TIMED_WRITE_AUTHENTICATED, TIMED_WRITE_REJECTED } -type IdentifyCallback = VoidCallback; -type PairCallback = VoidCallback; -type AddPairingCallback = PairingsCallback; -type RemovePairingCallback = PairingsCallback; -type ListPairingsCallback = PairingsCallback; -type HandleAccessoriesCallback = NodeCallback<{ accessories: any[] }>; - /** * Accessory is a virtual HomeKit device. It can publish an associated HAP server for iOS devices to communicate * with - or it can run behind another "Bridge" Accessory server. @@ -329,12 +323,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); } }); } - private _identificationRequest(paired: boolean, callback: VoidCallback) { + private identificationRequest(paired: boolean, callback: IdentifyCallback) { debug("[%s] Identification request", this.displayName); if (this.listeners(AccessoryEventTypes.IDENTIFY).length > 0) { @@ -409,14 +403,11 @@ export class Accessory extends EventEmitter { // listen for changes in characteristics and bubble them up service.on(ServiceEventTypes.CHARACTERISTIC_CHANGE, (change: ServiceCharacteristicChange) => { - this.emit(AccessoryEventTypes.SERVICE_CHARACTERISTIC_CHANGE, { - ...change, - service: service, - }); + this.emit(AccessoryEventTypes.SERVICE_CHARACTERISTIC_CHANGE, { ...change, service: service }); // if we're not bridged, when we'll want to process this event through our HAPServer if (!this.bridged) { - this._handleCharacteristicChange({ ...change, service: service, accessory: this }); + this.handleCharacteristicChange({ ...change, service: service, accessory: this }); } }); @@ -522,7 +513,7 @@ export class Accessory extends EventEmitter { // listen for changes in ANY characteristics of ANY services on this Accessory accessory.on(AccessoryEventTypes.SERVICE_CHARACTERISTIC_CHANGE, (change: AccessoryCharacteristicChange) => { - this._handleCharacteristicChange({ ...change, accessory: accessory }); + this.handleCharacteristicChange({ ...change, accessory: accessory }); }); accessory.on(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, () => { @@ -690,7 +681,7 @@ export class Accessory extends EventEmitter { * * @param controllerConstructor {Controller | ControllerConstructor} */ - configureController(controllerConstructor: Controller | ControllerConstructor) { + 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; @@ -985,7 +976,8 @@ 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) => { + 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."); @@ -1097,16 +1089,16 @@ 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.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.LISTENING, this.onListening.bind(this)); + this._server.on(HAPServerEventTypes.IDENTIFY, this.handleIdentify.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)); this._server.listen(info.port, parsed.serverAddress); @@ -1117,7 +1109,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) { @@ -1127,13 +1119,10 @@ export class Accessory extends EventEmitter { this._identifierCache = undefined; this.controllerStorage = new ControllerStorage(this); } + this.removeAllListeners(); } unpublish = () => { - if (this.activeCameraController) { - this.activeCameraController.handleShutdown(); - } - if (this._server) { this._server.stop(); this._server = undefined; @@ -1168,7 +1157,7 @@ export class Accessory extends EventEmitter { } } - _onListening = (port: number, hostname: string) => { + 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!.initPort(port); @@ -1177,13 +1166,11 @@ export class Accessory extends EventEmitter { this.emit(AccessoryEventTypes.LISTENING, port, hostname); } - private _handleIdentify(callback: IdentifyCallback) { - this._identificationRequest(false, callback); + private handleIdentify(callback: IdentifyCallback) { + this.identificationRequest(false, callback); } -// 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); @@ -1197,14 +1184,13 @@ export class Accessory extends EventEmitter { this.emit(AccessoryEventTypes.PAIRED); } -// called when a controller adds an additional pairing - _handleAddPairing = (controller: HAPSession, 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); return; } - if (!this._accessoryInfo.hasAdminPermissions(controller.username!)) { + if (!this._accessoryInfo.hasAdminPermissions(connection.username!)) { callback(Codes.AUTHENTICATION); return; } @@ -1226,18 +1212,18 @@ export class Accessory extends EventEmitter { callback(0); }; - _handleRemovePairing = (controller: HAPSession, username: string, callback: RemovePairingCallback) => { + private handleRemovePairing(connection: HAPConnection, username: HAPUsername, callback: RemovePairingCallback): void { if (!this._accessoryInfo) { callback(Codes.UNAVAILABLE); return; } - if (!this._accessoryInfo.hasAdminPermissions(controller.username!)) { + if (!this._accessoryInfo.hasAdminPermissions(connection.username!)) { callback(Codes.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 @@ -1253,13 +1239,13 @@ export class Accessory extends EventEmitter { } }; - _handleListPairings = (controller: HAPSession, callback: ListPairingsCallback) => { + private handleListPairings(connection: HAPConnection, callback: ListPairingsCallback): void { if (!this._accessoryInfo) { callback(Codes.UNAVAILABLE); return; } - if (!this._accessoryInfo.hasAdminPermissions(controller.username!)) { + if (!this._accessoryInfo.hasAdminPermissions(connection.username!)) { callback(Codes.AUTHENTICATION); return; } @@ -1267,18 +1253,18 @@ export class Accessory extends EventEmitter { callback(0, this._accessoryInfo.listPairings()); }; - private _handleAccessories(callback: HandleAccessoriesCallback): void { + private handleAccessories(callback: AccessoriesCallback): void { // 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 + callback({ + accessories: this.toHAP(), // array of Accessory HAP }); } - private _handleGetCharacteristics(request: CharacteristicsReadRequest, session: HAPSession, events: CharacteristicEvents, callback: (response: CharacteristicsReadResponse) => void): void { + private handleGetCharacteristics(connection: HAPConnection, request: CharacteristicsReadRequest, callback: ReadCharacteristicsCallback): void { const promises: Promise[] = []; for (const id of request.ids) { @@ -1297,12 +1283,12 @@ export class Accessory extends EventEmitter { if (characteristic.props.adminOnlyAccess && characteristic.props.adminOnlyAccess.includes(Access.READ)) { let verifiable = true; - if (!session || !session.username || !this._accessoryInfo) { + 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) } - if (!verifiable || !this._accessoryInfo!.hasAdminPermissions(session.username!)) { + if (!verifiable || !this._accessoryInfo!.hasAdminPermissions(connection.username!)) { promises.push(Promise.resolve({ aid: id.aid, @@ -1313,16 +1299,8 @@ export class Accessory extends EventEmitter { } } - // Explanation for "events" parameter - // 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. - // TODO introduce a timeout on those values? - const promise = characteristic.handleGetRequest(session, events).then(value => { + const promise = characteristic.handleGetRequest(connection).then(value => { debug('[%s] Got Characteristic "%s" value: %s', this.displayName, characteristic!.displayName, value); const data: CharacteristicsReadData = { @@ -1346,7 +1324,7 @@ export class Accessory extends EventEmitter { data.type = toShortForm(this.UUID, HomeKitTypes.BASE_UUID); } if (request.includeEvent) { - data.ev = events[id.aid + "." + id.iid]; + data.ev = connection.hasEventNotifications(id.aid, id.iid); } return data; @@ -1365,16 +1343,16 @@ export class Accessory extends EventEmitter { Promise.all(promises).then(value => callback({ characteristics: value })); } - private _handleSetCharacteristics(writeRequest: CharacteristicsWriteRequest, session: HAPSession, events: CharacteristicEvents, callback: (response: CharacteristicsWriteResponse) => void): void { + 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 { @@ -1435,12 +1413,12 @@ export class Accessory extends EventEmitter { if (characteristic.props.adminOnlyAccess && characteristic.props.adminOnlyAccess.includes(Access.NOTIFY)) { let verifiable = true; - if (!session || !session.username || !this._accessoryInfo) { + 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 (!verifiable || !this._accessoryInfo!.hasAdminPermissions(session.username!)) { + if (!verifiable || !this._accessoryInfo!.hasAdminPermissions(connection.username!)) { characteristics.push({ aid: data.aid, iid: data.iid, @@ -1454,19 +1432,17 @@ export class Accessory extends EventEmitter { } } - debug('[%s] %s Characteristic "%s" for events', this.displayName, data.ev ? "Registering" : "Unregistering", characteristic.displayName); + debug('[%s] %s Characteristic "%s" for events', this.displayName, data.ev ? "Registering" : "Unregistering", characteristic.displayName); // TODO maybe remove this event message? - const eventName = data.aid + "." + data.iid; - - if (data.ev && !events[eventName]) { - events[eventName] = true; + if (data.ev && !connection.hasEventNotifications(data.aid, data.iid)) { + connection.enableEventNotifications(data.aid, data.iid); characteristic.subscribe(); evResponse = true; } - if (!data.ev && events[eventName]) { + if (!data.ev && connection.hasEventNotifications(data.aid, data.iid)) { characteristic.unsubscribe(); - delete events[eventName]; + connection.disableEventNotifications(data.aid, data.iid); evResponse = false; } } @@ -1489,12 +1465,12 @@ export class Accessory extends EventEmitter { if (characteristic.props.adminOnlyAccess && characteristic.props.adminOnlyAccess.includes(Access.WRITE)) { let verifiable = true; - if (!session || !session.username || !this._accessoryInfo) { + 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 (!verifiable || !this._accessoryInfo!.hasAdminPermissions(session.username!)) { + if (!verifiable || !this._accessoryInfo!.hasAdminPermissions(connection.username!)) { characteristics.push({ aid: data.aid, iid: data.iid, @@ -1524,7 +1500,7 @@ export class Accessory extends EventEmitter { debug('[%s] Setting Characteristic "%s" to value %s', this.displayName, characteristic.displayName, data.value); - characteristic.handleSetRequest(data.value, session, events).then(value => { + characteristic.handleSetRequest(data.value, connection).then(value => { characteristics.push({ aid: data.aid, iid: data.iid, @@ -1565,8 +1541,8 @@ export class Accessory extends EventEmitter { } } - _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 controller: CameraController | undefined = undefined; @@ -1584,65 +1560,53 @@ export class Accessory extends EventEmitter { } 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: Status.RESOURCE_DOES_NOT_EXIST }); return; } - controller.handleSnapshotRequest(data["image-height"], data["image-width"], callback); + controller.handleSnapshotRequest(data["image-height"], data["image-width"], (error, resource) => { + if (!resource || resource.length === 0) { + error = new Error("supplied resource was undefined/empty!"); + } + + if (error) { + debug("[%s] Error getting snapshot: %s", this._accessoryInfo?.username, error.message); + callback({ httpCode: HAPHTTPCode.OK, status: Status.SERVICE_COMMUNICATION_FAILURE }); + } else { + callback(undefined, resource); + } + }); 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: Status.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); - } + for (const event of connection.getRegisteredEvents()) { + const ids = event.split("."); + const aid = parseInt(ids[0]); + const iid = parseInt(ids[1]); - _unsubscribeEvents = (events: CharacteristicEvents) => { - for (let key in events) { - if (key.indexOf('.') !== -1) { - try { - const id = key.split('.'); - const aid = Number.parseInt(id[0]); - const iid = Number.parseInt(id[1]); - - const characteristic = this.findCharacteristic(aid, iid); - if (characteristic) { - characteristic.unsubscribe(); - } - } catch (e) { - } + const characteristic = this.findCharacteristic(aid, iid); + if (characteristic) { + characteristic.unsubscribe(); } } } // Called internally above when a change was detected in one of our hosted Characteristics somewhere in our hierarchy. - _handleCharacteristicChange = (change: AccessoryCharacteristicChange & { accessory: Accessory }) => { + private handleCharacteristicChange(change: AccessoryCharacteristicChange & { accessory: Accessory }): void { if (!this._server) return; // we're not running a HAPServer, so there's no one to notify about this event - const 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) - const eventName = change.accessory.aid + '.' + change.characteristic.iid; - - // pull the events object associated with the original connection (if any) that initiated the change request, - // which we assigned in handleGetCharacteristics/handleSetCharacteristics. - const excludeEvents = change.context; - - // pass it along to notifyClients() so that it can omit the connection where events === excludeEvents. - this._server.notifyClients(eventName, data, excludeEvents); + this._server.sendEventNotifications(change.accessory.aid!, change.characteristic.iid!, change.newValue, change.originator); } _setupService = (service: Service) => { @@ -1655,12 +1619,12 @@ export class Accessory extends EventEmitter { }); // listen for changes in characteristics and bubble them up - service.on(ServiceEventTypes.CHARACTERISTIC_CHANGE, (change: any) => { + service.on(ServiceEventTypes.CHARACTERISTIC_CHANGE, (change: ServiceCharacteristicChange) => { this.emit(AccessoryEventTypes.SERVICE_CHARACTERISTIC_CHANGE, clone(change, {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 })); + this.handleCharacteristicChange(clone(change, {accessory:this, service })); }); } @@ -1680,7 +1644,7 @@ 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); } }); } diff --git a/src/lib/Advertiser.ts b/src/lib/Advertiser.ts index dc6920572..2a1364940 100644 --- a/src/lib/Advertiser.ts +++ b/src/lib/Advertiser.ts @@ -98,6 +98,7 @@ export class Advertiser extends EventEmitter { public async shutdown(): Promise { 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.ts b/src/lib/Characteristic.ts index ab16c5ab2..6d1371ba4 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -1,19 +1,11 @@ import Decimal from 'decimal.js'; import { EventEmitter } from "events"; -import { - CharacteristicValue, - HapCharacteristic, - Nullable, - SessionIdentifier, - ToHAPOptions, - VoidCallback, -} from '../types'; -import { CharacteristicEvents } from "./Accessory"; +import { CharacteristicValue, HapCharacteristic, Nullable, ToHAPOptions, VoidCallback, } from '../types'; import * as HomeKitTypes from './gen'; import { Status } from "./HAPServer"; import { IdentifierCache } from './model/IdentifierCache'; import { clone } from "./util/clone"; -import { HAPSession } from "./util/eventedhttp"; +import { HAPConnection } from "./util/eventedhttp"; import { once } from './util/once'; import { toShortForm } from './util/uuid'; @@ -83,9 +75,10 @@ export const enum Access { } export type CharacteristicChange = { + originator?: HAPConnection, newValue: Nullable; oldValue: Nullable; - context?: CharacteristicEvents; + context?: any; }; export interface SerializedCharacteristic { @@ -109,14 +102,14 @@ export type CharacteristicSetCallback = (error?: Status | null | Error, writeRes export declare interface Characteristic { - on(event: "get", listener: (callback: CharacteristicGetCallback, context: CharacteristicEvents, session: HAPSession) => void): this; - on(event: "set", listener: (value: CharacteristicValue, callback: CharacteristicSetCallback, context: CharacteristicEvents, session: HAPSession) => void): this + 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; on(event: "subscribe", listener: VoidCallback): this; on(event: "unsubscribe", listener: VoidCallback): this; - emit(event: "get", callback: CharacteristicGetCallback, context: CharacteristicEvents, session: HAPSession): boolean; - emit(event: "set", value: CharacteristicValue, callback: CharacteristicSetCallback, context: CharacteristicEvents, session: HAPSession): boolean; + emit(event: "get", callback: CharacteristicGetCallback, context: any, connection?: HAPConnection): boolean; + emit(event: "set", value: CharacteristicValue, callback: CharacteristicSetCallback, context: any, connection: HAPConnection): boolean; emit(event: "change", change: CharacteristicChange): boolean; emit(event: "subscribe"): boolean; emit(event: "unsubscribe"): boolean; @@ -406,7 +399,7 @@ export class Characteristic extends EventEmitter { props: CharacteristicProps; private subscriptions: number = 0; - constructor(public displayName: string, public UUID: string, props?: CharacteristicProps) { + public constructor(public displayName: string, public UUID: string, props?: CharacteristicProps) { super(); // @ts-ignore this.props = props || { @@ -440,7 +433,7 @@ export class Characteristic extends EventEmitter { * valid-values-range: (Optional) * } */ - setProps = (props: Partial) => { + public setProps(props: Partial): Characteristic { for (let key in (props || {})) if (Object.prototype.hasOwnProperty.call(props, key)) { // @ts-ignore @@ -485,18 +478,12 @@ export class Characteristic extends EventEmitter { * * @param callback * @param context - * @param sessionId * @internal use to return the current value on HAP requests * * @deprecated */ - getValue(callback?: CharacteristicGetCallback, context?: CharacteristicEvents, sessionId?: SessionIdentifier): void { - let session: HAPSession | undefined = undefined; - if (sessionId) { - session = HAPSession.getSession(sessionId) - } - - this.handleGetRequest(session!, context!).then(value => { + getValue(callback?: CharacteristicGetCallback, context?: any): void { + this.handleGetRequest(undefined, context).then(value => { if (callback) { callback(null, value); } @@ -507,11 +494,11 @@ export class Characteristic extends EventEmitter { }); } - setValue(newValue: Nullable, callback?: () => void, context?: CharacteristicEvents): Characteristic { + setValue(newValue: Nullable, callback?: () => void, context?: any): Characteristic { return this.updateValue(newValue, callback, context) } - updateValue(value: Nullable, callback?: () => void, context?: CharacteristicEvents): Characteristic { + updateValue(value: Nullable, callback?: () => void, context?: any): Characteristic { this.status = Status.SUCCESS; value = this.validateValue(value); //validateValue returns a value that has be coerced into a valid value. @@ -527,7 +514,7 @@ export class Characteristic extends EventEmitter { } if (this.eventOnlyCharacteristic || oldValue !== value) { - this.emit(CharacteristicEventTypes.CHANGE, { oldValue: oldValue, newValue: value, context: context }); + this.emit(CharacteristicEventTypes.CHANGE, { originator: undefined, oldValue: oldValue, newValue: value, context: context }); } return this; // for chaining @@ -536,11 +523,11 @@ export class Characteristic extends EventEmitter { /** * Called when a HAP requests wants to know the current value of the characteristic. * - * @param session - The HAP session from which the request originated from - * @param context - events context + * @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 */ - handleGetRequest(session: HAPSession, context: CharacteristicEvents): Promise> { + handleGetRequest(connection?: HAPConnection, context?: any): Promise> { if (!this.props.perms.includes(Perms.PAIRED_READ)) { // check if we are allowed to read from this characteristic return Promise.reject(Status.WRITE_ONLY_CHARACTERISTIC); } @@ -574,9 +561,9 @@ export class Characteristic extends EventEmitter { resolve(value); if (oldValue !== value) { // emit a change event if necessary - this.emit(CharacteristicEventTypes.CHANGE, { oldValue: oldValue, newValue: value, context: context }); + this.emit(CharacteristicEventTypes.CHANGE, { originator: connection, oldValue: oldValue, newValue: value, context: context }); } - }), context, session); + }), context, connection); }); } @@ -584,14 +571,13 @@ export class Characteristic extends EventEmitter { * Called when a HAP requests update the current value of the characteristic. * * @param value - The update value - * @param session - The session from which the request originated from - * @param context + * @param connection - The connection from which the request originated from * @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 */ - handleSetRequest(value: CharacteristicValue, session: HAPSession, context: CharacteristicEvents): Promise { + handleSetRequest(value: CharacteristicValue, connection: HAPConnection): Promise { this.status = Status.SUCCESS; // TODO return proper error code if incoming value is not valid! @@ -604,7 +590,7 @@ export class Characteristic extends EventEmitter { const change: CharacteristicChange = { oldValue: oldValue as CharacteristicValue, newValue: value as CharacteristicValue, - context: context, + context: undefined, } this.emit(CharacteristicEventTypes.CHANGE, change); } @@ -630,9 +616,9 @@ export class Characteristic extends EventEmitter { } if (this.eventOnlyCharacteristic || oldValue !== value) { - this.emit(CharacteristicEventTypes.CHANGE, { oldValue: oldValue, newValue: value, context: context }); + this.emit(CharacteristicEventTypes.CHANGE, { originator: connection, oldValue: oldValue, newValue: value }); } - }), context, session); + }), undefined, connection); }); } } diff --git a/src/lib/HAPServer.ts b/src/lib/HAPServer.ts index 3dcc9332d..5a63e4cc9 100644 --- a/src/lib/HAPServer.ts +++ b/src/lib/HAPServer.ts @@ -1,5 +1,6 @@ import crypto from 'crypto'; import createDebug from 'debug'; +import { EventEmitter } from "events"; import { SRP, SrpServer } from "fast-srp-hap"; import { IncomingMessage, ServerResponse } from "http"; import tweetnacl from 'tweetnacl'; @@ -11,13 +12,13 @@ import { CharacteristicsWriteRequest, CharacteristicsWriteResponse, consideredTrue, - PrepareWriteRequest + PrepareWriteRequest, + ResourceRequest } from "../internal-types"; -import { NodeCallback, PairingsCallback, SessionIdentifier, VoidCallback } from '../types'; -import { Accessory, CharacteristicEvents, Resource } from './Accessory'; -import { EventEmitter } from './EventEmitter'; +import { CharacteristicValue, NodeCallback, Nullable, VoidCallback } from '../types'; +import { Accessory } from './Accessory'; import { PairingInformation, PermissionTypes } from "./model/AccessoryInfo"; -import { EventedHTTPServer, EventedHTTPServerEvents, HAPSession } from './util/eventedhttp'; +import { EventedHTTPServer, EventedHTTPServerEvent, HAPConnection, HAPUsername } from './util/eventedhttp'; import * as hapCrypto from './util/hapCrypto'; import { once } from './util/once'; import * as tlv from './util/tlv'; @@ -57,6 +58,9 @@ const enum Methods { LIST_PAIRINGS = 0x05 } +/** + * Pairing states (pair-setup or pair-verify). Encoded in {@link TLVValues.SEQUENCE_NUM}. + */ const enum States { M1 = 0x01, M2 = 0x02, @@ -66,6 +70,9 @@ const enum States { M6 = 0x06 } +/** + * TLV error codes for the {@link TLVValues.ERROR_CODE} field. + */ export const enum Codes { // noinspection JSUnusedGlobalSymbols UNKNOWN = 0x01, @@ -97,42 +104,135 @@ export const enum Status { // when adding new status codes, remember to change upper bound in extractHAPStatusFromError } +/** + * 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 Status} 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 + + // server error + INTERNAL_SERVER_ERROR = 500, + SERVICE_UNAVAILABLE = 503, // e.g. max connections reached +} + +/** + * 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, +} + +type HAPRequestHandler = (connection: HAPConnection, url: URL, request: IncomingMessage, data: Buffer, response: ServerResponse) => void; + +export type IdentifyCallback = VoidCallback; + +export type PairingsCallback = (error: Codes | 0, data?: T) => void; +export type AddPairingCallback = PairingsCallback; +export type RemovePairingCallback = PairingsCallback; +export type ListPairingsCallback = PairingsCallback; +export type PairCallback = VoidCallback; +export type AccessoriesCallback = (result: { accessories: any[] }) => void; // TODO type accessories +export type ReadCharacteristicsCallback = (response: CharacteristicsReadResponse) => void; +export type WriteCharacteristicsCallback = (response: CharacteristicsWriteResponse) => void; +export type ResourceRequestCallback = (error: { httpCode: HAPHTTPCode, status: Status} | 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", - 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' + /** + * 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", + /** + * 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, hostname: string) => void; - [HAPServerEventTypes.PAIR]: (clientUsername: string, clientLTPK: Buffer, cb: VoidCallback) => void; - [HAPServerEventTypes.ADD_PAIRING]: (controller: HAPSession, username: string, publicKey: Buffer, permission: number, callback: PairingsCallback) => void; - [HAPServerEventTypes.REMOVE_PAIRING]: (controller: HAPSession, username: string, callback: PairingsCallback) => void; - [HAPServerEventTypes.LIST_PAIRINGS]: (controller: HAPSession, callback: PairingsCallback) => void; - [HAPServerEventTypes.ACCESSORIES]: (cb: NodeCallback) => void; - [HAPServerEventTypes.GET_CHARACTERISTICS]: ( - request: CharacteristicsReadRequest, - session: HAPSession, - events: CharacteristicEvents, - callback: (response: CharacteristicsReadResponse) => void, - ) => void; - [HAPServerEventTypes.SET_CHARACTERISTICS]: ( - writeRequest: CharacteristicsWriteRequest, - session: HAPSession, - events: CharacteristicEvents, - callback: (response: CharacteristicsWriteResponse) => void, - ) => 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: "pair", listener: (username: HAPUsername, clientLTPK: Buffer, callback: PairCallback) => void): this; + + on(event: "accessories", listener: (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: "pair", username: HAPUsername, clientLTPK: Buffer, callback: PairCallback): boolean; + + emit(event: "accessories", 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; } /** @@ -153,229 +253,161 @@ 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 '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: Record = { - '/identify': '_handleIdentify', - '/pair-setup': '_handlePair', - '/pair-verify': '_handlePairVerify', - '/pairings': '_handlePairings', - '/accessories': '_handleAccessories', - '/characteristics': '_handleCharacteristics', - '/prepare': '_prepareWrite', - '/resource': '_handleResource' - }; +export class HAPServer extends EventEmitter { - _httpServer: EventedHTTPServer; + 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 allowInsecureRequest: boolean; - _keepAliveTimerID: NodeJS.Timeout; constructor(public accessoryInfo: any) { 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 periodically, 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 + 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)); } - listen = (port: number = 0, host?: string) => { - this._httpServer.listen(port, host); + public listen(port: number = 0, host?: string): void { + this.httpServer.listen(port, host); } - stop = () => { - this._httpServer.stop(); - clearInterval(this._keepAliveTimerID); - } - - _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. */ - 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): void { + this.httpServer.broadcastEvent(aid, iid, value, originator); } - _onListening = (port: number, hostname: string) => { + 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: HAPSession, events: CharacteristicEvents) => { + 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 - let 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 - const url = new URL(request.url!, "http://hap-nodejs.local"); - // all request data received; now process this request - for (let path in HAPServer.handlers) - if (new RegExp('^' + path + '/?$').test(url.pathname)) { // match exact string and allow trailing slash - const handler = HAPServer.handlers[path]; - 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: Status.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: Status.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: HAPSession) => { - // instance of HAPEncryption (created in handlePairVerifyStepOne) - const 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: HAPSession) => { - // possibly an instance of HAPEncryption (created in handlePairVerifyStepOne) - const 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: HAPSession, events: any, requestData: any) => { - // /identify only works if the accessory 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: Status.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: Status.RESOURCE_BUSY })); } })); } - /** - * iOS <-> Accessory pairing process. - */ - _handlePair = (request: IncomingMessage, response: ServerResponse, session: HAPSession, events: any, requestData: Buffer) => { + private handlePairSetup(connection: HAPConnection, url: URL, request: IncomingMessage, data: Buffer, response: ServerResponse): void { // 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)); - return; + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(TLVValues.STATE, States.M2, TLVValues.ERROR_CODE, Codes.UNAVAILABLE)); + return; } if (this.unsuccessfulPairAttempts > 100) { - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); + 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, States.M2, TLVValues.ERROR_CODE, Codes.MAX_TRIES)); return; } - const objects = tlv.decode(requestData); - const sequence = objects[TLVValues.SEQUENCE_NUM][0]; // value is single byte with sequence number - if (sequence == States.M1) - this._handlePairStepOne(request, response, session); - 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 { + const tlvData = tlv.decode(data); + const sequence = tlvData[TLVValues.SEQUENCE_NUM][0]; // value is single byte with sequence number + if (sequence == States.M1) { + this.handlePairSetupM1(connection, request, response); + } else if (sequence == States.M3 && connection._pairSetupState === States.M2) { + this.handlePairSetupM3(connection, request, response, tlvData); + } else if (sequence == States.M5 && connection._pairSetupState === States.M4) { + this.handlePairSetupM5(connection, request, response, tlvData); + } else { // Invalid state/sequence number - response.writeHead(400, {"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)); return; } } - // M1 + M2 - _handlePairStepOne = (request: IncomingMessage, response: ServerResponse, session: HAPSession) => { + private handlePairSetupM1(connection: HAPConnection, request: IncomingMessage, response: ServerResponse): void { debug("[%s] Pair step 1/5", this.accessoryInfo.username); const salt = crypto.randomBytes(16, ); @@ -385,25 +417,24 @@ export class HAPServer extends EventEmitter { const srpServer = new SrpServer(srpParams, salt, Buffer.from("Pair-Setup"), Buffer.from(this.accessoryInfo.pincode), key) const srpB = srpServer.computeB(); // attach it to the current TCP session - session.srpServer = srpServer; - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); + connection.srpServer = srpServer; + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); response.end(tlv.encode(TLVValues.SEQUENCE_NUM, States.M2, TLVValues.SALT, salt, TLVValues.PUBLIC_KEY, srpB)); - session._pairSetupState = States.M2; + connection._pairSetupState = States.M2; }).catch(error => { debug("[%s] Error occurred when generating srp key: %s", this.accessoryInfo.username, error.message); - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); response.end(tlv.encode(TLVValues.STATE, States.M2, TLVValues.ERROR_CODE, Codes.UNKNOWN)); return; }); } - // M3 + M4 - _handlePairStepTwo = (request: IncomingMessage, response: ServerResponse, session: HAPSession, objects: Record) => { + private handlePairSetupM3(connection: HAPConnection, request: IncomingMessage, response: ServerResponse, tlvData: Record): void { debug("[%s] Pair step 2/5", this.accessoryInfo.username); - const A = objects[TLVValues.PUBLIC_KEY]; // "A is a public key that exists only for a single login session." - const 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 - const srpServer = session.srpServer!; + const srpServer = connection.srpServer!; srpServer.setA(A); try { srpServer.checkM1(M1); @@ -411,24 +442,23 @@ 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.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); response.end(tlv.encode(TLVValues.SEQUENCE_NUM, States.M4, TLVValues.ERROR_CODE, Codes.AUTHENTICATION)); - session._pairSetupState = undefined; + connection._pairSetupState = undefined; return; } // "M2 is the proof that the server actually knows your password." const M2 = srpServer.computeM2(); - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); response.end(tlv.encode(TLVValues.SEQUENCE_NUM, States.M4, TLVValues.PASSWORD_PROOF, M2)); - session._pairSetupState = States.M4; + connection._pairSetupState = States.M4; } - // M5-1 - _handlePairStepThree = (request: IncomingMessage, response: ServerResponse, session: HAPSession, 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 - const srpServer = session.srpServer!; - const encryptedData = objects[TLVValues.ENCRYPTED_DATA]; + 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); @@ -443,9 +473,9 @@ export class HAPServer extends EventEmitter { 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.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); response.end(tlv.encode(TLVValues.SEQUENCE_NUM, States.M4, TLVValues.ERROR_CODE, Codes.AUTHENTICATION)); - session._pairSetupState = undefined; + connection._pairSetupState = undefined; return; } // decode the client payload and pass it on to the next step @@ -453,31 +483,31 @@ export class HAPServer extends EventEmitter { const clientUsername = M5Packet[TLVValues.USERNAME]; const clientLTPK = M5Packet[TLVValues.PUBLIC_KEY]; const clientProof = M5Packet[TLVValues.PROOF]; - this._handlePairStepFour(request, response, session, clientUsername, clientLTPK, clientProof, outputKey); + this.handlePairSetupM5_2(connection, request, response, clientUsername, clientLTPK, clientProof, outputKey); } // M5-2 - _handlePairStepFour = (request: IncomingMessage, response: ServerResponse, session: HAPSession, 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); - const S_private = session.srpServer!.computeK(); + 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.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); response.end(tlv.encode(TLVValues.SEQUENCE_NUM, States.M6, TLVValues.ERROR_CODE, Codes.AUTHENTICATION)); - session._pairSetupState = undefined; + connection._pairSetupState = undefined; 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: HAPSession, 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); - const S_private = session.srpServer!.computeK(); + 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); @@ -494,39 +524,37 @@ 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(); - session._pairSetupState = undefined; + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(TLVValues.SEQUENCE_NUM, States.M6, TLVValues.ERROR_CODE, Codes.UNKNOWN)); + connection._pairSetupState = undefined; 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]))); - session._pairSetupState = undefined; + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(TLVValues.SEQUENCE_NUM, States.M6, TLVValues.ENCRYPTED_DATA, Buffer.concat([encrypted.ciphertext, encrypted.authTag]))); + connection._pairSetupState = undefined; })); } - /** - * iOS <-> Accessory pairing verification. - */ - _handlePairVerify = (request: IncomingMessage, response: ServerResponse, session: HAPSession, events: any, requestData: Buffer) => { - const objects = tlv.decode(requestData); - const sequence = objects[TLVValues.SEQUENCE_NUM][0]; // value is single byte with sequence number + 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 == States.M1) - this._handlePairVerifyStepOne(request, response, session, objects); - else if (sequence == States.M3 && session._pairVerifyState === States.M2) - this._handlePairVerifyStepTwo(request, response, session, events, objects); + this.handlePairVerifyM1(connection, request, response, tlvData); + else if (sequence == States.M3 && connection._pairVerifyState === States.M2) + this.handlePairVerifyM2(connection, request, response, tlvData); else { // Invalid state/sequence number - response.writeHead(400, {"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)); return; } } - _handlePairVerifyStepOne = (request: IncomingMessage, response: ServerResponse, session: HAPSession, objects: Record) => { + private handlePairVerifyM1(connection: HAPConnection, request: IncomingMessage, response: ServerResponse, tlvData: Record): void { debug("[%s] Pair verify step 1/2", this.accessoryInfo.username); - const clientPublicKey = objects[TLVValues.PUBLIC_KEY]; // Buffer + const clientPublicKey = tlvData[TLVValues.PUBLIC_KEY]; // Buffer // generate new encryption keys for this session const keyPair = hapCrypto.generateCurve25519KeyPair(); const secretKey = Buffer.from(keyPair.secretKey); @@ -547,18 +575,18 @@ export class HAPServer extends EventEmitter { enc.sharedSec = sharedSec; enc.hkdfPairEncKey = outputKey; // store this in the current TCP session - session.encryption = enc; + connection.encryption = enc; // compose the response data in TLV format 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.writeHead(HAPPairingHTTPCode.OK, {"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; + connection._pairVerifyState = States.M2; } - _handlePairVerifyStepTwo = (request: IncomingMessage, response: ServerResponse, session: HAPSession, 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); const encryptedData = objects[TLVValues.ENCRYPTED_DATA]; const messageData = Buffer.alloc(encryptedData.length - 16); @@ -567,16 +595,16 @@ export class HAPServer extends EventEmitter { encryptedData.copy(authTagData, 0, encryptedData.length - 16, encryptedData.length); // instance of HAPEncryption (created in handlePairVerifyStepOne) - const enc = session.encryption!; + const enc = connection.encryption!; let plaintext; try { plaintext = hapCrypto.chacha20_poly1305_decryptAndVerify(enc.hkdfPairEncKey, 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.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); response.end(tlv.encode(TLVValues.STATE, States.M4, TLVValues.ERROR_CODE, Codes.AUTHENTICATION)); - session._pairVerifyState = undefined; + connection._pairVerifyState = undefined; return; } @@ -590,21 +618,21 @@ export class HAPServer extends EventEmitter { // 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.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); response.end(tlv.encode(TLVValues.STATE, States.M4, TLVValues.ERROR_CODE, Codes.AUTHENTICATION)); - session._pairVerifyState = undefined; + 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.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); response.end(tlv.encode(TLVValues.STATE, States.M4, TLVValues.ERROR_CODE, Codes.AUTHENTICATION)); - session._pairVerifyState = undefined; + 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)); + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(TLVValues.SEQUENCE_NUM, States.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. @@ -614,24 +642,20 @@ export class HAPServer extends EventEmitter { enc.accessoryToControllerKey = hapCrypto.HKDF("sha512", encSalt, enc.sharedSec, infoRead, 32); enc.controllerToAccessoryKey = hapCrypto.HKDF("sha512", encSalt, enc.sharedSec, 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: HAPSession, 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: Status.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]; @@ -644,39 +668,39 @@ export class HAPServer extends EventEmitter { 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: Codes | 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, States.M2, TLVValues.ERROR_CODE, error)); return; } - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); response.end(tlv.encode(TLVValues.STATE, States.M2)); debug("[%s] Pairings: successfully executed ADD_PAIRING", this.accessoryInfo.username); })); } else if (method === Methods.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: Codes | 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, States.M2, TLVValues.ERROR_CODE, error)); return; } - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); + response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); response.end(tlv.encode(TLVValues.STATE, States.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)); + this.emit(HAPServerEventTypes.LIST_PAIRINGS, connection, once((error: Codes | 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, States.M2, TLVValues.ERROR_CODE, error)); return; } @@ -694,53 +718,39 @@ export class HAPServer extends EventEmitter { }); const list = tlv.encode(TLVValues.STATE, States.M2, ...tlvList); - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); + 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: HAPSession, events: any, requestData: any) => { - if (!this.allowInsecureRequest && !session.authenticated) { - response.writeHead(470, {"Content-Type": "application/hap+json"}); + 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: Status.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; - } - response.writeHead(200, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify(accessories)); + this.emit(HAPServerEventTypes.ACCESSORIES, once((result: { accessories: any[] }) => { + response.writeHead(HAPHTTPCode.OK, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify(result)); })); } - // Called when the client wishes to get or set particular characteristics - _handleCharacteristics(request: IncomingMessage, response: ServerResponse, session: HAPSession, events: CharacteristicEvents, requestData: { length: number; toString: () => string; }): void { - if (!this.allowInsecureRequest && !session.authenticated) { - response.writeHead(470, {"Content-Type": "application/hap+json"}); + 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: Status.INSUFFICIENT_PRIVILEGES})); return; } if (request.method === "GET") { - const url = new URL(request.url!, "http://hap-nodejs.local"); const searchParams = url.searchParams; const idParam = searchParams.get("id"); if (!idParam) { - // TODO proper error code - response.writeHead(500, {"Content-Type": "application/hap+json"}); + response.writeHead(HAPHTTPCode.BAD_REQUEST, {"Content-Type": "application/hap+json"}); response.end(JSON.stringify({ status: Status.INVALID_VALUE_IN_REQUEST })); return; } @@ -762,7 +772,7 @@ export class HAPServer extends EventEmitter { includeEvent: consideredTrue(searchParams.get("ev")), }; - this.emit(HAPServerEventTypes.GET_CHARACTERISTICS, readRequest, session, events, once((readResponse: CharacteristicsReadResponse) => { + this.emit(HAPServerEventTypes.GET_CHARACTERISTICS, connection, readRequest, once((readResponse: CharacteristicsReadResponse) => { const characteristics = readResponse.characteristics; let errorOccurred = false; // determine if we send a 207 Multi-Status @@ -782,27 +792,26 @@ export class HAPServer extends EventEmitter { } // 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.writeHead(errorOccurred? HAPHTTPCode.MULTI_STATUS: HAPHTTPCode.OK, {"Content-Type": "application/hap+json"}); response.end(JSON.stringify({ characteristics: characteristics })); })); - } else if (request.method == "PUT") { - if (!session.authenticated) { + } 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.writeHead(HAPPairingHTTPCode.CONNECTION_AUTHORIZATION_REQUIRED, {"Content-Type": "application/hap+json"}); response.end(JSON.stringify({status: Status.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})); return; } - const writeRequest = JSON.parse(requestData.toString()) as CharacteristicsWriteRequest; - const data = writeRequest.characteristics; + const writeRequest = JSON.parse(data.toString("utf8")) as CharacteristicsWriteRequest; - this.emit(HAPServerEventTypes.SET_CHARACTERISTICS, writeRequest, session, events, once((writeResponse: CharacteristicsWriteResponse) => { + this.emit(HAPServerEventTypes.SET_CHARACTERISTICS, connection, writeRequest, once((writeResponse: CharacteristicsWriteResponse) => { const characteristics = writeResponse.characteristics; let multiStatus = false; @@ -822,92 +831,91 @@ export class HAPServer extends EventEmitter { } // 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.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(); } })); } else { - // TODO handle that at least for debugging purposes + response.writeHead(HAPHTTPCode.BAD_REQUEST, {"Content-Type": "application/hap+json"}); // method not allowed + response.end(JSON.stringify({ status: Status.INVALID_VALUE_IN_REQUEST })); } } - // Called when controller requests a timed write - _prepareWrite = (request: IncomingMessage, response: ServerResponse, session: HAPSession, events: any, requestData: { length: number; toString: () => string; }) => { - if (!this.allowInsecureRequest && !session.authenticated) { - response.writeHead(470, {"Content-Type": "application/hap+json"}); + 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: Status.INSUFFICIENT_PRIVILEGES})); return; } if (request.method == "PUT") { - if (requestData.length == 0) { - response.writeHead(400, {"Content-Type": "application/hap+json"}); + if (data.length == 0) { + response.writeHead(HAPHTTPCode.BAD_REQUEST, {"Content-Type": "application/hap+json"}); response.end(JSON.stringify({status: Status.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.writeHead(HAPHTTPCode.OK, {"Content-Type": "application/hap+json"}); response.end(JSON.stringify({status: Status.SUCCESS})); return; + } else { + response.writeHead(HAPHTTPCode.BAD_REQUEST, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify({ status: Status.INVALID_VALUE_IN_REQUEST })); } + } else { + response.writeHead(HAPHTTPCode.BAD_REQUEST, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify({ status: Status.INVALID_VALUE_IN_REQUEST })); } }; - // Called when controller request snapshot - _handleResource = (request: IncomingMessage, response: ServerResponse, session: HAPSession, 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: Status.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: Status.INVALID_VALUE_IN_REQUEST })); return; } - // requestData is a JSON payload - const 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: { httpCode: HAPHTTPCode, status: Status} | 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: Status.INVALID_VALUE_IN_REQUEST })); } } diff --git a/src/lib/camera/RTPStreamManagement.ts b/src/lib/camera/RTPStreamManagement.ts index 1c8d4ff5e..1cb956d85 100644 --- a/src/lib/camera/RTPStreamManagement.ts +++ b/src/lib/camera/RTPStreamManagement.ts @@ -2,7 +2,7 @@ import crypto from 'crypto'; import createDebug from 'debug'; import net from "net"; // noinspection JSDeprecatedSymbols -import { CharacteristicEvents, LegacyCameraSource, LegacyCameraSourceAdapter, once, uuid } from "../../index"; +import { LegacyCameraSource, LegacyCameraSourceAdapter, once, uuid } from "../../index"; import { CharacteristicValue, Nullable, SessionIdentifier } from '../../types'; import { Characteristic, @@ -14,7 +14,7 @@ import { CameraController, CameraStreamingDelegate } from "../controller"; import { CameraRTPStreamManagement } from "../gen/HomeKit"; import { Status } from "../HAPServer"; import { Service } from '../Service'; -import { HAPSession } from "../util/eventedhttp"; +import { HAPConnection, HAPConnectionEvent } from "../util/eventedhttp"; import * as tlv from '../util/tlv'; import RTPProxy from './RTPProxy'; @@ -483,7 +483,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 @@ -530,18 +535,13 @@ export class RTPStreamManagement { return this.service; } - // Private - + /** + * @deprecated + */ handleCloseConnection(connectionID: SessionIdentifier): void { - if (this.connectionID && this.connectionID === connectionID) { - this._handleStopStream(); - } - } - - handleShutdown(): void { - if (this.connectionID) { - this._handleStopStream(); - } + // 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() { @@ -575,8 +575,8 @@ export class RTPStreamManagement { .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { callback(null, this.setupEndpointsResponse); }) - .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback, context: CharacteristicEvents, session: HAPSession) => { - this.handleSetupEndpoints(value, callback, session); + .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback, context: any, connection: HAPConnection) => { + this.handleSetupEndpoints(value, callback, connection); }); } @@ -590,8 +590,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; @@ -851,7 +858,7 @@ export class RTPStreamManagement { this.delegate.handleStreamRequest(request, error => callback? callback(error): undefined); } - private handleSetupEndpoints(value: CharacteristicValue, callback: CharacteristicSetCallback, session: HAPSession): void { + private handleSetupEndpoints(value: CharacteristicValue, callback: CharacteristicSetCallback, connection: HAPConnection): void { const data = Buffer.from(value as string, 'base64'); const objects = tlv.decode(data); @@ -866,7 +873,11 @@ export class RTPStreamManagement { return; } - this.connectionID = session.sessionID; + 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); @@ -930,7 +941,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, @@ -971,13 +982,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: HAPSession, 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; @@ -1014,7 +1025,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) { @@ -1048,7 +1059,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 da643df42..ad000eb77 100644 --- a/src/lib/controller/CameraController.ts +++ b/src/lib/controller/CameraController.ts @@ -355,15 +355,9 @@ export class CameraController extends EventEmitter imp } handleCloseConnection(sessionID: SessionIdentifier): void { - this.streamManagements.forEach(management => management.handleCloseConnection(sessionID)); - if (this.delegate instanceof LegacyCameraSourceAdapter) { this.delegate.forwardCloseConnection(sessionID); } } - handleShutdown(): void { - this.streamManagements.forEach(management => management.handleShutdown()); - } - } diff --git a/src/lib/controller/RemoteController.ts b/src/lib/controller/RemoteController.ts index e2fd1a25a..cf2542f27 100644 --- a/src/lib/controller/RemoteController.ts +++ b/src/lib/controller/RemoteController.ts @@ -1,7 +1,7 @@ import assert from 'assert'; import createDebug from 'debug'; import { CharacteristicValue } from "../../types"; -import { Accessory, CharacteristicEvents } from "../Accessory"; +import { Accessory } from "../Accessory"; import { Characteristic, CharacteristicEventTypes, @@ -28,7 +28,7 @@ import { DataStreamTransportManagement } from "../gen/HomeKit-DataStream"; import { AudioStreamManagement, Siri, TargetControl, TargetControlManagement } from "../gen/HomeKit-Remote"; import { Status } from "../HAPServer"; import { Service } from "../Service"; -import { HAPSession, HAPSessionEvents } from "../util/eventedhttp"; +import { HAPConnection, HAPConnectionEvent } from "../util/eventedhttp"; import * as tlv from '../util/tlv'; import { ControllerServiceMap, DefaultControllerType, SerializableController, StateChangeDelegate } from "./Controller"; import Timeout = NodeJS.Timeout; @@ -359,8 +359,8 @@ export class RemoteController extends EventEmitter private lastButtonEvent: string = ""; activeIdentifier: number = 0; // id of 0 means no device selected - private activeSession?: HAPSession; // 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; @@ -384,7 +384,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; @@ -394,7 +394,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, @@ -405,7 +405,7 @@ export class RemoteController extends EventEmitter rtpTime: 20, } }; - this.selectedAudioConfigurationString = this.buildSelectedAudioConfigurationTLV({ + this.selectedAudioConfigurationString = RemoteController.buildSelectedAudioConfigurationTLV({ audioCodecConfiguration: this.selectedAudioConfiguration, }); } @@ -415,7 +415,7 @@ export class RemoteController extends EventEmitter * * @param activeIdentifier {number} - target identifier */ - setActiveIdentifier = (activeIdentifier: number) => { + public setActiveIdentifier(activeIdentifier: number): void { if (activeIdentifier === this.activeIdentifier) { return; } @@ -439,8 +439,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; }; /** @@ -448,7 +448,7 @@ export class RemoteController extends EventEmitter * * @param targetIdentifier {number} */ - isConfigured = (targetIdentifier: number) => { + public isConfigured(targetIdentifier: number): boolean { return this.targetConfigurations[targetIdentifier] !== undefined; }; @@ -458,7 +458,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) { @@ -473,7 +473,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); }; @@ -482,7 +482,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); }; @@ -492,7 +492,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); }; @@ -503,14 +503,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 @@ -539,7 +539,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: { @@ -555,7 +555,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); @@ -602,7 +602,7 @@ export class RemoteController extends EventEmitter } }; - private handleAddTarget = (targetConfiguration?: TargetConfiguration): Status => { + private handleAddTarget(targetConfiguration?: TargetConfiguration): Status { if (!targetConfiguration) { return Status.INVALID_VALUE_IN_REQUEST; } @@ -617,7 +617,7 @@ export class RemoteController extends EventEmitter return Status.SUCCESS; }; - private handleUpdateTarget = (targetConfiguration?: TargetConfiguration): Status => { + private handleUpdateTarget(targetConfiguration?: TargetConfiguration): Status { if (!targetConfiguration) { return Status.INVALID_VALUE_IN_REQUEST; } @@ -661,7 +661,7 @@ export class RemoteController extends EventEmitter return Status.SUCCESS; }; - private handleRemoveTarget = (targetConfiguration?: TargetConfiguration): Status => { + private handleRemoveTarget(targetConfiguration?: TargetConfiguration): Status { if (!targetConfiguration) { return Status.INVALID_VALUE_IN_REQUEST; } @@ -693,7 +693,7 @@ export class RemoteController extends EventEmitter return Status.SUCCESS; }; - private handleResetTargets = (targetConfiguration?: TargetConfiguration): Status => { + private handleResetTargets(targetConfiguration?: TargetConfiguration): Status { if (targetConfiguration) { return Status.INVALID_VALUE_IN_REQUEST; } @@ -708,7 +708,7 @@ export class RemoteController extends EventEmitter return Status.SUCCESS; }; - private handleListTargets = (targetConfiguration?: TargetConfiguration): Status => { + private handleListTargets(targetConfiguration?: TargetConfiguration): Status { if (targetConfiguration) { return Status.INVALID_VALUE_IN_REQUEST; } @@ -718,23 +718,23 @@ export class RemoteController extends EventEmitter return Status.SUCCESS; }; - private handleActiveWrite(value: CharacteristicValue, callback: CharacteristicSetCallback, session: HAPSession): void { + 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(Status.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; @@ -745,14 +745,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"); @@ -760,8 +760,8 @@ export class RemoteController extends EventEmitter setTimeout(() => this.emit(RemoteControllerEvents.ACTIVE_CHANGE, false), 0); }; - private handleActiveSessionDisconnected = (session: HAPSession) => { - if (session !== this.activeSession) { + private handleActiveSessionDisconnected(connection: HAPConnection): void { + if (connection !== this.activeConnection) { return; } @@ -769,7 +769,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 + ")"); @@ -816,7 +816,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]); @@ -860,7 +860,7 @@ export class RemoteController extends EventEmitter }; }; - private updatedTargetConfiguration = () => { + private updatedTargetConfiguration(): void { const bufferList = []; for (const key in this.targetConfigurations) { // noinspection JSUnfilteredForInLoop @@ -912,7 +912,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 ); @@ -942,7 +942,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); @@ -950,7 +950,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"); } @@ -986,7 +986,7 @@ export class RemoteController extends EventEmitter audioSession.start(); }; - private handleSiriAudioStop = () => { + private handleSiriAudioStop(): void { if (this.activeAudioSession) { if (!this.activeAudioSession.isClosing()) { this.activeAudioSession.stop(); @@ -1000,7 +1000,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"]; @@ -1013,7 +1013,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; @@ -1026,7 +1026,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; @@ -1035,7 +1035,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) { @@ -1048,7 +1048,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); @@ -1072,15 +1072,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 @@ -1088,8 +1088,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, @@ -1097,7 +1097,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( @@ -1175,9 +1175,7 @@ export class RemoteController extends EventEmitter .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { 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) => { @@ -1187,8 +1185,8 @@ export class RemoteController extends EventEmitter .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { callback(undefined, this.isActive()); }) - .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback, context: CharacteristicEvents, session: HAPSession) => { - this.handleActiveWrite(value, callback, session); + .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback, context: any, connection: HAPConnection) => { + this.handleActiveWrite(value, callback, connection); }); this.targetControlService.getCharacteristic(Characteristic.ButtonEvent)! .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { @@ -1200,9 +1198,7 @@ export class RemoteController extends EventEmitter .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { callback(null, this.selectedAudioConfigurationString); }) - .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { - this.handleSelectedAudioConfigurationWrite(value, callback); - }) + .on(CharacteristicEventTypes.SET, this.handleSelectedAudioConfigurationWrite.bind(this)) .updateValue(this.selectedAudioConfigurationString); this.dataStreamManagement! @@ -1430,7 +1426,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; } @@ -1441,7 +1437,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); @@ -1449,7 +1445,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(); @@ -1458,7 +1454,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"); @@ -1470,7 +1466,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) { @@ -1480,7 +1476,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 3be84681c..c001b317f 100644 --- a/src/lib/datastream/DataStreamManagement.ts +++ b/src/lib/datastream/DataStreamManagement.ts @@ -1,6 +1,5 @@ import createDebug from "debug"; import { CharacteristicValue } from "../../types"; -import { CharacteristicEvents } from "../Accessory"; import { Characteristic, CharacteristicEventTypes, @@ -11,7 +10,7 @@ import { Event } from "../EventEmitter"; import { DataStreamTransportManagement } from "../gen/HomeKit-DataStream"; import { Status } from "../HAPServer"; import { Service } from "../Service"; -import { HAPSession } from "../util/eventedhttp"; +import { HAPConnection } from "../util/eventedhttp"; import * as tlv from '../util/tlv'; import { DataStreamServer, @@ -151,7 +150,7 @@ export class DataStreamManagement { return this; } - private handleSetupDataStreamTransportWrite(value: any, callback: CharacteristicSetCallback, session: HAPSession) { + private handleSetupDataStreamTransportWrite(value: any, callback: CharacteristicSetCallback, connection: HAPConnection) { const data = Buffer.from(value, 'base64'); const objects = tlv.decode(data); @@ -167,7 +166,7 @@ export class DataStreamManagement { 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([ @@ -214,8 +213,8 @@ export class DataStreamManagement { .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { callback(null, this.lastSetupDataStreamTransportResponse); }) - .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback, context: CharacteristicEvents, session: HAPSession) => { - this.handleSetupDataStreamTransportWrite(value, callback, session); + .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback, context: any, connection: HAPConnection) => { + 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 118345bc8..8931aabf3 100644 --- a/src/lib/datastream/DataStreamServer.ts +++ b/src/lib/datastream/DataStreamServer.ts @@ -1,11 +1,11 @@ import createDebug from 'debug'; import assert from 'assert'; +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, HAPSession} from "../util/eventedhttp"; import {EventEmitter as NodeEventEmitter} from "events"; import {EventEmitter} from "../EventEmitter"; import Timeout = NodeJS.Timeout; @@ -14,7 +14,7 @@ const debug = createDebug('HAP-NodeJS:DataStream:Server'); export type PreparedDataStreamSession = { - session: HAPSession, // reference to the hap session which created the request + connection: HAPConnection, // reference to the hap session which created the request accessoryToControllerEncryptionKey: Buffer, controllerToAccessoryEncryptionKey: Buffer, @@ -143,7 +143,7 @@ export type DataStreamServerEventMap = { * @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"; @@ -218,16 +218,16 @@ export class DataStreamServer extends EventEmitter { return this; } - prepareSession(session: HAPSession, 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!.sharedSec, DataStreamServer.accessoryToControllerInfo, 32); + const controllerToAccessoryEncryptionKey = hapCrypto.HKDF("sha512", salt, connection.encryption!.sharedSec, DataStreamServer.controllerToAccessoryInfo, 32); const preparedSession: PreparedDataStreamSession = { - session: session, + connection: connection, accessoryToControllerEncryptionKey: accessoryToControllerEncryptionKey, controllerToAccessoryEncryptionKey: controllerToAccessoryEncryptionKey, accessoryKeySalt: accessoryKeySalt, @@ -239,7 +239,7 @@ export class DataStreamServer extends EventEmitter { } 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); @@ -318,7 +318,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); @@ -329,9 +329,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 } } @@ -354,17 +354,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); @@ -426,13 +426,13 @@ export class DataStreamConnection extends EventEmitter 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; @@ -456,7 +456,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); @@ -484,7 +484,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; @@ -612,25 +612,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; } @@ -643,7 +644,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 []; } @@ -766,7 +767,7 @@ export class DataStreamConnection extends EventEmitter; + pairedClients: Record; pairedAdminClients: number; private configVersion: number = 1; configHash: string; @@ -58,22 +57,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) { @@ -88,8 +88,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; @@ -101,43 +101,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: HAPSession, 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: HAPSession, 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]; - HAPSession.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 falsy value if not paired. - getClientPublicKey = (username: string) => { + public getClientPublicKey(username: HAPUsername): Buffer | undefined { const pairingInformation = this.pairedClients[username]; if (pairingInformation) { return pairingInformation.publicKey; @@ -190,9 +190,9 @@ export class AccessoryInfo { }; for (let username in this.pairedClients) { - const pairingInformation = this.pairedClients[username]; + 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; } diff --git a/src/lib/util/eventedhttp.ts b/src/lib/util/eventedhttp.ts index b770edb1f..115f545eb 100644 --- a/src/lib/util/eventedhttp.ts +++ b/src/lib/util/eventedhttp.ts @@ -1,75 +1,42 @@ 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 { CharacteristicEvents } from "../Accessory"; -import { EventEmitter } from '../EventEmitter'; +import { CharacteristicValue, Nullable, SessionIdentifier } from '../../types'; import { HAPEncryption } from '../HAPServer'; +import * as hapCrypto from "./hapCrypto"; +import { getOSLoopbackAddress } from "./net-utils"; import * as uuid from './uuid'; const debug = createDebug('HAP-NodeJS:EventedHTTPServer'); -export const enum EventedHTTPServerEvents { +export type HAPUsername = string; +export type EventName = string; // "." + +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, hostname: string) => void; - [EventedHTTPServerEvents.REQUEST]: (request: IncomingMessage, response: ServerResponse, session: HAPSession, events: CharacteristicEvents) => void; - [EventedHTTPServerEvents.DECRYPT]: (data: Buffer, decrypted: { data: Buffer; error: Error | null }, session: HAPSession) => void; - [EventedHTTPServerEvents.ENCRYPT]: (data: Buffer, encrypted: { data: number | Buffer; }, session: HAPSession) => void; - [EventedHTTPServerEvents.CLOSE]: (events: any) => void; - [EventedHTTPServerEvents.SESSION_CLOSE]: (sessionID: SessionIdentifier, events: any) => void; -}; - -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; - } +export declare interface EventedHTTPServer { - 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; - } - } - } + 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; - if (internal) { - break; - } - } + 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; - const address = ipv6 || ipv6LinkLocal || ipv4; - if (!address) { - throw new Error("Could not find a valid loopback address on the platform!"); - } - return address; } -const loopbackAddress = findLoopbackAddress(); // loopback addressed used for the internal http server (::1 or 127.0.0.1) /** * EventedHTTPServer provides an HTTP-like server that supports HAP "extensions" for security and events. @@ -87,501 +54,509 @@ const loopbackAddress = findLoopbackAddress(); // loopback addressed used for th * * 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 determine 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 { - private readonly _tcpServer: net.Server; - private readonly _connections: EventedHTTPServerConnection[] = []; + 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(); constructor() { super(); - this._tcpServer = net.createServer(); + this.tcpServer = net.createServer(); } - listen = (targetPort: number, hostname?: string) => { - this._tcpServer.listen(targetPort, hostname); - - this._tcpServer.on('listening', () => { - const address = this._tcpServer.address(); - - if (address && typeof address !== 'string') { - const hostname = address.address; - const port = address.port; - - debug("Server listening on %s:%s", address.family === "IPv6"? `[${hostname}]`: hostname, port); - this.emit(EventedHTTPServerEvents.LISTENING, port, hostname); - } + 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); }); - this._tcpServer.on('connection', this._onConnection); + this.tcpServer.on("connection", this.onConnection.bind(this)); } - stop = () => { - this._tcpServer.close(); - this._connections.forEach((connection) => { + public stop(): void { + this.tcpServer.close(); + for (const connection of this.connections) { connection.close(); - }); - this._connections.splice(0, this._connections.length); - } - - sendEvent = ( - event: string, - data: Buffer | string, - contentType: string, - exclude?: Record - ) => { - for (const connection of this._connections) { - connection.sendEvent(event, data, contentType, exclude); } - } -// 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); - - // pass on session events to our listeners directly - connection.on(EventedHTTPServerEvents.REQUEST, (request: IncomingMessage, response: ServerResponse, session: HAPSession, events: any) => { this.emit(EventedHTTPServerEvents.REQUEST, request, response, session, events); }); - connection.on(EventedHTTPServerEvents.ENCRYPT, (data: Buffer, encrypted: { data: Buffer; }, session: HAPSession) => { this.emit(EventedHTTPServerEvents.ENCRYPT, data, encrypted, session); }); - connection.on(EventedHTTPServerEvents.DECRYPT, (data: Buffer, decrypted: { data: number | Buffer; }, session: HAPSession) => { this.emit(EventedHTTPServerEvents.DECRYPT, data, decrypted, session); }); - connection.on(EventedHTTPServerEvents.CLOSE, (events: any) => { this._handleConnectionClose(connection, events); }); - this._connections.push(connection); + this.removeAllListeners(); } - _handleConnectionClose = ( - connection: EventedHTTPServerConnection, - events: Record - ) => { - this.emit(EventedHTTPServerEvents.SESSION_CLOSE, connection.sessionID, events); + /** + * 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. + */ + public broadcastEvent(aid: number, iid: number, value: Nullable, originator?: HAPConnection): 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; + } - // remove it from our array of connections for events - this._connections.splice(this._connections.indexOf(connection), 1); + connection.sendEvent(aid, iid, value); + } } -} + private onConnection(socket: Socket): void { + const connection = new HAPConnection(this, socket); -export const enum HAPSessionEvents { - CLOSED = "closed", -} - -export type HAPSessionEventMap = { - [HAPSessionEvents.CLOSED]: () => void; -} - -export class HAPSession extends EventEmitter { - - 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; - 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); + } - HAPSession.sessionsBySessionID[this.sessionID] = this; + 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); + } } - public getLocalAddress(ipVersion: "ipv4" | "ipv6"): string { - const infos = os.networkInterfaces()[this._connection.networkInterface]; + private handleConnectionClose(connection: HAPConnection): void { + this.emit(EventedHTTPServerEvent.CONNECTION_CLOSED, connection); - 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._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; - - let sessions: HAPSession[] = this._server.sessions[username]; - if (!sessions) { - sessions = []; - this._server.sessions[username] = sessions; - } + public static destroyExistingConnectionsAfterUnpair(initiator: HAPConnection, username: string): void { + const connections: HAPConnection[] | undefined = initiator.server.connectionsByUsername.get(username); - 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 HAPSession.sessionsBySessionID[this.sessionID]; - - if (this.username) { - const sessions: HAPSession[] = 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: HAPSession, username: string) => { - const sessions: HAPSession[] = 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 networkInterface: string; - _pendingClientSocketData: Nullable; - _fullySetup: boolean; - _writingResponse: boolean; - _killSocketAfterWrite: boolean; - _pendingEventData: Buffer[]; - _clientSocket: Socket; - _httpServer: http.Server; - _serverSocket: Nullable; - _session: HAPSession; - _events: CharacteristicEvents; - _httpPort?: number; + + private readonly tcpSocket: Socket; + private readonly internalHttpServer: http.Server; + private httpSocket?: Socket; // set when in state FULLY_SET_UP + private internalHttpServerPort?: number; + + 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) + private readonly pendingEventData: Buffer[] = []; // queue of unencrypted event data waiting to be sent until after an in-progress HTTP response is being written + + 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? + _pairVerifyState?: number; + + private registeredEvents: Set = new Set(); + + timedWritePid?: number; + timedWriteTimeout?: NodeJS.Timeout; constructor(server: EventedHTTPServer, clientSocket: Socket) { super(); this.server = server; this.sessionID = uuid.generate(clientSocket.remoteAddress + ':' + clientSocket.remotePort); - this._remoteAddress = clientSocket.remoteAddress!; // cache because it becomes undefined in 'onClientSocketClose' - 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.remoteAddress = clientSocket.remoteAddress!; // cache because it becomes undefined in 'onClientSocketClose' + 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, 5000); - - // 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 + // this.tcpSocket.setKeepAlive(true, 5000); // TODO HAP accessory servers must not use keepalive messages, which periodically wake up iOS devices. + // 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, loopbackAddress); - // an arbitrary dict that users of this class can store values in to associate with this particular connection - this._session = new HAPSession(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); } - 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; - } - // 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); + /** + * 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 sendEvent(aid: number, iid: number, value: Nullable): void { + assert(aid != undefined, "HAPConnection.sendEvent: aid must be defined!"); + assert(iid != undefined, "HAPConnection.sendEvent: iid must be defined!"); + + if (!this.hasEventNotifications(aid, iid)) { return; } - 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); - } - // 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); + debug("[%s] Sending HTTP event '%s' with data: %s", this.remoteAddress, aid + "." + iid, value); + + // TODO queue events!!! + const data = Buffer.from(JSON.stringify({ + characteristics: [{ + aid: aid, + iid: iid, + value: value, + }], + }), "utf8"); + + const header = Buffer.from( + "EVENT/1.0 200 OK\r\n" + + "Content-Type: application/hap+json\r\n" + + "Content-Length: " + data.length + "\r\n" + + "\r\n", + "utf8" // buffer encoding + ); + + const buffer = Buffer.concat([header, data]); + + if (this.handlingRequest) { + // it is important to not encrypt the queued data, otherwise nonce counter will get incorrectly incremented + this.pendingEventData.push(buffer); } 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; - } - - this._clientSocket.write(data); + this.tcpSocket.write(this.encrypt(buffer)); } } - close = () => { - this._clientSocket.end(); + public enableEventNotifications(aid: number, iid: number): void { + this.registeredEvents.add(aid + "." + iid); } - _sendPendingEvents = () => { - if (this._pendingEventData.length === 0) { - return; - } + public disableEventNotifications(aid: number, iid: number): void { + this.registeredEvents.delete(aid + "." + iid); + } - // 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; - } + public hasEventNotifications(aid: number, iid: number): boolean { + return this.registeredEvents.has(aid + "." + iid); + } - this._clientSocket.write(event); - }); + public getRegisteredEvents(): Set { + return this.registeredEvents; + } - // clear the queue - this._pendingEventData = []; + 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.value > 0) { + return hapCrypto.layerEncrypt(data, this.encryption.accessoryToControllerCount, this.encryption.accessoryToControllerKey); + } + return data; // otherwise we don't encrypt and return plaintext } - // Called only once right after constructor finishes - _onHttpServerListening = () => { - const addressInfo = this._httpServer.address() as AddressInfo; // address() is only a string when listening to unix domain sockets - this._httpPort = addressInfo.port; + 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.controllerToAccessoryCount, this.encryption.controllerToAccessoryKey, this.encryption.extraInfo); + } + 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); + + this.internalHttpServer.on('close', this.onHttpServerClose.bind(this)); - debug("[%s] HTTP server listening on %s:%s", this._remoteAddress, addressString, addressInfo.port); - // 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, addressInfo.address); - 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.httpSocket = net.createConnection(this.internalHttpServerPort, 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; + }); + } + + public close(): void { + if (this.state >= HAPConnectionState.CLOSING) { + return; // already closed/closing + } - this._serverSocket.setNoDelay(true); // disable Nagle algorithm + this.state = HAPConnectionState.CLOSING; + this.tcpSocket.destroy(); } - // 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 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(); } } - // Received data from client (iOS) - _onClientSocketData = (data: Buffer) => { - // _writingResponse is reverted to false in _onHttpServerRequest(...) after response was written - this._writingResponse = true; + private sendPendingEvents(): void { + if (this.pendingEventData.length === 0) { + return; + } + + debug("[%s] Writing pending HTTP event data", this.remoteAddress); + + for (const event of this.pendingEventData) { + this.tcpSocket.write(this.encrypt(event)); + } - // 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.pendingEventData.splice(0, this.pendingEventData.length); + } - 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); + /** + * 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 + + 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(); - } else { - if (decrypted.data) { - data = decrypted.data; - } + return; + } - 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]); - } + 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 { + this.httpSocket!.write(data); // proxy it along to the HTTP server } } - // 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 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); } - // 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(); + /** + * 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); + + 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.sendPendingEvents(); + } } - // 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); + + if (this.httpSocket) { + this.httpSocket.destroy(); + } + this.internalHttpServer.close(); + + this.emit(HAPConnectionEvent.CLOSED); - _onHttpServerClose = () => { - debug("[%s] HTTP server was closed", this._remoteAddress); - // notify listeners that we are completely closed - this.emit(EventedHTTPServerEvents.CLOSE, this._events); + this.removeAllListeners(); // cleanup listeners, we are officially dead now } - _onHttpServerError = (err: Error & { code?: string }) => { - debug("[%s] HTTP server error: %s", this._remoteAddress, err.message); + 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 { diff --git a/src/lib/util/net-utils.ts b/src/lib/util/net-utils.ts new file mode 100644 index 000000000..24fd4b126 --- /dev/null +++ b/src/lib/util/net-utils.ts @@ -0,0 +1,46 @@ +import os from "os"; + +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; +} +const loopbackAddress = findLoopbackAddress(); // loopback addressed used for the internal http server (::1 or 127.0.0.1) + +export function getOSLoopbackAddress(): string { + return loopbackAddress; +} diff --git a/src/types.ts b/src/types.ts index 50910b4d2..b5ad0af47 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,4 @@ -import { CharacteristicEvents } from "./lib/Accessory"; -import { Status } from './lib/HAPServer'; -import { Characteristic, CharacteristicProps } from './lib/Characteristic'; +import { CharacteristicProps } from './lib/Characteristic'; export type Nullable = T | null; @@ -32,7 +30,6 @@ 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 From 8f2c80a6de86a3137b5d5476fd183ff32cc99935 Mon Sep 17 00:00:00 2001 From: Supereg Date: Mon, 5 Oct 2020 01:35:39 +0200 Subject: [PATCH 22/70] Removed some obsolete @ts-expect-error --- src/lib/Characteristic.spec.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/lib/Characteristic.spec.ts b/src/lib/Characteristic.spec.ts index 2a584e129..c42454e46 100644 --- a/src/lib/Characteristic.spec.ts +++ b/src/lib/Characteristic.spec.ts @@ -117,7 +117,6 @@ describe('Characteristic', () => { const characteristic = createCharacteristic(Formats.BOOL); characteristic.eventOnlyCharacteristic = true; - // @ts-expect-error characteristic.handleGetRequest().then(() => { expect(characteristic.status).toEqual(Status.SUCCESS); expect(characteristic.value).toEqual(null); @@ -127,7 +126,6 @@ describe('Characteristic', () => { it('should return cached values if no listeners are registered', () => { const characteristic = createCharacteristic(Formats.BOOL); - // @ts-expect-error characteristic.handleGetRequest().then(() => { expect(characteristic.status).toEqual(Status.SUCCESS); expect(characteristic.value).toEqual(null); @@ -288,10 +286,8 @@ describe('Characteristic', () => { const listenerCallback = jest.fn(); - // @ts-expect-error characteristic.handleGetRequest(); characteristic.on(CharacteristicEventTypes.GET, listenerCallback); - // @ts-expect-error characteristic.handleGetRequest(); expect(listenerCallback).toHaveBeenCalledTimes(1); From d765118ee878a8532449d6ba34d7fe41cc0d42c1 Mon Sep 17 00:00:00 2001 From: Supereg Date: Mon, 5 Oct 2020 16:40:40 +0200 Subject: [PATCH 23/70] Bump beta to 0.9.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5a62553f4..951cdb68c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "hap-nodejs", "version": "0.8.2", - "betaVersion": "0.8.3", + "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", From 9f3887d51d10340ec75f2dcf717d42456eca2cb2 Mon Sep 17 00:00:00 2001 From: Supereg Date: Mon, 5 Oct 2020 18:10:35 +0200 Subject: [PATCH 24/70] Properly delay and combine event notifications --- src/internal-types.ts | 12 ++- src/lib/Accessory.ts | 20 ++++- src/lib/HAPServer.ts | 9 +- src/lib/util/eventedhttp.ts | 160 ++++++++++++++++++++++++------------ 4 files changed, 141 insertions(+), 60 deletions(-) diff --git a/src/internal-types.ts b/src/internal-types.ts index 026b0dee2..390701fa6 100644 --- a/src/internal-types.ts +++ b/src/internal-types.ts @@ -1,6 +1,6 @@ import { Formats, Perms, Units } from "./lib/Characteristic"; import { Status } from "./lib/HAPServer"; -import { CharacteristicValue } from "./types"; +import { CharacteristicValue, Nullable } from "./types"; export interface CharacteristicId { aid: number, @@ -110,6 +110,16 @@ export interface ResourceRequest { "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; diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index 0e606acfb..ed1dfebca 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -38,7 +38,15 @@ import { } from "./controller"; import { EventEmitter } from './EventEmitter'; import * as HomeKitTypes from "./gen"; -import { CameraEventRecordingManagement, CameraOperatingMode, CameraRTPStreamManagement, } from "./gen/HomeKit"; +import { + CameraEventRecordingManagement, + CameraOperatingMode, + CameraRTPStreamManagement, + ContactSensorState, + MotionDetected, + ProgrammableSwitchEvent, +} from "./gen/HomeKit"; +import { ButtonEvent } from "./gen/HomeKit-Remote"; import { AccessoriesCallback, AddPairingCallback, @@ -1599,14 +1607,20 @@ export class Accessory extends EventEmitter { characteristic.unsubscribe(); } } + connection.clearRegisteredEvents(); } // Called internally above when a change was detected in one of our hosted Characteristics somewhere in our hierarchy. private handleCharacteristicChange(change: AccessoryCharacteristicChange & { accessory: Accessory }): void { - if (!this._server) + if (!this._server) { return; // we're not running a HAPServer, so there's no one to notify about this event + } + + const uuid = change.characteristic.UUID; + const immediateDelivery = uuid === ButtonEvent.UUID || uuid === ProgrammableSwitchEvent.UUID + || uuid === MotionDetected.UUID || uuid === ContactSensorState.UUID; - this._server.sendEventNotifications(change.accessory.aid!, change.characteristic.iid!, change.newValue, change.originator); + this._server.sendEventNotifications(change.accessory.aid!, change.characteristic.iid!, change.newValue, change.originator, immediateDelivery); } _setupService = (service: Service) => { diff --git a/src/lib/HAPServer.ts b/src/lib/HAPServer.ts index 5a63e4cc9..b0c58fc17 100644 --- a/src/lib/HAPServer.ts +++ b/src/lib/HAPServer.ts @@ -15,8 +15,7 @@ import { PrepareWriteRequest, ResourceRequest } from "../internal-types"; -import { CharacteristicValue, NodeCallback, Nullable, VoidCallback } from '../types'; -import { Accessory } from './Accessory'; +import { CharacteristicValue, Nullable, VoidCallback } from '../types'; import { PairingInformation, PermissionTypes } from "./model/AccessoryInfo"; import { EventedHTTPServer, EventedHTTPServerEvent, HAPConnection, HAPUsername } from './util/eventedhttp'; import * as hapCrypto from './util/hapCrypto'; @@ -289,9 +288,11 @@ export class HAPServer extends EventEmitter { * @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 sendEventNotifications(aid: number, iid: number, value: Nullable, originator?: HAPConnection): void { - this.httpServer.broadcastEvent(aid, iid, value, originator); + public sendEventNotifications(aid: number, iid: number, value: Nullable, originator?: HAPConnection, immediateDelivery?: boolean): void { + this.httpServer.broadcastEvent(aid, iid, value, originator, immediateDelivery); } private onListening(port: number, hostname: string): void { diff --git a/src/lib/util/eventedhttp.ts b/src/lib/util/eventedhttp.ts index 115f545eb..c99ec82f7 100644 --- a/src/lib/util/eventedhttp.ts +++ b/src/lib/util/eventedhttp.ts @@ -6,11 +6,13 @@ import { SrpServer } from "fast-srp-hap"; import http, { IncomingMessage, ServerResponse } from 'http'; import net, { AddressInfo, Socket } from 'net'; import os from "os"; +import { CharacteristicEventNotification, EventNotification } from "../../internal-types"; import { CharacteristicValue, Nullable, SessionIdentifier } from '../../types'; -import { HAPEncryption } from '../HAPServer'; +import { HAPEncryption } from "../HAPServer"; 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'); @@ -102,15 +104,17 @@ export class EventedHTTPServer extends EventEmitter { * @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): void { + 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; } - connection.sendEvent(aid, iid, value); + connection.sendEvent(aid, iid, value, immediateDelivery); } } @@ -222,7 +226,6 @@ export class HAPConnection extends EventEmitter { 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) - private readonly pendingEventData: Buffer[] = []; // queue of unencrypted event data waiting to be sent until after an in-progress HTTP response is being written username?: HAPUsername; // username is unique to every user in the home, basically identifies an Apple Id encryption?: HAPEncryption; // created in handlePairVerifyStepOne @@ -231,6 +234,9 @@ export class HAPConnection extends EventEmitter { _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; @@ -249,7 +255,8 @@ export class HAPConnection extends EventEmitter { 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 - // this.tcpSocket.setKeepAlive(true, 5000); // TODO HAP accessory servers must not use keepalive messages, which periodically wake up iOS devices. + // "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.internalHttpServer = http.createServer(); @@ -280,36 +287,114 @@ export class HAPConnection extends EventEmitter { return this.state === HAPConnectionState.AUTHENTICATED; } - public sendEvent(aid: number, iid: number, value: Nullable): void { + public close(): void { + if (this.state >= HAPConnectionState.CLOSING) { + return; // already closed/closing + } + + 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(); + } + } + + 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!"); - if (!this.hasEventNotifications(aid, iid)) { + const eventName = aid + "." + iid; + + if (!this.registeredEvents.has(eventName)) { return; } - debug("[%s] Sending HTTP event '%s' with data: %s", this.remoteAddress, aid + "." + iid, value); - // TODO queue events!!! - const data = Buffer.from(JSON.stringify({ - characteristics: [{ + if (immediateDelivery) { + // some characteristics are required to deliver notifications immediately + this.writeEventNotification({ + characteristics: [{ + aid: aid, + iid: iid, + value: value, + }], + }); + } else { + this.queuedEvents.set(eventName, { aid: aid, iid: iid, value: value, - }], - }), "utf8"); + }); + 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); + } + } + } + + 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(); + } + + private writePendingEventNotifications(): void { + for (const buffer of this.pendingEventData) { + this.tcpSocket.write(this.encrypt(buffer)); + } + this.pendingEventData.splice(0, this.pendingEventData.length); + } + + private writeQueuedEventNotifications(): void { + if (this.queuedEvents.size === 0 || this.eventsTimer) { + return; // don't send empty event notifications or if there is a timeout running + } + 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.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: " + data.length + "\r\n" + + "Content-Length: " + dataBuffer.length + "\r\n" + "\r\n", "utf8" // buffer encoding ); - const buffer = Buffer.concat([header, data]); - + const buffer = Buffer.concat([header, dataBuffer]); if (this.handlingRequest) { - // it is important to not encrypt the queued data, otherwise nonce counter will get incorrectly incremented + // 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)); @@ -332,6 +417,10 @@ export class HAPConnection extends EventEmitter { 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. @@ -382,40 +471,6 @@ export class HAPConnection extends EventEmitter { }); } - public close(): void { - if (this.state >= HAPConnectionState.CLOSING) { - return; // already closed/closing - } - - 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(); - } - } - - private sendPendingEvents(): void { - if (this.pendingEventData.length === 0) { - return; - } - - debug("[%s] Writing pending HTTP event data", this.remoteAddress); - - for (const event of this.pendingEventData) { - this.tcpSocket.write(this.encrypt(event)); - } - - this.pendingEventData.splice(0, this.pendingEventData.length); - } - /** * 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. @@ -476,7 +531,8 @@ export class HAPConnection extends EventEmitter { if (this.state === HAPConnectionState.TO_BE_TEARED_DOWN) { setTimeout(() => this.close(), 10); } else if (this.state < HAPConnectionState.TO_BE_TEARED_DOWN) { - this.sendPendingEvents(); + this.writePendingEventNotifications(); + this.writeQueuedEventNotifications(); } } From 45b63a767c26fb90515a9330c32ae7e02130790b Mon Sep 17 00:00:00 2001 From: Supereg Date: Mon, 5 Oct 2020 18:24:07 +0200 Subject: [PATCH 25/70] Some code cleanup regarding the HAPEncryption object --- src/lib/HAPServer.ts | 59 ++++++-------------------- src/lib/datastream/DataStreamServer.ts | 4 +- src/lib/util/eventedhttp.ts | 38 +++++++++++++++-- src/lib/util/hapCrypto.ts | 26 ++++++------ 4 files changed, 61 insertions(+), 66 deletions(-) diff --git a/src/lib/HAPServer.ts b/src/lib/HAPServer.ts index b0c58fc17..08262defd 100644 --- a/src/lib/HAPServer.ts +++ b/src/lib/HAPServer.ts @@ -17,7 +17,13 @@ import { } from "../internal-types"; import { CharacteristicValue, Nullable, VoidCallback } from '../types'; import { PairingInformation, PermissionTypes } from "./model/AccessoryInfo"; -import { EventedHTTPServer, EventedHTTPServerEvent, HAPConnection, HAPUsername } from './util/eventedhttp'; +import { + EventedHTTPServer, + EventedHTTPServerEvent, + HAPConnection, + HAPEncryption, + HAPUsername +} from './util/eventedhttp'; import * as hapCrypto from './util/hapCrypto'; import { once } from './util/once'; import * as tlv from './util/tlv'; @@ -568,15 +574,9 @@ export class HAPServer extends EventEmitter { 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); - // store keys in a new instance of HAPEncryption - const 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 - connection.encryption = enc; + + connection.encryption = new HAPEncryption(clientPublicKey, secretKey, publicKey, sharedSec, outputKey); + // compose the response data in TLV format const message = tlv.encode(TLVValues.USERNAME, usernameData, TLVValues.PROOF, serverProof); @@ -600,7 +600,7 @@ export class HAPServer extends EventEmitter { 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(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); @@ -640,8 +640,8 @@ export class HAPServer extends EventEmitter { 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.sharedSec, infoRead, 32); - enc.controllerToAccessoryKey = hapCrypto.HKDF("sha512", encSalt, enc.sharedSec, infoWrite, 32); + 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 connection.connectionAuthenticated(clientUsername.toString()); @@ -921,36 +921,3 @@ export class HAPServer extends EventEmitter { } } - - -/** - * Simple struct to hold vars needed to support HAP encryption. - */ - -export class HAPEncryption { - - clientPublicKey: Buffer; - secretKey: Buffer; - publicKey: Buffer; - sharedSec: Buffer; - hkdfPairEncKey: Buffer; - accessoryToControllerCount: { value: number; }; - controllerToAccessoryCount: { value: number; }; - accessoryToControllerKey: Buffer; - controllerToAccessoryKey: Buffer; - extraInfo: Record; - - constructor() { - // initialize member vars with null-object values - this.clientPublicKey = Buffer.alloc(0); - this.secretKey = Buffer.alloc(0); - this.publicKey = Buffer.alloc(0); - this.sharedSec = Buffer.alloc(0); - this.hkdfPairEncKey = Buffer.alloc(0); - this.accessoryToControllerCount = { value: 0 }; - this.controllerToAccessoryCount = { value: 0 }; - this.accessoryToControllerKey = Buffer.alloc(0); - this.controllerToAccessoryKey = Buffer.alloc(0); - this.extraInfo = {}; - } -} diff --git a/src/lib/datastream/DataStreamServer.ts b/src/lib/datastream/DataStreamServer.ts index 8931aabf3..2d77b5c3b 100644 --- a/src/lib/datastream/DataStreamServer.ts +++ b/src/lib/datastream/DataStreamServer.ts @@ -223,8 +223,8 @@ export class DataStreamServer extends EventEmitter { / const accessoryKeySalt = crypto.randomBytes(32); const salt = Buffer.concat([controllerKeySalt, accessoryKeySalt]); - const accessoryToControllerEncryptionKey = hapCrypto.HKDF("sha512", salt, connection.encryption!.sharedSec, DataStreamServer.accessoryToControllerInfo, 32); - const controllerToAccessoryEncryptionKey = hapCrypto.HKDF("sha512", salt, connection.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 = { connection: connection, diff --git a/src/lib/util/eventedhttp.ts b/src/lib/util/eventedhttp.ts index c99ec82f7..582cdb68a 100644 --- a/src/lib/util/eventedhttp.ts +++ b/src/lib/util/eventedhttp.ts @@ -8,7 +8,6 @@ import net, { AddressInfo, Socket } from 'net'; import os from "os"; import { CharacteristicEventNotification, EventNotification } from "../../internal-types"; import { CharacteristicValue, Nullable, SessionIdentifier } from '../../types'; -import { HAPEncryption } from "../HAPServer"; import * as hapCrypto from "./hapCrypto"; import { getOSLoopbackAddress } from "./net-utils"; import * as uuid from './uuid'; @@ -19,6 +18,37 @@ const debug = createDebug('HAP-NodeJS:EventedHTTPServer'); 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; + controllerToAccessoryKey: Buffer; + + 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; + + this.accessoryToControllerKey = Buffer.alloc(0); + this.controllerToAccessoryKey = Buffer.alloc(0); + } +} + export const enum EventedHTTPServerEvent { LISTENING = 'listening', CONNECTION_OPENED = "connection-opened", @@ -426,8 +456,8 @@ export class HAPConnection extends EventEmitter { // 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.value > 0) { - return hapCrypto.layerEncrypt(data, this.encryption.accessoryToControllerCount, this.encryption.accessoryToControllerKey); + 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 } @@ -435,7 +465,7 @@ export class HAPConnection extends EventEmitter { 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.controllerToAccessoryCount, this.encryption.controllerToAccessoryKey, this.encryption.extraInfo); + return hapCrypto.layerDecrypt(data, this.encryption); } return data; // otherwise we don't decrypt and return plaintext } diff --git a/src/lib/util/hapCrypto.ts b/src/lib/util/hapCrypto.ts index eb55df041..c5f926330 100644 --- a/src/lib/util/hapCrypto.ts +++ b/src/lib/util/hapCrypto.ts @@ -2,6 +2,7 @@ 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 + ". " + @@ -26,7 +27,7 @@ type Count = { value: any; } -export function layerEncrypt(data: Buffer, count: Count, key: Buffer) { +export function layerEncrypt(data: Buffer, encryption: HAPEncryption) { let result = Buffer.alloc(0); const total = data.length; for (let offset = 0; offset < total; ) { @@ -35,9 +36,9 @@ export function layerEncrypt(data: Buffer, count: Count, key: Buffer) { leLength.writeUInt16LE(length,0); const nonce = Buffer.alloc(8); - writeUInt64LE(count.value++, nonce, 0); + 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]); @@ -45,10 +46,10 @@ 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; } let result = Buffer.alloc(0); @@ -58,18 +59,15 @@ export function layerDecrypt(packet: Buffer, count: Count, key: Buffer, extraInf const realDataLength = packet.slice(offset, offset + 2).readUInt16LE(0); const availableDataLength = total - offset - 2 - 16; - if (realDataLength > availableDataLength) { - // Fragmented packet - extraInfo.leftoverData = packet.slice(offset); + if (realDataLength > availableDataLength) { // Fragmented packet + encryption.incompleteFrame = packet.slice(offset); break; - } else { - extraInfo.leftoverData = undefined; } const nonce = Buffer.alloc(8); - writeUInt64LE(count.value++, nonce, 0); + 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); } From 024b021696eaf8648f8ebaaf599627823dbc8152 Mon Sep 17 00:00:00 2001 From: Supereg Date: Mon, 5 Oct 2020 18:38:23 +0200 Subject: [PATCH 26/70] Minor code fixes in the AccessoryLoader --- src/lib/AccessoryLoader.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/lib/AccessoryLoader.ts b/src/lib/AccessoryLoader.ts index ca64b8909..a57b94dcd 100644 --- a/src/lib/AccessoryLoader.ts +++ b/src/lib/AccessoryLoader.ts @@ -22,7 +22,7 @@ const debug = createDebug('HAP-NodeJS:AccessoryLoader'); export function loadDirectory(dir: string): Accessory[] { // exported accessory objects loaded from this dir - let accessories: Accessory[] = []; + let accessories: unknown[] = []; fs.readdirSync(dir).forEach((file) => { const suffix = file.split('_').pop(); @@ -147,14 +147,6 @@ 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); - }; - // monkey-patch legacy "locals" property which used to exist. // @ts-ignore characteristic.locals = json.locals; From 3673c8dc748dd5504589c628efd7702b2065e821 Mon Sep 17 00:00:00 2001 From: Supereg Date: Mon, 5 Oct 2020 19:22:03 +0200 Subject: [PATCH 27/70] Adding support for additional authorization data to be checked by custom handler --- src/internal-types.ts | 5 ++- src/lib/Accessory.ts | 30 ++++++++++++++++-- src/lib/Characteristic.ts | 66 +++++++++++++++++++++++++++++---------- 3 files changed, 81 insertions(+), 20 deletions(-) diff --git a/src/internal-types.ts b/src/internal-types.ts index 390701fa6..1db7370f8 100644 --- a/src/internal-types.ts +++ b/src/internal-types.ts @@ -59,7 +59,10 @@ export interface CharacteristicWrite { value?: CharacteristicValue, ev?: boolean, // enable/disable event notifications for the accessory - authData?: string, // base64 encoded + 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 } diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index ed1dfebca..8e1855bee 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -1440,18 +1440,18 @@ export class Accessory extends EventEmitter { } } - debug('[%s] %s Characteristic "%s" for events', this.displayName, data.ev ? "Registering" : "Unregistering", characteristic.displayName); // TODO maybe remove this event message? - if (data.ev && !connection.hasEventNotifications(data.aid, data.iid)) { connection.enableEventNotifications(data.aid, data.iid); characteristic.subscribe(); evResponse = true; + debug('[%s] %s Characteristic "%s" for events', this.displayName, data.ev ? "Registering" : "Unregistering", characteristic.displayName); // TODO maybe remove this event message? } if (!data.ev && connection.hasEventNotifications(data.aid, data.iid)) { characteristic.unsubscribe(); connection.disableEventNotifications(data.aid, data.iid); evResponse = false; + debug('[%s] %s Characteristic "%s" for events', this.displayName, data.ev ? "Registering" : "Unregistering", characteristic.displayName); // TODO maybe remove this event message? } } @@ -1492,6 +1492,32 @@ export class Accessory extends EventEmitter { } } + 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 + + 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; + } + + if (!allowWrite) { + characteristics.push({ + aid: data.aid, + iid: data.iid, + status: Status.INSUFFICIENT_AUTHORIZATION, + }); + + if (characteristics.length === writeRequest.characteristics.length) { + callback(response); + } + continue; + } + } + 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); characteristics.push({ diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts index 6d1371ba4..1cbe2e4d2 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -100,6 +100,8 @@ export const enum CharacteristicEventTypes { export type CharacteristicGetCallback = (status?: Status | null | Error, value?: Nullable) => void; export type CharacteristicSetCallback = (error?: Status | null | Error, writeResponse?: 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; @@ -397,7 +399,12 @@ export class Characteristic extends EventEmitter { status: Status = Status.SUCCESS; eventOnlyCharacteristic: boolean = false; props: CharacteristicProps; + private subscriptions: number = 0; + /** + * @internal + */ + additionalAuthorizationHandler?: AdditionalAuthorizationHandler; public constructor(public displayName: string, public UUID: string, props?: CharacteristicProps) { super(); @@ -434,6 +441,8 @@ export class Characteristic extends EventEmitter { * } */ public setProps(props: Partial): Characteristic { + // TODO calling setProps after publish doesn't lead to a increment in the current configuration number + for (let key in (props || {})) if (Object.prototype.hasOwnProperty.call(props, key)) { // @ts-ignore @@ -452,25 +461,26 @@ export class Characteristic extends EventEmitter { } /** - * @internal + * 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. */ - subscribe(): void { - if (this.subscriptions === 0) { - this.emit(CharacteristicEventTypes.SUBSCRIBE); - } - this.subscriptions++; - } - - /** - * @internal - */ - unsubscribe(): void { - const wasOne = this.subscriptions === 1; - this.subscriptions--; - this.subscriptions = Math.max(this.subscriptions, 0); - if (wasOne) { - this.emit(CharacteristicEventTypes.UNSUBSCRIBE); + public setupAdditionalAuthorization(handler: AdditionalAuthorizationHandler): void { + if (!this.props.perms.includes(Perms.ADDITIONAL_AUTHORIZATION)) { + this.props.perms.push(Perms.ADDITIONAL_AUTHORIZATION); } + this.additionalAuthorizationHandler = handler; } /** @@ -623,6 +633,28 @@ export class Characteristic extends EventEmitter { } } + /** + * @internal + */ + subscribe(): void { + if (this.subscriptions === 0) { + this.emit(CharacteristicEventTypes.SUBSCRIBE); + } + this.subscriptions++; + } + + /** + * @internal + */ + unsubscribe(): void { + const wasOne = this.subscriptions === 1; + this.subscriptions--; + this.subscriptions = Math.max(this.subscriptions, 0); + if (wasOne) { + this.emit(CharacteristicEventTypes.UNSUBSCRIBE); + } + } + protected getDefaultValue(): Nullable { switch (this.props.format) { case Formats.BOOL: From 73bf81cbb71064e67022c5c3b83c4bee18e7943c Mon Sep 17 00:00:00 2001 From: Supereg Date: Tue, 6 Oct 2020 00:27:56 +0200 Subject: [PATCH 28/70] Adding timeouts to SET, GET and image request handlers. Some minor code cleanup. --- src/internal-types.ts | 48 +- src/lib/Accessory.ts | 655 ++++++++++++++----------- src/lib/Characteristic.ts | 146 +++--- src/lib/HAPServer.ts | 2 +- src/lib/Service.ts | 2 +- src/lib/controller/CameraController.ts | 620 ++++++++++++----------- src/lib/gen/HomeKit.ts | 1 - 7 files changed, 824 insertions(+), 650 deletions(-) diff --git a/src/internal-types.ts b/src/internal-types.ts index 1db7370f8..11e512dc1 100644 --- a/src/internal-types.ts +++ b/src/internal-types.ts @@ -15,9 +15,7 @@ export interface CharacteristicsReadRequest { includeEvent: boolean, } -export interface CharacteristicReadDataValue { - aid: number, - iid: number, +export interface PartialCharacteristicReadDataValue { value: CharacteristicValue | null, status?: Status.SUCCESS, @@ -40,16 +38,25 @@ export interface CharacteristicReadDataValue { ev?: boolean, } -export interface CharacteristicReadError { +export interface PartialCharacteristicReadError { + status: Status, +} + +export interface CharacteristicReadDataValue extends PartialCharacteristicReadDataValue { aid: number, iid: number, - status: Status, } -export type CharacteristicsReadData = CharacteristicReadDataValue | CharacteristicReadError; +export interface CharacteristicReadError extends PartialCharacteristicReadError { + aid: number, + iid: number, +} + +export type PartialCharacteristicReadData = PartialCharacteristicReadDataValue | PartialCharacteristicReadError; +export type CharacteristicReadData = CharacteristicReadDataValue | CharacteristicReadError; export interface CharacteristicsReadResponse { - characteristics: CharacteristicsReadData[], + characteristics: CharacteristicReadData[], } export interface CharacteristicWrite { @@ -72,29 +79,34 @@ export interface CharacteristicsWriteRequest { pid?: number } -export interface CharacteristicWriteDataValue { - aid: number, - iid: number, +export interface PartialCharacteristicWriteDataValue { value?: CharacteristicValue | null, - - // event - ev?: boolean, + ev?: boolean, // event status?: Status.SUCCESS, } -export interface CharacteristicWriteError { - aid: number, - iid: number, +export interface PartialCharacteristicWriteError { status: Status, value?: undefined, // defined to make things easier } -export type CharacteristicsWriteData = CharacteristicWriteDataValue | CharacteristicWriteError; +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: CharacteristicsWriteData[], + characteristics: CharacteristicWriteData[], } export type PrepareWriteRequest = { diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index 8e1855bee..565717401 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -2,13 +2,19 @@ import { MDNSServerOptions } from "@homebridge/ciao"; import assert from "assert"; import crypto from 'crypto'; import createDebug from 'debug'; +import { EventEmitter } from "events"; import net from "net"; import { - CharacteristicsReadData, + CharacteristicId, + CharacteristicReadData, CharacteristicsReadRequest, - CharacteristicsWriteData, + CharacteristicsReadResponse, CharacteristicsWriteRequest, CharacteristicsWriteResponse, + CharacteristicWrite, + CharacteristicWriteData, + PartialCharacteristicReadData, + PartialCharacteristicWriteData, ResourceRequest, ResourceRequestType } from "../internal-types"; @@ -36,7 +42,6 @@ import { ControllerType, isSerializableController, } from "./controller"; -import { EventEmitter } from './EventEmitter'; import * as HomeKitTypes from "./gen"; import { CameraEventRecordingManagement, @@ -75,9 +80,10 @@ import { ServiceId } from './Service'; import { clone } from './util/clone'; -import { HAPConnection, HAPUsername } from "./util/eventedhttp"; +import { EventName, HAPConnection, HAPUsername } from "./util/eventedhttp"; import * as uuid from "./util/uuid"; import { toShortForm } from "./util/uuid"; +import Timeout = NodeJS.Timeout; const debug = createDebug('HAP-NodeJS:Accessory'); const MAX_ACCESSORIES = 149; // Maximum number of bridged accessories per bridge. @@ -149,31 +155,13 @@ export interface ControllerContext { serviceMap: ControllerServiceMap, } - -export const enum AccessoryEventTypes { - IDENTIFY = "identify", - LISTENING = "listening", - SERVICE_CONFIGURATION_CHANGE = "service-configurationChange", - SERVICE_CHARACTERISTIC_CHANGE = "service-characteristic-change", - PAIRED = "paired", - UNPAIRED = "unpaired", -} - -type Events = { - identify: (paired:boolean, cb: VoidCallback) => void; - listening: (port: number, hostname: string) => void; - "service-configurationChange": VoidCallback; - "service-characteristic-change": (change: AccessoryCharacteristicChange) => 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", } -// noinspection JSUnusedGlobalSymbols -/** - * @deprecated Use AccessoryEventTypes instead - */ -export type EventAccessory = "identify" | "listening" | "service-configurationChange" | "service-characteristic-change"; - /** * @deprecated */ @@ -262,6 +250,48 @@ const enum WriteRequestState { TIMED_WRITE_REJECTED } +// noinspection JSUnusedGlobalSymbols +/** + * @deprecated Use AccessoryEventTypes instead + */ +export type EventAccessory = "identify" | "listening" | "service-configurationChange" | "service-characteristic-change"; + +export const enum AccessoryEventTypes { + IDENTIFY = "identify", + LISTENING = "listening", + SERVICE_CONFIGURATION_CHANGE = "service-configurationChange", + SERVICE_CHARACTERISTIC_CHANGE = "service-characteristic-change", + PAIRED = "paired", + UNPAIRED = "unpaired", + + CHARACTERISTIC_WARNING = "characteristic-warning", +} + +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: "paired", listener: () => void): this; + on(event: "unpaired", listener: () => void): this; + + on(event: "characteristic-warning", listener: (type: CharacteristicWarningType, iid: number) => 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: "paired"): boolean; + emit(event: "unpaired"): boolean; + + emit(event: "characteristic-warning", type: CharacteristicWarningType, iid: number): boolean; +} + /** * Accessory is a virtual HomeKit device. It can publish an associated HAP server for iOS devices to communicate * with - or it can run behind another "Bridge" Accessory server. @@ -282,7 +312,7 @@ const enum WriteRequestState { * @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. @@ -386,7 +416,7 @@ export class Accessory extends EventEmitter { if (!this.bridged) { this._updateConfiguration(); } 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) => { @@ -405,7 +435,7 @@ export class Accessory extends EventEmitter { if (!this.bridged) { this._updateConfiguration(); } else { - this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({accessory:this, service:service})); + this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, { service: service }); } }); @@ -443,7 +473,7 @@ export class Accessory extends EventEmitter { if (!this.bridged) { this._updateConfiguration(); } else { - this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({accessory:this, service:service})); + this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, { service: service }); for (const accessory of this.bridgedAccessories) { accessory.removeLinkedService(service); @@ -569,7 +599,7 @@ 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(); @@ -603,18 +633,22 @@ export class Accessory extends EventEmitter { } } - getBridgedAccessoryByAID = (aid: number) => { - for (let index in this.bridgedAccessories) { - const accessory = this.bridgedAccessories[index]; - if (accessory.aid === aid) return accessory; + protected getAccessoryByAID(aid: number): Accessory | undefined { + if (aid === 1) { + return this; } - } - private findCharacteristic(aid: number, iid: number): Characteristic | undefined { - // if aid === 1, the accessory is us (because we are the server), otherwise find it among our bridged - // accessories (if any) - const accessory = (aid === 1) ? this : this.getBridgedAccessoryByAID(aid); + for (const accessory of this.bridgedAccessories) { + if (accessory.aid === aid) { + return accessory; + } + } + + return undefined; + } + protected findCharacteristic(aid: number, iid: number): Characteristic | undefined { + const accessory = this.getAccessoryByAID(aid); return accessory && accessory.getCharacteristicByIID(iid); } @@ -641,7 +675,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!"); } @@ -689,7 +723,7 @@ export class Accessory extends EventEmitter { * * @param controllerConstructor {Controller | ControllerConstructor} */ - configureController(controllerConstructor: Controller | ControllerConstructor) { // TODO add support to remove controllers + 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; @@ -943,7 +977,7 @@ export class Accessory extends EventEmitter { /** * Returns a JSON representation of this Accessory suitable for delivering to HAP clients. */ - toHAP = (opt?: ToHAPOptions) => { + private toHAP(opt?: ToHAPOptions) { const servicesHAP = []; @@ -1098,7 +1132,7 @@ export class Accessory extends EventEmitter { 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.IDENTIFY, this.identificationRequest.bind(this, false)); 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)); @@ -1107,7 +1141,7 @@ export class Accessory extends EventEmitter { 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)); + this._server.on(HAPServerEventTypes.REQUEST_RESOURCE, this.handleResource.bind(this)); this._server.listen(info.port, parsed.serverAddress); } @@ -1130,7 +1164,7 @@ export class Accessory extends EventEmitter { this.removeAllListeners(); } - unpublish = () => { + public unpublish(): void { if (this._server) { this._server.stop(); this._server = undefined; @@ -1142,7 +1176,7 @@ export class Accessory extends EventEmitter { } } - _updateConfiguration = () => { + private _updateConfiguration(): void { 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 @@ -1174,10 +1208,6 @@ export class Accessory extends EventEmitter { this.emit(AccessoryEventTypes.LISTENING, port, hostname); } - private handleIdentify(callback: IdentifyCallback) { - this.identificationRequest(false, callback); - } - private handleInitialPairSetupFinished(username: string, publicKey: Buffer, callback: PairCallback): void { debug("[%s] Paired with client %s", this.displayName, username); @@ -1273,82 +1303,145 @@ export class Accessory extends EventEmitter { } private handleGetCharacteristics(connection: HAPConnection, request: CharacteristicsReadRequest, callback: ReadCharacteristicsCallback): void { - const promises: Promise[] = []; + const characteristics: CharacteristicReadData[] = []; + const response: CharacteristicsReadResponse = { characteristics: characteristics }; + + const missingCharacteristics: Set = new Set(); + let timeout: Timeout | undefined = setTimeout(() => { + for (const id of missingCharacteristics) { + const split = id.split("."); + const aid = parseInt(split[0]); + const iid = parseInt(split[1]); + + const accessory = this.getAccessoryByAID(aid); + let emitted = accessory?.emit(AccessoryEventTypes.CHARACTERISTIC_WARNING, CharacteristicWarningType.SLOW_READ, iid); + if (!emitted) { + const characteristic = accessory?.getCharacteristicByIID(iid); + console.log(`The read handler for the characteristic '${characteristic?.displayName || iid}' on the accessory '${accessory?.displayName}' was slow to respond!`); + } + } + + // 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; + + for (const id of missingCharacteristics) { + const split = id.split("."); + const aid = parseInt(split[0]); + const iid = parseInt(split[1]); + + const accessory = this.getAccessoryByAID(aid); + let emitted = accessory?.emit(AccessoryEventTypes.CHARACTERISTIC_WARNING, CharacteristicWarningType.TIMEOUT_READ, iid); + if (!emitted) { + const characteristic = accessory?.getCharacteristicByIID(iid); + console.log("The read handler for the characteristic '" + (characteristic?.displayName || iid) + "' on the accessory '" + accessory?.displayName + + "' didn't respond at all!. Please check that you properly call the callback!"); + } + + characteristics.push({ + aid: aid, + iid: iid, + status: Status.OPERATION_TIMED_OUT, + }); + } + missingCharacteristics.clear(); + + callback(response); + }, 7000); + }, 3000); for (const id of request.ids) { - const characteristic = this.findCharacteristic(id.aid, id.iid); + const name = id.aid + "." + id.iid; + missingCharacteristics.add(name); - if (!characteristic) { - debug('[%s] Could not find a Characteristic with aid of %s and iid of %s', this.displayName, id.aid, id.iid); + this.handleCharacteristicRead(connection, id, request).then(value => { + missingCharacteristics.delete(name); + characteristics.push({ + aid: id.aid, + iid: id.iid, + ...value, + }); + }, reason => { // this error block is only called if hap-nodejs itself messed up + console.error(`[${this.displayName}] Read request for characteristic ${id} encountered an error: ${reason.stack}`) - promises.push(Promise.resolve({ + missingCharacteristics.delete(name); + characteristics.push({ aid: id.aid, iid: id.iid, - status: Status.INVALID_VALUE_IN_REQUEST, - })); - continue; - } + status: Status.SERVICE_COMMUNICATION_FAILURE, + }); + }).then(() => { + if (!timeout) { + return; // if timeout is undefined, response was already sent out + } - 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) + if (missingCharacteristics.size === 0) { + if (timeout) { + clearTimeout(timeout); + timeout = undefined; + } + callback(response); } + }); + } + } - if (!verifiable || !this._accessoryInfo!.hasAdminPermissions(connection.username!)) { + private async handleCharacteristicRead(connection: HAPConnection, id: CharacteristicId, request: CharacteristicsReadRequest): Promise { + const characteristic = this.findCharacteristic(id.aid, id.iid); - promises.push(Promise.resolve({ - aid: id.aid, - iid: id.iid, - status: Status.INSUFFICIENT_PRIVILEGES, - })); - continue; - } + 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: Status.INVALID_VALUE_IN_REQUEST }; + } + + 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: Status.WRITE_ONLY_CHARACTERISTIC }; + } + + 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) } - // TODO introduce a timeout on those values? - const promise = characteristic.handleGetRequest(connection).then(value => { - debug('[%s] Got Characteristic "%s" value: %s', this.displayName, characteristic!.displayName, value); + if (!verifiable || !this._accessoryInfo!.hasAdminPermissions(connection.username!)) { + return { status: Status.INSUFFICIENT_PRIVILEGES }; + } + } - const data: CharacteristicsReadData = { - aid: id.aid, - iid: id.iid, - value: value == undefined? null: value, - }; + return characteristic.handleGetRequest(connection).then(value => { + debug('[%s] Got Characteristic "%s" value: %s', this.displayName, characteristic!.displayName, value); - 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, HomeKitTypes.BASE_UUID); - } - if (request.includeEvent) { - data.ev = connection.hasEventNotifications(id.aid, id.iid); - } + const data: PartialCharacteristicReadData = { + value: value == undefined? null: value, + }; - return data; - }, (reason: Status) => { - // @ts-expect-error - debug('[%s] Error getting value for characteristic "%s": %s', this.displayName, characteristic.displayName, Status[reason]); - return { - aid: id.aid, - iid: id.iid, - status: reason, - }; - }); - promises.push(promise); - } + 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, HomeKitTypes.BASE_UUID); + } + if (request.includeEvent) { + data.ev = connection.hasEventNotifications(id.aid, id.iid); + } - Promise.all(promises).then(value => callback({ characteristics: value })); + return data; + }, (reason: Status) => { + // @ts-expect-error + debug('[%s] Error getting value for characteristic "%s": %s', this.displayName, characteristic.displayName, Status[reason]); + return { status: reason }; + }); } private handleSetCharacteristics(connection: HAPConnection, writeRequest: CharacteristicsWriteRequest, callback: WriteCharacteristicsCallback): void { @@ -1369,227 +1462,207 @@ export class Accessory extends EventEmitter { } } - const characteristics: CharacteristicsWriteData[] = []; + const characteristics: CharacteristicWriteData[] = []; const response: CharacteristicsWriteResponse = { characteristics: characteristics }; - for (const data of writeRequest.characteristics) { - const characteristic = this.findCharacteristic(data.aid, data.iid); - let evResponse: boolean | undefined = undefined; + const missingCharacteristics: Set = new Set(); + let timeout: Timeout | undefined = setTimeout(() => { + for (const id of missingCharacteristics) { + const split = id.split("."); + const aid = parseInt(split[0]); + const iid = parseInt(split[1]); + + const accessory = this.getAccessoryByAID(aid); + let emitted = accessory?.emit(AccessoryEventTypes.CHARACTERISTIC_WARNING, CharacteristicWarningType.SLOW_WRITE, iid); + if (!emitted) { + const characteristic = accessory?.getCharacteristicByIID(iid); + console.log(`The write handler for the characteristic '${characteristic?.displayName || iid}' on the accessory '${accessory?.displayName}' was slow to respond!`); + } + } + + // 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; + + for (const id of missingCharacteristics) { + const split = id.split("."); + const aid = parseInt(split[0]); + const iid = parseInt(split[1]); + + const accessory = this.getAccessoryByAID(aid); + let emitted = accessory?.emit(AccessoryEventTypes.CHARACTERISTIC_WARNING, CharacteristicWarningType.TIMEOUT_WRITE, iid); + if (!emitted) { + const characteristic = accessory?.getCharacteristicByIID(iid); + console.log("The write handler for the characteristic '" + (characteristic?.displayName || iid) + "' on the accessory '" + accessory?.displayName + + "' didn't respond at all!. Please check that you properly call the callback!"); + } + + characteristics.push({ + aid: aid, + iid: iid, + status: Status.OPERATION_TIMED_OUT, + }); + } + missingCharacteristics.clear(); - if (!characteristic) { - debug('[%s] Could not find a Characteristic with aid of %s and iid of %s', this.displayName, data.aid, data.iid); + callback(response); + }, 7000); + }, 3000); + for (const data of writeRequest.characteristics) { + const id = data.aid + "." + data.iid; + missingCharacteristics.add(id); + + this.handleCharacteristicWrite(connection, data, writeState).then(value => { + missingCharacteristics.delete(id); characteristics.push({ aid: data.aid, iid: data.iid, - status: Status.INVALID_VALUE_IN_REQUEST, + ...value, }); + }, reason => { // this error block is only called if hap-nodejs itself messed up + console.error(`[${this.displayName}] Write request for characteristic ${id} encountered an error: ${reason.stack}`) - if (characteristics.length === writeRequest.characteristics.length) { - callback(response); - } - continue; - } - - if (writeState === WriteRequestState.TIMED_WRITE_REJECTED) { + missingCharacteristics.delete(id); characteristics.push({ aid: data.aid, iid: data.iid, - status: Status.INVALID_VALUE_IN_REQUEST, + status: Status.SERVICE_COMMUNICATION_FAILURE, }); - - if (characteristics.length === writeRequest.characteristics.length) { - callback(response); + }).then(() => { + if (!timeout) { + return; // if timeout is undefined, response was already sent out } - continue; - } - - 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); - characteristics.push({ - aid: data.aid, - iid: data.iid, - status: Status.NOTIFICATION_NOT_SUPPORTED, - }); - if (characteristics.length === writeRequest.characteristics.length) { - callback(response); + if (missingCharacteristics.size === 0) { // if everything returned send the response + if (timeout) { + clearTimeout(timeout); + timeout = undefined; } - continue; + callback(response); } + }) + } + } - 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 (!verifiable || !this._accessoryInfo!.hasAdminPermissions(connection.username!)) { - characteristics.push({ - aid: data.aid, - iid: data.iid, - status: Status.INSUFFICIENT_PRIVILEGES, - }); + private async handleCharacteristicWrite(connection: HAPConnection, data: CharacteristicWrite, writeState: WriteRequestState): Promise { + const characteristic = this.findCharacteristic(data.aid, data.iid); + let evResponse: boolean | undefined = undefined; - if (characteristics.length === writeRequest.characteristics.length) { - callback(response); - } - continue; - } - } + 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: Status.INVALID_VALUE_IN_REQUEST }; + } - if (data.ev && !connection.hasEventNotifications(data.aid, data.iid)) { - connection.enableEventNotifications(data.aid, data.iid); - characteristic.subscribe(); - evResponse = true; - debug('[%s] %s Characteristic "%s" for events', this.displayName, data.ev ? "Registering" : "Unregistering", characteristic.displayName); // TODO maybe remove this event message? - } + if (writeState === WriteRequestState.TIMED_WRITE_REJECTED) { + return { status: Status.INVALID_VALUE_IN_REQUEST }; + } - if (!data.ev && connection.hasEventNotifications(data.aid, data.iid)) { - characteristic.unsubscribe(); - connection.disableEventNotifications(data.aid, data.iid); - evResponse = false; - debug('[%s] %s Characteristic "%s" for events', this.displayName, data.ev ? "Registering" : "Unregistering", characteristic.displayName); // TODO maybe remove this event message? - } + 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: Status.NOTIFICATION_NOT_SUPPORTED }; } - if (data.value != undefined) { - // TODO move from here - 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); - characteristics.push({ - aid: data.aid, - iid: data.iid, - status: Status.READ_ONLY_CHARACTERISTIC, - }); - - if (characteristics.length === writeRequest.characteristics.length) { - callback(response); - } - continue; + 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 (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 (!verifiable || !this._accessoryInfo!.hasAdminPermissions(connection.username!)) { - characteristics.push({ - aid: data.aid, - iid: data.iid, - status: Status.INSUFFICIENT_PRIVILEGES, - }); - - if (characteristics.length === writeRequest.characteristics.length) { - callback(response); - } - continue; - } + if (!verifiable || !this._accessoryInfo!.hasAdminPermissions(connection.username!)) { + return { status: Status.INSUFFICIENT_PRIVILEGES }; } + } - 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 (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); + } - 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; - } + 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 (!allowWrite) { - characteristics.push({ - aid: data.aid, - iid: data.iid, - status: Status.INSUFFICIENT_AUTHORIZATION, - }); + 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: Status.READ_ONLY_CHARACTERISTIC }; + } - if (characteristics.length === writeRequest.characteristics.length) { - callback(response); - } - continue; - } + 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, data.aid, data.iid); - characteristics.push({ - aid: data.aid, - iid: data.iid, - status: Status.INVALID_VALUE_IN_REQUEST, - }); - - if (characteristics.length === writeRequest.characteristics.length) { - callback(response); - } - continue; + if (!verifiable || !this._accessoryInfo!.hasAdminPermissions(connection.username!)) { + return { status: Status.INSUFFICIENT_PRIVILEGES }; } + } - debug('[%s] Setting Characteristic "%s" to value %s', this.displayName, characteristic.displayName, data.value); - - characteristic.handleSetRequest(data.value, connection).then(value => { - characteristics.push({ - aid: data.aid, - iid: data.iid, + 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 - value: data.r && value? value: undefined, // if write response is requests and value is provided, return that + 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; + } - ev: evResponse, - }) + if (!allowWrite) { + return { status: Status.INSUFFICIENT_AUTHORIZATION }; + } + } - if (characteristics.length === writeRequest.characteristics.length) { - callback(response); - } - }, (status: Status) => { - // @ts-expect-error - debug('[%s] Error setting Characteristic "%s" to value %s: ', this.displayName, characteristic.displayName, data.value, Status[status]); + 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: Status.INVALID_VALUE_IN_REQUEST }; + } - characteristics.push({ - aid: data.aid, - iid: data.iid, - status: status, - }); + 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 - if (characteristics.length === writeRequest.characteristics.length) { - callback(response); - } - }); - } else { - characteristics.push({ - aid: data.aid, - iid: data.iid, ev: evResponse, - }); + }; + }, (status: Status) => { + // @ts-expect-error + debug('[%s] Error setting Characteristic "%s" to value %s: ', this.displayName, characteristic.displayName, data.value, Status[status]); - if (characteristics.length === writeRequest.characteristics.length) { - callback(response); - } - } + return { status: status }; + }); + } else { + return { ev: evResponse }; } } - private _handleResource(data: ResourceRequest, callback: ResourceRequestCallback): void { + 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; } @@ -1599,17 +1672,10 @@ export class Accessory extends EventEmitter { return; } - controller.handleSnapshotRequest(data["image-height"], data["image-width"], (error, resource) => { - if (!resource || resource.length === 0) { - error = new Error("supplied resource was undefined/empty!"); - } - - if (error) { - debug("[%s] Error getting snapshot: %s", this._accessoryInfo?.username, error.message); - callback({ httpCode: HAPHTTPCode.OK, status: Status.SERVICE_COMMUNICATION_FAILURE }); - } else { - callback(undefined, resource); - } + controller.handleSnapshotRequest(data["image-height"], data["image-width"], accessory?.displayName).then(buffer => { + callback(undefined, buffer); + }, (status: Status) => { + callback({ httpCode: HAPHTTPCode.OK, status: status }); }); return; } @@ -1636,7 +1702,6 @@ export class Accessory extends EventEmitter { connection.clearRegisteredEvents(); } -// Called internally above when a change was detected in one of our hosted Characteristics somewhere in our hierarchy. private handleCharacteristicChange(change: AccessoryCharacteristicChange & { accessory: Accessory }): void { if (!this._server) { return; // we're not running a HAPServer, so there's no one to notify about this event @@ -1654,7 +1719,7 @@ export class Accessory extends EventEmitter { if (!this.bridged) { this._updateConfiguration(); } else { - this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({accessory:this, service })); + this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, { service: service }); } }); diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts index 1cbe2e4d2..5dc5cd7d0 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -1,3 +1,4 @@ +import createDebug from "debug"; import Decimal from 'decimal.js'; import { EventEmitter } from "events"; import { CharacteristicValue, HapCharacteristic, Nullable, ToHAPOptions, VoidCallback, } from '../types'; @@ -9,6 +10,8 @@ import { HAPConnection } from "./util/eventedhttp"; import { once } from './util/once'; import { toShortForm } from './util/uuid'; +const debug = createDebug("HAP-NodeJS:Characteristic"); + export const enum Formats { BOOL = 'bool', INT = 'int', @@ -90,7 +93,19 @@ export interface SerializedCharacteristic { } 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", CHANGE = "change", SUBSCRIBE = "subscribe", @@ -397,7 +412,6 @@ export class Characteristic extends EventEmitter { iid: Nullable = null; value: Nullable = null; status: Status = Status.SUCCESS; - eventOnlyCharacteristic: boolean = false; props: CharacteristicProps; private subscriptions: number = 0; @@ -523,9 +537,7 @@ export class Characteristic extends EventEmitter { callback(); } - if (this.eventOnlyCharacteristic || oldValue !== value) { - this.emit(CharacteristicEventTypes.CHANGE, { originator: undefined, oldValue: oldValue, newValue: value, context: context }); - } + this.emit(CharacteristicEventTypes.CHANGE, { originator: undefined, oldValue: oldValue, newValue: value, context: context }); return this; // for chaining } @@ -542,38 +554,50 @@ export class Characteristic extends EventEmitter { return Promise.reject(Status.WRITE_ONLY_CHARACTERISTIC); } - // TODO remove this thing (maybe? doorbell people should setup event handler with null returned?) - if (this.eventOnlyCharacteristic) { + if (this.UUID === Characteristic.ProgrammableSwitchEvent.UUID) { + // special workaround for event only programmable switch event, which must always return null return Promise.resolve(null); } + if (this.listeners(CharacteristicEventTypes.GET).length === 0) { return this.status? Promise.reject(this.status): Promise.resolve(this.value); } return new Promise((resolve, reject) => { - this.emit(CharacteristicEventTypes.GET, once((status?: Error | Status | null, value?: Nullable) => { - if (status) { - this.status = typeof status === "number"? status: extractHAPStatusFromError(status); - reject(this.status); - return; - } + try { + this.emit(CharacteristicEventTypes.GET, once((status?: Error | Status | null, value?: Nullable) => { + if (status) { + if (typeof status === "number") { + this.status = status; + } else { + this.status = extractHAPStatusFromError(status); + debug("[%s] Received error from get handler %s", this.displayName, status.stack); + } + reject(this.status); + return; + } - this.status = Status.SUCCESS; + this.status = Status.SUCCESS; - value = this.validateValue(value); // validateValue returns a value that has be coerced into a valid value. - if (value == null) { // null or undefined - value = this.getDefaultValue(); - } + value = this.validateValue(value); // validateValue returns a value that has be coerced into a valid value. + if (value == null) { // null or undefined + value = this.getDefaultValue(); + } - const oldValue = this.value; - this.value = value; + const oldValue = this.value; + this.value = value; - resolve(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); + 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) { + console.warn(`[${this.displayName}] Unhandled error thrown inside read handler for characteristic: ${error.stack}`); + this.status = Status.SERVICE_COMMUNICATION_FAILURE; + reject(Status.SERVICE_COMMUNICATION_FAILURE); + } }); } @@ -588,47 +612,56 @@ export class Characteristic extends EventEmitter { * @internal */ handleSetRequest(value: CharacteristicValue, connection: HAPConnection): Promise { + if (!this.props.perms.includes(Perms.PAIRED_WRITE)) { // check if we are allowed to write to this characteristic + return Promise.reject(Status.READ_ONLY_CHARACTERISTIC); + } + this.status = Status.SUCCESS; - // TODO return proper error code if incoming value is not valid! + // TODO return proper hap status code if incoming value is not valid! value = this.validateValue(value)!; // validateValue returns a value that has be coerced into a valid value. const oldValue = this.value; if (this.listeners(CharacteristicEventTypes.SET).length === 0) { this.value = value; - if (this.eventOnlyCharacteristic || oldValue !== value) { - const change: CharacteristicChange = { - oldValue: oldValue as CharacteristicValue, - newValue: value as CharacteristicValue, - context: undefined, - } - this.emit(CharacteristicEventTypes.CHANGE, change); - } + this.emit(CharacteristicEventTypes.CHANGE, { originator: connection, oldValue: oldValue, newValue: value }); return Promise.resolve(); } else { return new Promise((resolve, reject) => { - this.emit(CharacteristicEventTypes.SET, value, once((status?: Error | Status | null, writeResponse?: Nullable) => { - if (status) { - this.status = typeof status === "number"? status: extractHAPStatusFromError(status); - reject(this.status); - return; - } - - this.status = Status.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 { - this.value = value; - resolve(); - } + try { + this.emit(CharacteristicEventTypes.SET, value, once((status?: Error | Status | null, writeResponse?: Nullable) => { + if (status) { + if (typeof status === "number") { + this.status = status; + } else { + this.status = extractHAPStatusFromError(status); + debug("[%s] Received error from set handler %s", this.displayName, status.stack); + } + reject(this.status); + return; + } + + this.status = Status.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) { + console.warn(`[${this.displayName}] SET handler returned write response value, though the characteristic doesn't support write response!`); + } + this.value = value; + resolve(); + } - if (this.eventOnlyCharacteristic || oldValue !== value) { this.emit(CharacteristicEventTypes.CHANGE, { originator: connection, oldValue: oldValue, newValue: value }); - } - }), undefined, connection); + }), undefined, connection); + } catch (error) { + console.warn(`[${this.displayName}] Unhandled error thrown inside write handler for characteristic: ${error.stack}`); + this.status = Status.SERVICE_COMMUNICATION_FAILURE; + reject(Status.SERVICE_COMMUNICATION_FAILURE); + } }); } } @@ -835,8 +868,8 @@ export class Characteristic extends EventEmitter { } } } - if (this.eventOnlyCharacteristic) { - // @ts-ignore + if (this.UUID === Characteristic.ProgrammableSwitchEvent.UUID) { + // special workaround for event only programmable switch event, which must always return null value = null; } @@ -897,7 +930,7 @@ export class Characteristic extends EventEmitter { UUID: characteristic.UUID, props: clone({}, characteristic.props), value: characteristic.value, - eventOnlyCharacteristic: characteristic.eventOnlyCharacteristic, + eventOnlyCharacteristic: characteristic.UUID === Characteristic.ProgrammableSwitchEvent.UUID, // support downgrades for now } }; @@ -910,7 +943,6 @@ export class Characteristic extends EventEmitter { const characteristic = new Characteristic(json.displayName, json.UUID, json.props); characteristic.value = json.value; - characteristic.eventOnlyCharacteristic = json.eventOnlyCharacteristic; return characteristic; }; diff --git a/src/lib/HAPServer.ts b/src/lib/HAPServer.ts index 08262defd..7a79f042c 100644 --- a/src/lib/HAPServer.ts +++ b/src/lib/HAPServer.ts @@ -90,7 +90,7 @@ export const enum Codes { BUSY = 0x07 // cannot accept pairing request at this time } -export const enum Status { +export const enum Status { // TODO rename before we spread adoption // noinspection JSUnusedGlobalSymbols SUCCESS = 0, INSUFFICIENT_PRIVILEGES = -70401, diff --git a/src/lib/Service.ts b/src/lib/Service.ts index d020804af..f91c58f69 100644 --- a/src/lib/Service.ts +++ b/src/lib/Service.ts @@ -40,7 +40,7 @@ export type ServiceCharacteristicChange = CharacteristicChange & { characteristi type Events = { [ServiceEventTypes.CHARACTERISTIC_CHANGE]: (change: ServiceCharacteristicChange) => void; - [ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE]: (change: ServiceConfigurationChange) => void; + [ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE]: (change: ServiceConfigurationChange) => void; // TODO remove service: this } // noinspection JSUnusedGlobalSymbols diff --git a/src/lib/controller/CameraController.ts b/src/lib/controller/CameraController.ts index ad000eb77..2ddae009f 100644 --- a/src/lib/controller/CameraController.ts +++ b/src/lib/controller/CameraController.ts @@ -1,82 +1,98 @@ -import {Controller, ControllerServiceMap, ControllerType, DefaultControllerType} from "./Controller"; +import crypto from 'crypto'; +import createDebug from "debug"; import { - CameraStreamingOptions, - Characteristic, - CharacteristicEventTypes, - CharacteristicGetCallback, - CharacteristicSetCallback, - CharacteristicValue, - LegacyCameraSourceAdapter, - PrepareStreamResponse, - PrepareStreamRequest, - RTPStreamManagement, - Service, SnapshotRequest, - StreamingRequest + CameraStreamingOptions, + Characteristic, + CharacteristicEventTypes, + CharacteristicGetCallback, + CharacteristicSetCallback, + CharacteristicValue, + LegacyCameraSourceAdapter, + PrepareStreamRequest, + PrepareStreamResponse, + RTPStreamManagement, + Service, + SnapshotRequest, + Status, + 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 { EventEmitter } from "../EventEmitter"; +import { Doorbell, Microphone, Speaker } from "../gen/HomeKit"; +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 | Status, 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; + [CameraControllerEvents.MICROPHONE_PROPERTIES_CHANGED]: (muted: boolean, volume: number) => void; + [CameraControllerEvents.SPEAKER_PROPERTIES_CHANGED]: (muted: boolean, volume: number) => void; } /** @@ -92,272 +108,322 @@ export type CameraControllerEventMap = { */ 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 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; + } - private readonly streamCount: number; - private readonly delegate: CameraStreamingDelegate; - private readonly streamingOptions: CameraStreamingOptions; - // private readonly recordingOptions: CameraRecordingOptions, // soon - private readonly legacyMode: boolean = false; + this.microphoneMuted = muted; + this.microphoneService.updateCharacteristic(Characteristic.Mute, muted); + } - streamManagements: RTPStreamManagement[] = []; + public setMicrophoneVolume(volume: number) { + if (!this.microphoneService) { + return; + } - private microphoneService?: Microphone; - private speakerService?: Speaker; + this.microphoneVolume = volume; + this.microphoneService.updateCharacteristic(Characteristic.Volume, volume); + } - private microphoneMuted: boolean = false; - private microphoneVolume: number = 100; - private speakerMuted: boolean = false; - private speakerVolume: number = 100; + public setSpeakerMuted(muted: boolean = true) { + if (!this.speakerService) { + return; + } - constructor(options: CameraControllerOptions, legacyMode: boolean = false) { - super(); - this.streamCount = Math.max(1, options.cameraStreamCount || 1); - this.delegate = options.delegate; - this.streamingOptions = options.streamingOptions; + this.speakerMuted = muted; + this.speakerService.updateCharacteristic(Characteristic.Mute, muted); + } - this.legacyMode = legacyMode; // legacy mode will prent from Microphone and Speaker services to get created to avoid collisions + public setSpeakerVolume(volume: number) { + if (!this.speakerService) { + 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.speakerVolume = volume; + this.speakerService.updateCharacteristic(Characteristic.Volume, volume); + } - public static generateSynchronisationSource() { - const ssrc = crypto.randomBytes(4); // range [-2.14748e+09 - 2.14748e+09] - ssrc[0] = 0; - return ssrc.readInt32BE(0); - } + private emitMicrophoneChange() { + this.emit(CameraControllerEvents.MICROPHONE_PROPERTIES_CHANGED, this.microphoneMuted, this.microphoneVolume); + } - // ----------------------------- MICROPHONE/SPEAKER API ------------------------------ + private emitSpeakerChange() { + this.emit(CameraControllerEvents.SPEAKER_PROPERTIES_CHANGED, this.speakerMuted, this.speakerVolume); + } - public setMicrophoneMuted(muted: boolean = true) { - if (!this.microphoneService) { - return; - } + // ----------------------------------------------------------------------------------- - this.microphoneMuted = muted; - this.microphoneService.updateCharacteristic(Characteristic.Mute, muted); + constructServices(): CameraControllerServiceMap { + for (let i = 0; i < this.streamCount; i++) { + this.streamManagements.push(new RTPStreamManagement(i, this.streamingOptions, this.delegate)); } - public setMicrophoneVolume(volume: number) { - if (!this.microphoneService) { - return; - } + 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.microphoneVolume = volume; - this.microphoneService.updateCharacteristic(Characteristic.Volume, volume); + if (this.streamingOptions.audio.twoWayAudio) { + this.speakerService = new Service.Speaker('', ''); + this.speakerService.setCharacteristic(Characteristic.Volume, this.speakerVolume); + } } - public setSpeakerMuted(muted: boolean = true) { - if (!this.speakerService) { - return; - } + const serviceMap: CameraControllerServiceMap = { + microphone: this.microphoneService, + speaker: this.speakerService, + }; - this.speakerMuted = muted; - this.speakerService.updateCharacteristic(Characteristic.Mute, muted); - } + this.streamManagements.forEach((management, index) => serviceMap[CameraController.STREAM_MANAGEMENT + index] = management.getService()); - public setSpeakerVolume(volume: number) { - if (!this.speakerService) { - return; - } + return serviceMap; + } - this.speakerVolume = volume; - this.speakerService.updateCharacteristic(Characteristic.Volume, volume); - } + initWithServices(serviceMap: CameraControllerServiceMap): void | CameraControllerServiceMap { + let modifiedServiceMap = false; - private emitMicrophoneChange() { - this.emit(CameraControllerEvents.MICROPHONE_PROPERTIES_CHANGED, this.microphoneMuted, this.microphoneVolume); - } + for (let i = 0; true; i++) { + let streamManagementService = serviceMap[CameraController.STREAM_MANAGEMENT + i]; - private emitSpeakerChange() { - this.emit(CameraControllerEvents.SPEAKER_PROPERTIES_CHANGED, this.speakerMuted, this.speakerVolume); - } + 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); - // ----------------------------------------------------------------------------------- + this.streamManagements.push(management); + serviceMap[CameraController.STREAM_MANAGEMENT + i] = management.getService(); - constructServices(): CameraControllerServiceMap { - for (let i = 0; i < this.streamCount; i++) { - this.streamManagements.push(new RTPStreamManagement(i, this.streamingOptions, this.delegate)); + 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 (!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); + // 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; + } - if (this.streamingOptions.audio.twoWayAudio) { - this.speakerService = new Service.Speaker('', ''); - this.speakerService.setCharacteristic(Characteristic.Volume, this.speakerVolume); - } - } + // 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; + } - const serviceMap: CameraControllerServiceMap = { - microphone: this.microphoneService, - speaker: this.speakerService, - }; + if (this.migrateFromDoorbell(serviceMap)) { + modifiedServiceMap = true; + } - this.streamManagements.forEach((management, index) => serviceMap[CameraController.STREAM_MANAGEMENT + index] = management.getService()); + if (modifiedServiceMap) { // serviceMap must only be returned if anything actually changed + return serviceMap; + } + } - return serviceMap; + // 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; } - initWithServices(serviceMap: CameraControllerServiceMap): void | CameraControllerServiceMap { - let modifiedServiceMap = false; + 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(); + }); + } - for (let i = 0; true; i++) { - let streamManagementService = serviceMap[CameraController.STREAM_MANAGEMENT + i]; + 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(); + }); + } + } - 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); + handleFactoryReset(): void { + this.streamManagements.forEach(management => management.handleFactoryReset()); + } - this.streamManagements.push(management); - serviceMap[CameraController.STREAM_MANAGEMENT + i] = management.getService(); + 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!`); - 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 - } - } - } + timeout = setTimeout(() => { + timeout = undefined; - // 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); + console.warn(`[${accessoryName}] Image snapshot handler didn't respond at all!`); - 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; - } + reject(Status.OPERATION_TIMED_OUT); + }, 10000); + }, 5000); - // SPEAKER - if (!this.legacyMode && this.streamingOptions.audio?.twoWayAudio) { // speaker should be present - if (serviceMap.speaker) { - this.speakerService = serviceMap.speaker; + 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 { - // 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; + debug("[%s] Error getting snapshot: %s", accessoryName, error.stack); + reject(Status.SERVICE_COMMUNICATION_FAILURE); } - } else if (serviceMap.speaker) { // speaker service supplied, though settings seemed to have changed - // we need to remove it - delete serviceMap.speaker; - modifiedServiceMap = true; - } - - if (this.migrateFromDoorbell(serviceMap)) { - modifiedServiceMap = true; - } - - if (modifiedServiceMap) { // serviceMap must only be returned if anything actually changed - return serviceMap; - } - } - - // 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; - } - - 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(); - }); - } - - 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(); - }); + return; + } + + if (!buffer || buffer.length === 0) { + console.warn(`[${accessoryName}] Snapshot request handler provided empty image buffer!`); + reject(Status.SERVICE_COMMUNICATION_FAILURE); + } else { + resolve(buffer); + } + }); + } catch (error) { + if (!timeout) { + return; + } else { + clearTimeout(timeout); + timeout = undefined; } - } - - handleFactoryReset(): void { - this.streamManagements.forEach(management => management.handleFactoryReset()); - } - handleSnapshotRequest(height: number, width: number, callback: NodeCallback) { - this.delegate.handleSnapshotRequest({ - height: height, - width: width, - }, callback); - } + console.warn(`[${accessoryName}] Unhandled error thrown inside snapshot request handler: ${error.stack}`); + reject(Status.SERVICE_COMMUNICATION_FAILURE); + } + }); + } - handleCloseConnection(sessionID: SessionIdentifier): void { - if (this.delegate instanceof LegacyCameraSourceAdapter) { - this.delegate.forwardCloseConnection(sessionID); - } + handleCloseConnection(sessionID: SessionIdentifier): void { + if (this.delegate instanceof LegacyCameraSourceAdapter) { + this.delegate.forwardCloseConnection(sessionID); } + } } diff --git a/src/lib/gen/HomeKit.ts b/src/lib/gen/HomeKit.ts index f6ab398d7..58bb3f612 100644 --- a/src/lib/gen/HomeKit.ts +++ b/src/lib/gen/HomeKit.ts @@ -1873,7 +1873,6 @@ export class ProgrammableSwitchEvent extends Characteristic { validValues: [0, 1, 2], perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); - this.eventOnlyCharacteristic = true; //Manual addition. this.value = this.getDefaultValue(); } } From f908a9e53eeaa7a14ab82f34bd1d66cbfbf7f108 Mon Sep 17 00:00:00 2001 From: Supereg Date: Tue, 6 Oct 2020 00:28:25 +0200 Subject: [PATCH 29/70] Fixed awfully wrong uuids for iOS 14 characteristics and services --- src/lib/gen/HomeKit.ts | 58 +++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/lib/gen/HomeKit.ts b/src/lib/gen/HomeKit.ts index 58bb3f612..ed729c000 100644 --- a/src/lib/gen/HomeKit.ts +++ b/src/lib/gen/HomeKit.ts @@ -3623,7 +3623,7 @@ Characteristic.SetupTransferTransport = SetupTransferTransport; */ export class ActivityInterval extends Characteristic { - static readonly UUID: string = '0000021E-0000-1000-8000-0000023B'; + static readonly UUID: string = '0000023B-0000-1000-8000-0026BB765291'; constructor() { super("Activity Interval", ActivityInterval.UUID); @@ -3646,7 +3646,7 @@ Characteristic.ActivityInterval = ActivityInterval; */ export class CCAEnergyDetectThreshold extends Characteristic { - static readonly UUID: string = '0000021E-0000-1000-8000-00000246'; + static readonly UUID: string = '00000246-0000-1000-8000-0026BB765291'; constructor() { super("CCA Energy Detect Threshold", CCAEnergyDetectThreshold.UUID); @@ -3667,7 +3667,7 @@ Characteristic.CCAEnergyDetectThreshold = CCAEnergyDetectThreshold; */ export class CCASignalDetectThreshold extends Characteristic { - static readonly UUID: string = '0000021E-0000-1000-8000-00000245'; + static readonly UUID: string = '00000245-0000-1000-8000-0026BB765291'; constructor() { super("CCA Signal Detect Threshold", CCASignalDetectThreshold.UUID); @@ -3688,7 +3688,7 @@ Characteristic.CCASignalDetectThreshold = CCASignalDetectThreshold; */ export class CharacteristicValueTransitionControl extends Characteristic { - static readonly UUID: string = '0000021E-0000-1000-8000-00000143'; + static readonly UUID: string = '00000143-0000-1000-8000-0026BB765291'; constructor() { super("Characteristic Value Transition Control", CharacteristicValueTransitionControl.UUID); @@ -3709,7 +3709,7 @@ Characteristic.CharacteristicValueTransitionControl = CharacteristicValueTransit */ export class SupportedCharacteristicValueTransitionConfiguration extends Characteristic { - static readonly UUID: string = '0000021E-0000-1000-8000-00000144'; + static readonly UUID: string = '00000144-0000-1000-8000-0026BB765291'; constructor() { super("Supported Characteristic Value Transition Configuration", SupportedCharacteristicValueTransitionConfiguration.UUID); @@ -3730,7 +3730,7 @@ Characteristic.SupportedCharacteristicValueTransitionConfiguration = SupportedCh */ export class CharacteristicValueActiveTransitionCount extends Characteristic { - static readonly UUID: string = '0000021E-0000-1000-8000-0000024B'; + static readonly UUID: string = '0000024B-0000-1000-8000-0026BB765291'; constructor() { super("Characteristic Value Active Transition Count", CharacteristicValueActiveTransitionCount.UUID); @@ -3751,7 +3751,7 @@ Characteristic.CharacteristicValueActiveTransitionCount = CharacteristicValueAct */ export class CurrentTransport extends Characteristic { - static readonly UUID: string = '0000021E-0000-1000-8000-0000022B'; + static readonly UUID: string = '0000022B-0000-1000-8000-0026BB765291'; constructor() { super("Current Transport", CurrentTransport.UUID); @@ -3772,7 +3772,7 @@ Characteristic.CurrentTransport = CurrentTransport; */ export class DataStreamHAPTransport extends Characteristic { - static readonly UUID: string = '0000021E-0000-1000-8000-00000138'; + static readonly UUID: string = '00000138-0000-1000-8000-0026BB765291'; constructor() { super("Data Stream HAP Transport", DataStreamHAPTransport.UUID); @@ -3793,7 +3793,7 @@ Characteristic.DataStreamHAPTransport = DataStreamHAPTransport; */ export class DataStreamHAPTransportInterrupt extends Characteristic { - static readonly UUID: string = '0000021E-0000-1000-8000-00000139'; + static readonly UUID: string = '00000139-0000-1000-8000-0026BB765291'; constructor() { super("Data Stream HAP Transport Interrupt", DataStreamHAPTransportInterrupt.UUID); @@ -3814,7 +3814,7 @@ Characteristic.DataStreamHAPTransportInterrupt = DataStreamHAPTransportInterrupt */ export class EventRetransmissionMaximum extends Characteristic { - static readonly UUID: string = '0000021E-0000-1000-8000-0000023D'; + static readonly UUID: string = '0000023D-0000-1000-8000-0026BB765291'; constructor() { super("Event Retransmission Maximum", EventRetransmissionMaximum.UUID); @@ -3835,7 +3835,7 @@ Characteristic.EventRetransmissionMaximum = EventRetransmissionMaximum; */ export class EventTransmissionCounters extends Characteristic { - static readonly UUID: string = '0000021E-0000-1000-8000-0000023E'; + static readonly UUID: string = '0000023E-0000-1000-8000-0026BB765291'; constructor() { super("Event Transmission Counters", EventTransmissionCounters.UUID); @@ -3856,7 +3856,7 @@ Characteristic.EventTransmissionCounters = EventTransmissionCounters; */ export class HeartBeat extends Characteristic { - static readonly UUID: string = '0000021E-0000-1000-8000-0000024A'; + static readonly UUID: string = '0000024A-0000-1000-8000-0026BB765291'; constructor() { super("Heart Beat", HeartBeat.UUID); @@ -3877,7 +3877,7 @@ Characteristic.HeartBeat = HeartBeat; */ export class MACRetransmissionMaximum extends Characteristic { - static readonly UUID: string = '0000021E-0000-1000-8000-00000247'; + static readonly UUID: string = '00000247-0000-1000-8000-0026BB765291'; constructor() { super("MAC Retransmission Maximum", MACRetransmissionMaximum.UUID); @@ -3898,7 +3898,7 @@ Characteristic.MACRetransmissionMaximum = MACRetransmissionMaximum; */ export class MACTransmissionCounters extends Characteristic { - static readonly UUID: string = '0000021E-0000-1000-8000-00000248'; + static readonly UUID: string = '00000248-0000-1000-8000-0026BB765291'; constructor() { super("MAC Transmission Counters", MACTransmissionCounters.UUID); @@ -3919,7 +3919,7 @@ Characteristic.MACTransmissionCounters = MACTransmissionCounters; */ export class OperatingStateResponse extends Characteristic { - static readonly UUID: string = '0000021E-0000-1000-8000-00000232'; + static readonly UUID: string = '00000232-0000-1000-8000-0026BB765291'; constructor() { super("Operating State Response", OperatingStateResponse.UUID); @@ -3940,7 +3940,7 @@ Characteristic.OperatingStateResponse = OperatingStateResponse; */ export class Ping extends Characteristic { - static readonly UUID: string = '0000021E-0000-1000-8000-0000023C'; + static readonly UUID: string = '0000023C-0000-1000-8000-0026BB765291'; constructor() { super("Ping", Ping.UUID); @@ -3961,7 +3961,7 @@ Characteristic.Ping = Ping; */ export class ReceiverSensitivity extends Characteristic { - static readonly UUID: string = '0000021E-0000-1000-8000-00000244'; + static readonly UUID: string = '00000244-0000-1000-8000-0026BB765291'; constructor() { super("Receiver Sensitivity", ReceiverSensitivity.UUID); @@ -3982,7 +3982,7 @@ Characteristic.ReceiverSensitivity = ReceiverSensitivity; */ export class ReceivedSignalStrengthIndication extends Characteristic { - static readonly UUID: string = '0000021E-0000-1000-8000-0000023F'; + static readonly UUID: string = '0000023F-0000-1000-8000-0026BB765291'; constructor() { super("Received Signal Strength Indication", ReceivedSignalStrengthIndication.UUID); @@ -4003,7 +4003,7 @@ Characteristic.ReceivedSignalStrengthIndication = ReceivedSignalStrengthIndicati */ export class SleepInterval extends Characteristic { - static readonly UUID: string = '0000021E-0000-1000-8000-0000023A'; + static readonly UUID: string = '0000023A-0000-1000-8000-0026BB765291'; constructor() { super("Sleep Interval", SleepInterval.UUID); @@ -4026,7 +4026,7 @@ Characteristic.SleepInterval = SleepInterval; */ export class SignalToNoiseRatio extends Characteristic { - static readonly UUID: string = '0000021E-0000-1000-8000-00000241'; + static readonly UUID: string = '00000241-0000-1000-8000-0026BB765291'; constructor() { super("Signal-to-noise Ration", SignalToNoiseRatio.UUID); @@ -4047,7 +4047,7 @@ Characteristic.SignalToNoiseRatio = SignalToNoiseRatio; */ export class SupportedDiagnosticsSnapshot extends Characteristic { - static readonly UUID: string = '0000021E-0000-1000-8000-00000238'; + static readonly UUID: string = '00000238-0000-1000-8000-0026BB765291'; constructor() { super("Supported Diagnostics Snapshot", SupportedDiagnosticsSnapshot.UUID); @@ -4068,7 +4068,7 @@ Characteristic.SupportedDiagnosticsSnapshot = SupportedDiagnosticsSnapshot; */ export class TransmitPower extends Characteristic { - static readonly UUID: string = '0000021E-0000-1000-8000-00000242'; + static readonly UUID: string = '00000242-0000-1000-8000-0026BB765291'; constructor() { super("Transmit Power", TransmitPower.UUID); @@ -4089,7 +4089,7 @@ Characteristic.TransmitPower = TransmitPower; */ export class TransmitPowerMaximum extends Characteristic { - static readonly UUID: string = '0000021E-0000-1000-8000-00000243'; + static readonly UUID: string = '00000243-0000-1000-8000-0026BB765291'; constructor() { super("Transmit Power Maximum", TransmitPowerMaximum.UUID); @@ -4110,7 +4110,7 @@ Characteristic.TransmitPowerMaximum = TransmitPowerMaximum; */ export class VideoAnalysisActive extends Characteristic { - static readonly UUID: string = '0000021E-0000-1000-8000-00000229'; + static readonly UUID: string = '00000229-0000-1000-8000-0026BB765291'; constructor() { super("Video Analysis Active", VideoAnalysisActive.UUID); @@ -4131,7 +4131,7 @@ Characteristic.VideoAnalysisActive = VideoAnalysisActive; */ export class WiFiCapabilities extends Characteristic { - static readonly UUID: string = '0000021E-0000-1000-8000-0000022C'; + static readonly UUID: string = '0000022C-0000-1000-8000-0026BB765291'; constructor() { super("Wi-Fi Capabilities", WiFiCapabilities.UUID); @@ -4152,7 +4152,7 @@ Characteristic.WiFiCapabilities = WiFiCapabilities; */ export class WiFiConfigurationControl extends Characteristic { - static readonly UUID: string = '0000021E-0000-1000-8000-0000022D'; + static readonly UUID: string = '0000022D-0000-1000-8000-0026BB765291'; constructor() { super("Wi-Fi Configuration Control", WiFiConfigurationControl.UUID); @@ -5375,7 +5375,7 @@ Service.TransferTransportManagement = TransferTransportManagement; */ export class AccessoryRuntimeInformation extends Service { - static readonly UUID: string = '00000203-0000-1000-8000-00000239'; + static readonly UUID: string = '00000239-0000-1000-8000-0026BB765291'; constructor(displayName?: string, subtype?: string) { super(displayName, AccessoryRuntimeInformation.UUID, subtype); @@ -5399,7 +5399,7 @@ Service.AccessoryRuntimeInformation = AccessoryRuntimeInformation; */ export class Diagnostics extends Service { - static readonly UUID: string = '00000203-0000-1000-8000-00000237'; + static readonly UUID: string = '00000237-0000-1000-8000-0026BB765291'; constructor(displayName?: string, subtype?: string) { super(displayName, Diagnostics.UUID, subtype); @@ -5418,7 +5418,7 @@ Service.Diagnostics = Diagnostics; */ export class WiFiTransport extends Service { - static readonly UUID: string = '00000203-0000-1000-8000-0000022A'; + static readonly UUID: string = '0000022A-0000-1000-8000-0026BB765291'; constructor(displayName?: string, subtype?: string) { super(displayName, WiFiTransport.UUID, subtype); From 89ad4ac292756b0065ad83931ae22ee92c333d57 Mon Sep 17 00:00:00 2001 From: Supereg Date: Tue, 6 Oct 2020 00:59:29 +0200 Subject: [PATCH 30/70] Remove last traces of custom EventEmitter type --- src/lib/Accessory.ts | 7 ++- src/lib/EventEmitter.ts | 48 ---------------- src/lib/Service.ts | 65 ++++++++++------------ src/lib/controller/CameraController.ts | 13 +++-- src/lib/controller/RemoteController.ts | 52 ++++++++++------- src/lib/datastream/DataStreamManagement.ts | 7 ++- src/lib/datastream/DataStreamServer.ts | 52 +++++++++-------- src/lib/tv/AccessControlManagement.ts | 13 +++-- 8 files changed, 116 insertions(+), 141 deletions(-) delete mode 100644 src/lib/EventEmitter.ts diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index 565717401..76f469381 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -75,7 +75,6 @@ import { SerializedService, Service, ServiceCharacteristicChange, - ServiceConfigurationChange, ServiceEventTypes, ServiceId } from './Service'; @@ -244,6 +243,10 @@ export type AccessoryCharacteristicChange = ServiceCharacteristicChange & { service: Service; }; +export interface ServiceConfigurationChange { + service: Service; +} + const enum WriteRequestState { REGULAR_REQUEST, TIMED_WRITE_AUTHENTICATED, @@ -419,7 +422,7 @@ export class Accessory extends EventEmitter { this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, { service: service }); } - service.on(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, (change: ServiceConfigurationChange) => { + service.on(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, () => { if (!service.isPrimaryService && service === this.primaryService) { // service changed form primary to non primary service this.primaryService = undefined; 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/Service.ts b/src/lib/Service.ts index f91c58f69..05186c686 100644 --- a/src/lib/Service.ts +++ b/src/lib/Service.ts @@ -1,3 +1,4 @@ +import { EventEmitter } from "events"; import { CharacteristicValue, HapService, Nullable, ToHAPOptions, WithUUID, } from '../types'; import { Characteristic, @@ -5,12 +6,13 @@ import { CharacteristicEventTypes, SerializedCharacteristic } from './Characteristic'; -import { EventEmitter } from './EventEmitter'; import * as HomeKitTypes from './gen'; import { IdentifierCache } from './model/IdentifierCache'; -import { clone } from './util/clone'; import { toShortForm } from './util/uuid'; +/** + * HAP spec allows a maximum of 100 characteristics per service! + */ const MAX_CHARACTERISTICS = 100; export interface SerializedService { @@ -27,28 +29,27 @@ export interface SerializedService { export type ServiceId = string; // string with the format: UUID + (subtype | "") -export const enum ServiceEventTypes { - CHARACTERISTIC_CHANGE = "characteristic-change", - SERVICE_CONFIGURATION_CHANGE = "service-configurationChange", -} - -export type ServiceConfigurationChange = { - service: Service; -}; - export type ServiceCharacteristicChange = CharacteristicChange & { characteristic: Characteristic }; -type Events = { - [ServiceEventTypes.CHARACTERISTIC_CHANGE]: (change: ServiceCharacteristicChange) => void; - [ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE]: (change: ServiceConfigurationChange) => void; // TODO remove service: this -} - // 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", +} + +export declare interface Service { + on(event: "characteristic-change", listener: (change: ServiceCharacteristicChange) => void): this; + on(event: "service-configurationChange", listener: () => void): this; + + emit(event: "characteristic-change", change: ServiceCharacteristicChange): boolean; + emit(event: "service-configurationChange"): boolean; +} + /** * 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 @@ -68,11 +69,8 @@ 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 { +export class Service extends EventEmitter { static AccessControl: typeof HomeKitTypes.Generated.AccessControl; static AccessoryInformation: typeof HomeKitTypes.Generated.AccessoryInformation; @@ -100,10 +98,6 @@ export class Service extends EventEmitter { static HumiditySensor: typeof HomeKitTypes.Generated.HumiditySensor; static InputSource: typeof HomeKitTypes.TV.InputSource; static IrrigationSystem: typeof HomeKitTypes.Generated.IrrigationSystem; - /** - * @deprecated Removed in iOS 11. Use ServiceLabel instead. - */ - static Label: typeof HomeKitTypes.Generated.ServiceLabel; static LeakSensor: typeof HomeKitTypes.Generated.LeakSensor; static LightSensor: typeof HomeKitTypes.Generated.LightSensor; static Lightbulb: typeof HomeKitTypes.Generated.Lightbulb; @@ -185,12 +179,11 @@ export class Service extends EventEmitter { 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 | {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 (let index in this.characteristics) { const existing = this.characteristics[index]; @@ -214,7 +207,7 @@ export class Service extends EventEmitter { this.characteristics.push(characteristic); - this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({ service: this })); + this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE); return characteristic; } @@ -229,7 +222,7 @@ export class Service extends EventEmitter { */ setPrimaryService = (isPrimary: boolean = true) => { this.isPrimaryService = isPrimary; - this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({ service: this })); + this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE); }; /** @@ -239,7 +232,7 @@ export class Service extends EventEmitter { */ setHiddenService = (isHidden: boolean = true) => { 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. @@ -247,7 +240,7 @@ export class Service extends EventEmitter { //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 })); + this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE); } removeLinkedService = (oldLinkedService: Service) => { @@ -255,7 +248,7 @@ export class Service extends EventEmitter { const index = this.linkedServices.indexOf(oldLinkedService); if (index !== -1) this.linkedServices.splice(index, 1); - this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({ service: this })); + this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE); } removeCharacteristic = (characteristic: Characteristic) => { @@ -274,7 +267,7 @@ 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); } } diff --git a/src/lib/controller/CameraController.ts b/src/lib/controller/CameraController.ts index 2ddae009f..fb46f40ed 100644 --- a/src/lib/controller/CameraController.ts +++ b/src/lib/controller/CameraController.ts @@ -1,5 +1,6 @@ import crypto from 'crypto'; import createDebug from "debug"; +import { EventEmitter } from "events"; import { CameraStreamingOptions, Characteristic, @@ -17,7 +18,6 @@ import { StreamingRequest } from "../.."; import { SessionIdentifier } from "../../types"; -import { EventEmitter } from "../EventEmitter"; import { Doorbell, Microphone, Speaker } from "../gen/HomeKit"; import { Controller, ControllerServiceMap, ControllerType, DefaultControllerType } from "./Controller"; import Timeout = NodeJS.Timeout; @@ -90,9 +90,12 @@ export const enum CameraControllerEvents { 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; } /** @@ -106,7 +109,7 @@ 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 { +export class CameraController extends EventEmitter implements Controller { private static readonly STREAM_MANAGEMENT = "streamManagement"; // key to index all RTPStreamManagement services diff --git a/src/lib/controller/RemoteController.ts b/src/lib/controller/RemoteController.ts index cf2542f27..271140fd2 100644 --- a/src/lib/controller/RemoteController.ts +++ b/src/lib/controller/RemoteController.ts @@ -1,5 +1,6 @@ import assert from 'assert'; import createDebug from 'debug'; +import { EventEmitter } from "events"; import { CharacteristicValue } from "../../types"; import { Accessory } from "../Accessory"; import { @@ -23,7 +24,6 @@ import { RequestHandler, Topics } from "../datastream"; -import { EventEmitter } from "../EventEmitter"; import { DataStreamTransportManagement } from "../gen/HomeKit-DataStream"; import { AudioStreamManagement, Siri, TargetControl, TargetControlManagement } from "../gen/HomeKit-Remote"; import { Status } from "../HAPServer"; @@ -48,6 +48,7 @@ const enum SupportedButtonConfigurationTypes { } export const enum ButtonType { + // noinspection JSUnusedGlobalSymbols UNDEFINED = 0x00, MENU = 0x01, PLAY_PAUSE = 0x02, @@ -71,6 +72,7 @@ const enum TargetControlList { } enum Operation { + // noinspection JSUnusedGlobalSymbols UNDEFINED = 0x00, LIST = 0x01, ADD = 0x02, @@ -87,6 +89,7 @@ const enum TargetConfigurationTypes { } export const enum TargetCategory { + // noinspection JSUnusedGlobalSymbols UNDEFINED = 0x00, APPLE_TV = 0x18 } @@ -148,6 +151,7 @@ const enum SelectedAudioInputStreamConfigurationTypes { // ---------- const enum SupportedAudioStreamConfigurationTypes { + // noinspection JSUnusedGlobalSymbols AUDIO_CODEC_CONFIGURATION = 0x01, COMFORT_NOISE_SUPPORT = 0x02, } @@ -158,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, @@ -263,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", @@ -273,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; + + 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; -export type RemoteControllerEventMap = { - [RemoteControllerEvents.ACTIVE_CHANGE]: (active: boolean) => void; - [RemoteControllerEvents.ACTIVE_IDENTIFIER_CHANGE]: (activeIdentifier: number) => void; + emit(event: "active-change", active: boolean): boolean; + emit(event: "active-identifier-change", activeIdentifier: number): boolean; - [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: "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 { @@ -334,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; @@ -1249,14 +1261,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; diff --git a/src/lib/datastream/DataStreamManagement.ts b/src/lib/datastream/DataStreamManagement.ts index c001b317f..4534154ed 100644 --- a/src/lib/datastream/DataStreamManagement.ts +++ b/src/lib/datastream/DataStreamManagement.ts @@ -6,15 +6,15 @@ import { CharacteristicGetCallback, CharacteristicSetCallback } from "../Characteristic"; -import { Event } from "../EventEmitter"; import { DataStreamTransportManagement } from "../gen/HomeKit-DataStream"; import { Status } from "../HAPServer"; import { Service } from "../Service"; import { HAPConnection } from "../util/eventedhttp"; import * as tlv from '../util/tlv'; import { + DataStreamConnection, DataStreamServer, - DataStreamServerEventMap, + DataStreamServerEvents, GlobalEventHandler, GlobalRequestHandler } from "./DataStreamServer"; @@ -145,7 +145,8 @@ 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; } diff --git a/src/lib/datastream/DataStreamServer.ts b/src/lib/datastream/DataStreamServer.ts index 2d77b5c3b..82aab7d0a 100644 --- a/src/lib/datastream/DataStreamServer.ts +++ b/src/lib/datastream/DataStreamServer.ts @@ -1,13 +1,12 @@ -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 {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'); @@ -124,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 { // TODO removeAllEvent handlers on closing +export class DataStreamServer extends EventEmitter { // TODO removeAllEvent handlers on closing static readonly version = "1.0"; @@ -391,18 +393,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; } /** @@ -421,7 +427,7 @@ 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; diff --git a/src/lib/tv/AccessControlManagement.ts b/src/lib/tv/AccessControlManagement.ts index 07ce8861c..f18402cb6 100644 --- a/src/lib/tv/AccessControlManagement.ts +++ b/src/lib/tv/AccessControlManagement.ts @@ -1,6 +1,6 @@ +import { EventEmitter } from "events"; import { AccessControl } from "../gen/HomeKit"; import { Service } from "../Service"; -import { EventEmitter } from "../EventEmitter"; import { Characteristic, CharacteristicEventTypes, @@ -44,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; From e16a3e450d3353624879e5d3b6db225e8a2c260c Mon Sep 17 00:00:00 2001 From: Supereg Date: Tue, 6 Oct 2020 01:10:15 +0200 Subject: [PATCH 31/70] Fix tests --- src/lib/Characteristic.spec.ts | 41 ++++++++++++++++------------------ src/lib/Characteristic.ts | 2 +- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/lib/Characteristic.spec.ts b/src/lib/Characteristic.spec.ts index c42454e46..6460c39b8 100644 --- a/src/lib/Characteristic.spec.ts +++ b/src/lib/Characteristic.spec.ts @@ -11,12 +11,12 @@ import { uuid } from '..'; -const createCharacteristic = (type: Formats) => { - return new Characteristic('Test', uuid.generate('Foo'), { format: type, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE] }); +const createCharacteristic = (type: Formats, customUUID?: string) => { + 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', () => { @@ -113,22 +113,23 @@ describe('Characteristic', () => { }); describe('#handleGetRequest()', () => { - it('should handle special event only characteristics', () => { - const characteristic = createCharacteristic(Formats.BOOL); - characteristic.eventOnlyCharacteristic = true; + it('should handle special event only characteristics', (callback) => { + const characteristic = createCharacteristic(Formats.BOOL, Characteristic.ProgrammableSwitchEvent.UUID); characteristic.handleGetRequest().then(() => { expect(characteristic.status).toEqual(Status.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.handleGetRequest().then(() => { expect(characteristic.status).toEqual(Status.SUCCESS); expect(characteristic.value).toEqual(null); + callback(); }); }); }); @@ -281,16 +282,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(); - characteristic.handleGetRequest(); - characteristic.on(CharacteristicEventTypes.GET, listenerCallback); - characteristic.handleGetRequest(); - - expect(listenerCallback).toHaveBeenCalledTimes(1); + characteristic.handleGetRequest().then(() => { + characteristic.on(CharacteristicEventTypes.GET, listenerCallback); + characteristic.handleGetRequest(); + expect(listenerCallback).toHaveBeenCalledTimes(1); + callback(); + }) }); }); @@ -314,9 +316,8 @@ describe('Characteristic', () => { 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', () => { + const characteristic = createCharacteristic(Formats.STRING, Characteristic.ProgrammableSwitchEvent.UUID); const VALUE = 'NewValue'; const listenerCallback = jest.fn(); @@ -398,9 +399,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({ @@ -424,7 +424,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', () => { @@ -441,7 +440,6 @@ describe('Characteristic', () => { adminOnlyAccess: [Access.NOTIFY, Access.READ], }, value: "testValue", - eventOnlyCharacteristic: true, }; const characteristic = Characteristic.deserialize(json); @@ -450,7 +448,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); }); }); }); diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts index 5dc5cd7d0..cd2ff75b9 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -89,7 +89,7 @@ export interface SerializedCharacteristic { UUID: string, props: CharacteristicProps, value: Nullable, - eventOnlyCharacteristic: boolean, + eventOnlyCharacteristic?: boolean, } export const enum CharacteristicEventTypes { From 50be234eeaca60aecfa21c5ab18faf8994a76ccb Mon Sep 17 00:00:00 2001 From: Supereg Date: Tue, 6 Oct 2020 16:06:52 +0200 Subject: [PATCH 32/70] setValue method will now fire a SET event again --- src/lib/Characteristic.spec.ts | 1 + src/lib/Characteristic.ts | 35 +++++++++++++++------- src/lib/camera/RTPStreamManagement.ts | 12 ++++++-- src/lib/controller/RemoteController.ts | 15 ++++++---- src/lib/datastream/DataStreamManagement.ts | 18 +++++------ 5 files changed, 54 insertions(+), 27 deletions(-) diff --git a/src/lib/Characteristic.spec.ts b/src/lib/Characteristic.spec.ts index 6460c39b8..67765dba5 100644 --- a/src/lib/Characteristic.spec.ts +++ b/src/lib/Characteristic.spec.ts @@ -440,6 +440,7 @@ describe('Characteristic', () => { adminOnlyAccess: [Access.NOTIFY, Access.READ], }, value: "testValue", + eventOnlyCharacteristic: false, }; const characteristic = Characteristic.deserialize(json); diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts index cd2ff75b9..501dd3f8b 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -89,7 +89,7 @@ export interface SerializedCharacteristic { UUID: string, props: CharacteristicProps, value: Nullable, - eventOnlyCharacteristic?: boolean, + eventOnlyCharacteristic: boolean, } export const enum CharacteristicEventTypes { @@ -120,13 +120,13 @@ export type AdditionalAuthorizationHandler = (additionalAuthorizationData: strin 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: "set", listener: (value: CharacteristicValue, callback: CharacteristicSetCallback, context: any, connection?: HAPConnection) => void): this on(event: "change", listener: (change: CharacteristicChange) => void): this; on(event: "subscribe", listener: VoidCallback): this; on(event: "unsubscribe", listener: VoidCallback): this; emit(event: "get", callback: CharacteristicGetCallback, context: any, connection?: HAPConnection): boolean; - emit(event: "set", value: CharacteristicValue, callback: CharacteristicSetCallback, context: any, connection: HAPConnection): boolean; + emit(event: "set", value: CharacteristicValue, callback: CharacteristicSetCallback, context: any, connection?: HAPConnection): boolean; emit(event: "change", change: CharacteristicChange): boolean; emit(event: "subscribe"): boolean; emit(event: "unsubscribe"): boolean; @@ -518,8 +518,22 @@ export class Characteristic extends EventEmitter { }); } - setValue(newValue: Nullable, callback?: () => void, context?: any): Characteristic { - return this.updateValue(newValue, callback, context) + setValue(value: CharacteristicValue, callback?: CharacteristicSetCallback, context?: any): Characteristic { + 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; } updateValue(value: Nullable, callback?: () => void, context?: any): Characteristic { @@ -604,14 +618,15 @@ export class Characteristic extends EventEmitter { /** * Called when a HAP requests update the current value of the characteristic. * - * @param value - The update value + * @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 */ - handleSetRequest(value: CharacteristicValue, connection: HAPConnection): Promise { + handleSetRequest(value: CharacteristicValue, connection?: HAPConnection, context?: any): Promise { if (!this.props.perms.includes(Perms.PAIRED_WRITE)) { // check if we are allowed to write to this characteristic return Promise.reject(Status.READ_ONLY_CHARACTERISTIC); } @@ -624,7 +639,7 @@ export class Characteristic extends EventEmitter { if (this.listeners(CharacteristicEventTypes.SET).length === 0) { this.value = value; - this.emit(CharacteristicEventTypes.CHANGE, { originator: connection, oldValue: oldValue, newValue: value }); + this.emit(CharacteristicEventTypes.CHANGE, { originator: connection, oldValue: oldValue, newValue: value, context: context }); return Promise.resolve(); } else { return new Promise((resolve, reject) => { @@ -655,8 +670,8 @@ export class Characteristic extends EventEmitter { resolve(); } - this.emit(CharacteristicEventTypes.CHANGE, { originator: connection, oldValue: oldValue, newValue: value }); - }), undefined, connection); + this.emit(CharacteristicEventTypes.CHANGE, { originator: connection, oldValue: oldValue, newValue: value, context: context }); + }), context, connection); } catch (error) { console.warn(`[${this.displayName}] Unhandled error thrown inside write handler for characteristic: ${error.stack}`); this.status = Status.SERVICE_COMMUNICATION_FAILURE; diff --git a/src/lib/camera/RTPStreamManagement.ts b/src/lib/camera/RTPStreamManagement.ts index 1cb956d85..e9cf6a3df 100644 --- a/src/lib/camera/RTPStreamManagement.ts +++ b/src/lib/camera/RTPStreamManagement.ts @@ -17,6 +17,7 @@ import { Service } from '../Service'; import { HAPConnection, HAPConnectionEvent } from "../util/eventedhttp"; import * as tlv from '../util/tlv'; import RTPProxy from './RTPProxy'; +import assert from "assert"; const debug = createDebug('HAP-NodeJS:Camera:RTPStreamManagement'); // ---------------------------------- TLV DEFINITIONS START ---------------------------------- @@ -566,16 +567,21 @@ 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, connection: HAPConnection) => { + .on(CharacteristicEventTypes.SET, (value, callback, context, connection) => { + if (!connection) { + debug("Set event handler for SetupEndpoints cannot be called from plugin. Connection undefined!"); + callback(Status.INVALID_VALUE_IN_REQUEST); + return; + } this.handleSetupEndpoints(value, callback, connection); }); } diff --git a/src/lib/controller/RemoteController.ts b/src/lib/controller/RemoteController.ts index 271140fd2..4c6a9dcf9 100644 --- a/src/lib/controller/RemoteController.ts +++ b/src/lib/controller/RemoteController.ts @@ -1184,20 +1184,25 @@ export class RemoteController extends EventEmitter implements SerializableContro } this.targetControlManagementService.getCharacteristic(Characteristic.TargetControlList)! - .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { + .on(CharacteristicEventTypes.GET, callback => { callback(null, this.targetConfigurationsString); }) .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, connection: HAPConnection) => { + .on(CharacteristicEventTypes.SET, (value, callback, context, connection) => { + if (!connection) { + debug("Set event handler for Remote.Active cannot be called from plugin. Connection undefined!"); + callback(Status.INVALID_VALUE_IN_REQUEST); + return; + } this.handleActiveWrite(value, callback, connection); }); this.targetControlService.getCharacteristic(Characteristic.ButtonEvent)! @@ -1207,7 +1212,7 @@ export class RemoteController extends EventEmitter implements SerializableContro if (this.audioSupported) { this.audioStreamManagementService!.getCharacteristic(Characteristic.SelectedAudioStreamConfiguration)! - .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { + .on(CharacteristicEventTypes.GET, callback => { callback(null, this.selectedAudioConfigurationString); }) .on(CharacteristicEventTypes.SET, this.handleSelectedAudioConfigurationWrite.bind(this)) diff --git a/src/lib/datastream/DataStreamManagement.ts b/src/lib/datastream/DataStreamManagement.ts index 4534154ed..644e70480 100644 --- a/src/lib/datastream/DataStreamManagement.ts +++ b/src/lib/datastream/DataStreamManagement.ts @@ -1,11 +1,6 @@ +import assert from "assert"; import createDebug from "debug"; -import { CharacteristicValue } from "../../types"; -import { - Characteristic, - CharacteristicEventTypes, - CharacteristicGetCallback, - CharacteristicSetCallback -} from "../Characteristic"; +import { Characteristic, CharacteristicEventTypes, CharacteristicSetCallback } from "../Characteristic"; import { DataStreamTransportManagement } from "../gen/HomeKit-DataStream"; import { Status } from "../HAPServer"; import { Service } from "../Service"; @@ -211,10 +206,15 @@ 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, connection: HAPConnection) => { + .on(CharacteristicEventTypes.SET, (value, callback, context, connection) => { + if (!connection) { + debug("Set event handler for SetupDataStreamTransport cannot be called from plugin! Connection undefined!"); + callback(Status.INVALID_VALUE_IN_REQUEST); + return; + } this.handleSetupDataStreamTransportWrite(value, callback, connection); }) .updateValue(this.lastSetupDataStreamTransportResponse); From a063bc17d7f8d18a980a979e9b2206024546bddf Mon Sep 17 00:00:00 2001 From: Supereg Date: Tue, 6 Oct 2020 16:40:50 +0200 Subject: [PATCH 33/70] Fixing tests --- src/lib/Characteristic.spec.ts | 20 ++++++++++++-------- src/lib/Characteristic.ts | 9 +++++---- src/lib/Service.ts | 2 +- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/lib/Characteristic.spec.ts b/src/lib/Characteristic.spec.ts index 67765dba5..19d478b26 100644 --- a/src/lib/Characteristic.spec.ts +++ b/src/lib/Characteristic.spec.ts @@ -304,10 +304,8 @@ describe('Characteristic', () => { const VALUE = 'NewValue'; const listenerCallback = jest.fn(); - // @ts-expect-error characteristic.handleSetRequest(VALUE); characteristic.on(CharacteristicEventTypes.SET, listenerCallback); - // @ts-expect-error characteristic.handleSetRequest(VALUE); expect(listenerCallback).toHaveBeenCalledTimes(1); @@ -316,19 +314,25 @@ describe('Characteristic', () => { describe(`@${CharacteristicEventTypes.CHANGE}`, () => { - it('should call listeners for the event when the characteristic is event-only, and the value is set', () => { + 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', () => { diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts index 501dd3f8b..25ac8dafd 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -518,6 +518,7 @@ export class Characteristic extends EventEmitter { }); } + // TODO document difference to updateValue (also in Service.ts) setValue(value: CharacteristicValue, callback?: CharacteristicSetCallback, context?: any): Characteristic { this.handleSetRequest(value, undefined, context).then(value => { if (callback) { @@ -627,10 +628,6 @@ export class Characteristic extends EventEmitter { * @internal */ handleSetRequest(value: CharacteristicValue, connection?: HAPConnection, context?: any): Promise { - if (!this.props.perms.includes(Perms.PAIRED_WRITE)) { // check if we are allowed to write to this characteristic - return Promise.reject(Status.READ_ONLY_CHARACTERISTIC); - } - this.status = Status.SUCCESS; // TODO return proper hap status code if incoming value is not valid! @@ -642,6 +639,10 @@ export class Characteristic extends EventEmitter { this.emit(CharacteristicEventTypes.CHANGE, { originator: connection, oldValue: oldValue, newValue: value, context: context }); return Promise.resolve(); } else { + // the executor of the promise is called on the next tick, thus we set the updated value immediately until the set + // event is executed. + this.value = value; + return new Promise((resolve, reject) => { try { this.emit(CharacteristicEventTypes.SET, value, once((status?: Error | Status | null, writeResponse?: Nullable) => { diff --git a/src/lib/Service.ts b/src/lib/Service.ts index 05186c686..087705408 100644 --- a/src/lib/Service.ts +++ b/src/lib/Service.ts @@ -164,7 +164,7 @@ export class Service extends EventEmitter { this.getCharacteristic(Characteristic.Name) || this.addCharacteristic(Characteristic.Name); - nameCharacteristic.setValue(displayName); + nameCharacteristic.updateValue(displayName); } } From 7f00b15e7cd3d26fd9c477e384b4265e98eb4826 Mon Sep 17 00:00:00 2001 From: Supereg Date: Thu, 8 Oct 2020 04:05:54 +0200 Subject: [PATCH 34/70] * Reworked input validation for characteristic values and properties * Send correct -70410 response when receiving invalid value in request (out of range etc) * GET handler is now called on /accessories to have latest value on discover * Added more documentation --- package-lock.json | 1178 ++++++++++++-------- package.json | 9 +- src/internal-types.ts | 73 +- src/lib/Accessory.ts | 166 +-- src/lib/AccessoryLoader.ts | 9 +- src/lib/Characteristic.spec.ts | 104 +- src/lib/Characteristic.ts | 872 ++++++++++----- src/lib/HAPServer.ts | 76 +- src/lib/Service.ts | 110 +- src/lib/camera/RTPStreamManagement.ts | 17 +- src/lib/controller/CameraController.ts | 12 +- src/lib/controller/RemoteController.ts | 44 +- src/lib/datastream/DataStreamManagement.ts | 9 +- src/lib/gen/HomeKit-Bridge.ts | 63 +- src/lib/gen/HomeKit-DataStream.ts | 6 +- src/lib/gen/HomeKit-Remote.ts | 15 +- src/lib/gen/HomeKit-TV.ts | 51 +- src/lib/gen/HomeKit.ts | 533 +++------ src/types.ts | 26 - 19 files changed, 1928 insertions(+), 1445 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8cd2f6c7e..006cdc958 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,21 +410,14 @@ } }, "@homebridge/ciao": { - "version": "1.1.0-beta.17", - "resolved": "https://registry.npmjs.org/@homebridge/ciao/-/ciao-1.1.0-beta.17.tgz", - "integrity": "sha512-zl4VTFrjX+mmJXnA/0pGl/Ej4Hy6iLMefBEyZGRSgk+1mygHcygQaGvk+bAuYm5VJ72XJVLCoFVxzG9CDc8g2A==", + "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", "source-map-support": "^0.5.19", "tslib": "^2.0.1" - }, - "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==" - } } }, "@istanbuljs/load-nyc-config": { @@ -433,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", @@ -477,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", @@ -517,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", @@ -547,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", @@ -597,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", @@ -641,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", @@ -690,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", @@ -710,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", @@ -723,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", @@ -753,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", @@ -768,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", @@ -814,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", @@ -850,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", @@ -880,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" + } } } }, @@ -914,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", @@ -927,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", @@ -946,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" @@ -1023,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": { @@ -1050,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": { @@ -1078,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", @@ -1217,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", @@ -1280,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", @@ -1292,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", @@ -1311,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" } }, @@ -1641,14 +1718,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": { @@ -1744,7 +1813,8 @@ "decimal.js": { "version": "10.2.1", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", - "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==" + "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==", + "dev": true }, "decode-uri-component": { "version": "0.2.0", @@ -2001,23 +2071,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", @@ -2153,10 +2223,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", @@ -2729,20 +2798,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", @@ -2772,43 +2841,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", @@ -2922,35 +3005,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", @@ -2985,13 +3068,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" @@ -3021,22 +3118,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", @@ -3071,13 +3168,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" @@ -3086,24 +3197,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", @@ -3131,27 +3242,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", @@ -3179,6 +3304,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" + } } } }, @@ -3189,12 +3328,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", @@ -3202,18 +3341,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", @@ -3241,39 +3380,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", @@ -3302,13 +3455,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" @@ -3317,19 +3484,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", @@ -3365,12 +3532,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" @@ -3379,21 +3546,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", @@ -3423,21 +3590,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": { @@ -3447,12 +3614,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" @@ -3461,14 +3628,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", @@ -3477,9 +3644,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", @@ -3511,19 +3678,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", @@ -3567,25 +3734,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", @@ -3613,24 +3780,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", @@ -3662,37 +3843,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", @@ -3720,47 +3901,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", @@ -3788,13 +3983,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": "*", @@ -3802,32 +4011,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", @@ -3857,21 +4067,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": { @@ -3881,12 +4091,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" @@ -3943,23 +4153,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", @@ -4001,12 +4211,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" @@ -4015,24 +4225,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", @@ -4060,13 +4270,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": "*", @@ -4980,9 +5204,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": { @@ -5214,9 +5438,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", @@ -5424,9 +5648,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": { @@ -5739,9 +5963,9 @@ } }, "tslib": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", - "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==" + "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", @@ -5867,9 +6091,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 }, @@ -5962,9 +6186,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 951cdb68c..d74ab787b 100644 --- a/package.json +++ b/package.json @@ -49,25 +49,24 @@ "@types" ], "dependencies": { - "@homebridge/ciao": "~1.1.0-beta.17", + "@homebridge/ciao": "~1.1.0-beta.23", "fast-srp-hap": "2.0.2", "tweetnacl": "^1.0.3", "debug": "^4.3.0", - "decimal.js": "^10.2.1", "ip": "^1.1.3", "node-persist": "^0.0.11", "futoin-hkdf": "~1.3.2", "source-map-support": "^0.5.19", - "tslib": "^2.0.1" + "tslib": "^2.0.2" }, "devDependencies": { "@types/debug": "^4.1.5", "@types/jest": "^26.0.14", "@types/node": "^10.17.20", - "jest": "^26.4.2", + "jest": "^26.5.2", "rimraf": "^3.0.2", "semver": "^7.3.2", - "simple-plist": "^1.1.0", + "simple-plist": "^1.1.1", "ts-jest": "^26.4.1", "ts-node": "^9.0.0", "typescript": "^4.0.3" diff --git a/src/internal-types.ts b/src/internal-types.ts index 11e512dc1..8f017ae15 100644 --- a/src/internal-types.ts +++ b/src/internal-types.ts @@ -1,7 +1,66 @@ import { Formats, Perms, Units } from "./lib/Characteristic"; -import { Status } from "./lib/HAPServer"; +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"?: [number, 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, @@ -18,14 +77,14 @@ export interface CharacteristicsReadRequest { export interface PartialCharacteristicReadDataValue { value: CharacteristicValue | null, - status?: Status.SUCCESS, + status?: HAPStatus.SUCCESS, // type type?: string, // characteristics uuid // metadata - format?: Formats, - unit?: Units, + format?: string, + unit?: string, minValue?: number, maxValue?: number, minStep?: number, @@ -39,7 +98,7 @@ export interface PartialCharacteristicReadDataValue { } export interface PartialCharacteristicReadError { - status: Status, + status: HAPStatus, } export interface CharacteristicReadDataValue extends PartialCharacteristicReadDataValue { @@ -83,11 +142,11 @@ export interface PartialCharacteristicWriteDataValue { value?: CharacteristicValue | null, ev?: boolean, // event - status?: Status.SUCCESS, + status?: HAPStatus.SUCCESS, } export interface PartialCharacteristicWriteError { - status: Status, + status: HAPStatus, value?: undefined, // defined to make things easier } diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index 76f469381..92d5d684e 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -5,6 +5,7 @@ import createDebug from 'debug'; import { EventEmitter } from "events"; import net from "net"; import { + AccessoryJsonObject, CharacteristicId, CharacteristicReadData, CharacteristicsReadRequest, @@ -25,7 +26,6 @@ import { IPAddress, MacAddress, Nullable, - ToHAPOptions, VoidCallback, WithUUID, } from '../types'; @@ -59,25 +59,19 @@ import { HAPHTTPCode, HAPServer, HAPServerEventTypes, + HAPStatus, IdentifyCallback, ListPairingsCallback, PairCallback, ReadCharacteristicsCallback, RemovePairingCallback, ResourceRequestCallback, - Status, WriteCharacteristicsCallback } 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 { 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"; @@ -978,33 +972,49 @@ export class Accessory extends EventEmitter { } /** - * 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. */ - private 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!"); - const servicesHAP = []; + const accessory: AccessoryJsonObject = { + aid: this.aid!, + services: await Promise.all(this.services.map(service => service.toHAP(connection))), + }; - for (let index in this.services) { - const service = this.services[index]; - servicesHAP.push(service.toHAP(opt)); + const accessories: AccessoryJsonObject[] = [accessory]; + + if (this.bridge) { + for (const accessory of this.bridgedAccessories) { + accessories.push((await accessory.toHAP(connection))[0]); + } } - const accessoriesHAP = [{ - aid: this.aid, - services: servicesHAP - }]; + return accessories; + } - // now add any Accessories we are bridging - for (let index in this.bridgedAccessories) { - const accessory = this.bridgedAccessories[index]; - const bridgedAccessoryHAP = accessory.toHAP(opt); + /** + * Returns a JSON representation of this accessory without characteristic values. + */ + private internalHAPRepresentation(): AccessoryJsonObject[] { + assert(this.aid, "aid cannot be undefined for accessory '" + this.displayName + "'"); + assert(this.services.length, "accessory '" + this.displayName + "' does not have any services!"); + + const accessory: AccessoryJsonObject = { + aid: this.aid!, + services: this.services.map(service => service.internalHAPRepresentation()), + }; + + 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.bridge) { + for (const accessory of this.bridgedAccessories) { + accessories.push(accessory.internalHAPRepresentation()[0]); + } } - return accessoriesHAP; + return accessories; } /** @@ -1096,7 +1106,7 @@ 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". - const config = this.toHAP({omitValues: true}); + const config = this.internalHAPRepresentation() // now convert it into a hash code and check it against the last one we made, if we have one const shasum = crypto.createHash('sha1'); @@ -1184,7 +1194,7 @@ 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". - const config = this.toHAP({omitValues: true}); + const config = this.internalHAPRepresentation() // now convert it into a hash code and check it against the last one we made, if we have one const shasum = crypto.createHash('sha1'); @@ -1294,14 +1304,16 @@ export class Accessory extends EventEmitter { callback(0, this._accessoryInfo.listPairings()); }; - private handleAccessories(callback: AccessoriesCallback): void { + 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({ - 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 }); }); } @@ -1320,7 +1332,7 @@ export class Accessory extends EventEmitter { let emitted = accessory?.emit(AccessoryEventTypes.CHARACTERISTIC_WARNING, CharacteristicWarningType.SLOW_READ, iid); if (!emitted) { const characteristic = accessory?.getCharacteristicByIID(iid); - console.log(`The read handler for the characteristic '${characteristic?.displayName || iid}' on the accessory '${accessory?.displayName}' was slow to respond!`); + console.warn(`The read handler for the characteristic '${characteristic?.displayName || iid}' on the accessory '${accessory?.displayName}' was slow to respond!`); } } @@ -1337,14 +1349,14 @@ export class Accessory extends EventEmitter { let emitted = accessory?.emit(AccessoryEventTypes.CHARACTERISTIC_WARNING, CharacteristicWarningType.TIMEOUT_READ, iid); if (!emitted) { const characteristic = accessory?.getCharacteristicByIID(iid); - console.log("The read handler for the characteristic '" + (characteristic?.displayName || iid) + "' on the accessory '" + accessory?.displayName + + console.error("The read handler for the characteristic '" + (characteristic?.displayName || iid) + "' on the accessory '" + accessory?.displayName + "' didn't respond at all!. Please check that you properly call the callback!"); } characteristics.push({ aid: aid, iid: iid, - status: Status.OPERATION_TIMED_OUT, + status: HAPStatus.OPERATION_TIMED_OUT, }); } missingCharacteristics.clear(); @@ -1358,26 +1370,27 @@ export class Accessory extends EventEmitter { missingCharacteristics.add(name); this.handleCharacteristicRead(connection, id, request).then(value => { - missingCharacteristics.delete(name); - characteristics.push({ + return { aid: id.aid, iid: id.iid, ...value, - }); + }; }, reason => { // this error block is only called if hap-nodejs itself messed up console.error(`[${this.displayName}] Read request for characteristic ${id} encountered an error: ${reason.stack}`) - missingCharacteristics.delete(name); - characteristics.push({ + return { aid: id.aid, iid: id.iid, - status: Status.SERVICE_COMMUNICATION_FAILURE, - }); - }).then(() => { + status: HAPStatus.SERVICE_COMMUNICATION_FAILURE, + }; + }).then(value => { if (!timeout) { return; // if timeout is undefined, response was already sent out } + missingCharacteristics.delete(name); + characteristics.push(value); + if (missingCharacteristics.size === 0) { if (timeout) { clearTimeout(timeout); @@ -1394,12 +1407,12 @@ export class Accessory extends EventEmitter { 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: Status.INVALID_VALUE_IN_REQUEST }; + return { status: HAPStatus.INVALID_VALUE_IN_REQUEST }; } 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: Status.WRITE_ONLY_CHARACTERISTIC }; + return { status: HAPStatus.WRITE_ONLY_CHARACTERISTIC }; } if (characteristic.props.adminOnlyAccess && characteristic.props.adminOnlyAccess.includes(Access.READ)) { @@ -1410,7 +1423,7 @@ export class Accessory extends EventEmitter { } if (!verifiable || !this._accessoryInfo!.hasAdminPermissions(connection.username!)) { - return { status: Status.INSUFFICIENT_PRIVILEGES }; + return { status: HAPStatus.INSUFFICIENT_PRIVILEGES }; } } @@ -1440,9 +1453,9 @@ export class Accessory extends EventEmitter { } return data; - }, (reason: Status) => { + }, (reason: HAPStatus) => { // @ts-expect-error - debug('[%s] Error getting value for characteristic "%s": %s', this.displayName, characteristic.displayName, Status[reason]); + debug('[%s] Error getting value for characteristic "%s": %s', this.displayName, characteristic.displayName, HAPStatus[reason]); return { status: reason }; }); } @@ -1479,7 +1492,7 @@ export class Accessory extends EventEmitter { let emitted = accessory?.emit(AccessoryEventTypes.CHARACTERISTIC_WARNING, CharacteristicWarningType.SLOW_WRITE, iid); if (!emitted) { const characteristic = accessory?.getCharacteristicByIID(iid); - console.log(`The write handler for the characteristic '${characteristic?.displayName || iid}' on the accessory '${accessory?.displayName}' was slow to respond!`); + console.warn(`The write handler for the characteristic '${characteristic?.displayName || iid}' on the accessory '${accessory?.displayName}' was slow to respond!`); } } @@ -1496,14 +1509,14 @@ export class Accessory extends EventEmitter { let emitted = accessory?.emit(AccessoryEventTypes.CHARACTERISTIC_WARNING, CharacteristicWarningType.TIMEOUT_WRITE, iid); if (!emitted) { const characteristic = accessory?.getCharacteristicByIID(iid); - console.log("The write handler for the characteristic '" + (characteristic?.displayName || iid) + "' on the accessory '" + accessory?.displayName + + console.error("The write handler for the characteristic '" + (characteristic?.displayName || iid) + "' on the accessory '" + accessory?.displayName + "' didn't respond at all!. Please check that you properly call the callback!"); } characteristics.push({ aid: aid, iid: iid, - status: Status.OPERATION_TIMED_OUT, + status: HAPStatus.OPERATION_TIMED_OUT, }); } missingCharacteristics.clear(); @@ -1517,26 +1530,27 @@ export class Accessory extends EventEmitter { missingCharacteristics.add(id); this.handleCharacteristicWrite(connection, data, writeState).then(value => { - missingCharacteristics.delete(id); - characteristics.push({ + return { aid: data.aid, iid: data.iid, ...value, - }); + }; }, reason => { // this error block is only called if hap-nodejs itself messed up console.error(`[${this.displayName}] Write request for characteristic ${id} encountered an error: ${reason.stack}`) - missingCharacteristics.delete(id); - characteristics.push({ + return { aid: data.aid, iid: data.iid, - status: Status.SERVICE_COMMUNICATION_FAILURE, - }); - }).then(() => { + status: HAPStatus.SERVICE_COMMUNICATION_FAILURE, + }; + }).then(value => { if (!timeout) { return; // if timeout is undefined, response was already sent out } + missingCharacteristics.delete(id); + characteristics.push(value); + if (missingCharacteristics.size === 0) { // if everything returned send the response if (timeout) { clearTimeout(timeout); @@ -1554,17 +1568,17 @@ export class Accessory extends EventEmitter { 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: Status.INVALID_VALUE_IN_REQUEST }; + return { status: HAPStatus.INVALID_VALUE_IN_REQUEST }; } if (writeState === WriteRequestState.TIMED_WRITE_REJECTED) { - return { status: Status.INVALID_VALUE_IN_REQUEST }; + return { status: HAPStatus.INVALID_VALUE_IN_REQUEST }; } 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: Status.NOTIFICATION_NOT_SUPPORTED }; + return { status: HAPStatus.NOTIFICATION_NOT_SUPPORTED }; } if (characteristic.props.adminOnlyAccess && characteristic.props.adminOnlyAccess.includes(Access.NOTIFY)) { @@ -1575,7 +1589,7 @@ export class Accessory extends EventEmitter { } if (!verifiable || !this._accessoryInfo!.hasAdminPermissions(connection.username!)) { - return { status: Status.INSUFFICIENT_PRIVILEGES }; + return { status: HAPStatus.INSUFFICIENT_PRIVILEGES }; } } @@ -1598,7 +1612,7 @@ export class Accessory extends EventEmitter { 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: Status.READ_ONLY_CHARACTERISTIC }; + return { status: HAPStatus.READ_ONLY_CHARACTERISTIC }; } if (characteristic.props.adminOnlyAccess && characteristic.props.adminOnlyAccess.includes(Access.WRITE)) { @@ -1609,7 +1623,7 @@ export class Accessory extends EventEmitter { } if (!verifiable || !this._accessoryInfo!.hasAdminPermissions(connection.username!)) { - return { status: Status.INSUFFICIENT_PRIVILEGES }; + return { status: HAPStatus.INSUFFICIENT_PRIVILEGES }; } } @@ -1626,13 +1640,13 @@ export class Accessory extends EventEmitter { } if (!allowWrite) { - return { status: Status.INSUFFICIENT_AUTHORIZATION }; + return { status: HAPStatus.INSUFFICIENT_AUTHORIZATION }; } } 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: Status.INVALID_VALUE_IN_REQUEST }; + return { status: HAPStatus.INVALID_VALUE_IN_REQUEST }; } return characteristic.handleSetRequest(data.value, connection).then(value => { @@ -1642,9 +1656,9 @@ export class Accessory extends EventEmitter { ev: evResponse, }; - }, (status: Status) => { + }, (status: HAPStatus) => { // @ts-expect-error - debug('[%s] Error setting Characteristic "%s" to value %s: ', this.displayName, characteristic.displayName, data.value, Status[status]); + debug('[%s] Error setting Characteristic "%s" to value %s: ', this.displayName, characteristic.displayName, data.value, HAPStatus[status]); return { status: status }; }); @@ -1671,20 +1685,20 @@ export class Accessory extends EventEmitter { if (!controller) { debug("[%s] received snapshot request though no camera controller was associated!"); - callback({ httpCode: HAPHTTPCode.NOT_FOUND, status: Status.RESOURCE_DOES_NOT_EXIST }); + callback({ httpCode: HAPHTTPCode.NOT_FOUND, status: HAPStatus.RESOURCE_DOES_NOT_EXIST }); return; } controller.handleSnapshotRequest(data["image-height"], data["image-width"], accessory?.displayName).then(buffer => { callback(undefined, buffer); - }, (status: Status) => { + }, (status: HAPStatus) => { callback({ httpCode: HAPHTTPCode.OK, status: status }); }); return; } debug("[%s] received request for unsupported image type: " + data["resource-type"], this._accessoryInfo?.username); - callback({ httpCode: HAPHTTPCode.NOT_FOUND, status: Status.RESOURCE_DOES_NOT_EXIST}); + callback({ httpCode: HAPHTTPCode.NOT_FOUND, status: HAPStatus.RESOURCE_DOES_NOT_EXIST}); } private handleHAPConnectionClosed(connection: HAPConnection): void { diff --git a/src/lib/AccessoryLoader.ts b/src/lib/AccessoryLoader.ts index a57b94dcd..5a436583a 100644 --- a/src/lib/AccessoryLoader.ts +++ b/src/lib/AccessoryLoader.ts @@ -134,11 +134,7 @@ export function parseServiceJSON(json: any) { export function parseCharacteristicJSON(json: any) { const characteristicUUID = json.cType; - const 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, @@ -147,6 +143,9 @@ export function parseCharacteristicJSON(json: any) { perms: json.perms // example: ["pw","pr","ev"] }); + // copy simple properties + characteristic.value = json.initialValue; + // monkey-patch legacy "locals" property which used to exist. // @ts-ignore characteristic.locals = json.locals; diff --git a/src/lib/Characteristic.spec.ts b/src/lib/Characteristic.spec.ts index 19d478b26..5d1f7b639 100644 --- a/src/lib/Characteristic.spec.ts +++ b/src/lib/Characteristic.spec.ts @@ -1,19 +1,20 @@ -import './gen'; import { Access, Characteristic, CharacteristicEventTypes, CharacteristicProps, Formats, + HAPStatus, Perms, - SerializedCharacteristic, Status, + SerializedCharacteristic, Units, uuid } from '..'; +import './gen'; -const createCharacteristic = (type: Formats, customUUID?: string) => { +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, customUUID?: string) => { return new Characteristic('Test', customUUID || uuid.generate('Foo'), props); @@ -117,7 +118,7 @@ describe('Characteristic', () => { const characteristic = createCharacteristic(Formats.BOOL, Characteristic.ProgrammableSwitchEvent.UUID); characteristic.handleGetRequest().then(() => { - expect(characteristic.status).toEqual(Status.SUCCESS); + expect(characteristic.status).toEqual(HAPStatus.SUCCESS); expect(characteristic.value).toEqual(null); callback(); }); @@ -127,20 +128,76 @@ describe('Characteristic', () => { const characteristic = createCharacteristic(Formats.BOOL); characteristic.handleGetRequest().then(() => { - expect(characteristic.status).toEqual(Status.SUCCESS); + 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); // @ts-expect-error - expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE); }); it('should validate a float property', () => { @@ -151,10 +208,10 @@ describe('Characteristic', () => { perms: [], }); // @ts-expect-error - expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + 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, @@ -162,77 +219,77 @@ describe('Characteristic', () => { perms: [], }); // @ts-expect-error - expect(characteristic.validateValue(VALUE)).toEqual(1.5); + expect(characteristic.validateUserInput(VALUE)).toEqual(1.6); }); it('should validate a UINT8 property', () => { const VALUE = 10; const characteristic = createCharacteristic(Formats.UINT8); // @ts-expect-error - expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE); }); it('should validate a UINT16 property', () => { const VALUE = 10; const characteristic = createCharacteristic(Formats.UINT16); // @ts-expect-error - expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE); }); it('should validate a UINT32 property', () => { const VALUE = 10; const characteristic = createCharacteristic(Formats.UINT32); // @ts-expect-error - expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE); }); it('should validate a UINT64 property', () => { const VALUE = 10; const characteristic = createCharacteristic(Formats.UINT64); // @ts-expect-error - expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE); }); it('should validate a boolean property', () => { const VALUE = true; const characteristic = createCharacteristic(Formats.BOOL); // @ts-expect-error - expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE); }); it('should validate a string property', () => { const VALUE = 'Test'; const characteristic = createCharacteristic(Formats.STRING); // @ts-expect-error - expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + 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); // @ts-expect-error - expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE); }); it('should validate a TLV8 property', () => { const VALUE = ''; const characteristic = createCharacteristic(Formats.TLV8); // @ts-expect-error - expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE); }); it('should validate a dictionary property', () => { const VALUE = {}; const characteristic = createCharacteristic(Formats.DICTIONARY); // @ts-expect-error - expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE); }); it('should validate an array property', () => { const VALUE = ['asd']; const characteristic = createCharacteristic(Formats.ARRAY); // @ts-expect-error - expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE); }); }); @@ -344,6 +401,7 @@ describe('Characteristic', () => { const updateValueCallback = jest.fn(); characteristic.on(CharacteristicEventTypes.CHANGE, listenerCallback); + // noinspection JSDeprecatedSymbols characteristic.updateValue(VALUE, updateValueCallback) expect(listenerCallback).toHaveBeenCalledTimes(1); @@ -420,7 +478,7 @@ describe('Characteristic', () => { 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); diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts index 25ac8dafd..07e5c3cff 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -1,9 +1,10 @@ +import assert from "assert"; import createDebug from "debug"; -import Decimal from 'decimal.js'; import { EventEmitter } from "events"; -import { CharacteristicValue, HapCharacteristic, Nullable, ToHAPOptions, VoidCallback, } from '../types'; +import { CharacteristicJsonObject } from "../internal-types"; +import { CharacteristicValue, Nullable, VoidCallback, } from '../types'; import * as HomeKitTypes from './gen'; -import { Status } from "./HAPServer"; +import { HAPStatus } from "./HAPServer"; import { IdentifierCache } from './model/IdentifierCache'; import { clone } from "./util/clone"; import { HAPConnection } from "./util/eventedhttp"; @@ -14,28 +15,79 @@ 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 /** @@ -48,7 +100,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', @@ -57,16 +109,28 @@ export const enum Perms { } export interface CharacteristicProps { - format: Formats; + format: Formats | string; perms: Perms[]; - unit?: Units; + 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[]; + /** + * Two element array where the first value specifies the lowest valid value and + * the second element specifies the highest valid value. + */ validValueRanges?: [number, number]; adminOnlyAccess?: Access[]; } @@ -107,13 +171,23 @@ export const enum CharacteristicEventTypes { * 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", } -export type CharacteristicGetCallback = (status?: Status | null | Error, value?: Nullable) => void; -export type CharacteristicSetCallback = (error?: Status | null | Error, writeResponse?: Nullable) => void; +export type CharacteristicGetCallback = (status?: HAPStatus | null | Error, value?: Nullable) => void; +export type CharacteristicSetCallback = (error?: HAPStatus | null | Error, writeResponse?: Nullable) => void; export type AdditionalAuthorizationHandler = (additionalAuthorizationData: string | undefined) => boolean; @@ -122,13 +196,34 @@ 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 + */ 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; } @@ -136,50 +231,29 @@ export declare interface Characteristic { /** * 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 { /** * @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; @@ -409,9 +483,11 @@ export class Characteristic extends EventEmitter { static WiFiConfigurationControl: typeof HomeKitTypes.Generated.WiFiConfigurationControl; // 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: Status = Status.SUCCESS; + status: HAPStatus = HAPStatus.SUCCESS; props: CharacteristicProps; private subscriptions: number = 0; @@ -420,51 +496,80 @@ export class Characteristic extends EventEmitter { */ additionalAuthorizationHandler?: AdditionalAuthorizationHandler; - public constructor(public displayName: string, public UUID: string, props?: CharacteristicProps) { + 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 } /** - * 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) - * } + * @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 (let key in (props || {})) - if (Object.prototype.hasOwnProperty.call(props, key)) { - // @ts-ignore - this.props[key] = props[key]; + // 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, "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) { + console.warn(""); + 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'!"); @@ -474,6 +579,7 @@ export class Characteristic extends EventEmitter { return this; } + // 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 @@ -518,8 +624,37 @@ export class Characteristic extends EventEmitter { }); } - // TODO document difference to updateValue (also in Service.ts) + /** + * 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 @@ -537,14 +672,27 @@ export class Characteristic extends EventEmitter { 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 = Status.SUCCESS; - value = this.validateValue(value); //validateValue returns a value that has be coerced into a valid value. - - if (value == undefined) { - value = this.getDefaultValue(); - } + this.status = HAPStatus.SUCCESS; + value = this.validateUserInput(value); const oldValue = this.value; this.value = value; @@ -566,7 +714,7 @@ export class Characteristic extends EventEmitter { */ handleGetRequest(connection?: HAPConnection, context?: any): Promise> { if (!this.props.perms.includes(Perms.PAIRED_READ)) { // check if we are allowed to read from this characteristic - return Promise.reject(Status.WRITE_ONLY_CHARACTERISTIC); + return Promise.reject(HAPStatus.WRITE_ONLY_CHARACTERISTIC); } if (this.UUID === Characteristic.ProgrammableSwitchEvent.UUID) { @@ -580,7 +728,7 @@ export class Characteristic extends EventEmitter { return new Promise((resolve, reject) => { try { - this.emit(CharacteristicEventTypes.GET, once((status?: Error | Status | null, value?: Nullable) => { + this.emit(CharacteristicEventTypes.GET, once((status?: Error | HAPStatus | null, value?: Nullable) => { if (status) { if (typeof status === "number") { this.status = status; @@ -592,13 +740,9 @@ export class Characteristic extends EventEmitter { return; } - this.status = Status.SUCCESS; - - value = this.validateValue(value); // validateValue returns a value that has be coerced into a valid value. - if (value == null) { // null or undefined - value = this.getDefaultValue(); - } + this.status = HAPStatus.SUCCESS; + value = this.validateUserInput(value); const oldValue = this.value; this.value = value; @@ -610,8 +754,8 @@ export class Characteristic extends EventEmitter { }), context, connection); } catch (error) { console.warn(`[${this.displayName}] Unhandled error thrown inside read handler for characteristic: ${error.stack}`); - this.status = Status.SERVICE_COMMUNICATION_FAILURE; - reject(Status.SERVICE_COMMUNICATION_FAILURE); + this.status = HAPStatus.SERVICE_COMMUNICATION_FAILURE; + reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE); } }); } @@ -628,10 +772,16 @@ export class Characteristic extends EventEmitter { * @internal */ handleSetRequest(value: CharacteristicValue, connection?: HAPConnection, context?: any): Promise { - this.status = Status.SUCCESS; + this.status = HAPStatus.SUCCESS; + + if (!this.validClientSuppliedValue(value)) { + return Promise.reject(HAPStatus.INVALID_VALUE_IN_REQUEST); + } + + if (isNumericFormat(this.props.format)) { + value = this.roundNumericValue(value as number); + } - // TODO return proper hap status code if incoming value is not valid! - value = this.validateValue(value)!; // validateValue returns a value that has be coerced into a valid value. const oldValue = this.value; if (this.listeners(CharacteristicEventTypes.SET).length === 0) { @@ -645,7 +795,7 @@ export class Characteristic extends EventEmitter { return new Promise((resolve, reject) => { try { - this.emit(CharacteristicEventTypes.SET, value, once((status?: Error | Status | null, writeResponse?: Nullable) => { + this.emit(CharacteristicEventTypes.SET, value, once((status?: Error | HAPStatus | null, writeResponse?: Nullable) => { if (status) { if (typeof status === "number") { this.status = status; @@ -657,7 +807,7 @@ export class Characteristic extends EventEmitter { return; } - this.status = Status.SUCCESS; + 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 @@ -675,14 +825,15 @@ export class Characteristic extends EventEmitter { }), context, connection); } catch (error) { console.warn(`[${this.displayName}] Unhandled error thrown inside write handler for characteristic: ${error.stack}`); - this.status = Status.SERVICE_COMMUNICATION_FAILURE; - reject(Status.SERVICE_COMMUNICATION_FAILURE); + 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 { @@ -693,6 +844,8 @@ export class Characteristic extends EventEmitter { } /** + * 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 { @@ -705,6 +858,7 @@ export class Characteristic extends EventEmitter { } protected getDefaultValue(): Nullable { + // noinspection JSDeprecatedSymbols switch (this.props.format) { case Formats.BOOL: return false; @@ -723,222 +877,367 @@ export class Characteristic extends EventEmitter { } } - private 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; + 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; + } + + /** + * 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 !== "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 !== "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 !== "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 !== "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 !== "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; + 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; - //All of the following data types return from this switch. - case Formats.BOOL: - // @ts-ignore - return (newValue == true); //We don't need to make sure this returns true or false case Formats.STRING: { - let myString = newValue as string || ''; //If null or undefined or anything odd, make it a blank string - myString = String(myString); - let 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 + 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: { - let 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 == undefined? null: newValue; + 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 == undefined? null: newValue; } - if (isNumericType) { - if (newValue === false) { - return 0; + if (typeof value === "number") { + if (numericMin != null && value < numericMin) { + return false; } - if (newValue === true) { - return 1; + if (numericMax != null && value > numericMax) { + return false; } - 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 determine the lowest value within the step. - try { - let decimalVal = new Decimal(parseFloat(newValue as string)); - const 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 (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 true; + } + + /** + * 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) { + console.warn(`[${this.displayName}] characteristic was supplied illegal value: undefined. Supplying illegal values will throw errors in the future!`); + return this.getDefaultValue(); + } else if (value === null) { + return null; // null is allowed + } + + + let numericMin: number | undefined = undefined; + let numericMax: number | undefined = undefined; + + switch (this.props.format) { + 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 === "string") { + console.warn(`[${this.displayName}] 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 === "string") { + console.warn(`[${this.displayName}] 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 === "string") { + console.warn(`[${this.displayName}] 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 === "string") { + console.warn(`[${this.displayName}] 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 === "string") { + console.warn(`[${this.displayName}] 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 === "string") { + console.warn(`[${this.displayName}] 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") { + console.warn(`[${this.displayName}] 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)); + } + + 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) { + console.warn(`[${this.displayName}] characteristic was supplied illegal value: string '${value}' exceeded max length of ${maxLength}. Supplying illegal values will throw errors in the future!`); + value = value.substring(0, maxLength); + } + + return value; + } + case Formats.DATA: + 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 value; // we trust that this is valid tlv8 + } + + if (typeof value === "number") { + if (numericMin != null && value < numericMin) { + console.warn(`[${this.displayName}] characteristic was supplied illegal value: number ${value} exceeded minimum of ${numericMin}. Supplying illegal values will throw errors in the future!`); + value = numericMin; } - if (this.props.validValues !== undefined) - if (!this.props.validValues.includes(newValue as number)) - return this.value!; //Fails Valid Values Test - if (this.props.validValueRanges !== undefined) { //This is another way Apple has to handle min/max - if (newValue! < this.props.validValueRanges[0]) - newValue = this.props.validValueRanges[0]; - if (newValue! > this.props.validValueRanges[1]) - newValue = this.props.validValueRanges[1]; + if (numericMax != null && value > numericMax) { + console.warn(`[${this.displayName}] characteristic was supplied illegal value: number ${value} exceeded maximum of ${numericMax}. Supplying illegal values will throw errors in the future!`); + 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]) { + console.warn(`[${this.displayName}] 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]) { + console.warn(`[${this.displayName}] 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 newValue == undefined? null: newValue; + + 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 + * Returns a JSON representation of this characteristic suitable for delivering to HAP clients. + * @internal used to generate response to /accessories query */ - toHAP(opt?: ToHAPOptions): HapCharacteristic { - // ensure our value fits within our constraints if present - let value = this.value; // TODO query value for characteristics which support it - - 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) { - const pow = Math.pow(10, decimalPlaces(this.props.minStep)); - value = Math.round(value * pow) / pow; - } - } - } - if (this.UUID === Characteristic.ProgrammableSwitchEvent.UUID) { + 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 - value = 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 = { - iid: this.iid!, + 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, HomeKitTypes.BASE_UUID), + iid: this.iid!, + 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) { - const 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 omitted - hap.maxLen = len; - } + 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, } - // if we're not readable, omit the "value" property - otherwise iOS will complain about non-compliance - if (this.props.perms.indexOf(Perms.PAIRED_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; } /** + * Serialize characteristic into json string. * - * @param characteristic - * @internal + * @param characteristic - Characteristic object. + * @internal used to store characteristic on disk */ static serialize(characteristic: Characteristic): SerializedCharacteristic { return { @@ -951,9 +1250,10 @@ export class Characteristic extends EventEmitter { }; /** + * Deserialize characteristic from json string. * - * @param json - * @internal + * @param json - Json string representing a characteristic. + * @internal used to recreate characteristic from disk */ static deserialize(json: SerializedCharacteristic): Characteristic { const characteristic = new Characteristic(json.displayName, json.UUID, json.props); @@ -965,31 +1265,37 @@ export class Characteristic extends EventEmitter { } -// Mike Samuel -// http://stackoverflow.com/questions/10454518/javascript-how-to-retrieve-the-number-of-decimals-of-a-string-number -function decimalPlaces(num: number) { - const 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 = Status.SERVICE_COMMUNICATION_FAILURE; + let errorValue = HAPStatus.SERVICE_COMMUNICATION_FAILURE; if (numberPattern.test(error.message)) { const value = parseInt(error.message); - if (value >= Status.INSUFFICIENT_PRIVILEGES && value <= Status.NOT_ALLOWED_IN_CURRENT_STATE) { + 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/HAPServer.ts b/src/lib/HAPServer.ts index 7a79f042c..0ecd09c08 100644 --- a/src/lib/HAPServer.ts +++ b/src/lib/HAPServer.ts @@ -6,6 +6,7 @@ import { IncomingMessage, ServerResponse } from "http"; import tweetnacl from 'tweetnacl'; import { URL } from 'url'; import { + AccessoriesResponse, CharacteristicId, CharacteristicsReadRequest, CharacteristicsReadResponse, @@ -90,7 +91,7 @@ export const enum Codes { BUSY = 0x07 // cannot accept pairing request at this time } -export const enum Status { // TODO rename before we spread adoption +export const enum HAPStatus { // noinspection JSUnusedGlobalSymbols SUCCESS = 0, INSUFFICIENT_PRIVILEGES = -70401, @@ -109,11 +110,17 @@ export const enum Status { // TODO rename before we spread adoption // when adding new status codes, remember to change upper bound in extractHAPStatusFromError } +/** + * @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 Status} property. + * 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). */ @@ -153,15 +160,17 @@ type HAPRequestHandler = (connection: HAPConnection, url: URL, request: Incoming export type IdentifyCallback = VoidCallback; +export type HAPHttpError = { httpCode: HAPHTTPCode, status: HAPStatus}; + export type PairingsCallback = (error: Codes | 0, data?: T) => void; export type AddPairingCallback = PairingsCallback; export type RemovePairingCallback = PairingsCallback; export type ListPairingsCallback = PairingsCallback; export type PairCallback = VoidCallback; -export type AccessoriesCallback = (result: { accessories: any[] }) => void; // TODO type accessories +export type AccessoriesCallback = (error: HAPHttpError | undefined, result?: AccessoriesResponse) => void; export type ReadCharacteristicsCallback = (response: CharacteristicsReadResponse) => void; export type WriteCharacteristicsCallback = (response: CharacteristicsWriteResponse) => void; -export type ResourceRequestCallback = (error: { httpCode: HAPHTTPCode, status: Status} | undefined, resource?: Buffer) => void; +export type ResourceRequestCallback = (error: HAPHttpError | undefined, resource?: Buffer) => void; export const enum HAPServerEventTypes { /** @@ -216,7 +225,7 @@ export declare interface HAPServer { on(event: "list-pairings", listener: (connection: HAPConnection, callback: ListPairingsCallback) => void): this; on(event: "pair", listener: (username: HAPUsername, clientLTPK: Buffer, callback: PairCallback) => void): this; - on(event: "accessories", listener: (callback: AccessoriesCallback) => 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; @@ -232,7 +241,7 @@ export declare interface HAPServer { emit(event: "list-pairings", connection: HAPConnection, callback: ListPairingsCallback): boolean; emit(event: "pair", username: HAPUsername, clientLTPK: Buffer, callback: PairCallback): boolean; - emit(event: "accessories", callback : AccessoriesCallback): 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; @@ -319,7 +328,7 @@ export class HAPServer extends EventEmitter { 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: Status.RESOURCE_DOES_NOT_EXIST })); + response.end(JSON.stringify({ status: HAPStatus.RESOURCE_DOES_NOT_EXIST })); } else { const data = Buffer.concat(buffers); try { @@ -327,7 +336,7 @@ export class HAPServer extends EventEmitter { } 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: Status.RESOURCE_BUSY })); // resource busy try again, does somehow fit? + response.end(JSON.stringify({ status: HAPStatus.RESOURCE_BUSY })); // resource busy try again, does somehow fit? } } }); @@ -367,7 +376,7 @@ export class HAPServer extends EventEmitter { // POST body is empty if (!this.allowInsecureRequest && this.accessoryInfo.paired()) { response.writeHead(HAPHTTPCode.BAD_REQUEST, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({ status: Status.INSUFFICIENT_PRIVILEGES })); + response.end(JSON.stringify({ status: HAPStatus.INSUFFICIENT_PRIVILEGES })); return; } @@ -379,7 +388,7 @@ export class HAPServer extends EventEmitter { } else { debug("[%s] Identification error: %s", this.accessoryInfo.username, err.message); response.writeHead(HAPHTTPCode.INTERNAL_SERVER_ERROR, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({ status: Status.RESOURCE_BUSY })); + response.end(JSON.stringify({ status: HAPStatus.RESOURCE_BUSY })); } })); } @@ -652,7 +661,7 @@ export class HAPServer extends EventEmitter { // Only accept /pairing request if there is a secure session if (!this.allowInsecureRequest && !connection.isAuthenticated()) { response.writeHead(HAPPairingHTTPCode.CONNECTION_AUTHORIZATION_REQUIRED, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({ status: Status.INSUFFICIENT_PRIVILEGES })); + response.end(JSON.stringify({ status: HAPStatus.INSUFFICIENT_PRIVILEGES })); return; } @@ -729,20 +738,25 @@ export class HAPServer extends EventEmitter { 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: Status.INSUFFICIENT_PRIVILEGES})); + response.end(JSON.stringify({status: HAPStatus.INSUFFICIENT_PRIVILEGES})); return; } // call out to listeners to retrieve the latest accessories JSON - this.emit(HAPServerEventTypes.ACCESSORIES, once((result: { accessories: any[] }) => { - response.writeHead(HAPHTTPCode.OK, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify(result)); + 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)); + } })); } 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: Status.INSUFFICIENT_PRIVILEGES})); + response.end(JSON.stringify({status: HAPStatus.INSUFFICIENT_PRIVILEGES})); return; } @@ -752,7 +766,7 @@ export class HAPServer extends EventEmitter { const idParam = searchParams.get("id"); if (!idParam) { response.writeHead(HAPHTTPCode.BAD_REQUEST, {"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; } @@ -787,7 +801,7 @@ export class HAPServer extends EventEmitter { if (errorOccurred) { // on a 207 Multi-Status EVERY characteristic MUST include a status property for (const data of characteristics) { if (!data.status) { // a status is undefined if the request was successful - data.status = Status.SUCCESS; // a value of zero indicates success + data.status = HAPStatus.SUCCESS; // a value of zero indicates success } } } @@ -800,13 +814,13 @@ export class HAPServer extends EventEmitter { if (!connection.isAuthenticated()) { if (!request.headers || (request.headers && request.headers["authorization"] !== this.accessoryInfo.pincode)) { response.writeHead(HAPPairingHTTPCode.CONNECTION_AUTHORIZATION_REQUIRED, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({status: Status.INSUFFICIENT_PRIVILEGES})); + response.end(JSON.stringify({status: HAPStatus.INSUFFICIENT_PRIVILEGES})); return; } } 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; } @@ -827,7 +841,7 @@ export class HAPServer extends EventEmitter { if (multiStatus) { for (const data of characteristics) { // on a 207 Multi-Status EVERY characteristic MUST include a status property if (data.status === undefined) { - data.status = Status.SUCCESS; + data.status = HAPStatus.SUCCESS; } } @@ -842,21 +856,21 @@ export class HAPServer extends EventEmitter { })); } else { response.writeHead(HAPHTTPCode.BAD_REQUEST, {"Content-Type": "application/hap+json"}); // method not allowed - response.end(JSON.stringify({ status: Status.INVALID_VALUE_IN_REQUEST })); + response.end(JSON.stringify({ status: HAPStatus.INVALID_VALUE_IN_REQUEST })); } } 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: Status.INSUFFICIENT_PRIVILEGES})); + response.end(JSON.stringify({status: HAPStatus.INSUFFICIENT_PRIVILEGES})); return; } if (request.method == "PUT") { if (data.length == 0) { response.writeHead(HAPHTTPCode.BAD_REQUEST, {"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; } @@ -876,15 +890,15 @@ export class HAPServer extends EventEmitter { }, prepareRequest.ttl); response.writeHead(HAPHTTPCode.OK, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({status: Status.SUCCESS})); + response.end(JSON.stringify({status: HAPStatus.SUCCESS})); return; } else { response.writeHead(HAPHTTPCode.BAD_REQUEST, {"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 })); } } else { response.writeHead(HAPHTTPCode.BAD_REQUEST, {"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 })); } }; @@ -892,20 +906,20 @@ export class HAPServer extends EventEmitter { 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: Status.INSUFFICIENT_PRIVILEGES })); + response.end(JSON.stringify({ status: HAPStatus.INSUFFICIENT_PRIVILEGES })); return; } } if (request.method === "POST") { if (data.length === 0) { response.writeHead(HAPHTTPCode.BAD_REQUEST, {"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; } 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, resourceRequest, once((error: { httpCode: HAPHTTPCode, status: Status} | undefined, resource: Buffer) => { + 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 })); @@ -916,7 +930,7 @@ export class HAPServer extends EventEmitter { })); } else { response.writeHead(HAPHTTPCode.BAD_REQUEST, {"Content-Type": "application/hap+json"}); // method not allowed - response.end(JSON.stringify({ status: Status.INVALID_VALUE_IN_REQUEST })); + response.end(JSON.stringify({ status: HAPStatus.INVALID_VALUE_IN_REQUEST })); } } diff --git a/src/lib/Service.ts b/src/lib/Service.ts index 087705408..5fd7c7453 100644 --- a/src/lib/Service.ts +++ b/src/lib/Service.ts @@ -1,5 +1,7 @@ +import assert from "assert"; import { EventEmitter } from "events"; -import { CharacteristicValue, HapService, Nullable, ToHAPOptions, WithUUID, } from '../types'; +import { ServiceJsonObject } from "../internal-types"; +import { CharacteristicValue, Nullable, WithUUID } from '../types'; import { Characteristic, CharacteristicChange, @@ -8,7 +10,9 @@ import { } from './Characteristic'; import * as HomeKitTypes from './gen'; import { IdentifierCache } from './model/IdentifierCache'; +import { HAPConnection } from "./util/eventedhttp"; import { toShortForm } from './util/uuid'; +import Timeout = NodeJS.Timeout; /** * HAP spec allows a maximum of 100 characteristics per service! @@ -417,39 +421,97 @@ export class Service extends EventEmitter { } /** - * 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) => { - const characteristicsHAP = []; + toHAP(connection: HAPConnection): Promise { + return new Promise((resolve, reject) => { + 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, HomeKitTypes.BASE_UUID), + iid: this.iid!, + characteristics: [], + hidden: this.isHiddenService? true: undefined, + primary: this.isPrimaryService? true: undefined, + } - for (let index in this.characteristics) { - const 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) { + console.warn(`The read handler for the characteristic '${characteristic.displayName}' was slow to respond!`); + } - if (this.isPrimaryService) { - hap['primary'] = this.isPrimaryService; - } + timeout = setTimeout(() => { + timeout = undefined; + + for (const characteristic of missingCharacteristics) { + console.error("The read handler for the characteristic '" + characteristic?.displayName + "' didn't respond at all!. " + + "Please check that you properly call the callback!"); + 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, HomeKitTypes.BASE_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 (let index in this.linkedServices) { - const 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) => { diff --git a/src/lib/camera/RTPStreamManagement.ts b/src/lib/camera/RTPStreamManagement.ts index e9cf6a3df..6cd8db2e3 100644 --- a/src/lib/camera/RTPStreamManagement.ts +++ b/src/lib/camera/RTPStreamManagement.ts @@ -4,20 +4,14 @@ 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 { HAPStatus } from "../HAPServer"; import { Service } from '../Service'; import { HAPConnection, HAPConnectionEvent } from "../util/eventedhttp"; import * as tlv from '../util/tlv'; import RTPProxy from './RTPProxy'; -import assert from "assert"; const debug = createDebug('HAP-NodeJS:Camera:RTPStreamManagement'); // ---------------------------------- TLV DEFINITIONS START ---------------------------------- @@ -536,6 +530,7 @@ export class RTPStreamManagement { return this.service; } + // noinspection JSUnusedGlobalSymbols,JSUnusedLocalSymbols /** * @deprecated */ @@ -579,7 +574,7 @@ export class RTPStreamManagement { .on(CharacteristicEventTypes.SET, (value, callback, context, connection) => { if (!connection) { debug("Set event handler for SetupEndpoints cannot be called from plugin. Connection undefined!"); - callback(Status.INVALID_VALUE_IN_REQUEST); + callback(HAPStatus.INVALID_VALUE_IN_REQUEST); return; } this.handleSetupEndpoints(value, callback, connection); @@ -628,7 +623,7 @@ export class RTPStreamManagement { if (sessionIdentifier !== this.sessionIdentifier) { debug(`Received unknown session Identifier with request to ${SessionControlCommand[requestType]}`); - callback(Status.INVALID_VALUE_IN_REQUEST); + callback(HAPStatus.INVALID_VALUE_IN_REQUEST); return; } @@ -661,7 +656,7 @@ export class RTPStreamManagement { case SessionControlCommand.SUSPEND_SESSION: default: debug(`Unhandled request type ${SessionControlCommand[requestType]}`); - callback(Status.INVALID_VALUE_IN_REQUEST); + callback(HAPStatus.INVALID_VALUE_IN_REQUEST); return; } } diff --git a/src/lib/controller/CameraController.ts b/src/lib/controller/CameraController.ts index fb46f40ed..131ae1b00 100644 --- a/src/lib/controller/CameraController.ts +++ b/src/lib/controller/CameraController.ts @@ -14,7 +14,7 @@ import { RTPStreamManagement, Service, SnapshotRequest, - Status, + HAPStatus, StreamingRequest } from "../.."; import { SessionIdentifier } from "../../types"; @@ -49,7 +49,7 @@ export interface CameraControllerOptions { // recordingOptions: CameraRecordingOptions, // soon } -export type SnapshotRequestCallback = (error?: Error | Status, 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; @@ -376,7 +376,7 @@ export class CameraController extends EventEmitter implements Controller Status; + let handler: (targetConfiguration?: TargetConfiguration) => HAPStatus; switch (operation) { case Operation.ADD: handler = this.handleAddTarget; @@ -598,12 +598,12 @@ export class RemoteController extends EventEmitter implements SerializableContro handler = this.handleListTargets; break; default: - callback(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) { @@ -614,9 +614,9 @@ export class RemoteController extends EventEmitter implements SerializableContro } }; - 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; @@ -626,12 +626,12 @@ export class RemoteController extends EventEmitter implements SerializableContro 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[] = []; @@ -670,17 +670,17 @@ export class RemoteController extends EventEmitter implements SerializableContro 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) { @@ -702,12 +702,12 @@ export class RemoteController extends EventEmitter implements SerializableContro } 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"); @@ -717,23 +717,23 @@ export class RemoteController extends EventEmitter implements SerializableContro 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, connection: HAPConnection): void { if (this.activeIdentifier === 0) { debug("Tried to change active state. There is no active target set though"); - callback(Status.INVALID_VALUE_IN_REQUEST); + callback(HAPStatus.INVALID_VALUE_IN_REQUEST); return; } @@ -1200,7 +1200,7 @@ export class RemoteController extends EventEmitter implements SerializableContro .on(CharacteristicEventTypes.SET, (value, callback, context, connection) => { if (!connection) { debug("Set event handler for Remote.Active cannot be called from plugin. Connection undefined!"); - callback(Status.INVALID_VALUE_IN_REQUEST); + callback(HAPStatus.INVALID_VALUE_IN_REQUEST); return; } this.handleActiveWrite(value, callback, connection); diff --git a/src/lib/datastream/DataStreamManagement.ts b/src/lib/datastream/DataStreamManagement.ts index 644e70480..c22388251 100644 --- a/src/lib/datastream/DataStreamManagement.ts +++ b/src/lib/datastream/DataStreamManagement.ts @@ -1,8 +1,7 @@ -import assert from "assert"; import createDebug from "debug"; import { Characteristic, CharacteristicEventTypes, CharacteristicSetCallback } from "../Characteristic"; import { DataStreamTransportManagement } from "../gen/HomeKit-DataStream"; -import { Status } from "../HAPServer"; +import { HAPStatus } from "../HAPServer"; import { Service } from "../Service"; import { HAPConnection } from "../util/eventedhttp"; import * as tlv from '../util/tlv'; @@ -158,7 +157,7 @@ export class DataStreamManagement { if (sessionCommandType === SessionCommandType.START_SESSION) { if (transportType !== TransportType.HOMEKIT_DATA_STREAM || controllerKeySalt.length !== 32) { - callback(Status.INVALID_VALUE_IN_REQUEST); + callback(HAPStatus.INVALID_VALUE_IN_REQUEST); return; } @@ -178,7 +177,7 @@ export class DataStreamManagement { callback(null, response.toString('base64')); }); } else { - callback(Status.INVALID_VALUE_IN_REQUEST); + callback(HAPStatus.INVALID_VALUE_IN_REQUEST); return; } } @@ -212,7 +211,7 @@ export class DataStreamManagement { .on(CharacteristicEventTypes.SET, (value, callback, context, connection) => { if (!connection) { debug("Set event handler for SetupDataStreamTransport cannot be called from plugin! Connection undefined!"); - callback(Status.INVALID_VALUE_IN_REQUEST); + callback(HAPStatus.INVALID_VALUE_IN_REQUEST); return; } this.handleSetupDataStreamTransportWrite(value, callback, connection); diff --git a/src/lib/gen/HomeKit-Bridge.ts b/src/lib/gen/HomeKit-Bridge.ts index 9e8e59029..462dc3d9b 100644 --- a/src/lib/gen/HomeKit-Bridge.ts +++ b/src/lib/gen/HomeKit-Bridge.ts @@ -16,8 +16,7 @@ export class AppMatchingIdentifier extends Characteristic { static readonly UUID: string = '000000A4-0000-1000-8000-0026BB765291'; constructor() { - super('App Matching Identifier', AppMatchingIdentifier.UUID); - this.setProps({ + super('App Matching Identifier', AppMatchingIdentifier.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ] }); @@ -36,8 +35,7 @@ export class ProgrammableSwitchOutputState extends Characteristic { static readonly UUID: string = '00000074-0000-1000-8000-0026BB765291'; constructor() { - super('Programmable Switch Output State', ProgrammableSwitchOutputState.UUID); - this.setProps({ + super('Programmable Switch Output State', ProgrammableSwitchOutputState.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -59,8 +57,7 @@ export class SoftwareRevision extends Characteristic { static readonly UUID: string = '00000054-0000-1000-8000-0026BB765291'; constructor() { - super('Software Revision', SoftwareRevision.UUID); - this.setProps({ + super('Software Revision', SoftwareRevision.UUID, { format: Formats.STRING, perms: [Perms.PAIRED_READ] }); @@ -137,8 +134,7 @@ export class AccessoryIdentifier extends Characteristic { static readonly UUID: string = '00000057-0000-1000-8000-0026BB765291'; constructor() { - super('Accessory Identifier', AccessoryIdentifier.UUID); - this.setProps({ + super('Accessory Identifier', AccessoryIdentifier.UUID, { format: Formats.STRING, perms: [Perms.PAIRED_READ] }); @@ -157,8 +153,7 @@ export class Category extends Characteristic { static readonly UUID: string = '000000A3-0000-1000-8000-0026BB765291'; constructor() { - super('Category', Category.UUID); - this.setProps({ + super('Category', Category.UUID, { format: Formats.UINT16, maxValue: 16, minValue: 1, @@ -180,8 +175,7 @@ export class ConfigureBridgedAccessory extends Characteristic { static readonly UUID: string = '000000A0-0000-1000-8000-0026BB765291'; constructor() { - super('Configure Bridged Accessory', ConfigureBridgedAccessory.UUID); - this.setProps({ + super('Configure Bridged Accessory', ConfigureBridgedAccessory.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_WRITE] }); @@ -200,8 +194,7 @@ export class ConfigureBridgedAccessoryStatus extends Characteristic { static readonly UUID: string = '0000009D-0000-1000-8000-0026BB765291'; constructor() { - super('Configure Bridged Accessory Status', ConfigureBridgedAccessoryStatus.UUID); - this.setProps({ + super('Configure Bridged Accessory Status', ConfigureBridgedAccessoryStatus.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); @@ -220,8 +213,7 @@ export class CurrentTime extends Characteristic { static readonly UUID: string = '0000009B-0000-1000-8000-0026BB765291'; constructor() { - super('Current Time', CurrentTime.UUID); - this.setProps({ + super('Current Time', CurrentTime.UUID, { format: Formats.STRING, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE] }); @@ -240,8 +232,7 @@ export class DayoftheWeek extends Characteristic { static readonly UUID: string = '00000098-0000-1000-8000-0026BB765291'; constructor() { - super('Day of the Week', DayoftheWeek.UUID); - this.setProps({ + super('Day of the Week', DayoftheWeek.UUID, { format: Formats.UINT8, maxValue: 7, minValue: 1, @@ -267,8 +258,7 @@ export class DiscoverBridgedAccessories extends Characteristic { static readonly UUID: string = '0000009E-0000-1000-8000-0026BB765291'; constructor() { - super('Discover Bridged Accessories', DiscoverBridgedAccessories.UUID); - this.setProps({ + super('Discover Bridged Accessories', DiscoverBridgedAccessories.UUID, { format: Formats.UINT8, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); @@ -287,8 +277,7 @@ export class DiscoveredBridgedAccessories extends Characteristic { static readonly UUID: string = '0000009F-0000-1000-8000-0026BB765291'; constructor() { - super('Discovered Bridged Accessories', DiscoveredBridgedAccessories.UUID); - this.setProps({ + super('Discovered Bridged Accessories', DiscoveredBridgedAccessories.UUID, { format: Formats.UINT16, perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); @@ -307,8 +296,7 @@ export class LinkQuality extends Characteristic { static readonly UUID: string = '0000009C-0000-1000-8000-0026BB765291'; constructor() { - super('Link Quality', LinkQuality.UUID); - this.setProps({ + super('Link Quality', LinkQuality.UUID, { format: Formats.UINT8, maxValue: 4, minValue: 1, @@ -330,8 +318,7 @@ export class Reachable extends Characteristic { static readonly UUID: string = '00000063-0000-1000-8000-0026BB765291'; constructor() { - super('Reachable', Reachable.UUID); - this.setProps({ + super('Reachable', Reachable.UUID, { format: Formats.BOOL, perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); @@ -350,8 +337,7 @@ export class RelayControlPoint extends Characteristic { static readonly UUID: string = '0000005E-0000-1000-8000-0026BB765291'; constructor() { - super('Relay Control Point', RelayControlPoint.UUID); - this.setProps({ + super('Relay Control Point', RelayControlPoint.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); @@ -370,8 +356,7 @@ export class RelayEnabled extends Characteristic { static readonly UUID: string = '0000005B-0000-1000-8000-0026BB765291'; constructor() { - super('Relay Enabled', RelayEnabled.UUID); - this.setProps({ + super('Relay Enabled', RelayEnabled.UUID, { format: Formats.BOOL, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); @@ -390,8 +375,7 @@ export class RelayState extends Characteristic { static readonly UUID: string = '0000005C-0000-1000-8000-0026BB765291'; constructor() { - super('Relay State', RelayState.UUID); - this.setProps({ + super('Relay State', RelayState.UUID, { format: Formats.UINT8, perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); @@ -410,8 +394,7 @@ export class TimeUpdate extends Characteristic { static readonly UUID: string = '0000009A-0000-1000-8000-0026BB765291'; constructor() { - super('Time Update', TimeUpdate.UUID); - this.setProps({ + super('Time Update', TimeUpdate.UUID, { format: Formats.BOOL, perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); @@ -430,8 +413,7 @@ export class TunnelConnectionTimeout extends Characteristic { static readonly UUID: string = '00000061-0000-1000-8000-0026BB765291'; constructor() { - super('Tunnel Connection Timeout ', TunnelConnectionTimeout.UUID); - this.setProps({ + super('Tunnel Connection Timeout ', TunnelConnectionTimeout.UUID, { format: Formats.UINT32, perms: [Perms.PAIRED_WRITE, Perms.PAIRED_READ, Perms.NOTIFY] }); @@ -450,8 +432,7 @@ export class TunneledAccessoryAdvertising extends Characteristic { static readonly UUID: string = '00000060-0000-1000-8000-0026BB765291'; constructor() { - super('Tunneled Accessory Advertising', TunneledAccessoryAdvertising.UUID); - this.setProps({ + super('Tunneled Accessory Advertising', TunneledAccessoryAdvertising.UUID, { format: Formats.BOOL, perms: [Perms.PAIRED_WRITE, Perms.PAIRED_READ, Perms.NOTIFY] }); @@ -470,8 +451,7 @@ export class TunneledAccessoryConnected extends Characteristic { static readonly UUID: string = '00000059-0000-1000-8000-0026BB765291'; constructor() { - super('Tunneled Accessory Connected', TunneledAccessoryConnected.UUID); - this.setProps({ + super('Tunneled Accessory Connected', TunneledAccessoryConnected.UUID, { format: Formats.BOOL, perms: [Perms.PAIRED_WRITE, Perms.PAIRED_READ, Perms.NOTIFY] }); @@ -490,8 +470,7 @@ export class TunneledAccessoryStateNumber extends Characteristic { static readonly UUID: string = '00000058-0000-1000-8000-0026BB765291'; constructor() { - super('Tunneled Accessory State Number', TunneledAccessoryStateNumber.UUID); - this.setProps({ + super('Tunneled Accessory State Number', TunneledAccessoryStateNumber.UUID, { format: Formats.FLOAT, perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); diff --git a/src/lib/gen/HomeKit-DataStream.ts b/src/lib/gen/HomeKit-DataStream.ts index f45a6c8c6..0437dc290 100644 --- a/src/lib/gen/HomeKit-DataStream.ts +++ b/src/lib/gen/HomeKit-DataStream.ts @@ -13,8 +13,7 @@ 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({ + super('Supported Data Stream Transport Configuration', SupportedDataStreamTransportConfiguration.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ] }); @@ -34,8 +33,7 @@ export class SetupDataStreamTransport extends Characteristic { static readonly UUID: string = '00000131-0000-1000-8000-0026BB765291'; constructor() { - super('Setup Data Stream Transport', SetupDataStreamTransport.UUID); - this.setProps({ + super('Setup Data Stream Transport', SetupDataStreamTransport.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.WRITE_RESPONSE] }); diff --git a/src/lib/gen/HomeKit-Remote.ts b/src/lib/gen/HomeKit-Remote.ts index 03e9a619c..e6565f05b 100644 --- a/src/lib/gen/HomeKit-Remote.ts +++ b/src/lib/gen/HomeKit-Remote.ts @@ -13,8 +13,7 @@ export class TargetControlSupportedConfiguration extends Characteristic { static readonly UUID: string = '00000123-0000-1000-8000-0026BB765291'; constructor() { - super('Target Control Supported Configuration', TargetControlSupportedConfiguration.UUID); - this.setProps({ + super('Target Control Supported Configuration', TargetControlSupportedConfiguration.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ] }); @@ -33,8 +32,7 @@ export class TargetControlList extends Characteristic { static readonly UUID: string = '00000124-0000-1000-8000-0026BB765291'; constructor() { - super('Target Control List', TargetControlList.UUID); - this.setProps({ + super('Target Control List', TargetControlList.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_WRITE, Perms.PAIRED_READ, Perms.WRITE_RESPONSE], adminOnlyAccess: [Access.READ, Access.WRITE], @@ -55,8 +53,7 @@ export class ButtonEvent extends Characteristic { static readonly UUID: string = '00000126-0000-1000-8000-0026BB765291'; constructor() { - super('Button Event', ButtonEvent.UUID); - this.setProps({ + super('Button Event', ButtonEvent.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ, Perms.NOTIFY], adminOnlyAccess: [Access.NOTIFY], @@ -77,8 +74,7 @@ export class SelectedAudioStreamConfiguration extends Characteristic { static readonly UUID: string = '00000128-0000-1000-8000-0026BB765291'; constructor() { - super('Selected Audio Stream Configuration', SelectedAudioStreamConfiguration.UUID); - this.setProps({ + super('Selected Audio Stream Configuration', SelectedAudioStreamConfiguration.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE] }); @@ -100,8 +96,7 @@ export class SiriInputType extends Characteristic { static readonly UUID: string = '00000132-0000-1000-8000-0026BB765291'; constructor() { - super('Siri Input Type', SiriInputType.UUID); - this.setProps({ + super('Siri Input Type', SiriInputType.UUID, { format: Formats.UINT8, minValue: 0, maxValue: 0, diff --git a/src/lib/gen/HomeKit-TV.ts b/src/lib/gen/HomeKit-TV.ts index c34f333db..f43f5546a 100644 --- a/src/lib/gen/HomeKit-TV.ts +++ b/src/lib/gen/HomeKit-TV.ts @@ -12,8 +12,7 @@ export class ActiveIdentifier extends Characteristic { static readonly UUID: string = '000000E7-0000-1000-8000-0026BB765291'; constructor() { - super('Active Identifier', ActiveIdentifier.UUID); - this.setProps({ + super('Active Identifier', ActiveIdentifier.UUID, { format: Formats.UINT32, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); @@ -32,8 +31,7 @@ export class ConfiguredName extends Characteristic { static readonly UUID: string = '000000E3-0000-1000-8000-0026BB765291'; constructor() { - super('Configured Name', ConfiguredName.UUID); - this.setProps({ + super('Configured Name', ConfiguredName.UUID, { format: Formats.STRING, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); @@ -56,8 +54,7 @@ export class SleepDiscoveryMode extends Characteristic { static readonly UUID: string = '000000E8-0000-1000-8000-0026BB765291'; constructor() { - super('Sleep Discovery Mode', SleepDiscoveryMode.UUID); - this.setProps({ + super('Sleep Discovery Mode', SleepDiscoveryMode.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -83,8 +80,7 @@ export class ClosedCaptions extends Characteristic { static readonly UUID: string = '000000DD-0000-1000-8000-0026BB765291'; constructor() { - super('Closed Captions', ClosedCaptions.UUID); - this.setProps({ + super('Closed Captions', ClosedCaptions.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -106,8 +102,7 @@ export class DisplayOrder extends Characteristic { static readonly UUID: string = '00000136-0000-1000-8000-0026BB765291'; constructor() { - super('Display Order', DisplayOrder.UUID); - this.setProps({ + super('Display Order', DisplayOrder.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); @@ -133,8 +128,7 @@ export class CurrentMediaState extends Characteristic { static readonly UUID: string = '000000E0-0000-1000-8000-0026BB765291'; constructor() { - super('Current Media State', CurrentMediaState.UUID); - this.setProps({ + super('Current Media State', CurrentMediaState.UUID, { format: Formats.UINT8, maxValue: 5, minValue: 0, @@ -161,8 +155,7 @@ export class TargetMediaState extends Characteristic { static readonly UUID: string = '00000137-0000-1000-8000-0026BB765291'; constructor() { - super('Target Media State', TargetMediaState.UUID); - this.setProps({ + super('Target Media State', TargetMediaState.UUID, { format: Formats.UINT8, maxValue: 2, minValue: 0, @@ -194,8 +187,7 @@ export class PictureMode extends Characteristic { static readonly UUID: string = '000000E2-0000-1000-8000-0026BB765291'; constructor() { - super('Picture Mode', PictureMode.UUID); - this.setProps({ + super('Picture Mode', PictureMode.UUID, { format: Formats.UINT8, maxValue: 13, minValue: 0, @@ -221,8 +213,7 @@ export class PowerModeSelection extends Characteristic { static readonly UUID: string = '000000DF-0000-1000-8000-0026BB765291'; constructor() { - super('Power Mode Selection', PowerModeSelection.UUID); - this.setProps({ + super('Power Mode Selection', PowerModeSelection.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -259,8 +250,7 @@ export class RemoteKey extends Characteristic { static readonly UUID: string = '000000E1-0000-1000-8000-0026BB765291'; constructor() { - super('Remote Key', RemoteKey.UUID); - this.setProps({ + super('Remote Key', RemoteKey.UUID, { format: Formats.UINT8, maxValue: 16, minValue: 0, @@ -295,8 +285,7 @@ export class InputSourceType extends Characteristic { static readonly UUID: string = '000000DB-0000-1000-8000-0026BB765291'; constructor() { - super('Input Source Type', InputSourceType.UUID); - this.setProps({ + super('Input Source Type', InputSourceType.UUID, { format: Formats.UINT8, maxValue: 10, minValue: 0, @@ -327,8 +316,7 @@ export class InputDeviceType extends Characteristic { static readonly UUID: string = '000000DC-0000-1000-8000-0026BB765291'; constructor() { - super('Input Device Type', InputDeviceType.UUID); - this.setProps({ + super('Input Device Type', InputDeviceType.UUID, { format: Formats.UINT8, maxValue: 6, minValue: 0, @@ -350,8 +338,7 @@ export class Identifier extends Characteristic { static readonly UUID: string = '000000E6-0000-1000-8000-0026BB765291'; constructor() { - super('Identifier', Identifier.UUID); - this.setProps({ + super('Identifier', Identifier.UUID, { format: Formats.UINT32, minValue: 0, minStep: 1, @@ -376,8 +363,7 @@ export class CurrentVisibilityState extends Characteristic { static readonly UUID: string = '00000135-0000-1000-8000-0026BB765291'; constructor() { - super('Current Visibility State', CurrentVisibilityState.UUID); - this.setProps({ + super('Current Visibility State', CurrentVisibilityState.UUID, { format: Formats.UINT8, maxValue: 3, minValue: 0, @@ -403,8 +389,7 @@ export class TargetVisibilityState extends Characteristic { static readonly UUID: string = '00000134-0000-1000-8000-0026BB765291'; constructor() { - super('Target Visibility State', TargetVisibilityState.UUID); - this.setProps({ + super('Target Visibility State', TargetVisibilityState.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -432,8 +417,7 @@ export class VolumeControlType extends Characteristic { static readonly UUID: string = '000000E9-0000-1000-8000-0026BB765291'; constructor() { - super('Volume Control Type', VolumeControlType.UUID); - this.setProps({ + super('Volume Control Type', VolumeControlType.UUID, { format: Formats.UINT8, maxValue: 3, minValue: 0, @@ -459,8 +443,7 @@ export class VolumeSelector extends Characteristic { static readonly UUID: string = '000000EA-0000-1000-8000-0026BB765291'; constructor() { - super('Volume Selector', VolumeSelector.UUID); - this.setProps({ + super('Volume Selector', VolumeSelector.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, diff --git a/src/lib/gen/HomeKit.ts b/src/lib/gen/HomeKit.ts index ed729c000..bf2e2ea9b 100644 --- a/src/lib/gen/HomeKit.ts +++ b/src/lib/gen/HomeKit.ts @@ -12,8 +12,7 @@ export class AccessControlLevel extends Characteristic { static readonly UUID: string = '000000E5-0000-1000-8000-0026BB765291'; constructor() { - super('Access Control Level', AccessControlLevel.UUID); - this.setProps({ + super('Access Control Level', AccessControlLevel.UUID, { format: Formats.UINT16, perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], maxValue: 2, @@ -35,8 +34,7 @@ export class AccessoryFlags extends Characteristic { static readonly UUID: string = '000000A6-0000-1000-8000-0026BB765291'; constructor() { - super('Accessory Flags', AccessoryFlags.UUID); - this.setProps({ + super('Accessory Flags', AccessoryFlags.UUID, { format: Formats.UINT32, perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); @@ -55,8 +53,7 @@ export class ProductData extends Characteristic { static readonly UUID: string = '00000220-0000-1000-8000-0026BB765291'; constructor() { - super('Product Data', ProductData.UUID); - this.setProps({ + super('Product Data', ProductData.UUID, { format: Formats.DATA, perms: [Perms.PAIRED_READ] }); @@ -79,8 +76,7 @@ export class Active extends Characteristic { static readonly UUID: string = '000000B0-0000-1000-8000-0026BB765291'; constructor() { - super('Active', Active.UUID); - this.setProps({ + super('Active', Active.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -102,8 +98,7 @@ export class AdministratorOnlyAccess extends Characteristic { static readonly UUID: string = '00000001-0000-1000-8000-0026BB765291'; constructor() { - super('Administrator Only Access', AdministratorOnlyAccess.UUID); - this.setProps({ + super('Administrator Only Access', AdministratorOnlyAccess.UUID, { format: Formats.BOOL, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); @@ -122,8 +117,7 @@ export class AirParticulateDensity extends Characteristic { static readonly UUID: string = '00000064-0000-1000-8000-0026BB765291'; constructor() { - super('Air Particulate Density', AirParticulateDensity.UUID); - this.setProps({ + super('Air Particulate Density', AirParticulateDensity.UUID, { format: Formats.FLOAT, maxValue: 1000, minValue: 0, @@ -149,8 +143,7 @@ export class AirParticulateSize extends Characteristic { static readonly UUID: string = '00000065-0000-1000-8000-0026BB765291'; constructor() { - super('Air Particulate Size', AirParticulateSize.UUID); - this.setProps({ + super('Air Particulate Size', AirParticulateSize.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -181,8 +174,7 @@ export class AirQuality extends Characteristic { static readonly UUID: string = '00000095-0000-1000-8000-0026BB765291'; constructor() { - super('Air Quality', AirQuality.UUID); - this.setProps({ + super('Air Quality', AirQuality.UUID, { format: Formats.UINT8, maxValue: 5, minValue: 0, @@ -205,8 +197,7 @@ export class AudioFeedback extends Characteristic { static readonly UUID: string = '00000005-0000-1000-8000-0026BB765291'; constructor() { - super('Audio Feedback', AudioFeedback.UUID); - this.setProps({ + super('Audio Feedback', AudioFeedback.UUID, { format: Formats.BOOL, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); @@ -225,8 +216,7 @@ export class BatteryLevel extends Characteristic { static readonly UUID: string = '00000068-0000-1000-8000-0026BB765291'; constructor() { - super('Battery Level', BatteryLevel.UUID); - this.setProps({ + super('Battery Level', BatteryLevel.UUID, { format: Formats.UINT8, unit: Units.PERCENTAGE, maxValue: 100, @@ -249,8 +239,7 @@ export class Brightness extends Characteristic { static readonly UUID: string = '00000008-0000-1000-8000-0026BB765291'; constructor() { - super('Brightness', Brightness.UUID); - this.setProps({ + super('Brightness', Brightness.UUID, { format: Formats.INT, unit: Units.PERCENTAGE, maxValue: 100, @@ -277,8 +266,7 @@ export class CarbonDioxideDetected extends Characteristic { static readonly UUID: string = '00000092-0000-1000-8000-0026BB765291'; constructor() { - super('Carbon Dioxide Detected', CarbonDioxideDetected.UUID); - this.setProps({ + super('Carbon Dioxide Detected', CarbonDioxideDetected.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -300,8 +288,7 @@ export class CarbonDioxideLevel extends Characteristic { static readonly UUID: string = '00000093-0000-1000-8000-0026BB765291'; constructor() { - super('Carbon Dioxide Level', CarbonDioxideLevel.UUID); - this.setProps({ + super('Carbon Dioxide Level', CarbonDioxideLevel.UUID, { format: Formats.FLOAT, maxValue: 100000, minValue: 0, @@ -322,8 +309,7 @@ export class CarbonDioxidePeakLevel extends Characteristic { static readonly UUID: string = '00000094-0000-1000-8000-0026BB765291'; constructor() { - super('Carbon Dioxide Peak Level', CarbonDioxidePeakLevel.UUID); - this.setProps({ + super('Carbon Dioxide Peak Level', CarbonDioxidePeakLevel.UUID, { format: Formats.FLOAT, maxValue: 100000, minValue: 0, @@ -348,8 +334,7 @@ export class CarbonMonoxideDetected extends Characteristic { static readonly UUID: string = '00000069-0000-1000-8000-0026BB765291'; constructor() { - super('Carbon Monoxide Detected', CarbonMonoxideDetected.UUID); - this.setProps({ + super('Carbon Monoxide Detected', CarbonMonoxideDetected.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -371,8 +356,7 @@ export class CarbonMonoxideLevel extends Characteristic { static readonly UUID: string = '00000090-0000-1000-8000-0026BB765291'; constructor() { - super('Carbon Monoxide Level', CarbonMonoxideLevel.UUID); - this.setProps({ + super('Carbon Monoxide Level', CarbonMonoxideLevel.UUID, { format: Formats.FLOAT, maxValue: 100, minValue: 0, @@ -393,8 +377,7 @@ export class CarbonMonoxidePeakLevel extends Characteristic { static readonly UUID: string = '00000091-0000-1000-8000-0026BB765291'; constructor() { - super('Carbon Monoxide Peak Level', CarbonMonoxidePeakLevel.UUID); - this.setProps({ + super('Carbon Monoxide Peak Level', CarbonMonoxidePeakLevel.UUID, { format: Formats.FLOAT, maxValue: 100, minValue: 0, @@ -420,8 +403,7 @@ export class ChargingState extends Characteristic { static readonly UUID: string = '0000008F-0000-1000-8000-0026BB765291'; constructor() { - super('Charging State', ChargingState.UUID); - this.setProps({ + super('Charging State', ChargingState.UUID, { format: Formats.UINT8, maxValue: 2, minValue: 0, @@ -443,8 +425,7 @@ export class ColorTemperature extends Characteristic { static readonly UUID: string = '000000CE-0000-1000-8000-0026BB765291'; constructor() { - super('Color Temperature', ColorTemperature.UUID); - this.setProps({ + super('Color Temperature', ColorTemperature.UUID, { format: Formats.UINT32, maxValue: 500, minValue: 140, @@ -470,8 +451,7 @@ export class ContactSensorState extends Characteristic { static readonly UUID: string = '0000006A-0000-1000-8000-0026BB765291'; constructor() { - super('Contact Sensor State', ContactSensorState.UUID); - this.setProps({ + super('Contact Sensor State', ContactSensorState.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -493,8 +473,7 @@ export class CoolingThresholdTemperature extends Characteristic { static readonly UUID: string = '0000000D-0000-1000-8000-0026BB765291'; constructor() { - super('Cooling Threshold Temperature', CoolingThresholdTemperature.UUID); - this.setProps({ + super('Cooling Threshold Temperature', CoolingThresholdTemperature.UUID, { format: Formats.FLOAT, unit: Units.CELSIUS, maxValue: 35, @@ -522,8 +501,7 @@ export class CurrentAirPurifierState extends Characteristic { static readonly UUID: string = '000000A9-0000-1000-8000-0026BB765291'; constructor() { - super('Current Air Purifier State', CurrentAirPurifierState.UUID); - this.setProps({ + super('Current Air Purifier State', CurrentAirPurifierState.UUID, { format: Formats.UINT8, maxValue: 2, minValue: 0, @@ -546,8 +524,7 @@ export class CurrentAmbientLightLevel extends Characteristic { static readonly UUID: string = '0000006B-0000-1000-8000-0026BB765291'; constructor() { - super('Current Ambient Light Level', CurrentAmbientLightLevel.UUID); - this.setProps({ + super('Current Ambient Light Level', CurrentAmbientLightLevel.UUID, { format: Formats.FLOAT, unit: Units.LUX, maxValue: 100000, @@ -576,8 +553,7 @@ export class CurrentDoorState extends Characteristic { static readonly UUID: string = '0000000E-0000-1000-8000-0026BB765291'; constructor() { - super('Current Door State', CurrentDoorState.UUID); - this.setProps({ + super('Current Door State', CurrentDoorState.UUID, { format: Formats.UINT8, maxValue: 4, minValue: 0, @@ -604,8 +580,7 @@ export class CurrentFanState extends Characteristic { static readonly UUID: string = '000000AF-0000-1000-8000-0026BB765291'; constructor() { - super('Current Fan State', CurrentFanState.UUID); - this.setProps({ + super('Current Fan State', CurrentFanState.UUID, { format: Formats.UINT8, maxValue: 2, minValue: 0, @@ -633,8 +608,7 @@ export class CurrentHeaterCoolerState extends Characteristic { static readonly UUID: string = '000000B1-0000-1000-8000-0026BB765291'; constructor() { - super('Current Heater Cooler State', CurrentHeaterCoolerState.UUID); - this.setProps({ + super('Current Heater Cooler State', CurrentHeaterCoolerState.UUID, { format: Formats.UINT8, maxValue: 3, minValue: 0, @@ -661,8 +635,7 @@ export class CurrentHeatingCoolingState extends Characteristic { static readonly UUID: string = '0000000F-0000-1000-8000-0026BB765291'; constructor() { - super('Current Heating Cooling State', CurrentHeatingCoolingState.UUID); - this.setProps({ + super('Current Heating Cooling State', CurrentHeatingCoolingState.UUID, { format: Formats.UINT8, maxValue: 2, minValue: 0, @@ -684,8 +657,7 @@ export class CurrentHorizontalTiltAngle extends Characteristic { static readonly UUID: string = '0000006C-0000-1000-8000-0026BB765291'; constructor() { - super('Current Horizontal Tilt Angle', CurrentHorizontalTiltAngle.UUID); - this.setProps({ + super('Current Horizontal Tilt Angle', CurrentHorizontalTiltAngle.UUID, { format: Formats.INT, unit: Units.ARC_DEGREE, maxValue: 90, @@ -714,8 +686,7 @@ export class CurrentHumidifierDehumidifierState extends Characteristic { static readonly UUID: string = '000000B3-0000-1000-8000-0026BB765291'; constructor() { - super('Current Humidifier Dehumidifier State', CurrentHumidifierDehumidifierState.UUID); - this.setProps({ + super('Current Humidifier Dehumidifier State', CurrentHumidifierDehumidifierState.UUID, { format: Formats.UINT8, maxValue: 3, minValue: 0, @@ -737,8 +708,7 @@ export class CurrentPosition extends Characteristic { static readonly UUID: string = '0000006D-0000-1000-8000-0026BB765291'; constructor() { - super('Current Position', CurrentPosition.UUID); - this.setProps({ + super('Current Position', CurrentPosition.UUID, { format: Formats.UINT8, unit: Units.PERCENTAGE, maxValue: 100, @@ -761,8 +731,7 @@ export class CurrentRelativeHumidity extends Characteristic { static readonly UUID: string = '00000010-0000-1000-8000-0026BB765291'; constructor() { - super('Current Relative Humidity', CurrentRelativeHumidity.UUID); - this.setProps({ + super('Current Relative Humidity', CurrentRelativeHumidity.UUID, { format: Formats.FLOAT, unit: Units.PERCENTAGE, maxValue: 100, @@ -790,8 +759,7 @@ export class CurrentSlatState extends Characteristic { static readonly UUID: string = '000000AA-0000-1000-8000-0026BB765291'; constructor() { - super('Current Slat State', CurrentSlatState.UUID); - this.setProps({ + super('Current Slat State', CurrentSlatState.UUID, { format: Formats.UINT8, maxValue: 2, minValue: 0, @@ -813,8 +781,7 @@ export class CurrentTemperature extends Characteristic { static readonly UUID: string = '00000011-0000-1000-8000-0026BB765291'; constructor() { - super('Current Temperature', CurrentTemperature.UUID); - this.setProps({ + super('Current Temperature', CurrentTemperature.UUID, { format: Formats.FLOAT, unit: Units.CELSIUS, maxValue: 100, @@ -837,8 +804,7 @@ export class CurrentTiltAngle extends Characteristic { static readonly UUID: string = '000000C1-0000-1000-8000-0026BB765291'; constructor() { - super('Current Tilt Angle', CurrentTiltAngle.UUID); - this.setProps({ + super('Current Tilt Angle', CurrentTiltAngle.UUID, { format: Formats.INT, unit: Units.ARC_DEGREE, maxValue: 90, @@ -861,8 +827,7 @@ export class CurrentVerticalTiltAngle extends Characteristic { static readonly UUID: string = '0000006E-0000-1000-8000-0026BB765291'; constructor() { - super('Current Vertical Tilt Angle', CurrentVerticalTiltAngle.UUID); - this.setProps({ + super('Current Vertical Tilt Angle', CurrentVerticalTiltAngle.UUID, { format: Formats.INT, unit: Units.ARC_DEGREE, maxValue: 90, @@ -885,8 +850,7 @@ export class DigitalZoom extends Characteristic { static readonly UUID: string = '0000011D-0000-1000-8000-0026BB765291'; constructor() { - super('Digital Zoom', DigitalZoom.UUID); - this.setProps({ + super('Digital Zoom', DigitalZoom.UUID, { format: Formats.FLOAT, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); @@ -909,8 +873,7 @@ export class FilterChangeIndication extends Characteristic { static readonly UUID: string = '000000AC-0000-1000-8000-0026BB765291'; constructor() { - super('Filter Change Indication', FilterChangeIndication.UUID); - this.setProps({ + super('Filter Change Indication', FilterChangeIndication.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -932,8 +895,7 @@ export class FilterLifeLevel extends Characteristic { static readonly UUID: string = '000000AB-0000-1000-8000-0026BB765291'; constructor() { - super('Filter Life Level', FilterLifeLevel.UUID); - this.setProps({ + super('Filter Life Level', FilterLifeLevel.UUID, { format: Formats.FLOAT, maxValue: 100, minValue: 0, @@ -954,6 +916,8 @@ export class FirmwareRevision extends Characteristic { static readonly UUID: string = '00000052-0000-1000-8000-0026BB765291'; constructor() { + // TODO change later + // @ts-expect-error super('Firmware Revision', FirmwareRevision.UUID); this.setProps({ format: Formats.STRING, @@ -974,8 +938,7 @@ export class HardwareRevision extends Characteristic { static readonly UUID: string = '00000053-0000-1000-8000-0026BB765291'; constructor() { - super('Hardware Revision', HardwareRevision.UUID); - this.setProps({ + super('Hardware Revision', HardwareRevision.UUID, { format: Formats.STRING, perms: [Perms.PAIRED_READ] }); @@ -994,8 +957,7 @@ export class HeatingThresholdTemperature extends Characteristic { static readonly UUID: string = '00000012-0000-1000-8000-0026BB765291'; constructor() { - super('Heating Threshold Temperature', HeatingThresholdTemperature.UUID); - this.setProps({ + super('Heating Threshold Temperature', HeatingThresholdTemperature.UUID, { format: Formats.FLOAT, unit: Units.CELSIUS, maxValue: 25, @@ -1018,8 +980,7 @@ export class HoldPosition extends Characteristic { static readonly UUID: string = '0000006F-0000-1000-8000-0026BB765291'; constructor() { - super('Hold Position', HoldPosition.UUID); - this.setProps({ + super('Hold Position', HoldPosition.UUID, { format: Formats.BOOL, perms: [Perms.PAIRED_WRITE] }); @@ -1038,8 +999,7 @@ export class Hue extends Characteristic { static readonly UUID: string = '00000013-0000-1000-8000-0026BB765291'; constructor() { - super('Hue', Hue.UUID); - this.setProps({ + super('Hue', Hue.UUID, { format: Formats.FLOAT, unit: Units.ARC_DEGREE, maxValue: 360, @@ -1062,8 +1022,7 @@ export class Identify extends Characteristic { static readonly UUID: string = '00000014-0000-1000-8000-0026BB765291'; constructor() { - super('Identify', Identify.UUID); - this.setProps({ + super('Identify', Identify.UUID, { format: Formats.BOOL, perms: [Perms.PAIRED_WRITE] }); @@ -1082,8 +1041,7 @@ export class ImageMirroring extends Characteristic { static readonly UUID: string = '0000011F-0000-1000-8000-0026BB765291'; constructor() { - super('Image Mirroring', ImageMirroring.UUID); - this.setProps({ + super('Image Mirroring', ImageMirroring.UUID, { format: Formats.BOOL, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); @@ -1102,8 +1060,7 @@ export class ImageRotation extends Characteristic { static readonly UUID: string = '0000011E-0000-1000-8000-0026BB765291'; constructor() { - super('Image Rotation', ImageRotation.UUID); - this.setProps({ + super('Image Rotation', ImageRotation.UUID, { format: Formats.FLOAT, unit: Units.ARC_DEGREE, maxValue: 270, @@ -1130,8 +1087,7 @@ export class InUse extends Characteristic { static readonly UUID: string = '000000D2-0000-1000-8000-0026BB765291'; constructor() { - super('In Use', InUse.UUID); - this.setProps({ + super('In Use', InUse.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -1157,8 +1113,7 @@ export class IsConfigured extends Characteristic { static readonly UUID: string = '000000D6-0000-1000-8000-0026BB765291'; constructor() { - super('Is Configured', IsConfigured.UUID); - this.setProps({ + super('Is Configured', IsConfigured.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -1184,8 +1139,7 @@ export class LeakDetected extends Characteristic { static readonly UUID: string = '00000070-0000-1000-8000-0026BB765291'; constructor() { - super('Leak Detected', LeakDetected.UUID); - this.setProps({ + super('Leak Detected', LeakDetected.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -1207,8 +1161,7 @@ export class LockControlPoint extends Characteristic { static readonly UUID: string = '00000019-0000-1000-8000-0026BB765291'; constructor() { - super('Lock Control Point', LockControlPoint.UUID); - this.setProps({ + super('Lock Control Point', LockControlPoint.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_WRITE] }); @@ -1233,8 +1186,7 @@ export class LockCurrentState extends Characteristic { static readonly UUID: string = '0000001D-0000-1000-8000-0026BB765291'; constructor() { - super('Lock Current State', LockCurrentState.UUID); - this.setProps({ + super('Lock Current State', LockCurrentState.UUID, { format: Formats.UINT8, maxValue: 3, minValue: 0, @@ -1267,8 +1219,7 @@ export class LockLastKnownAction extends Characteristic { static readonly UUID: string = '0000001C-0000-1000-8000-0026BB765291'; constructor() { - super('Lock Last Known Action', LockLastKnownAction.UUID); - this.setProps({ + super('Lock Last Known Action', LockLastKnownAction.UUID, { format: Formats.UINT8, maxValue: 8, minValue: 0, @@ -1290,8 +1241,7 @@ 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({ + super('Lock Management Auto Security Timeout', LockManagementAutoSecurityTimeout.UUID, { format: Formats.UINT32, unit: Units.SECONDS, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] @@ -1315,8 +1265,7 @@ export class LockPhysicalControls extends Characteristic { static readonly UUID: string = '000000A7-0000-1000-8000-0026BB765291'; constructor() { - super('Lock Physical Controls', LockPhysicalControls.UUID); - this.setProps({ + super('Lock Physical Controls', LockPhysicalControls.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -1342,8 +1291,7 @@ export class LockTargetState extends Characteristic { static readonly UUID: string = '0000001E-0000-1000-8000-0026BB765291'; constructor() { - super('Lock Target State', LockTargetState.UUID); - this.setProps({ + super('Lock Target State', LockTargetState.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -1365,8 +1313,7 @@ export class Logs extends Characteristic { static readonly UUID: string = '0000001F-0000-1000-8000-0026BB765291'; constructor() { - super('Logs', Logs.UUID); - this.setProps({ + super('Logs', Logs.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); @@ -1385,8 +1332,7 @@ export class Manufacturer extends Characteristic { static readonly UUID: string = '00000020-0000-1000-8000-0026BB765291'; constructor() { - super('Manufacturer', Manufacturer.UUID); - this.setProps({ + super('Manufacturer', Manufacturer.UUID, { format: Formats.STRING, perms: [Perms.PAIRED_READ] }); @@ -1405,8 +1351,7 @@ export class Model extends Characteristic { static readonly UUID: string = '00000021-0000-1000-8000-0026BB765291'; constructor() { - super('Model', Model.UUID); - this.setProps({ + super('Model', Model.UUID, { format: Formats.STRING, perms: [Perms.PAIRED_READ] }); @@ -1425,8 +1370,7 @@ export class MotionDetected extends Characteristic { static readonly UUID: string = '00000022-0000-1000-8000-0026BB765291'; constructor() { - super('Motion Detected', MotionDetected.UUID); - this.setProps({ + super('Motion Detected', MotionDetected.UUID, { format: Formats.BOOL, perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); @@ -1445,8 +1389,7 @@ export class Mute extends Characteristic { static readonly UUID: string = '0000011A-0000-1000-8000-0026BB765291'; constructor() { - super('Mute', Mute.UUID); - this.setProps({ + super('Mute', Mute.UUID, { format: Formats.BOOL, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); @@ -1465,8 +1408,7 @@ export class Name extends Characteristic { static readonly UUID: string = '00000023-0000-1000-8000-0026BB765291'; constructor() { - super('Name', Name.UUID); - this.setProps({ + super('Name', Name.UUID, { format: Formats.STRING, perms: [Perms.PAIRED_READ] }); @@ -1485,8 +1427,7 @@ export class NightVision extends Characteristic { static readonly UUID: string = '0000011B-0000-1000-8000-0026BB765291'; constructor() { - super('Night Vision', NightVision.UUID); - this.setProps({ + super('Night Vision', NightVision.UUID, { format: Formats.BOOL, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY, Perms.TIMED_WRITE] }); @@ -1505,8 +1446,7 @@ export class NitrogenDioxideDensity extends Characteristic { static readonly UUID: string = '000000C4-0000-1000-8000-0026BB765291'; constructor() { - super('Nitrogen Dioxide Density', NitrogenDioxideDensity.UUID); - this.setProps({ + super('Nitrogen Dioxide Density', NitrogenDioxideDensity.UUID, { format: Formats.FLOAT, maxValue: 1000, minValue: 0, @@ -1528,8 +1468,7 @@ export class ObstructionDetected extends Characteristic { static readonly UUID: string = '00000024-0000-1000-8000-0026BB765291'; constructor() { - super('Obstruction Detected', ObstructionDetected.UUID); - this.setProps({ + super('Obstruction Detected', ObstructionDetected.UUID, { format: Formats.BOOL, perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); @@ -1552,8 +1491,7 @@ export class OccupancyDetected extends Characteristic { static readonly UUID: string = '00000071-0000-1000-8000-0026BB765291'; constructor() { - super('Occupancy Detected', OccupancyDetected.UUID); - this.setProps({ + super('Occupancy Detected', OccupancyDetected.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -1575,8 +1513,7 @@ export class On extends Characteristic { static readonly UUID: string = '00000025-0000-1000-8000-0026BB765291'; constructor() { - super('On', On.UUID); - this.setProps({ + super('On', On.UUID, { format: Formats.BOOL, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); @@ -1595,8 +1532,7 @@ export class OpticalZoom extends Characteristic { static readonly UUID: string = '0000011C-0000-1000-8000-0026BB765291'; constructor() { - super('Optical Zoom', OpticalZoom.UUID); - this.setProps({ + super('Optical Zoom', OpticalZoom.UUID, { format: Formats.FLOAT, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); @@ -1615,8 +1551,7 @@ export class OutletInUse extends Characteristic { static readonly UUID: string = '00000026-0000-1000-8000-0026BB765291'; constructor() { - super('Outlet In Use', OutletInUse.UUID); - this.setProps({ + super('Outlet In Use', OutletInUse.UUID, { format: Formats.BOOL, perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); @@ -1635,8 +1570,7 @@ export class OzoneDensity extends Characteristic { static readonly UUID: string = '000000C3-0000-1000-8000-0026BB765291'; constructor() { - super('Ozone Density', OzoneDensity.UUID); - this.setProps({ + super('Ozone Density', OzoneDensity.UUID, { format: Formats.FLOAT, maxValue: 1000, minValue: 0, @@ -1658,8 +1592,7 @@ export class PairSetup extends Characteristic { static readonly UUID: string = '0000004C-0000-1000-8000-0026BB765291'; constructor() { - super('Pair Setup', PairSetup.UUID); - this.setProps({ + super('Pair Setup', PairSetup.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE] }); @@ -1678,8 +1611,7 @@ export class PairVerify extends Characteristic { static readonly UUID: string = '0000004E-0000-1000-8000-0026BB765291'; constructor() { - super('Pair Verify', PairVerify.UUID); - this.setProps({ + super('Pair Verify', PairVerify.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE] }); @@ -1698,8 +1630,7 @@ export class PairingFeatures extends Characteristic { static readonly UUID: string = '0000004F-0000-1000-8000-0026BB765291'; constructor() { - super('Pairing Features', PairingFeatures.UUID); - this.setProps({ + super('Pairing Features', PairingFeatures.UUID, { format: Formats.UINT8, perms: [Perms.PAIRED_READ] }); @@ -1718,8 +1649,7 @@ export class PairingPairings extends Characteristic { static readonly UUID: string = '00000050-0000-1000-8000-0026BB765291'; constructor() { - super('Pairing Pairings', PairingPairings.UUID); - this.setProps({ + super('Pairing Pairings', PairingPairings.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE] }); @@ -1738,8 +1668,7 @@ export class PasswordSetting extends Characteristic { static readonly UUID: string = '000000E4-0000-1000-8000-0026BB765291'; constructor() { - super('Password Setting', PasswordSetting.UUID); - this.setProps({ + super('Password Setting', PasswordSetting.UUID, { format: Formats.TLV8, perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], }); @@ -1758,8 +1687,7 @@ export class PM10Density extends Characteristic { static readonly UUID: string = '000000C7-0000-1000-8000-0026BB765291'; constructor() { - super('PM10 Density', PM10Density.UUID); - this.setProps({ + super('PM10 Density', PM10Density.UUID, { format: Formats.FLOAT, maxValue: 1000, minValue: 0, @@ -1781,8 +1709,7 @@ 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({ + super('PM2.5 Density', PM2_5Density.UUID, { format: Formats.FLOAT, maxValue: 1000, minValue: 0, @@ -1809,8 +1736,7 @@ export class PositionState extends Characteristic { static readonly UUID: string = '00000072-0000-1000-8000-0026BB765291'; constructor() { - super('Position State', PositionState.UUID); - this.setProps({ + super('Position State', PositionState.UUID, { format: Formats.UINT8, maxValue: 2, minValue: 0, @@ -1837,8 +1763,7 @@ export class ProgramMode extends Characteristic { static readonly UUID: string = '000000D1-0000-1000-8000-0026BB765291'; constructor() { - super('Program Mode', ProgramMode.UUID); - this.setProps({ + super('Program Mode', ProgramMode.UUID, { format: Formats.UINT8, maxValue: 2, minValue: 0, @@ -1865,8 +1790,7 @@ export class ProgrammableSwitchEvent extends Characteristic { static readonly UUID: string = '00000073-0000-1000-8000-0026BB765291'; constructor() { - super('Programmable Switch Event', ProgrammableSwitchEvent.UUID); - this.setProps({ + super('Programmable Switch Event', ProgrammableSwitchEvent.UUID, { format: Formats.UINT8, maxValue: 2, minValue: 0, @@ -1888,8 +1812,7 @@ export class RelativeHumidityDehumidifierThreshold extends Characteristic { static readonly UUID: string = '000000C9-0000-1000-8000-0026BB765291'; constructor() { - super('Relative Humidity Dehumidifier Threshold', RelativeHumidityDehumidifierThreshold.UUID); - this.setProps({ + super('Relative Humidity Dehumidifier Threshold', RelativeHumidityDehumidifierThreshold.UUID, { format: Formats.FLOAT, maxValue: 100, minValue: 0, @@ -1911,8 +1834,7 @@ export class RelativeHumidityHumidifierThreshold extends Characteristic { static readonly UUID: string = '000000CA-0000-1000-8000-0026BB765291'; constructor() { - super('Relative Humidity Humidifier Threshold', RelativeHumidityHumidifierThreshold.UUID); - this.setProps({ + super('Relative Humidity Humidifier Threshold', RelativeHumidityHumidifierThreshold.UUID, { format: Formats.FLOAT, unit: Units.PERCENTAGE, maxValue: 100, @@ -1935,8 +1857,7 @@ export class RemainingDuration extends Characteristic { static readonly UUID: string = '000000D4-0000-1000-8000-0026BB765291'; constructor() { - super('Remaining Duration', RemainingDuration.UUID); - this.setProps({ + super('Remaining Duration', RemainingDuration.UUID, { format: Formats.UINT32, maxValue: 3600, minValue: 0, @@ -1958,8 +1879,7 @@ export class ResetFilterIndication extends Characteristic { static readonly UUID: string = '000000AD-0000-1000-8000-0026BB765291'; constructor() { - super('Reset Filter Indication', ResetFilterIndication.UUID); - this.setProps({ + super('Reset Filter Indication', ResetFilterIndication.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 1, @@ -1985,8 +1905,7 @@ export class RotationDirection extends Characteristic { static readonly UUID: string = '00000028-0000-1000-8000-0026BB765291'; constructor() { - super('Rotation Direction', RotationDirection.UUID); - this.setProps({ + super('Rotation Direction', RotationDirection.UUID, { format: Formats.INT, maxValue: 1, minValue: 0, @@ -2008,8 +1927,7 @@ export class RotationSpeed extends Characteristic { static readonly UUID: string = '00000029-0000-1000-8000-0026BB765291'; constructor() { - super('Rotation Speed', RotationSpeed.UUID); - this.setProps({ + super('Rotation Speed', RotationSpeed.UUID, { format: Formats.FLOAT, unit: Units.PERCENTAGE, maxValue: 100, @@ -2032,8 +1950,7 @@ export class Saturation extends Characteristic { static readonly UUID: string = '0000002F-0000-1000-8000-0026BB765291'; constructor() { - super('Saturation', Saturation.UUID); - this.setProps({ + super('Saturation', Saturation.UUID, { format: Formats.FLOAT, unit: Units.PERCENTAGE, maxValue: 100, @@ -2056,8 +1973,7 @@ export class SecuritySystemAlarmType extends Characteristic { static readonly UUID: string = '0000008E-0000-1000-8000-0026BB765291'; constructor() { - super('Security System Alarm Type', SecuritySystemAlarmType.UUID); - this.setProps({ + super('Security System Alarm Type', SecuritySystemAlarmType.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -2086,8 +2002,7 @@ export class SecuritySystemCurrentState extends Characteristic { static readonly UUID: string = '00000066-0000-1000-8000-0026BB765291'; constructor() { - super('Security System Current State', SecuritySystemCurrentState.UUID); - this.setProps({ + super('Security System Current State', SecuritySystemCurrentState.UUID, { format: Formats.UINT8, maxValue: 4, minValue: 0, @@ -2115,8 +2030,7 @@ export class SecuritySystemTargetState extends Characteristic { static readonly UUID: string = '00000067-0000-1000-8000-0026BB765291'; constructor() { - super('Security System Target State', SecuritySystemTargetState.UUID); - this.setProps({ + super('Security System Target State', SecuritySystemTargetState.UUID, { format: Formats.UINT8, maxValue: 3, minValue: 0, @@ -2138,8 +2052,7 @@ export class SelectedRTPStreamConfiguration extends Characteristic { static readonly UUID: string = '00000117-0000-1000-8000-0026BB765291'; constructor() { - super('Selected RTP Stream Configuration', SelectedRTPStreamConfiguration.UUID); - this.setProps({ + super('Selected RTP Stream Configuration', SelectedRTPStreamConfiguration.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE] }); @@ -2158,8 +2071,7 @@ export class SerialNumber extends Characteristic { static readonly UUID: string = '00000030-0000-1000-8000-0026BB765291'; constructor() { - super('Serial Number', SerialNumber.UUID); - this.setProps({ + super('Serial Number', SerialNumber.UUID, { format: Formats.STRING, perms: [Perms.PAIRED_READ] }); @@ -2178,8 +2090,7 @@ export class ServiceLabelIndex extends Characteristic { static readonly UUID: string = '000000CB-0000-1000-8000-0026BB765291'; constructor() { - super('Service Label Index', ServiceLabelIndex.UUID); - this.setProps({ + super('Service Label Index', ServiceLabelIndex.UUID, { format: Formats.UINT8, maxValue: 255, minValue: 1, @@ -2205,8 +2116,7 @@ export class ServiceLabelNamespace extends Characteristic { static readonly UUID: string = '000000CD-0000-1000-8000-0026BB765291'; constructor() { - super('Service Label Namespace', ServiceLabelNamespace.UUID); - this.setProps({ + super('Service Label Namespace', ServiceLabelNamespace.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -2228,8 +2138,7 @@ export class SetDuration extends Characteristic { static readonly UUID: string = '000000D3-0000-1000-8000-0026BB765291'; constructor() { - super('Set Duration', SetDuration.UUID); - this.setProps({ + super('Set Duration', SetDuration.UUID, { format: Formats.UINT32, maxValue: 3600, minValue: 0, @@ -2251,8 +2160,7 @@ export class SetupEndpoints extends Characteristic { static readonly UUID: string = '00000118-0000-1000-8000-0026BB765291'; constructor() { - super('Setup Endpoints', SetupEndpoints.UUID); - this.setProps({ + super('Setup Endpoints', SetupEndpoints.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE] }); @@ -2275,8 +2183,7 @@ export class SlatType extends Characteristic { static readonly UUID: string = '000000C0-0000-1000-8000-0026BB765291'; constructor() { - super('Slat Type', SlatType.UUID); - this.setProps({ + super('Slat Type', SlatType.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -2302,8 +2209,7 @@ export class SmokeDetected extends Characteristic { static readonly UUID: string = '00000076-0000-1000-8000-0026BB765291'; constructor() { - super('Smoke Detected', SmokeDetected.UUID); - this.setProps({ + super('Smoke Detected', SmokeDetected.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -2325,8 +2231,7 @@ export class StatusActive extends Characteristic { static readonly UUID: string = '00000075-0000-1000-8000-0026BB765291'; constructor() { - super('Status Active', StatusActive.UUID); - this.setProps({ + super('Status Active', StatusActive.UUID, { format: Formats.BOOL, perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); @@ -2349,8 +2254,7 @@ export class StatusFault extends Characteristic { static readonly UUID: string = '00000077-0000-1000-8000-0026BB765291'; constructor() { - super('Status Fault', StatusFault.UUID); - this.setProps({ + super('Status Fault', StatusFault.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -2376,8 +2280,7 @@ export class StatusJammed extends Characteristic { static readonly UUID: string = '00000078-0000-1000-8000-0026BB765291'; constructor() { - super('Status Jammed', StatusJammed.UUID); - this.setProps({ + super('Status Jammed', StatusJammed.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -2403,8 +2306,7 @@ export class StatusLowBattery extends Characteristic { static readonly UUID: string = '00000079-0000-1000-8000-0026BB765291'; constructor() { - super('Status Low Battery', StatusLowBattery.UUID); - this.setProps({ + super('Status Low Battery', StatusLowBattery.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -2431,8 +2333,7 @@ export class StatusTampered extends Characteristic { static readonly UUID: string = '0000007A-0000-1000-8000-0026BB765291'; constructor() { - super('Status Tampered', StatusTampered.UUID); - this.setProps({ + super('Status Tampered', StatusTampered.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -2454,8 +2355,7 @@ export class StreamingStatus extends Characteristic { static readonly UUID: string = '00000120-0000-1000-8000-0026BB765291'; constructor() { - super('Streaming Status', StreamingStatus.UUID); - this.setProps({ + super('Streaming Status', StreamingStatus.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); @@ -2474,8 +2374,7 @@ export class SulphurDioxideDensity extends Characteristic { static readonly UUID: string = '000000C5-0000-1000-8000-0026BB765291'; constructor() { - super('Sulphur Dioxide Density', SulphurDioxideDensity.UUID); - this.setProps({ + super('Sulphur Dioxide Density', SulphurDioxideDensity.UUID, { format: Formats.FLOAT, maxValue: 1000, minValue: 0, @@ -2497,8 +2396,7 @@ export class SupportedAudioStreamConfiguration extends Characteristic { static readonly UUID: string = '00000115-0000-1000-8000-0026BB765291'; constructor() { - super('Supported Audio Stream Configuration', SupportedAudioStreamConfiguration.UUID); - this.setProps({ + super('Supported Audio Stream Configuration', SupportedAudioStreamConfiguration.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ] }); @@ -2517,8 +2415,7 @@ export class SupportedRTPConfiguration extends Characteristic { static readonly UUID: string = '00000116-0000-1000-8000-0026BB765291'; constructor() { - super('Supported RTP Configuration', SupportedRTPConfiguration.UUID); - this.setProps({ + super('Supported RTP Configuration', SupportedRTPConfiguration.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ] }); @@ -2537,8 +2434,7 @@ export class SupportedVideoStreamConfiguration extends Characteristic { static readonly UUID: string = '00000114-0000-1000-8000-0026BB765291'; constructor() { - super('Supported Video Stream Configuration', SupportedVideoStreamConfiguration.UUID); - this.setProps({ + super('Supported Video Stream Configuration', SupportedVideoStreamConfiguration.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ] }); @@ -2561,8 +2457,7 @@ export class SwingMode extends Characteristic { static readonly UUID: string = '000000B6-0000-1000-8000-0026BB765291'; constructor() { - super('Swing Mode', SwingMode.UUID); - this.setProps({ + super('Swing Mode', SwingMode.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -2588,8 +2483,7 @@ export class TargetAirPurifierState extends Characteristic { static readonly UUID: string = '000000A8-0000-1000-8000-0026BB765291'; constructor() { - super('Target Air Purifier State', TargetAirPurifierState.UUID); - this.setProps({ + super('Target Air Purifier State', TargetAirPurifierState.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -2616,8 +2510,7 @@ export class TargetAirQuality extends Characteristic { static readonly UUID: string = '000000AE-0000-1000-8000-0026BB765291'; constructor() { - super('Target Air Quality', TargetAirQuality.UUID); - this.setProps({ + super('Target Air Quality', TargetAirQuality.UUID, { format: Formats.UINT8, maxValue: 2, minValue: 0, @@ -2643,8 +2536,7 @@ export class TargetDoorState extends Characteristic { static readonly UUID: string = '00000032-0000-1000-8000-0026BB765291'; constructor() { - super('Target Door State', TargetDoorState.UUID); - this.setProps({ + super('Target Door State', TargetDoorState.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -2670,8 +2562,7 @@ export class TargetFanState extends Characteristic { static readonly UUID: string = '000000BF-0000-1000-8000-0026BB765291'; constructor() { - super('Target Fan State', TargetFanState.UUID); - this.setProps({ + super('Target Fan State', TargetFanState.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -2698,8 +2589,7 @@ export class TargetHeaterCoolerState extends Characteristic { static readonly UUID: string = '000000B2-0000-1000-8000-0026BB765291'; constructor() { - super('Target Heater Cooler State', TargetHeaterCoolerState.UUID); - this.setProps({ + super('Target Heater Cooler State', TargetHeaterCoolerState.UUID, { format: Formats.UINT8, maxValue: 2, minValue: 0, @@ -2727,8 +2617,7 @@ export class TargetHeatingCoolingState extends Characteristic { static readonly UUID: string = '00000033-0000-1000-8000-0026BB765291'; constructor() { - super('Target Heating Cooling State', TargetHeatingCoolingState.UUID); - this.setProps({ + super('Target Heating Cooling State', TargetHeatingCoolingState.UUID, { format: Formats.UINT8, maxValue: 3, minValue: 0, @@ -2750,8 +2639,7 @@ export class TargetHorizontalTiltAngle extends Characteristic { static readonly UUID: string = '0000007B-0000-1000-8000-0026BB765291'; constructor() { - super('Target Horizontal Tilt Angle', TargetHorizontalTiltAngle.UUID); - this.setProps({ + super('Target Horizontal Tilt Angle', TargetHorizontalTiltAngle.UUID, { format: Formats.INT, unit: Units.ARC_DEGREE, maxValue: 90, @@ -2784,8 +2672,7 @@ export class TargetHumidifierDehumidifierState extends Characteristic { static readonly UUID: string = '000000B4-0000-1000-8000-0026BB765291'; constructor() { - super('Target Humidifier Dehumidifier State', TargetHumidifierDehumidifierState.UUID); - this.setProps({ + super('Target Humidifier Dehumidifier State', TargetHumidifierDehumidifierState.UUID, { format: Formats.UINT8, maxValue: 2, minValue: 0, @@ -2807,8 +2694,7 @@ export class TargetPosition extends Characteristic { static readonly UUID: string = '0000007C-0000-1000-8000-0026BB765291'; constructor() { - super('Target Position', TargetPosition.UUID); - this.setProps({ + super('Target Position', TargetPosition.UUID, { format: Formats.UINT8, unit: Units.PERCENTAGE, maxValue: 100, @@ -2831,8 +2717,7 @@ export class TargetRelativeHumidity extends Characteristic { static readonly UUID: string = '00000034-0000-1000-8000-0026BB765291'; constructor() { - super('Target Relative Humidity', TargetRelativeHumidity.UUID); - this.setProps({ + super('Target Relative Humidity', TargetRelativeHumidity.UUID, { format: Formats.FLOAT, unit: Units.PERCENTAGE, maxValue: 100, @@ -2859,8 +2744,7 @@ export class TargetSlatState extends Characteristic { static readonly UUID: string = '000000BE-0000-1000-8000-0026BB765291'; constructor() { - super('Target Slat State', TargetSlatState.UUID); - this.setProps({ + super('Target Slat State', TargetSlatState.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -2882,8 +2766,7 @@ export class TargetTemperature extends Characteristic { static readonly UUID: string = '00000035-0000-1000-8000-0026BB765291'; constructor() { - super('Target Temperature', TargetTemperature.UUID); - this.setProps({ + super('Target Temperature', TargetTemperature.UUID, { format: Formats.FLOAT, unit: Units.CELSIUS, maxValue: 38, @@ -2906,8 +2789,7 @@ export class TargetTiltAngle extends Characteristic { static readonly UUID: string = '000000C2-0000-1000-8000-0026BB765291'; constructor() { - super('Target Tilt Angle', TargetTiltAngle.UUID); - this.setProps({ + super('Target Tilt Angle', TargetTiltAngle.UUID, { format: Formats.INT, unit: Units.ARC_DEGREE, maxValue: 90, @@ -2930,8 +2812,7 @@ export class TargetVerticalTiltAngle extends Characteristic { static readonly UUID: string = '0000007D-0000-1000-8000-0026BB765291'; constructor() { - super('Target Vertical Tilt Angle', TargetVerticalTiltAngle.UUID); - this.setProps({ + super('Target Vertical Tilt Angle', TargetVerticalTiltAngle.UUID, { format: Formats.INT, unit: Units.ARC_DEGREE, maxValue: 90, @@ -2958,8 +2839,7 @@ export class TemperatureDisplayUnits extends Characteristic { static readonly UUID: string = '00000036-0000-1000-8000-0026BB765291'; constructor() { - super('Temperature Display Units', TemperatureDisplayUnits.UUID); - this.setProps({ + super('Temperature Display Units', TemperatureDisplayUnits.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -2987,8 +2867,7 @@ export class ValveType extends Characteristic { static readonly UUID: string = '000000D5-0000-1000-8000-0026BB765291'; constructor() { - super('Valve Type', ValveType.UUID); - this.setProps({ + super('Valve Type', ValveType.UUID, { format: Formats.UINT8, maxValue: 3, minValue: 0, @@ -3010,8 +2889,7 @@ export class Version extends Characteristic { static readonly UUID: string = '00000037-0000-1000-8000-0026BB765291'; constructor() { - super('Version', Version.UUID); - this.setProps({ + super('Version', Version.UUID, { format: Formats.STRING, perms: [Perms.PAIRED_READ] }); @@ -3030,8 +2908,7 @@ export class VOCDensity extends Characteristic { static readonly UUID: string = '000000C8-0000-1000-8000-0026BB765291'; constructor() { - super('VOC Density', VOCDensity.UUID); - this.setProps({ + super('VOC Density', VOCDensity.UUID, { format: Formats.FLOAT, maxValue: 1000, minValue: 0, @@ -3053,8 +2930,7 @@ export class Volume extends Characteristic { static readonly UUID: string = '00000119-0000-1000-8000-0026BB765291'; constructor() { - super('Volume', Volume.UUID); - this.setProps({ + super('Volume', Volume.UUID, { format: Formats.UINT8, unit: Units.PERCENTAGE, maxValue: 100, @@ -3077,8 +2953,7 @@ export class WaterLevel extends Characteristic { static readonly UUID: string = '000000B5-0000-1000-8000-0026BB765291'; constructor() { - super('Water Level', WaterLevel.UUID); - this.setProps({ + super('Water Level', WaterLevel.UUID, { format: Formats.FLOAT, maxValue: 100, minValue: 0, @@ -3102,8 +2977,7 @@ export class RecordingAudioActive extends Characteristic { static readonly UUID: string = '00000226-0000-1000-8000-0026BB765291'; constructor() { - super('Recording Audio Active', RecordingAudioActive.UUID); - this.setProps({ + super('Recording Audio Active', RecordingAudioActive.UUID, { format: Formats.UINT8, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); @@ -3122,8 +2996,7 @@ export class SupportedCameraRecordingConfiguration extends Characteristic { static readonly UUID: string = '00000205-0000-1000-8000-0026BB765291'; constructor() { - super('Supported Camera Recording Configuration', SupportedCameraRecordingConfiguration.UUID); - this.setProps({ + super('Supported Camera Recording Configuration', SupportedCameraRecordingConfiguration.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); @@ -3142,8 +3015,7 @@ export class SupportedVideoRecordingConfiguration extends Characteristic { static readonly UUID: string = '00000206-0000-1000-8000-0026BB765291'; constructor() { - super('Supported Video Recording Configuration', SupportedVideoRecordingConfiguration.UUID); - this.setProps({ + super('Supported Video Recording Configuration', SupportedVideoRecordingConfiguration.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); @@ -3162,8 +3034,7 @@ export class SupportedAudioRecordingConfiguration extends Characteristic { static readonly UUID: string = '00000207-0000-1000-8000-0026BB765291'; constructor() { - super('Supported Audio Recording Configuration', SupportedAudioRecordingConfiguration.UUID); - this.setProps({ + super('Supported Audio Recording Configuration', SupportedAudioRecordingConfiguration.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); @@ -3182,8 +3053,7 @@ export class SelectedCameraRecordingConfiguration extends Characteristic { static readonly UUID: string = '00000209-0000-1000-8000-0026BB765291'; constructor() { - super('Selected Camera Recording Configuration', SelectedCameraRecordingConfiguration.UUID); - this.setProps({ + super('Selected Camera Recording Configuration', SelectedCameraRecordingConfiguration.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); @@ -3205,8 +3075,7 @@ export class CameraOperatingModeIndicator extends Characteristic { static readonly UUID: string = '0000021D-0000-1000-8000-0026BB765291'; constructor() { - super('Camera Operating Mode Indicator', CameraOperatingModeIndicator.UUID); - this.setProps({ + super('Camera Operating Mode Indicator', CameraOperatingModeIndicator.UUID, { format: Formats.UINT8, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY, Perms.TIMED_WRITE] }); @@ -3228,8 +3097,7 @@ export class EventSnapshotsActive extends Characteristic { static readonly UUID: string = '00000223-0000-1000-8000-0026BB765291'; constructor() { - super('Event Snapshots Active', EventSnapshotsActive.UUID); - this.setProps({ + super('Event Snapshots Active', EventSnapshotsActive.UUID, { format: Formats.UINT8, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); @@ -3248,8 +3116,7 @@ export class DiagonalFieldOfView extends Characteristic { static readonly UUID: string = '00000224-0000-1000-8000-0026BB765291'; constructor() { - super('Diagonal Field Of View', DiagonalFieldOfView.UUID); - this.setProps({ + super('Diagonal Field Of View', DiagonalFieldOfView.UUID, { format: Formats.FLOAT, unit: Units.ARC_DEGREE, maxValue: 360, @@ -3275,8 +3142,7 @@ export class HomeKitCameraActive extends Characteristic { static readonly UUID: string = '0000021B-0000-1000-8000-0026BB765291'; constructor() { - super('HomeKit Camera Active', HomeKitCameraActive.UUID); - this.setProps({ + super('HomeKit Camera Active', HomeKitCameraActive.UUID, { format: Formats.UINT8, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY, Perms.TIMED_WRITE] }); @@ -3298,8 +3164,7 @@ export class ManuallyDisabled extends Characteristic { static readonly UUID: string = '00000227-0000-1000-8000-0026BB765291'; constructor() { - super('Manually disabled', ManuallyDisabled.UUID); - this.setProps({ + super('Manually disabled', ManuallyDisabled.UUID, { format: Formats.BOOL, perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); @@ -3321,8 +3186,7 @@ export class ThirdPartyCameraActive extends Characteristic { static readonly UUID: string = '0000021C-0000-1000-8000-0026BB765291'; constructor() { - super('Third Party Camera Active', ThirdPartyCameraActive.UUID); - this.setProps({ + super('Third Party Camera Active', ThirdPartyCameraActive.UUID, { format: Formats.UINT8, perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); @@ -3344,8 +3208,7 @@ export class PeriodicSnapshotsActive extends Characteristic { static readonly UUID: string = '00000225-0000-1000-8000-0026BB765291'; constructor() { - super('Periodic Snapshots Active', PeriodicSnapshotsActive.UUID); - this.setProps({ + super('Periodic Snapshots Active', PeriodicSnapshotsActive.UUID, { format: Formats.UINT8, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] }); @@ -3364,8 +3227,7 @@ export class NetworkClientProfileControl extends Characteristic { static readonly UUID: string = '0000020C-0000-1000-8000-0026BB765291'; constructor() { - super('Network Client Profile Control', NetworkClientProfileControl.UUID); - this.setProps({ + super('Network Client Profile Control', NetworkClientProfileControl.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY, Perms.TIMED_WRITE, Perms.WRITE_RESPONSE] }); @@ -3384,8 +3246,7 @@ export class NetworkClientStatusControl extends Characteristic { static readonly UUID: string = '0000020D-0000-1000-8000-0026BB765291'; constructor() { - super('Network Client Status Control', NetworkClientStatusControl.UUID); - this.setProps({ + super('Network Client Status Control', NetworkClientStatusControl.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.WRITE_RESPONSE] }); @@ -3407,8 +3268,7 @@ export class RouterStatus extends Characteristic { static readonly UUID: string = '0000020E-0000-1000-8000-0026BB765291'; constructor() { - super('Router Status', RouterStatus.UUID); - this.setProps({ + super('Router Status', RouterStatus.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -3430,8 +3290,7 @@ export class SupportedRouterConfiguration extends Characteristic { static readonly UUID: string = '00000210-0000-1000-8000-0026BB765291'; constructor() { - super('Supported Router Configuration', SupportedRouterConfiguration.UUID); - this.setProps({ + super('Supported Router Configuration', SupportedRouterConfiguration.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ] }); @@ -3450,8 +3309,7 @@ export class WANConfigurationList extends Characteristic { static readonly UUID: string = '00000211-0000-1000-8000-0026BB765291'; constructor() { - super('WAN Configuration List', WANConfigurationList.UUID); - this.setProps({ + super('WAN Configuration List', WANConfigurationList.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); @@ -3470,8 +3328,7 @@ export class WANStatusList extends Characteristic { static readonly UUID: string = '00000212-0000-1000-8000-0026BB765291'; constructor() { - super('WAN Status List', WANStatusList.UUID); - this.setProps({ + super('WAN Status List', WANStatusList.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ, Perms.NOTIFY] }); @@ -3493,8 +3350,7 @@ export class ManagedNetworkEnable extends Characteristic { static readonly UUID: string = '00000215-0000-1000-8000-0026BB765291'; constructor() { - super('Managed Network Enable', ManagedNetworkEnable.UUID); - this.setProps({ + super('Managed Network Enable', ManagedNetworkEnable.UUID, { format: Formats.UINT8, maxValue: 1, minValue: 0, @@ -3516,8 +3372,7 @@ export class NetworkAccessViolationControl extends Characteristic { static readonly UUID: string = '0000021F-0000-1000-8000-0026BB765291'; constructor() { - super('Network Access Violation Control', NetworkAccessViolationControl.UUID); - this.setProps({ + super('Network Access Violation Control', NetworkAccessViolationControl.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY, Perms.TIMED_WRITE, Perms.WRITE_RESPONSE] }); @@ -3540,8 +3395,7 @@ export class WiFiSatelliteStatus extends Characteristic { static readonly UUID: string = '0000021E-0000-1000-8000-0026BB765291'; constructor() { - super('Wi-Fi Satellite Status', WiFiSatelliteStatus.UUID); - this.setProps({ + super('Wi-Fi Satellite Status', WiFiSatelliteStatus.UUID, { format: Formats.UINT8, maxValue: 2, minValue: 0, @@ -3564,8 +3418,7 @@ export class WakeConfiguration extends Characteristic { static readonly UUID: string = '00000222-0000-1000-8000-0026BB765291'; constructor() { - super('Wake Configuration', WakeConfiguration.UUID); - this.setProps({ + super('Wake Configuration', WakeConfiguration.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ], }); @@ -3585,8 +3438,7 @@ export class SupportedTransferTransportConfiguration extends Characteristic { static readonly UUID: string = '00000202-0000-1000-8000-0026BB765291'; constructor() { - super('Supported Transfer Transport Configuration', SupportedTransferTransportConfiguration.UUID); - this.setProps({ + super('Supported Transfer Transport Configuration', SupportedTransferTransportConfiguration.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ], }); @@ -3606,8 +3458,7 @@ export class SetupTransferTransport extends Characteristic { static readonly UUID: string = '00000201-0000-1000-8000-0026BB765291'; constructor() { - super('Setup Transfer Transport', SetupTransferTransport.UUID); - this.setProps({ + super('Setup Transfer Transport', SetupTransferTransport.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_WRITE, Perms.WRITE_RESPONSE], }); @@ -3626,8 +3477,7 @@ export class ActivityInterval extends Characteristic { static readonly UUID: string = '0000023B-0000-1000-8000-0026BB765291'; constructor() { - super("Activity Interval", ActivityInterval.UUID); - this.setProps({ + super("Activity Interval", ActivityInterval.UUID, { format: Formats.UINT32, minValue: 0, minStep: 1, @@ -3649,8 +3499,7 @@ export class CCAEnergyDetectThreshold extends Characteristic { static readonly UUID: string = '00000246-0000-1000-8000-0026BB765291'; constructor() { - super("CCA Energy Detect Threshold", CCAEnergyDetectThreshold.UUID); - this.setProps({ + super("CCA Energy Detect Threshold", CCAEnergyDetectThreshold.UUID, { format: Formats.INT, perms: [Perms.PAIRED_READ], }); @@ -3670,8 +3519,7 @@ export class CCASignalDetectThreshold extends Characteristic { static readonly UUID: string = '00000245-0000-1000-8000-0026BB765291'; constructor() { - super("CCA Signal Detect Threshold", CCASignalDetectThreshold.UUID); - this.setProps({ + super("CCA Signal Detect Threshold", CCASignalDetectThreshold.UUID, { format: Formats.INT, perms: [Perms.PAIRED_READ], }); @@ -3691,8 +3539,7 @@ export class CharacteristicValueTransitionControl extends Characteristic { static readonly UUID: string = '00000143-0000-1000-8000-0026BB765291'; constructor() { - super("Characteristic Value Transition Control", CharacteristicValueTransitionControl.UUID); - this.setProps({ + super("Characteristic Value Transition Control", CharacteristicValueTransitionControl.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE], }); @@ -3712,8 +3559,7 @@ export class SupportedCharacteristicValueTransitionConfiguration extends Charact static readonly UUID: string = '00000144-0000-1000-8000-0026BB765291'; constructor() { - super("Supported Characteristic Value Transition Configuration", SupportedCharacteristicValueTransitionConfiguration.UUID); - this.setProps({ + super("Supported Characteristic Value Transition Configuration", SupportedCharacteristicValueTransitionConfiguration.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ], }); @@ -3733,8 +3579,7 @@ export class CharacteristicValueActiveTransitionCount extends Characteristic { static readonly UUID: string = '0000024B-0000-1000-8000-0026BB765291'; constructor() { - super("Characteristic Value Active Transition Count", CharacteristicValueActiveTransitionCount.UUID); - this.setProps({ + super("Characteristic Value Active Transition Count", CharacteristicValueActiveTransitionCount.UUID, { format: Formats.UINT8, perms: [Perms.NOTIFY, Perms.PAIRED_READ], }) @@ -3754,8 +3599,7 @@ export class CurrentTransport extends Characteristic { static readonly UUID: string = '0000022B-0000-1000-8000-0026BB765291'; constructor() { - super("Current Transport", CurrentTransport.UUID); - this.setProps({ + super("Current Transport", CurrentTransport.UUID, { format: Formats.BOOL, perms: [Perms.PAIRED_READ], }); @@ -3775,8 +3619,7 @@ export class DataStreamHAPTransport extends Characteristic { static readonly UUID: string = '00000138-0000-1000-8000-0026BB765291'; constructor() { - super("Data Stream HAP Transport", DataStreamHAPTransport.UUID); - this.setProps({ + super("Data Stream HAP Transport", DataStreamHAPTransport.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.WRITE_RESPONSE], }); @@ -3796,8 +3639,7 @@ export class DataStreamHAPTransportInterrupt extends Characteristic { static readonly UUID: string = '00000139-0000-1000-8000-0026BB765291'; constructor() { - super("Data Stream HAP Transport Interrupt", DataStreamHAPTransportInterrupt.UUID); - this.setProps({ + super("Data Stream HAP Transport Interrupt", DataStreamHAPTransportInterrupt.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ, Perms.NOTIFY], }); @@ -3817,8 +3659,7 @@ export class EventRetransmissionMaximum extends Characteristic { static readonly UUID: string = '0000023D-0000-1000-8000-0026BB765291'; constructor() { - super("Event Retransmission Maximum", EventRetransmissionMaximum.UUID); - this.setProps({ + super("Event Retransmission Maximum", EventRetransmissionMaximum.UUID, { format: Formats.UINT8, perms: [Perms.PAIRED_READ], }); @@ -3838,8 +3679,7 @@ export class EventTransmissionCounters extends Characteristic { static readonly UUID: string = '0000023E-0000-1000-8000-0026BB765291'; constructor() { - super("Event Transmission Counters", EventTransmissionCounters.UUID); - this.setProps({ + super("Event Transmission Counters", EventTransmissionCounters.UUID, { format: Formats.UINT32, perms: [Perms.PAIRED_READ], }); @@ -3859,8 +3699,7 @@ export class HeartBeat extends Characteristic { static readonly UUID: string = '0000024A-0000-1000-8000-0026BB765291'; constructor() { - super("Heart Beat", HeartBeat.UUID); - this.setProps({ + super("Heart Beat", HeartBeat.UUID, { format: Formats.UINT32, perms: [Perms.NOTIFY, Perms.PAIRED_READ], }) @@ -3880,8 +3719,7 @@ export class MACRetransmissionMaximum extends Characteristic { static readonly UUID: string = '00000247-0000-1000-8000-0026BB765291'; constructor() { - super("MAC Retransmission Maximum", MACRetransmissionMaximum.UUID); - this.setProps({ + super("MAC Retransmission Maximum", MACRetransmissionMaximum.UUID, { format: Formats.UINT8, perms: [Perms.PAIRED_READ], }); @@ -3901,8 +3739,7 @@ export class MACTransmissionCounters extends Characteristic { static readonly UUID: string = '00000248-0000-1000-8000-0026BB765291'; constructor() { - super("MAC Transmission Counters", MACTransmissionCounters.UUID); - this.setProps({ + super("MAC Transmission Counters", MACTransmissionCounters.UUID, { format: Formats.DATA, perms: [Perms.PAIRED_READ], }); @@ -3922,8 +3759,7 @@ export class OperatingStateResponse extends Characteristic { static readonly UUID: string = '00000232-0000-1000-8000-0026BB765291'; constructor() { - super("Operating State Response", OperatingStateResponse.UUID); - this.setProps({ + super("Operating State Response", OperatingStateResponse.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ, Perms.NOTIFY], }); @@ -3943,8 +3779,7 @@ export class Ping extends Characteristic { static readonly UUID: string = '0000023C-0000-1000-8000-0026BB765291'; constructor() { - super("Ping", Ping.UUID); - this.setProps({ + super("Ping", Ping.UUID, { format: Formats.DATA, perms: [Perms.PAIRED_READ], }); @@ -3964,8 +3799,7 @@ export class ReceiverSensitivity extends Characteristic { static readonly UUID: string = '00000244-0000-1000-8000-0026BB765291'; constructor() { - super("Receiver Sensitivity", ReceiverSensitivity.UUID); - this.setProps({ + super("Receiver Sensitivity", ReceiverSensitivity.UUID, { format: Formats.INT, perms: [Perms.PAIRED_READ], }); @@ -3985,8 +3819,7 @@ export class ReceivedSignalStrengthIndication extends Characteristic { static readonly UUID: string = '0000023F-0000-1000-8000-0026BB765291'; constructor() { - super("Received Signal Strength Indication", ReceivedSignalStrengthIndication.UUID); - this.setProps({ + super("Received Signal Strength Indication", ReceivedSignalStrengthIndication.UUID, { format: Formats.INT, perms: [Perms.PAIRED_READ], }); @@ -4006,8 +3839,7 @@ export class SleepInterval extends Characteristic { static readonly UUID: string = '0000023A-0000-1000-8000-0026BB765291'; constructor() { - super("Sleep Interval", SleepInterval.UUID); - this.setProps({ + super("Sleep Interval", SleepInterval.UUID, { format: Formats.UINT32, minValue: 0, minStep: 1, @@ -4029,8 +3861,7 @@ export class SignalToNoiseRatio extends Characteristic { static readonly UUID: string = '00000241-0000-1000-8000-0026BB765291'; constructor() { - super("Signal-to-noise Ration", SignalToNoiseRatio.UUID); - this.setProps({ + super("Signal-to-noise Ration", SignalToNoiseRatio.UUID, { format: Formats.INT, perms: [Perms.PAIRED_READ], }); @@ -4050,8 +3881,7 @@ export class SupportedDiagnosticsSnapshot extends Characteristic { static readonly UUID: string = '00000238-0000-1000-8000-0026BB765291'; constructor() { - super("Supported Diagnostics Snapshot", SupportedDiagnosticsSnapshot.UUID); - this.setProps({ + super("Supported Diagnostics Snapshot", SupportedDiagnosticsSnapshot.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ], }); @@ -4071,8 +3901,7 @@ export class TransmitPower extends Characteristic { static readonly UUID: string = '00000242-0000-1000-8000-0026BB765291'; constructor() { - super("Transmit Power", TransmitPower.UUID); - this.setProps({ + super("Transmit Power", TransmitPower.UUID, { format: Formats.INT, perms: [Perms.PAIRED_READ], }); @@ -4092,8 +3921,7 @@ export class TransmitPowerMaximum extends Characteristic { static readonly UUID: string = '00000243-0000-1000-8000-0026BB765291'; constructor() { - super("Transmit Power Maximum", TransmitPowerMaximum.UUID); - this.setProps({ + super("Transmit Power Maximum", TransmitPowerMaximum.UUID, { format: Formats.INT, perms: [Perms.PAIRED_READ], }); @@ -4113,8 +3941,7 @@ export class VideoAnalysisActive extends Characteristic { static readonly UUID: string = '00000229-0000-1000-8000-0026BB765291'; constructor() { - super("Video Analysis Active", VideoAnalysisActive.UUID); - this.setProps({ + super("Video Analysis Active", VideoAnalysisActive.UUID, { format: Formats.UINT8, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY], }); @@ -4134,8 +3961,7 @@ export class WiFiCapabilities extends Characteristic { static readonly UUID: string = '0000022C-0000-1000-8000-0026BB765291'; constructor() { - super("Wi-Fi Capabilities", WiFiCapabilities.UUID); - this.setProps({ + super("Wi-Fi Capabilities", WiFiCapabilities.UUID, { format: Formats.UINT32, perms: [Perms.PAIRED_READ], }); @@ -4155,8 +3981,7 @@ export class WiFiConfigurationControl extends Characteristic { static readonly UUID: string = '0000022D-0000-1000-8000-0026BB765291'; constructor() { - super("Wi-Fi Configuration Control", WiFiConfigurationControl.UUID); - this.setProps({ + super("Wi-Fi Configuration Control", WiFiConfigurationControl.UUID, { format: Formats.TLV8, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY, Perms.TIMED_WRITE, Perms.WRITE_RESPONSE], }); diff --git a/src/types.ts b/src/types.ts index b5ad0af47..f7a897405 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,13 +1,7 @@ -import { CharacteristicProps } from './lib/Characteristic'; - export type Nullable = T | null; export type WithUUID = T & { UUID: string }; -export interface ToHAPOptions { - omitValues: boolean; -} - /** * UUID string uniquely identifying every HAP connection. */ @@ -31,27 +25,7 @@ export type Callback = (...args: any[]) => void; export type NodeCallback = (err: Nullable | undefined, data?: T) => void; export type VoidCallback = (err?: Nullable) => void; export type PrimitiveTypes = string | number | boolean; - -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 CharacteristicValue = PrimitiveTypes | PrimitiveTypes[] | { [key: string]: PrimitiveTypes }; -export type HapService = { - iid: number; - type: string; - - characteristics: HapCharacteristic[]; - primary: boolean; - hidden: boolean; - linked: number[]; -} /** * @deprecated replaced by {@link AudioStreamingCodec} */ From 2d7aabaabc1724c5a1ec29a0c905f5ccbb9acfa5 Mon Sep 17 00:00:00 2001 From: Supereg Date: Thu, 8 Oct 2020 04:18:42 +0200 Subject: [PATCH 35/70] Service code cleanup --- src/lib/Accessory.ts | 16 +++---- src/lib/HAPServer.ts | 91 ++++++++++++++++++++------------------ src/lib/Service.ts | 103 +++++++++++++++++++++++++++++++------------ 3 files changed, 132 insertions(+), 78 deletions(-) diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index 92d5d684e..05fd5f8f1 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -55,7 +55,7 @@ import { ButtonEvent } from "./gen/HomeKit-Remote"; import { AccessoriesCallback, AddPairingCallback, - Codes, + TLVErrorCode, HAPHTTPCode, HAPServer, HAPServerEventTypes, @@ -1237,19 +1237,19 @@ export class Accessory extends EventEmitter { 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(connection.username!)) { - callback(Codes.AUTHENTICATION); + callback(TLVErrorCode.AUTHENTICATION); return; } const existingKey = this._accessoryInfo.getClientPublicKey(username); if (existingKey) { if (existingKey.toString() !== publicKey.toString()) { - callback(Codes.UNKNOWN); + callback(TLVErrorCode.UNKNOWN); return; } @@ -1265,12 +1265,12 @@ export class Accessory extends EventEmitter { private handleRemovePairing(connection: HAPConnection, username: HAPUsername, callback: RemovePairingCallback): void { if (!this._accessoryInfo) { - callback(Codes.UNAVAILABLE); + callback(TLVErrorCode.UNAVAILABLE); return; } if (!this._accessoryInfo.hasAdminPermissions(connection.username!)) { - callback(Codes.AUTHENTICATION); + callback(TLVErrorCode.AUTHENTICATION); return; } @@ -1292,12 +1292,12 @@ export class Accessory extends EventEmitter { private handleListPairings(connection: HAPConnection, callback: ListPairingsCallback): void { if (!this._accessoryInfo) { - callback(Codes.UNAVAILABLE); + callback(TLVErrorCode.UNAVAILABLE); return; } if (!this._accessoryInfo.hasAdminPermissions(connection.username!)) { - callback(Codes.AUTHENTICATION); + callback(TLVErrorCode.AUTHENTICATION); return; } diff --git a/src/lib/HAPServer.ts b/src/lib/HAPServer.ts index 0ecd09c08..b7cfea29c 100644 --- a/src/lib/HAPServer.ts +++ b/src/lib/HAPServer.ts @@ -54,7 +54,7 @@ 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, @@ -67,7 +67,7 @@ const enum Methods { /** * Pairing states (pair-setup or pair-verify). Encoded in {@link TLVValues.SEQUENCE_NUM}. */ -const enum States { +const enum PairingStates { M1 = 0x01, M2 = 0x02, M3 = 0x03, @@ -79,7 +79,7 @@ const enum States { /** * TLV error codes for the {@link TLVValues.ERROR_CODE} field. */ -export const enum Codes { +export const enum TLVErrorCode { // noinspection JSUnusedGlobalSymbols UNKNOWN = 0x01, INVALID_REQUEST = 0x02, @@ -110,6 +110,13 @@ export const enum HAPStatus { // when adding new status codes, remember to change upper bound in extractHAPStatusFromError } +// 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 */ @@ -162,7 +169,7 @@ export type IdentifyCallback = VoidCallback; export type HAPHttpError = { httpCode: HAPHTTPCode, status: HAPStatus}; -export type PairingsCallback = (error: Codes | 0, data?: T) => void; +export type PairingsCallback = (error: TLVErrorCode | 0, data?: T) => void; export type AddPairingCallback = PairingsCallback; export type RemovePairingCallback = PairingsCallback; export type ListPairingsCallback = PairingsCallback; @@ -397,28 +404,28 @@ export class HAPServer extends EventEmitter { // Can only be directly paired with one iOS device if (!this.allowInsecureRequest && this.accessoryInfo.paired()) { response.writeHead(HAPPairingHTTPCode.OK, {"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; } 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, States.M2, TLVValues.ERROR_CODE, Codes.MAX_TRIES)); + response.end(tlv.encode(TLVValues.STATE, PairingStates.M2, TLVValues.ERROR_CODE, TLVErrorCode.MAX_TRIES)); return; } const tlvData = tlv.decode(data); const sequence = tlvData[TLVValues.SEQUENCE_NUM][0]; // value is single byte with sequence number - if (sequence == States.M1) { + if (sequence == PairingStates.M1) { this.handlePairSetupM1(connection, request, response); - } else if (sequence == States.M3 && connection._pairSetupState === States.M2) { + } else if (sequence == PairingStates.M3 && connection._pairSetupState === PairingStates.M2) { this.handlePairSetupM3(connection, request, response, tlvData); - } else if (sequence == States.M5 && connection._pairSetupState === States.M4) { + } else if (sequence == PairingStates.M5 && connection._pairSetupState === PairingStates.M4) { this.handlePairSetupM5(connection, request, response, tlvData); } else { // Invalid state/sequence number response.writeHead(HAPPairingHTTPCode.BAD_REQUEST, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.STATE, sequence + 1, TLVValues.ERROR_CODE, Codes.UNKNOWN)); + response.end(tlv.encode(TLVValues.STATE, sequence + 1, TLVValues.ERROR_CODE, TLVErrorCode.UNKNOWN)); return; } } @@ -435,12 +442,12 @@ export class HAPServer extends EventEmitter { // attach it to the current TCP session connection.srpServer = srpServer; response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.SEQUENCE_NUM, States.M2, TLVValues.SALT, salt, TLVValues.PUBLIC_KEY, srpB)); - connection._pairSetupState = States.M2; + response.end(tlv.encode(TLVValues.SEQUENCE_NUM, PairingStates.M2, TLVValues.SALT, salt, TLVValues.PUBLIC_KEY, srpB)); + connection._pairSetupState = PairingStates.M2; }).catch(error => { debug("[%s] Error occurred when generating srp key: %s", this.accessoryInfo.username, error.message); response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.STATE, States.M2, TLVValues.ERROR_CODE, Codes.UNKNOWN)); + response.end(tlv.encode(TLVValues.STATE, PairingStates.M2, TLVValues.ERROR_CODE, TLVErrorCode.UNKNOWN)); return; }); } @@ -459,15 +466,15 @@ export class HAPServer extends EventEmitter { this.unsuccessfulPairAttempts++; debug("[%s] Error while checking pincode: %s", this.accessoryInfo.username, err.message); response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.SEQUENCE_NUM, States.M4, TLVValues.ERROR_CODE, Codes.AUTHENTICATION)); + response.end(tlv.encode(TLVValues.SEQUENCE_NUM, PairingStates.M4, TLVValues.ERROR_CODE, TLVErrorCode.AUTHENTICATION)); connection._pairSetupState = undefined; return; } // "M2 is the proof that the server actually knows your password." const M2 = srpServer.computeM2(); response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.SEQUENCE_NUM, States.M4, TLVValues.PASSWORD_PROOF, M2)); - connection._pairSetupState = States.M4; + response.end(tlv.encode(TLVValues.SEQUENCE_NUM, PairingStates.M4, TLVValues.PASSWORD_PROOF, M2)); + connection._pairSetupState = PairingStates.M4; } private handlePairSetupM5(connection: HAPConnection, request: IncomingMessage, response: ServerResponse, tlvData: Record): void { @@ -490,7 +497,7 @@ export class HAPServer extends EventEmitter { } catch (error) { debug("[%s] Error while decrypting and verifying M5 subTlv: %s", this.accessoryInfo.username); response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.SEQUENCE_NUM, States.M4, TLVValues.ERROR_CODE, Codes.AUTHENTICATION)); + response.end(tlv.encode(TLVValues.SEQUENCE_NUM, PairingStates.M4, TLVValues.ERROR_CODE, TLVErrorCode.AUTHENTICATION)); connection._pairSetupState = undefined; return; } @@ -513,7 +520,7 @@ export class HAPServer extends EventEmitter { if (!tweetnacl.sign.detached.verify(completeData, clientProof, clientLTPK)) { debug("[%s] Invalid signature", this.accessoryInfo.username); response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.SEQUENCE_NUM, States.M6, TLVValues.ERROR_CODE, Codes.AUTHENTICATION)); + response.end(tlv.encode(TLVValues.SEQUENCE_NUM, PairingStates.M6, TLVValues.ERROR_CODE, TLVErrorCode.AUTHENTICATION)); connection._pairSetupState = undefined; return; } @@ -541,13 +548,13 @@ export class HAPServer extends EventEmitter { if (err) { debug("[%s] Error adding pairing info: %s", this.accessoryInfo.username, err.message); response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.SEQUENCE_NUM, States.M6, TLVValues.ERROR_CODE, Codes.UNKNOWN)); + response.end(tlv.encode(TLVValues.SEQUENCE_NUM, PairingStates.M6, TLVValues.ERROR_CODE, TLVErrorCode.UNKNOWN)); connection._pairSetupState = undefined; return; } // send final pairing response to client response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.SEQUENCE_NUM, States.M6, TLVValues.ENCRYPTED_DATA, Buffer.concat([encrypted.ciphertext, encrypted.authTag]))); + response.end(tlv.encode(TLVValues.SEQUENCE_NUM, PairingStates.M6, TLVValues.ENCRYPTED_DATA, Buffer.concat([encrypted.ciphertext, encrypted.authTag]))); connection._pairSetupState = undefined; })); } @@ -556,14 +563,14 @@ export class HAPServer extends EventEmitter { const tlvData = tlv.decode(data); const sequence = tlvData[TLVValues.SEQUENCE_NUM][0]; // value is single byte with sequence number - if (sequence == States.M1) + if (sequence == PairingStates.M1) this.handlePairVerifyM1(connection, request, response, tlvData); - else if (sequence == States.M3 && connection._pairVerifyState === States.M2) + else if (sequence == PairingStates.M3 && connection._pairVerifyState === PairingStates.M2) this.handlePairVerifyM2(connection, request, response, tlvData); else { // Invalid state/sequence number response.writeHead(HAPPairingHTTPCode.BAD_REQUEST, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.STATE, sequence + 1, TLVValues.ERROR_CODE, Codes.UNKNOWN)); + response.end(tlv.encode(TLVValues.STATE, sequence + 1, TLVValues.ERROR_CODE, TLVErrorCode.UNKNOWN)); return; } } @@ -592,8 +599,8 @@ export class HAPServer extends EventEmitter { const encrypted = hapCrypto.chacha20_poly1305_encryptAndSeal(outputKey, Buffer.from("PV-Msg02"), null, message); response.writeHead(HAPPairingHTTPCode.OK, {"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)); - connection._pairVerifyState = States.M2; + 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; } private handlePairVerifyM2(connection: HAPConnection, request: IncomingMessage, response: ServerResponse, objects: Record): void { @@ -613,7 +620,7 @@ export class HAPServer extends EventEmitter { } catch (error) { debug("[%s] M3: Failed to decrypt and/or verify", this.accessoryInfo.username); response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.STATE, States.M4, TLVValues.ERROR_CODE, Codes.AUTHENTICATION)); + response.end(tlv.encode(TLVValues.STATE, PairingStates.M4, TLVValues.ERROR_CODE, TLVErrorCode.AUTHENTICATION)); connection._pairVerifyState = undefined; return; } @@ -629,20 +636,20 @@ export class HAPServer extends EventEmitter { if (!clientPublicKey) { debug("[%s] Client %s attempting to verify, but we are not paired; rejecting client", this.accessoryInfo.username, clientUsername); response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.STATE, States.M4, TLVValues.ERROR_CODE, Codes.AUTHENTICATION)); + 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(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.STATE, States.M4, TLVValues.ERROR_CODE, Codes.AUTHENTICATION)); + 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(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.SEQUENCE_NUM, States.M4)); + 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. @@ -669,48 +676,48 @@ export class HAPServer extends EventEmitter { 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, connection, identifier, publicKey, permissions, once((error: Codes | 0) => { + 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, States.M2, TLVValues.ERROR_CODE, error)); + response.end(tlv.encode(TLVValues.STATE, PairingStates.M2, TLVValues.ERROR_CODE, error)); return; } response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.STATE, States.M2)); + 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, connection, identifier, once((error: Codes | 0) => { + 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, States.M2, TLVValues.ERROR_CODE, error)); + response.end(tlv.encode(TLVValues.STATE, PairingStates.M2, TLVValues.ERROR_CODE, error)); return; } response.writeHead(HAPPairingHTTPCode.OK, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(TLVValues.STATE, States.M2)); + 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, connection, once((error: Codes | 0, data?: PairingInformation[]) => { + } 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, States.M2, TLVValues.ERROR_CODE, error)); + response.end(tlv.encode(TLVValues.STATE, PairingStates.M2, TLVValues.ERROR_CODE, error)); return; } @@ -727,7 +734,7 @@ export class HAPServer extends EventEmitter { ); }); - const list = tlv.encode(TLVValues.STATE, States.M2, ...tlvList); + 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); diff --git a/src/lib/Service.ts b/src/lib/Service.ts index 5fd7c7453..d2dcf42b6 100644 --- a/src/lib/Service.ts +++ b/src/lib/Service.ts @@ -147,17 +147,32 @@ export class Service extends EventEmitter { static WiFiTransport: typeof HomeKitTypes.Generated.WiFiTransport; // 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 @@ -179,7 +194,7 @@ export class Service extends EventEmitter { * * @returns the serviceId */ - getServiceId(): ServiceId { + public getServiceId(): ServiceId { return this.UUID + (this.subtype || ""); } @@ -224,7 +239,7 @@ 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); }; @@ -234,28 +249,40 @@ 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); } -//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); + 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. - const index = this.linkedServices.indexOf(oldLinkedService); - if (index !== -1) + const index = this.linkedServices.indexOf(service); + if (index !== -1) { this.linkedServices.splice(index, 1); + } this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE); } - removeCharacteristic = (characteristic: Characteristic) => { + public removeCharacteristic(characteristic: Characteristic): void { let targetCharacteristicIndex; for (let index in this.characteristics) { @@ -276,11 +303,11 @@ export class Service extends EventEmitter { } // 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. @@ -309,7 +336,7 @@ export class Service extends EventEmitter { } } - testCharacteristic = >(name: string | T) => { + public testCharacteristic>(name: string | T): boolean { // checks for the existence of a characteristic object in the service let index, characteristic; for (index in this.characteristics) { @@ -323,18 +350,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') { @@ -345,6 +372,7 @@ 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 @@ -353,8 +381,9 @@ export class Service extends EventEmitter { * 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 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}`); } @@ -396,7 +425,10 @@ export class Service extends EventEmitter { Object.values(foreignCharacteristics).forEach(characteristic => this.addCharacteristic(characteristic)); } - getCharacteristicByIID = (iid: number) => { + /** + * @internal + */ + getCharacteristicByIID(iid: number): Characteristic | undefined { for (let index in this.characteristics) { const characteristic = this.characteristics[index]; if (characteristic.iid === iid) @@ -404,7 +436,10 @@ export class Service extends EventEmitter { } } - _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; @@ -425,7 +460,7 @@ export class Service extends EventEmitter { * @internal used to generate response to /accessories query */ toHAP(connection: HAPConnection): Promise { - return new Promise((resolve, reject) => { + 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!"); @@ -514,14 +549,20 @@ export class Service extends EventEmitter { return service; } - _setupCharacteristic = (characteristic: Characteristic) => { + /** + * @internal + */ + _setupCharacteristic(characteristic: Characteristic): void { // listen for changes in characteristics and bubble them up characteristic.on(CharacteristicEventTypes.CHANGE, (change: CharacteristicChange) => { this.emit(ServiceEventTypes.CHARACTERISTIC_CHANGE, { ...change, characteristic: characteristic }); }); } - _sideloadCharacteristics = (targetCharacteristics: Characteristic[]) => { + /** + * @internal + */ + _sideloadCharacteristics(targetCharacteristics: Characteristic[]): void { for (let index in targetCharacteristics) { const target = targetCharacteristics[index]; this._setupCharacteristic(target); @@ -530,7 +571,10 @@ export class Service extends EventEmitter { this.characteristics = targetCharacteristics.slice(); } - static serialize = (service: Service): SerializedService => { + /** + * @internal + */ + static serialize(service: Service): SerializedService { return { displayName: service.displayName, UUID: service.UUID, @@ -544,7 +588,10 @@ export class Service extends EventEmitter { }; }; - static deserialize = (json: SerializedService): Service => { + /** + * @internal + */ + static deserialize(json: SerializedService): Service { const service = new Service(json.displayName, json.UUID, json.subtype); service.isHiddenService = !!json.hiddenService; From 074ede19cb18ff347bd08262bcfdbe7219ca0a9c Mon Sep 17 00:00:00 2001 From: Supereg Date: Thu, 8 Oct 2020 05:14:59 +0200 Subject: [PATCH 36/70] Debounce current configuration number increments --- src/lib/Accessory.ts | 181 +++++++++++++-------------------- src/lib/Advertiser.ts | 4 - src/lib/Service.ts | 2 + src/lib/model/AccessoryInfo.ts | 54 ++++++++-- 4 files changed, 121 insertions(+), 120 deletions(-) diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index 05fd5f8f1..f2fbc058b 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -341,6 +341,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."); @@ -411,40 +413,13 @@ export class Accessory extends EventEmitter { } if (!this.bridged) { - this._updateConfiguration(); + this.enqueueConfigurationUpdate(); } else { this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, { service: service }); } - service.on(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, () => { - 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, { service: service }); - } - }); - - // listen for changes in characteristics and bubble them up - service.on(ServiceEventTypes.CHARACTERISTIC_CHANGE, (change: ServiceCharacteristicChange) => { - this.emit(AccessoryEventTypes.SERVICE_CHARACTERISTIC_CHANGE, { ...change, service: service }); - - // if we're not bridged, when we'll want to process this event through our HAPServer - if (!this.bridged) { - this.handleCharacteristicChange({ ...change, service: service, accessory: this }); - } - }); + service.on(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, this.handleServiceConfigurationChangeEvent.bind(this, service)); + service.on(ServiceEventTypes.CHARACTERISTIC_CHANGE, this.handleCharacteristicChangeEvent.bind(this, service)); return service; } @@ -456,7 +431,7 @@ export class Accessory extends EventEmitter { service.setPrimaryService(); }; - removeService = (service: Service) => { + public removeService(service: Service): void { const index = this.services.indexOf(service); if (index >= 0) { @@ -468,13 +443,9 @@ export class Accessory extends EventEmitter { 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, { service: service }); - - for (const accessory of this.bridgedAccessories) { - accessory.removeLinkedService(service); - } } service.removeAllListeners(); @@ -531,29 +502,24 @@ export class Accessory extends EventEmitter { } addBridgedAccessory = (accessory: Accessory, deferUpdate: boolean = false) => { - if (accessory._isBridge) + if (accessory._isBridge) { throw new Error("Cannot Bridge another Bridge!"); + } // check for UUID conflict - for (let index in this.bridgedAccessories) { - const existing = this.bridgedAccessories[index]; - if (existing.UUID === accessory.UUID) - throw new Error("Cannot add a bridged Accessory with the same UUID as another bridged Accessory: " + existing.UUID); + for (const accessory of this.bridgedAccessories) { + if (accessory.UUID === accessory.UUID) { + throw new Error("Cannot add a bridged Accessory with the same UUID as another bridged Accessory: " + accessory.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: AccessoryCharacteristicChange) => { - this.handleCharacteristicChange({ ...change, accessory: accessory }); - }); - - accessory.on(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, () => { - this._updateConfiguration(); - }); + accessory.on(AccessoryEventTypes.SERVICE_CHARACTERISTIC_CHANGE, change => this.handleCharacteristicChangeEvent(change.service, change)); + accessory.on(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, this.enqueueConfigurationUpdate.bind(this)); accessory.bridged = true; accessory.bridge = this; @@ -563,7 +529,7 @@ export class Accessory extends EventEmitter { this.controllerStorage.linkAccessory(accessory); // init controllers of bridged accessory if(!deferUpdate) { - this._updateConfiguration(); + this.enqueueConfigurationUpdate(); } return accessory; @@ -575,7 +541,7 @@ export class Accessory extends EventEmitter { this.addBridgedAccessory(accessory, true); } - this._updateConfiguration(); + this.enqueueConfigurationUpdate(); } removeBridgedAccessory = (accessory: Accessory, deferUpdate: boolean) => { @@ -599,7 +565,7 @@ export class Accessory extends EventEmitter { accessory.removeAllListeners(); // TODO remove all listeners from services and characteristics as well if(!deferUpdate) { - this._updateConfiguration(); + this.enqueueConfigurationUpdate(); } } @@ -609,14 +575,14 @@ export class Accessory extends EventEmitter { this.removeBridgedAccessory(accessory, true); } - this._updateConfiguration(); + this.enqueueConfigurationUpdate(); } removeAllBridgedAccessories = () => { for (let i = this.bridgedAccessories.length - 1; i >= 0; i --) { this.removeBridgedAccessory(this.bridgedAccessories[i], true); } - this._updateConfiguration(); + this.enqueueConfigurationUpdate(); } private getCharacteristicByIID(iid: number): Characteristic | undefined { @@ -823,7 +789,7 @@ export class Accessory extends EventEmitter { }); } - setupURI = () => { + setupURI(): string { if (this._setupURI) { return this._setupURI; } @@ -902,7 +868,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 @@ -1106,18 +1072,8 @@ 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". - const config = this.internalHAPRepresentation() - - // now convert it into a hash code and check it against the last one we made, if we have one - const shasum = crypto.createHash('sha1'); - shasum.update(JSON.stringify(config)); - const 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(); // TODO ensure this stuff is ordered + this._accessoryInfo.checkForCurrentConfigurationNumberIncrement(config, true); this.validateAccessory(true); @@ -1189,27 +1145,27 @@ export class Accessory extends EventEmitter { } } - private _updateConfiguration(): void { - 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". - const config = this.internalHAPRepresentation() - - // now convert it into a hash code and check it against the last one we made, if we have one - const shasum = crypto.createHash('sha1'); - shasum.update(JSON.stringify(config)); - const 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); + // 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 } private onListening(port: number, hostname: string): void { @@ -1719,36 +1675,45 @@ export class Accessory extends EventEmitter { connection.clearRegisteredEvents(); } - private handleCharacteristicChange(change: AccessoryCharacteristicChange & { accessory: Accessory }): void { - if (!this._server) { - return; // we're not running a HAPServer, so there's no one to notify about this event - } + 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; + } - const uuid = change.characteristic.UUID; - const immediateDelivery = uuid === ButtonEvent.UUID || uuid === ProgrammableSwitchEvent.UUID - || uuid === MotionDetected.UUID || uuid === ContactSensorState.UUID; + this.primaryService = service; + } - this._server.sendEventNotifications(change.accessory.aid!, change.characteristic.iid!, change.newValue, change.originator, immediateDelivery); + 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, { service: service }); + private handleCharacteristicChangeEvent(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: ServiceCharacteristicChange) => { - this.emit(AccessoryEventTypes.SERVICE_CHARACTERISTIC_CHANGE, clone(change, {service })); + const uuid = change.characteristic.UUID; + const immediateDelivery = uuid === ButtonEvent.UUID || uuid === ProgrammableSwitchEvent.UUID + || uuid === MotionDetected.UUID || uuid === ContactSensorState.UUID; - // 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 })); + this._server.sendEventNotifications(this.aid!, change.characteristic.iid!, change.newValue, change.originator, immediateDelivery); + } + } - }); + _setupService = (service: Service) => { + service.on(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, this.handleServiceConfigurationChangeEvent.bind(this, service)); + service.on(ServiceEventTypes.CHARACTERISTIC_CHANGE, this.handleCharacteristicChangeEvent.bind(this, service)); } _sideloadServices = (targetServices: Service[]) => { diff --git a/src/lib/Advertiser.ts b/src/lib/Advertiser.ts index 2a1364940..226f97cd1 100644 --- a/src/lib/Advertiser.ts +++ b/src/lib/Advertiser.ts @@ -83,10 +83,6 @@ export class Advertiser extends EventEmitter { return this.advertisedService!.advertise(); } - public isServiceCreated(): boolean { - return !!this.advertisedService; - } - public updateAdvertisement(): void { this.advertisedService!.updateTxt(this.createTxt()); } diff --git a/src/lib/Service.ts b/src/lib/Service.ts index d2dcf42b6..5a6348ff3 100644 --- a/src/lib/Service.ts +++ b/src/lib/Service.ts @@ -198,6 +198,8 @@ export class Service extends EventEmitter { return this.UUID + (this.subtype || ""); } + 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. diff --git a/src/lib/model/AccessoryInfo.ts b/src/lib/model/AccessoryInfo.ts index badc5b224..74ef37686 100644 --- a/src/lib/model/AccessoryInfo.ts +++ b/src/lib/model/AccessoryInfo.ts @@ -1,11 +1,16 @@ import assert from 'assert'; +import crypto from "crypto"; import tweetnacl from 'tweetnacl'; 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, @@ -36,8 +41,9 @@ export class AccessoryInfo { 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; @@ -151,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; + + if (configHash !== this.configHash) { + this.configVersion++; + this.configHash = configHash; - this.configHash = hash; - this.save(); + 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 { @@ -187,6 +221,7 @@ export class AccessoryInfo { configVersion: this.configVersion, configHash: this.configHash, setupID: this.setupID, + lastFirmwareVersion: this.lastFirmwareVersion, }; for (let username in this.pairedClients) { @@ -211,6 +246,8 @@ export class AccessoryInfo { AccessoryInfo.assertValidUsername(username); const accessoryInfo = new AccessoryInfo(username); + accessoryInfo.lastFirmwareVersion = packageJson.version; + // Create a new unique key pair for this accessory. const keyPair = tweetnacl.sign.keyPair(); @@ -255,11 +292,12 @@ export class AccessoryInfo { info.setupID = saved.setupID || ""; + info.lastFirmwareVersion = saved.lastFirmwareVersion || packageJson.version; + info.ensureConfigVersionBounds(); return info; - } - else { + } else { return null; } } From df0761d9566274fa05b65a4dd15be170d322237d Mon Sep 17 00:00:00 2001 From: Supereg Date: Thu, 8 Oct 2020 05:15:25 +0200 Subject: [PATCH 37/70] Boolean values for numeric formats are now properly converted --- src/lib/Characteristic.ts | 48 +++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts index 07e5c3cff..6f03ff50b 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -907,7 +907,9 @@ export class Characteristic extends EventEmitter { } break; case Formats.INT: // 32-bit signed int - if (typeof value !== "number") { + if (typeof value === "boolean") { + value = value? 1: 0; + } if (typeof value !== "number") { return false; } @@ -915,7 +917,9 @@ export class Characteristic extends EventEmitter { numericMax = minWithUndefined(this.props.maxValue, 2147483647); break; case Formats.FLOAT: - if (typeof value !== "number") { + if (typeof value === "boolean") { + value = value? 1: 0; + } if (typeof value !== "number") { return false; } @@ -927,7 +931,9 @@ export class Characteristic extends EventEmitter { } break; case Formats.UINT8: - if (typeof value !== "number") { + if (typeof value === "boolean") { + value = value? 1: 0; + } if (typeof value !== "number") { return false; } @@ -935,7 +941,9 @@ export class Characteristic extends EventEmitter { numericMax = minWithUndefined(this.props.maxValue, 255); break; case Formats.UINT16: - if (typeof value !== "number") { + if (typeof value === "boolean") { + value = value? 1: 0; + } if (typeof value !== "number") { return false; } @@ -943,7 +951,9 @@ export class Characteristic extends EventEmitter { numericMax = minWithUndefined(this.props.maxValue, 65535); break; case Formats.UINT32: - if (typeof value !== "number") { + if (typeof value === "boolean") { + value = value? 1: 0; + } if (typeof value !== "number") { return false; } @@ -951,7 +961,9 @@ export class Characteristic extends EventEmitter { numericMax = minWithUndefined(this.props.maxValue, 4294967295); break; case Formats.UINT64: - if (typeof value !== "number") { + if (typeof value === "boolean") { + value = value? 1: 0; + } if (typeof value !== "number") { return false; } @@ -1044,7 +1056,9 @@ export class Characteristic extends EventEmitter { } } case Formats.INT: { - if (typeof value === "string") { + if (typeof value === "boolean") { + value = value? 1: 0; + } if (typeof value === "string") { console.warn(`[${this.displayName}] 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") { @@ -1056,7 +1070,9 @@ export class Characteristic extends EventEmitter { break; } case Formats.FLOAT: { - if (typeof value === "string") { + if (typeof value === "boolean") { + value = value? 1: 0; + } if (typeof value === "string") { console.warn(`[${this.displayName}] 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") { @@ -1072,7 +1088,9 @@ export class Characteristic extends EventEmitter { break; } case Formats.UINT8: { - if (typeof value === "string") { + if (typeof value === "boolean") { + value = value? 1: 0; + } if (typeof value === "string") { console.warn(`[${this.displayName}] 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") { @@ -1084,7 +1102,9 @@ export class Characteristic extends EventEmitter { break; } case Formats.UINT16: { - if (typeof value === "string") { + if (typeof value === "boolean") { + value = value? 1: 0; + } if (typeof value === "string") { console.warn(`[${this.displayName}] 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") { @@ -1096,7 +1116,9 @@ export class Characteristic extends EventEmitter { break; } case Formats.UINT32: { - if (typeof value === "string") { + if (typeof value === "boolean") { + value = value? 1: 0; + } if (typeof value === "string") { console.warn(`[${this.displayName}] 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") { @@ -1108,7 +1130,9 @@ export class Characteristic extends EventEmitter { break; } case Formats.UINT64: { - if (typeof value === "string") { + if (typeof value === "boolean") { + value = value? 1: 0; + } if (typeof value === "string") { console.warn(`[${this.displayName}] 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") { From 7deb4505810b56b969473afa3af961b6b5e17a54 Mon Sep 17 00:00:00 2001 From: Supereg Date: Thu, 8 Oct 2020 05:20:38 +0200 Subject: [PATCH 38/70] Ensure timeouts don't prevent shutdown --- src/lib/Accessory.ts | 5 +++++ src/lib/controller/CameraController.ts | 2 ++ src/lib/controller/RemoteController.ts | 1 + src/lib/datastream/DataStreamServer.ts | 1 + src/lib/util/eventedhttp.ts | 1 + 5 files changed, 10 insertions(+) diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index f2fbc058b..41d726088 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -1163,6 +1163,7 @@ export class Accessory extends EventEmitter { } } }, 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 @@ -1319,7 +1320,9 @@ export class Accessory extends EventEmitter { callback(response); }, 7000); + timeout.unref(); }, 3000); + timeout.unref(); for (const id of request.ids) { const name = id.aid + "." + id.iid; @@ -1479,7 +1482,9 @@ export class Accessory extends EventEmitter { callback(response); }, 7000); + timeout.unref(); }, 3000); + timeout.unref(); for (const data of writeRequest.characteristics) { const id = data.aid + "." + data.iid; diff --git a/src/lib/controller/CameraController.ts b/src/lib/controller/CameraController.ts index 131ae1b00..a1656c078 100644 --- a/src/lib/controller/CameraController.ts +++ b/src/lib/controller/CameraController.ts @@ -378,7 +378,9 @@ export class CameraController extends EventEmitter implements Controller this.timeoutPreparedSession(preparedSession), 10000), }; + preparedSession.connectTimeout!.unref(); this.preparedSessions.push(preparedSession); this.checkTCPServerEstablished(preparedSession, () => callback(preparedSession)); diff --git a/src/lib/util/eventedhttp.ts b/src/lib/util/eventedhttp.ts index 582cdb68a..918d85062 100644 --- a/src/lib/util/eventedhttp.ts +++ b/src/lib/util/eventedhttp.ts @@ -364,6 +364,7 @@ export class HAPConnection extends EventEmitter { }); 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(); } } } From 4a3f9e722e22bb85e6ed75845572f99361619509 Mon Sep 17 00:00:00 2001 From: Supereg Date: Thu, 8 Oct 2020 05:30:32 +0200 Subject: [PATCH 39/70] Fixed adding bridged accessories --- src/lib/Accessory.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index 41d726088..7fe628f3d 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -501,15 +501,15 @@ export class Accessory extends EventEmitter { debug('Reachability update is no longer being supported.'); } - addBridgedAccessory = (accessory: Accessory, deferUpdate: boolean = false) => { + public addBridgedAccessory(accessory: Accessory, deferUpdate: boolean = false): Accessory { if (accessory._isBridge) { throw new Error("Cannot Bridge another Bridge!"); } // check for UUID conflict - for (const accessory of this.bridgedAccessories) { - if (accessory.UUID === accessory.UUID) { - throw new Error("Cannot add a bridged Accessory with the same UUID as another bridged Accessory: " + 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); } } From 7073a82986c546fc66e0150197b57b6d104e1909 Mon Sep 17 00:00:00 2001 From: Supereg Date: Thu, 8 Oct 2020 05:52:14 +0200 Subject: [PATCH 40/70] Fixed bridged accessories not being returned on /accessories request --- src/lib/Accessory.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index 7fe628f3d..1d7cd023a 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -951,10 +951,11 @@ export class Accessory extends EventEmitter { const accessories: AccessoryJsonObject[] = [accessory]; - if (this.bridge) { - for (const accessory of this.bridgedAccessories) { - accessories.push((await accessory.toHAP(connection))[0]); - } + if (!this.bridged) { + accessories.push(... await Promise.all( + this.bridgedAccessories + .map(accessory => accessory.toHAP(connection).then(value => value[0])) + )); } return accessories; @@ -974,7 +975,7 @@ export class Accessory extends EventEmitter { const accessories: AccessoryJsonObject[] = [accessory]; - if (this.bridge) { + if (!this.bridged) { for (const accessory of this.bridgedAccessories) { accessories.push(accessory.internalHAPRepresentation()[0]); } From 0ac698faf5d8f32714a227819f94620c5eaaae9f Mon Sep 17 00:00:00 2001 From: Supereg Date: Thu, 8 Oct 2020 17:45:47 +0200 Subject: [PATCH 41/70] Mute event notification calls when aid/iid are not yet set Catch any errors which may occur when sending events --- src/lib/Accessory.ts | 7 ++++++- src/lib/HAPServer.ts | 11 ++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index 1d7cd023a..44651d0b7 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -1709,11 +1709,16 @@ export class Accessory extends EventEmitter { return; // we're not running a HAPServer, so there's no one to notify about this event } + if (this.aid == undefined || change.characteristic.iid == undefined) { + debug("[%s] Muting event notification for %s as ids aren't yet assigned!", this.displayName, change.characteristic.displayName); + return; + } + const uuid = change.characteristic.UUID; const immediateDelivery = uuid === ButtonEvent.UUID || uuid === ProgrammableSwitchEvent.UUID || uuid === MotionDetected.UUID || uuid === ContactSensorState.UUID; - this._server.sendEventNotifications(this.aid!, change.characteristic.iid!, change.newValue, change.originator, immediateDelivery); + this._server.sendEventNotifications(this.aid, change.characteristic.iid, change.newValue, change.originator, immediateDelivery); } } diff --git a/src/lib/HAPServer.ts b/src/lib/HAPServer.ts index b7cfea29c..ec245c234 100644 --- a/src/lib/HAPServer.ts +++ b/src/lib/HAPServer.ts @@ -17,7 +17,7 @@ import { ResourceRequest } from "../internal-types"; import { CharacteristicValue, Nullable, VoidCallback } from '../types'; -import { PairingInformation, PermissionTypes } from "./model/AccessoryInfo"; +import { AccessoryInfo, PairingInformation, PermissionTypes } from "./model/AccessoryInfo"; import { EventedHTTPServer, EventedHTTPServerEvent, @@ -277,12 +277,13 @@ export declare interface HAPServer { */ 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 allowInsecureRequest: boolean; - constructor(public accessoryInfo: any) { + constructor(accessoryInfo: AccessoryInfo) { super(); this.accessoryInfo = accessoryInfo; this.allowInsecureRequest = false; @@ -314,7 +315,11 @@ export class HAPServer extends EventEmitter { * Namely for the {@link ButtonEvent} and the {@link ProgrammableSwitchEvent} characteristics. */ public sendEventNotifications(aid: number, iid: number, value: Nullable, originator?: HAPConnection, immediateDelivery?: boolean): void { - this.httpServer.broadcastEvent(aid, iid, value, originator, immediateDelivery); + try { + this.httpServer.broadcastEvent(aid, iid, value, originator, immediateDelivery); + } catch (error) { + console.warn("[" + this.accessoryInfo.username + "] Error when sending event notifications: " + error.message); + } } private onListening(port: number, hostname: string): void { From 5f8a827ee11308962ec46cb98ca764b66d58e466 Mon Sep 17 00:00:00 2001 From: Supereg Date: Thu, 8 Oct 2020 18:05:31 +0200 Subject: [PATCH 42/70] Return http error when receiving duplicate values in /characteristics set/get requests --- src/lib/Accessory.ts | 41 ++++++++++++++++++++++++++--------------- src/lib/HAPServer.ts | 22 +++++++++++++++++----- 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index 44651d0b7..fb29a0ee3 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -55,7 +55,6 @@ import { ButtonEvent } from "./gen/HomeKit-Remote"; import { AccessoriesCallback, AddPairingCallback, - TLVErrorCode, HAPHTTPCode, HAPServer, HAPServerEventTypes, @@ -66,6 +65,7 @@ import { ReadCharacteristicsCallback, RemovePairingCallback, ResourceRequestCallback, + TLVErrorCode, WriteCharacteristicsCallback } from './HAPServer'; import { AccessoryInfo, PermissionTypes } from './model/AccessoryInfo'; @@ -1279,7 +1279,13 @@ export class Accessory extends EventEmitter { const characteristics: CharacteristicReadData[] = []; const response: CharacteristicsReadResponse = { characteristics: characteristics }; - const missingCharacteristics: Set = new Set(); + 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; + } + let timeout: Timeout | undefined = setTimeout(() => { for (const id of missingCharacteristics) { const split = id.split("."); @@ -1319,7 +1325,7 @@ export class Accessory extends EventEmitter { } missingCharacteristics.clear(); - callback(response); + callback(undefined, response); }, 7000); timeout.unref(); }, 3000); @@ -1327,8 +1333,6 @@ export class Accessory extends EventEmitter { for (const id of request.ids) { const name = id.aid + "." + id.iid; - missingCharacteristics.add(name); - this.handleCharacteristicRead(connection, id, request).then(value => { return { aid: id.aid, @@ -1336,7 +1340,7 @@ export class Accessory extends EventEmitter { ...value, }; }, reason => { // this error block is only called if hap-nodejs itself messed up - console.error(`[${this.displayName}] Read request for characteristic ${id} encountered an error: ${reason.stack}`) + console.error(`[${this.displayName}] Read request for characteristic ${name} encountered an error: ${reason.stack}`) return { aid: id.aid, @@ -1356,7 +1360,7 @@ export class Accessory extends EventEmitter { clearTimeout(timeout); timeout = undefined; } - callback(response); + callback(undefined, response); } }); } @@ -1441,7 +1445,16 @@ export class Accessory extends EventEmitter { const characteristics: CharacteristicWriteData[] = []; const response: CharacteristicsWriteResponse = { characteristics: characteristics }; - const missingCharacteristics: Set = new Set(); + 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; + } + let timeout: Timeout | undefined = setTimeout(() => { for (const id of missingCharacteristics) { const split = id.split("."); @@ -1481,16 +1494,14 @@ export class Accessory extends EventEmitter { } missingCharacteristics.clear(); - callback(response); + callback(undefined, response); }, 7000); timeout.unref(); }, 3000); timeout.unref(); for (const data of writeRequest.characteristics) { - const id = data.aid + "." + data.iid; - missingCharacteristics.add(id); - + const name = data.aid + "." + data.iid; this.handleCharacteristicWrite(connection, data, writeState).then(value => { return { aid: data.aid, @@ -1498,7 +1509,7 @@ export class Accessory extends EventEmitter { ...value, }; }, reason => { // this error block is only called if hap-nodejs itself messed up - console.error(`[${this.displayName}] Write request for characteristic ${id} encountered an error: ${reason.stack}`) + console.error(`[${this.displayName}] Write request for characteristic ${name} encountered an error: ${reason.stack}`) return { aid: data.aid, @@ -1510,7 +1521,7 @@ export class Accessory extends EventEmitter { return; // if timeout is undefined, response was already sent out } - missingCharacteristics.delete(id); + missingCharacteristics.delete(name); characteristics.push(value); if (missingCharacteristics.size === 0) { // if everything returned send the response @@ -1518,7 +1529,7 @@ export class Accessory extends EventEmitter { clearTimeout(timeout); timeout = undefined; } - callback(response); + callback(undefined, response); } }) } diff --git a/src/lib/HAPServer.ts b/src/lib/HAPServer.ts index ec245c234..abce3bb5f 100644 --- a/src/lib/HAPServer.ts +++ b/src/lib/HAPServer.ts @@ -140,7 +140,7 @@ export const enum HAPHTTPCode { // client error BAD_REQUEST = 400, // e.g. malformed request NOT_FOUND = 404, - UNPROCESSABLE_ENTITY = 422, // for well-formed requests tha contain invalid http parameters + UNPROCESSABLE_ENTITY = 422, // for well-formed requests tha contain invalid http parameters (semantics are wrong and not syntax) // server error INTERNAL_SERVER_ERROR = 500, @@ -175,8 +175,8 @@ export type RemovePairingCallback = PairingsCallback; export type ListPairingsCallback = PairingsCallback; export type PairCallback = VoidCallback; export type AccessoriesCallback = (error: HAPHttpError | undefined, result?: AccessoriesResponse) => void; -export type ReadCharacteristicsCallback = (response: CharacteristicsReadResponse) => void; -export type WriteCharacteristicsCallback = (response: CharacteristicsWriteResponse) => 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 { @@ -799,7 +799,13 @@ export class HAPServer extends EventEmitter { includeEvent: consideredTrue(searchParams.get("ev")), }; - this.emit(HAPServerEventTypes.GET_CHARACTERISTICS, connection, readRequest, once((readResponse: CharacteristicsReadResponse) => { + 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 @@ -838,7 +844,13 @@ export class HAPServer extends EventEmitter { const writeRequest = JSON.parse(data.toString("utf8")) as CharacteristicsWriteRequest; - this.emit(HAPServerEventTypes.SET_CHARACTERISTICS, connection, writeRequest, once((writeResponse: CharacteristicsWriteResponse) => { + 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; From 0bedbc56fcefceed2ec67b15b7dc4d4739776464 Mon Sep 17 00:00:00 2001 From: Supereg Date: Thu, 8 Oct 2020 18:28:10 +0200 Subject: [PATCH 43/70] Prevent that SerialNumber or Model gets a value assigned with length less or equal to 1 character (fixes #824) Otherwise HomeKit will reject the whole accessory! --- src/lib/Characteristic.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts index 6f03ff50b..5817eba65 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -1151,6 +1151,11 @@ export class Characteristic extends EventEmitter { 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)) { + console.error(new Error(`[${this.displayName}] characteristic must have a length of more than 1 character otherwise HomeKit will reject this accessory. Ignoring it.`).stack); + 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) { console.warn(`[${this.displayName}] characteristic was supplied illegal value: string '${value}' exceeded max length of ${maxLength}. Supplying illegal values will throw errors in the future!`); From 6f6a971d9cc6671abe7b0f02f7de2efda10697ef Mon Sep 17 00:00:00 2001 From: Supereg Date: Thu, 8 Oct 2020 23:26:53 +0200 Subject: [PATCH 44/70] Fix event delivery for bridged accessories --- src/accessories/Lock_accessory.ts | 48 ++++++++++++------------------- src/lib/Accessory.ts | 14 ++++----- 2 files changed, 26 insertions(+), 36 deletions(-) diff --git a/src/accessories/Lock_accessory.ts b/src/accessories/Lock_accessory.ts index fb5b6d542..688cad685 100644 --- a/src/accessories/Lock_accessory.ts +++ b/src/accessories/Lock_accessory.ts @@ -3,12 +3,10 @@ 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 const FAKE_LOCK = { @@ -45,62 +43,54 @@ 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. - const 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/lib/Accessory.ts b/src/lib/Accessory.ts index fb29a0ee3..62cc950a3 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -419,7 +419,7 @@ export class Accessory extends EventEmitter { } service.on(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, this.handleServiceConfigurationChangeEvent.bind(this, service)); - service.on(ServiceEventTypes.CHARACTERISTIC_CHANGE, this.handleCharacteristicChangeEvent.bind(this, service)); + service.on(ServiceEventTypes.CHARACTERISTIC_CHANGE, this.handleCharacteristicChangeEvent.bind(this, this, service)); return service; } @@ -518,7 +518,7 @@ export class Accessory extends EventEmitter { } // listen for changes in ANY characteristics of ANY services on this Accessory - accessory.on(AccessoryEventTypes.SERVICE_CHARACTERISTIC_CHANGE, change => this.handleCharacteristicChangeEvent(change.service, change)); + 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; @@ -1712,7 +1712,7 @@ export class Accessory extends EventEmitter { } } - private handleCharacteristicChangeEvent(service: Service, change: ServiceCharacteristicChange): void { + 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 { @@ -1720,8 +1720,8 @@ export class Accessory extends EventEmitter { return; // we're not running a HAPServer, so there's no one to notify about this event } - if (this.aid == undefined || change.characteristic.iid == undefined) { - debug("[%s] Muting event notification for %s as ids aren't yet assigned!", this.displayName, change.characteristic.displayName); + 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; } @@ -1729,13 +1729,13 @@ export class Accessory extends EventEmitter { const immediateDelivery = uuid === ButtonEvent.UUID || uuid === ProgrammableSwitchEvent.UUID || uuid === MotionDetected.UUID || uuid === ContactSensorState.UUID; - this._server.sendEventNotifications(this.aid, change.characteristic.iid, change.newValue, change.originator, immediateDelivery); + this._server.sendEventNotifications(accessory.aid, change.characteristic.iid, change.newValue, change.originator, immediateDelivery); } } _setupService = (service: Service) => { service.on(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, this.handleServiceConfigurationChangeEvent.bind(this, service)); - service.on(ServiceEventTypes.CHARACTERISTIC_CHANGE, this.handleCharacteristicChangeEvent.bind(this, service)); + service.on(ServiceEventTypes.CHARACTERISTIC_CHANGE, this.handleCharacteristicChangeEvent.bind(this, this, service)); } _sideloadServices = (targetServices: Service[]) => { From ac57c4d7238e21710fefeafb97a59b9376e477b6 Mon Sep 17 00:00:00 2001 From: Supereg Date: Fri, 9 Oct 2020 00:09:51 +0200 Subject: [PATCH 45/70] Adjust the bind option to always bind on unspecified addresses to keep a socket on the loopback address for config-ui --- src/lib/Accessory.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index 62cc950a3..9bb66bd7a 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -207,11 +207,11 @@ export interface PublishInfo { * The mdns advertisement will not advertise any ipv6 records. * * - bind: "169.254.104.90" - * This will bind the HAP server to the address 169.254.104.90. - * The application will throw an error if the address is not available at startup. + * 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, + * 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"] @@ -219,6 +219,10 @@ export interface PublishInfo { * 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)[]; /** @@ -1935,8 +1939,9 @@ export class Accessory extends EventEmitter { if (entries.size === 1) { const entry = entries.values().next().value; // grab the first one - if (net.isIP(entry)) { - serverAddress = entry; + 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 } From 9d63614f61ff0510c8e1f71ee766fcfd1623afe5 Mon Sep 17 00:00:00 2001 From: Supereg Date: Fri, 9 Oct 2020 00:27:08 +0200 Subject: [PATCH 46/70] Ignore empty bind option --- src/lib/Accessory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index 9bb66bd7a..4b6bd040d 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -1945,7 +1945,7 @@ export class Accessory extends EventEmitter { } else { serverAddress = "::"; // the interface could have both ipv4 and ipv6 addresses } - } else { + } 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) { From 3f59de24a10cc069ea155d5396c75222c7c3b935 Mon Sep 17 00:00:00 2001 From: Supereg Date: Mon, 12 Oct 2020 14:39:42 +0200 Subject: [PATCH 47/70] Mitigate crash when check accessory configuration and aid/iids aren't set yet --- src/lib/Accessory.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index 4b6bd040d..66019bc28 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -890,8 +890,7 @@ export class Accessory extends EventEmitter { this.aid = 1; } - for (let index in this.services) { - const service = this.services[index]; + for (const service of this.services) { if (this._isBridge) { service._assignIDs(identifierCache, this.UUID, 2000000000); } else { @@ -969,6 +968,7 @@ export class Accessory extends EventEmitter { * Returns a JSON representation of this accessory without characteristic values. */ private internalHAPRepresentation(): AccessoryJsonObject[] { + 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!"); From 83804fb112812132472113f7c17a1acb5bc321ab Mon Sep 17 00:00:00 2001 From: oznu Date: Mon, 12 Oct 2020 23:48:00 +1100 Subject: [PATCH 48/70] Add support for promise based Characteristic setters and getters (#849) --- src/accessories/Fan_accessory.ts | 20 ++--- src/index.ts | 1 + src/lib/Characteristic.ts | 122 ++++++++++++++++++++++++++++++- src/lib/util/hapStatusError.ts | 26 +++++++ 4 files changed, 154 insertions(+), 15 deletions(-) create mode 100644 src/lib/util/hapStatusError.ts diff --git a/src/accessories/Fan_accessory.ts b/src/accessories/Fan_accessory.ts index aa197214d..7f118c45a 100644 --- a/src/accessories/Fan_accessory.ts +++ b/src/accessories/Fan_accessory.ts @@ -4,9 +4,7 @@ import { AccessoryEventTypes, Categories, Characteristic, - CharacteristicEventTypes, CharacteristicSetCallback, CharacteristicValue, - NodeCallback, Service, uuid, VoidCallback @@ -62,9 +60,8 @@ fan.on(AccessoryEventTypes.IDENTIFY, (paired: boolean, callback: VoidCallback) = 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 @@ -72,19 +69,17 @@ 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. - const err = null; // in case there were any problems - if (FAKE_FAN.powerOn) { - callback(err, true); + return true; } else { - callback(err, false); + return false; } }); @@ -92,10 +87,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/index.ts b/src/index.ts index 8b7520883..b2266bd3e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,6 +24,7 @@ export * from './lib/controller'; export * from './lib/util/clone'; export * from './lib/util/once'; export * from './lib/util/tlv'; +export * from './lib/util/hapStatusError'; export * from './types'; export const LegacyTypes = legacyTypes; diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts index 5817eba65..8a446a96d 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -8,6 +8,7 @@ import { HAPStatus } from "./HAPServer"; import { IdentifierCache } from './model/IdentifierCache'; 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'; @@ -490,6 +491,16 @@ export class Characteristic extends EventEmitter { status: HAPStatus = HAPStatus.SUCCESS; props: CharacteristicProps; + /** + * The .onGet handler + */ + private getHandler?: () => Promise> | Nullable; + + /** + * The .onSet handler + */ + private setHandler?: (value: CharacteristicValue) => Promise | void> | Nullable | void; + private subscriptions: number = 0; /** * @internal @@ -508,6 +519,50 @@ export class Characteristic extends EventEmitter { 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: () => Promise> | Nullable) { + if (typeof handler !== 'function' || handler.length !== 0) { + console.warn(`[${this.displayName}] .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 expects a set 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: (value: CharacteristicValue) => Promise | void> | Nullable | void) { + if (typeof handler !== 'function' || handler.length !== 1) { + console.warn(`[${this.displayName}] .onSet handler must be a function with exactly one input argument.`); + return this; + } + this.setHandler = handler; + return this; + } + /** * Updates the properties of this characteristic. * Properties passed via the parameter will be set. Any parameter set to null will be deleted. @@ -712,7 +767,7 @@ export class Characteristic extends EventEmitter { * @param context - Deprecated parameter. There for backwards compatibility. * @internal Used by the Accessory to load the characteristic value */ - handleGetRequest(connection?: HAPConnection, context?: any): Promise> { + 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 return Promise.reject(HAPStatus.WRITE_ONLY_CHARACTERISTIC); } @@ -722,6 +777,33 @@ export class Characteristic extends EventEmitter { return Promise.resolve(null); } + if (this.getHandler) { + if (this.listeners(CharacteristicEventTypes.GET).length > 0) { + console.warn(`[${this.displayName}] Ignoring on('get') handler as onGet handler was defined instead.`); + } + + try { + let value = await this.getHandler(); + this.status = HAPStatus.SUCCESS; + value = this.validateUserInput(value); + 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 (error instanceof HapStatusError) { + this.status = (error as HapStatusError).hapStatus; + throw this.status; + } else { + console.warn(`[${this.displayName}] Unhandled error thrown inside read handler for characteristic: ${error.stack}`); + this.status = HAPStatus.SERVICE_COMMUNICATION_FAILURE; + throw HAPStatus.SERVICE_COMMUNICATION_FAILURE; + } + } + } + if (this.listeners(CharacteristicEventTypes.GET).length === 0) { return this.status? Promise.reject(this.status): Promise.resolve(this.value); } @@ -732,6 +814,8 @@ export class Characteristic extends EventEmitter { if (status) { if (typeof status === "number") { this.status = status; + } else if (status instanceof HapStatusError) { + this.status = (status as HapStatusError).hapStatus; } else { this.status = extractHAPStatusFromError(status); debug("[%s] Received error from get handler %s", this.displayName, status.stack); @@ -771,7 +855,7 @@ export class Characteristic extends EventEmitter { * write response value is resolved. * @internal */ - handleSetRequest(value: CharacteristicValue, connection?: HAPConnection, context?: any): Promise { + async handleSetRequest(value: CharacteristicValue, connection?: HAPConnection, context?: any): Promise { this.status = HAPStatus.SUCCESS; if (!this.validClientSuppliedValue(value)) { @@ -784,6 +868,38 @@ export class Characteristic extends EventEmitter { const oldValue = this.value; + if (this.setHandler) { + if (this.listeners(CharacteristicEventTypes.SET).length > 0) { + console.warn(`[${this.displayName}] Ignoring on('set') handler as onSet handler was defined instead.`); + } + + try { + const writeResponse = await this.setHandler(value); + this.status = HAPStatus.SUCCESS; + + if (writeResponse != null && writeResponse !== undefined && this.props.perms.includes(Perms.WRITE_RESPONSE)) { + this.value = writeResponse; + } else { + if (writeResponse != null && writeResponse !== undefined) { + console.warn(`[${this.displayName}] SET handler returned write response value, though the characteristic doesn't support write response!`); + } + this.value = value; + } + + this.emit(CharacteristicEventTypes.CHANGE, { originator: connection, oldValue: oldValue, newValue: value, context: context }); + return this.value; + } catch (error) { + if (error instanceof HapStatusError) { + this.status = (error as HapStatusError).hapStatus; + throw this.status; + } else { + console.warn(`[${this.displayName}] Unhandled error thrown inside write handler for characteristic: ${error.stack}`); + this.status = HAPStatus.SERVICE_COMMUNICATION_FAILURE; + throw HAPStatus.SERVICE_COMMUNICATION_FAILURE; + } + } + } + if (this.listeners(CharacteristicEventTypes.SET).length === 0) { this.value = value; this.emit(CharacteristicEventTypes.CHANGE, { originator: connection, oldValue: oldValue, newValue: value, context: context }); @@ -799,6 +915,8 @@ export class Characteristic extends EventEmitter { if (status) { if (typeof status === "number") { this.status = status; + } else if (status instanceof HapStatusError) { + this.status = (status as HapStatusError).hapStatus; } else { this.status = extractHAPStatusFromError(status); debug("[%s] Received error from set handler %s", this.displayName, status.stack); 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; + } + } +} + From c8b5791add926aaeb2a9b60d5090a1f56e5c14e3 Mon Sep 17 00:00:00 2001 From: Supereg Date: Mon, 12 Oct 2020 15:15:17 +0200 Subject: [PATCH 49/70] Minor adjustments handling promise errors on characteristic handlers. Additionally we added a custom try-catch for the validateInput function to print a more meaningful error message --- src/accessories/Fan_accessory.ts | 3 +- src/lib/Characteristic.ts | 67 ++++++++++++++++++++------------ 2 files changed, 43 insertions(+), 27 deletions(-) diff --git a/src/accessories/Fan_accessory.ts b/src/accessories/Fan_accessory.ts index 7f118c45a..028bb9bd1 100644 --- a/src/accessories/Fan_accessory.ts +++ b/src/accessories/Fan_accessory.ts @@ -77,8 +77,7 @@ fan if (FAKE_FAN.powerOn) { return true; - } - else { + } else { return false; } }); diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts index 8a446a96d..6c792b776 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -189,6 +189,8 @@ export const enum CharacteristicEventTypes { 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; @@ -492,14 +494,14 @@ export class Characteristic extends EventEmitter { props: CharacteristicProps; /** - * The .onGet handler + * The {@link onGet} handler */ - private getHandler?: () => Promise> | Nullable; + private getHandler?: CharacteristicGetHandler; /** - * The .onSet handler + * The {@link onSet} handler */ - private setHandler?: (value: CharacteristicValue) => Promise | void> | Nullable | void; + private setHandler?: CharacteristicSetHandler; private subscriptions: number = 0; /** @@ -523,7 +525,7 @@ export class Characteristic extends EventEmitter { * 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 () => { @@ -532,7 +534,7 @@ export class Characteristic extends EventEmitter { * ``` * @param handler */ - public onGet(handler: () => Promise> | Nullable) { + public onGet(handler: CharacteristicGetHandler) { if (typeof handler !== 'function' || handler.length !== 0) { console.warn(`[${this.displayName}] .onGet handler must be a function with exactly zero input arguments.`); return this; @@ -543,9 +545,10 @@ export class Characteristic extends EventEmitter { /** * Accepts a function that will be called when setting the value of a Characteristic. - * If the Characteristic expects a set response value, the returned value will be used. + * 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) => { @@ -554,7 +557,7 @@ export class Characteristic extends EventEmitter { * ``` * @param handler */ - public onSet(handler: (value: CharacteristicValue) => Promise | void> | Nullable | void) { + public onSet(handler: CharacteristicSetHandler) { if (typeof handler !== 'function' || handler.length !== 1) { console.warn(`[${this.displayName}] .onSet handler must be a function with exactly one input argument.`); return this; @@ -769,12 +772,12 @@ export class Characteristic extends EventEmitter { */ 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 - return Promise.reject(HAPStatus.WRITE_ONLY_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 Promise.resolve(null); + return null; } if (this.getHandler) { @@ -785,7 +788,15 @@ export class Characteristic extends EventEmitter { try { let value = await this.getHandler(); this.status = HAPStatus.SUCCESS; - value = this.validateUserInput(value); + + try { + value = this.validateUserInput(value); + } catch (error) { + console.warn(`[${this.displayName}] 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; @@ -793,19 +804,24 @@ export class Characteristic extends EventEmitter { this.emit(CharacteristicEventTypes.CHANGE, { originator: connection, oldValue: oldValue, newValue: value, context: context }); } } catch (error) { - if (error instanceof HapStatusError) { - this.status = (error as HapStatusError).hapStatus; - throw this.status; + if (typeof error === "number") { + this.status = error; + } else if (error instanceof HapStatusError) { + this.status = error.hapStatus; } else { console.warn(`[${this.displayName}] Unhandled error thrown inside read handler for characteristic: ${error.stack}`); this.status = HAPStatus.SERVICE_COMMUNICATION_FAILURE; - throw HAPStatus.SERVICE_COMMUNICATION_FAILURE; } + throw this.status; } } if (this.listeners(CharacteristicEventTypes.GET).length === 0) { - return this.status? Promise.reject(this.status): Promise.resolve(this.value); + if (this.status) { + throw this.status; + } else { + return this.value; + } } return new Promise((resolve, reject) => { @@ -815,7 +831,7 @@ export class Characteristic extends EventEmitter { if (typeof status === "number") { this.status = status; } else if (status instanceof HapStatusError) { - this.status = (status as HapStatusError).hapStatus; + this.status = status.hapStatus; } else { this.status = extractHAPStatusFromError(status); debug("[%s] Received error from get handler %s", this.displayName, status.stack); @@ -877,10 +893,10 @@ export class Characteristic extends EventEmitter { const writeResponse = await this.setHandler(value); this.status = HAPStatus.SUCCESS; - if (writeResponse != null && writeResponse !== undefined && this.props.perms.includes(Perms.WRITE_RESPONSE)) { + if (writeResponse != null && this.props.perms.includes(Perms.WRITE_RESPONSE)) { this.value = writeResponse; } else { - if (writeResponse != null && writeResponse !== undefined) { + if (writeResponse != null) { console.warn(`[${this.displayName}] SET handler returned write response value, though the characteristic doesn't support write response!`); } this.value = value; @@ -889,14 +905,15 @@ export class Characteristic extends EventEmitter { this.emit(CharacteristicEventTypes.CHANGE, { originator: connection, oldValue: oldValue, newValue: value, context: context }); return this.value; } catch (error) { - if (error instanceof HapStatusError) { - this.status = (error as HapStatusError).hapStatus; - throw this.status; + if (typeof error === "number") { + this.status = error; + } else if (error instanceof HapStatusError) { + this.status = error.hapStatus; } else { console.warn(`[${this.displayName}] Unhandled error thrown inside write handler for characteristic: ${error.stack}`); this.status = HAPStatus.SERVICE_COMMUNICATION_FAILURE; - throw HAPStatus.SERVICE_COMMUNICATION_FAILURE; } + throw this.status; } } @@ -916,7 +933,7 @@ export class Characteristic extends EventEmitter { if (typeof status === "number") { this.status = status; } else if (status instanceof HapStatusError) { - this.status = (status as HapStatusError).hapStatus; + this.status = status.hapStatus; } else { this.status = extractHAPStatusFromError(status); debug("[%s] Received error from set handler %s", this.displayName, status.stack); From 9b30452ec7c7026bba5ef9bbb4b88ead4bae46cc Mon Sep 17 00:00:00 2001 From: Supereg Date: Mon, 12 Oct 2020 22:25:04 +0200 Subject: [PATCH 50/70] Improved some typing. Only assignIds on main accessory --- src/internal-types.ts | 2 +- src/lib/Accessory.ts | 10 ++++++---- src/lib/Characteristic.ts | 8 ++++---- src/lib/util/eventedhttp.ts | 2 ++ 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/internal-types.ts b/src/internal-types.ts index 8f017ae15..444bf29cf 100644 --- a/src/internal-types.ts +++ b/src/internal-types.ts @@ -40,7 +40,7 @@ export interface CharacteristicJsonObject { maxLen?: number, maxDataLen?: number, "valid-values"?: number[], - "valid-values-range"?: [number, number], + "valid-values-range"?: [min: number, max: number], } export interface ServiceJsonObject { diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index 66019bc28..dfc8b102b 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -967,8 +967,10 @@ export class Accessory extends EventEmitter { /** * Returns a JSON representation of this accessory without characteristic values. */ - private internalHAPRepresentation(): AccessoryJsonObject[] { - this._assignIDs(this._identifierCache!); // make sure our aid/iid's are all assigned + 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!"); @@ -981,7 +983,7 @@ export class Accessory extends EventEmitter { if (!this.bridged) { for (const accessory of this.bridgedAccessories) { - accessories.push(accessory.internalHAPRepresentation()[0]); + accessories.push(accessory.internalHAPRepresentation(false)[0]); } } @@ -1077,7 +1079,7 @@ 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". - const config = this.internalHAPRepresentation(); // TODO ensure this stuff is ordered + const config = this.internalHAPRepresentation(false); // TODO ensure this stuff is ordered this._accessoryInfo.checkForCurrentConfigurationNumberIncrement(config, true); this.validateAccessory(true); diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts index 6c792b776..f6aa9567b 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -132,7 +132,7 @@ export interface CharacteristicProps { * Two element array where the first value specifies the lowest valid value and * the second element specifies the highest valid value. */ - validValueRanges?: [number, number]; + validValueRanges?: [min: number, max: number]; adminOnlyAccess?: Access[]; } @@ -582,7 +582,7 @@ export class Characteristic extends EventEmitter { this.props.format = props.format; } if (props.perms) { - assert(props.perms, "characteristic prop perms cannot be empty array"); + assert(props.perms.length, "characteristic prop perms cannot be empty array"); this.props.perms = props.perms; } if (props.unit !== undefined) { @@ -1008,7 +1008,7 @@ export class Characteristic extends EventEmitter { case Formats.ARRAY: return []; default: - return this.props.minValue || 0; + return this.props.minValue ?? 0; } } @@ -1016,7 +1016,7 @@ export class Characteristic extends EventEmitter { if (!this.props.minStep) { return Math.round(value); // round to 0 decimal places } - const base = this.props.minValue || 0; + 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; } diff --git a/src/lib/util/eventedhttp.ts b/src/lib/util/eventedhttp.ts index 918d85062..b45959d4c 100644 --- a/src/lib/util/eventedhttp.ts +++ b/src/lib/util/eventedhttp.ts @@ -357,6 +357,8 @@ export class HAPConnection extends EventEmitter { }], }); } else { + // 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, From e329727922541b8aed49df8abe9c8ffb0ec3d99b Mon Sep 17 00:00:00 2001 From: Supereg Date: Mon, 12 Oct 2020 22:29:22 +0200 Subject: [PATCH 51/70] Fixed assertions and tests --- src/lib/Characteristic.spec.ts | 6 +++--- src/lib/Characteristic.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/Characteristic.spec.ts b/src/lib/Characteristic.spec.ts index 5d1f7b639..ae91226c8 100644 --- a/src/lib/Characteristic.spec.ts +++ b/src/lib/Characteristic.spec.ts @@ -26,7 +26,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); @@ -205,7 +205,7 @@ describe('Characteristic', () => { const characteristic = createCharacteristicWithProps({ format: Formats.FLOAT, minStep: 0.001, - perms: [], + perms: [Perms.NOTIFY], }); // @ts-expect-error expect(characteristic.validateUserInput(VALUE)).toEqual(VALUE); @@ -216,7 +216,7 @@ describe('Characteristic', () => { const characteristic = createCharacteristicWithProps({ format: Formats.FLOAT, minStep: 0.1, - perms: [], + perms: [Perms.NOTIFY], }); // @ts-expect-error expect(characteristic.validateUserInput(VALUE)).toEqual(1.6); diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts index f6aa9567b..6eae3cd70 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -582,7 +582,7 @@ export class Characteristic extends EventEmitter { this.props.format = props.format; } if (props.perms) { - assert(props.perms.length, "characteristic prop perms cannot be empty array"); + assert(props.perms.length > 0, "characteristic prop perms cannot be empty array"); this.props.perms = props.perms; } if (props.unit !== undefined) { From 917fa6a154e44a62b8aebeac87e9d84ac514bc12 Mon Sep 17 00:00:00 2001 From: Supereg Date: Mon, 12 Oct 2020 23:24:58 +0200 Subject: [PATCH 52/70] Adding ability to track code coverage --- .github/workflows/nodejs.yml | 20 +++++++++++++++++++- jest.config.js | 11 ++++++++--- package.json | 5 +++-- 3 files changed, 30 insertions(+), 6 deletions(-) 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..5da30ff81 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,4 +1,9 @@ module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', -}; \ No newline at end of file + preset: "ts-jest", + testEnvironment: "node", + coverageReporters: ["lcov"], + collectCoverageFrom: [ + "src/**", + "!src/accessories/**" + ], +}; diff --git a/package.json b/package.json index d74ab787b..306c8e21b 100644 --- a/package.json +++ b/package.json @@ -12,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": [ From 8fe0b1529157d1243e746577545c320cf142199e Mon Sep 17 00:00:00 2001 From: Supereg Date: Tue, 13 Oct 2020 16:39:38 +0200 Subject: [PATCH 53/70] Make warnings less dramatic --- src/lib/Characteristic.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts index 6eae3cd70..d15e0ad20 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -1293,7 +1293,7 @@ export class Characteristic extends EventEmitter { 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) { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: string '${value}' exceeded max length of ${maxLength}. Supplying illegal values will throw errors in the future!`); + console.warn(`[${this.displayName}] characteristic was supplied illegal value: string '${value}' exceeded max length of ${maxLength}.`); value = value.substring(0, maxLength); } @@ -1315,11 +1315,11 @@ export class Characteristic extends EventEmitter { if (typeof value === "number") { if (numericMin != null && value < numericMin) { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: number ${value} exceeded minimum of ${numericMin}. Supplying illegal values will throw errors in the future!`); + console.warn(`[${this.displayName}] characteristic was supplied illegal value: number ${value} exceeded minimum of ${numericMin}.`); value = numericMin; } if (numericMax != null && value > numericMax) { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: number ${value} exceeded maximum of ${numericMax}. Supplying illegal values will throw errors in the future!`); + console.warn(`[${this.displayName}] characteristic was supplied illegal value: number ${value} exceeded maximum of ${numericMax}.`); value = numericMax; } From 7fde47b637e1306d2545618237b09507a8b89c7c Mon Sep 17 00:00:00 2001 From: Supereg Date: Tue, 13 Oct 2020 23:32:54 +0200 Subject: [PATCH 54/70] Improved handling of undefined and null values for AccessoryInformation service --- src/lib/Accessory.ts | 2 -- src/lib/Characteristic.ts | 16 +++++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index dfc8b102b..d5bb881ba 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -845,13 +845,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); diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts index d15e0ad20..e23501a39 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -874,7 +874,9 @@ export class Characteristic extends EventEmitter { async handleSetRequest(value: CharacteristicValue, connection?: HAPConnection, context?: any): Promise { this.status = HAPStatus.SUCCESS; - if (!this.validClientSuppliedValue(value)) { + 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); } @@ -1167,9 +1169,13 @@ export class Characteristic extends EventEmitter { */ private validateUserInput(value?: Nullable): Nullable { if (value === undefined) { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: undefined. Supplying illegal values will throw errors in the future!`); - return this.getDefaultValue(); + throw new Error(`[${this.displayName}] characteristic was supplied illegal value: undefined!`); } else if (value === null) { + if (this.UUID === Characteristic.Model.UUID || this.UUID === Characteristic.SerialNumber.UUID) { // mirrors the statement in case: Formats.STRING + console.error(new Error(`[${this.displayName}] characteristic must have a non null value otherwise HomeKit will reject this accessory. Ignoring new value.`).stack); + return this.value; // don't change the value + } + return null; // null is allowed } @@ -1286,8 +1292,8 @@ export class Characteristic extends EventEmitter { 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)) { - console.error(new Error(`[${this.displayName}] characteristic must have a length of more than 1 character otherwise HomeKit will reject this accessory. Ignoring it.`).stack); + if (value.length <= 1 && (this.UUID === Characteristic.Model.UUID || this.UUID === Characteristic.SerialNumber.UUID)) { // mirrors the case value = null at the beginning + console.error(new Error(`[${this.displayName}] characteristic must have a length of more than 1 character otherwise HomeKit will reject this accessory. Ignoring new value.`).stack); return this.value; // just return the current value } From 5e9d970d67dc57fa6ffd6f104ea4260473fa7bda Mon Sep 17 00:00:00 2001 From: Supereg Date: Wed, 14 Oct 2020 16:26:17 +0200 Subject: [PATCH 55/70] Improve default value handling of AccessoryInformation characteristics Removed error thrown when setting undefined --- src/lib/Characteristic.ts | 16 ++++++++++++++-- src/lib/gen/HomeKit.ts | 11 ++++------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts index e23501a39..0e6b498a4 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -1000,7 +1000,18 @@ export class Characteristic extends EventEmitter { case Formats.BOOL: return false; case Formats.STRING: - return ""; + 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: @@ -1169,7 +1180,8 @@ export class Characteristic extends EventEmitter { */ private validateUserInput(value?: Nullable): Nullable { if (value === undefined) { - throw new Error(`[${this.displayName}] characteristic was supplied illegal value: undefined!`); + console.warn(`[${this.displayName}] 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 console.error(new Error(`[${this.displayName}] characteristic must have a non null value otherwise HomeKit will reject this accessory. Ignoring new value.`).stack); diff --git a/src/lib/gen/HomeKit.ts b/src/lib/gen/HomeKit.ts index bf2e2ea9b..41f50adb8 100644 --- a/src/lib/gen/HomeKit.ts +++ b/src/lib/gen/HomeKit.ts @@ -916,10 +916,7 @@ export class FirmwareRevision extends Characteristic { static readonly UUID: string = '00000052-0000-1000-8000-0026BB765291'; constructor() { - // TODO change later - // @ts-expect-error - super('Firmware Revision', FirmwareRevision.UUID); - this.setProps({ + super('Firmware Revision', FirmwareRevision.UUID, { format: Formats.STRING, perms: [Perms.PAIRED_READ] }); @@ -4026,10 +4023,10 @@ export class AccessoryInformation extends Service { // Required Characteristics this.addCharacteristic(Characteristic.Identify); - this.addCharacteristic(Characteristic.Manufacturer).updateValue("Default-Manufacturer"); - this.addCharacteristic(Characteristic.Model).updateValue("Default-Model"); + this.addCharacteristic(Characteristic.Manufacturer); + this.addCharacteristic(Characteristic.Model); this.addCharacteristic(Characteristic.Name); - this.addCharacteristic(Characteristic.SerialNumber).updateValue("Default-SerialNumber"); + this.addCharacteristic(Characteristic.SerialNumber); // Optional Characteristics this.addOptionalCharacteristic(Characteristic.AccessoryFlags); From 091e2a4f31ed0734791f99553b8b28b1ffeefc85 Mon Sep 17 00:00:00 2001 From: Supereg Date: Fri, 16 Oct 2020 23:23:07 +0200 Subject: [PATCH 56/70] Reworked characteristic and service definition generation --- jest.config.js | 4 +- package-lock.json | 6 + package.json | 1 + src/accessories/AirConditioner_accessory.ts | 1 - src/accessories/Fan_accessory.ts | 1 - src/accessories/Light_accessory.ts | 1 - src/accessories/Lock_accessory.ts | 1 - src/accessories/Outlet_accessory.ts | 1 - src/accessories/SmartSpeaker_accessory.ts | 9 +- src/accessories/Sprinkler_accessory.ts | 1 - src/accessories/TV_accessory.ts | 1 - .../TemperatureSensor_accessory.ts | 1 - src/index.ts | 3 +- src/lib/Accessory.spec.ts | 1 - src/lib/Accessory.ts | 20 +- src/lib/Characteristic.spec.ts | 1 - src/lib/Characteristic.ts | 667 ++- src/lib/Service.spec.ts | 1 - src/lib/Service.ts | 229 +- src/lib/camera/RTPStreamManagement.ts | 2 +- src/lib/controller/CameraController.ts | 4 +- src/lib/controller/DoorbellController.ts | 14 +- src/lib/controller/RemoteController.ts | 16 +- src/lib/datastream/DataStreamManagement.ts | 2 +- .../CharacteristicDefinitions.spec.ts | 1298 ++++ .../definitions/CharacteristicDefinitions.ts | 4347 ++++++++++++++ .../definitions/ServiceDefinitions.spec.ts | 1452 +++++ src/lib/definitions/ServiceDefinitions.ts | 1465 +++++ src/lib/definitions/generate-definitions.ts | 769 +++ .../definitions/generator-configuration.ts | 155 + src/lib/definitions/index.ts | 2 + src/lib/gen/HomeKit-Bridge.ts | 644 -- src/lib/gen/HomeKit-DataStream.ts | 66 - src/lib/gen/HomeKit-Remote.ts | 190 - src/lib/gen/HomeKit-TV.ts | 543 -- src/lib/gen/HomeKit.ts | 5258 ----------------- src/lib/gen/import.js | 190 - src/lib/gen/importAsClasses.ts | 210 - src/lib/gen/index.ts | 13 - src/lib/tv/AccessControlManagement.ts | 6 +- src/lib/util/uuid.ts | 6 +- src/types/simple-plist.d.ts | 23 + 42 files changed, 10155 insertions(+), 7470 deletions(-) create mode 100644 src/lib/definitions/CharacteristicDefinitions.spec.ts create mode 100644 src/lib/definitions/CharacteristicDefinitions.ts create mode 100644 src/lib/definitions/ServiceDefinitions.spec.ts create mode 100644 src/lib/definitions/ServiceDefinitions.ts create mode 100644 src/lib/definitions/generate-definitions.ts create mode 100644 src/lib/definitions/generator-configuration.ts create mode 100644 src/lib/definitions/index.ts delete mode 100644 src/lib/gen/HomeKit-Bridge.ts delete mode 100644 src/lib/gen/HomeKit-DataStream.ts delete mode 100644 src/lib/gen/HomeKit-Remote.ts delete mode 100644 src/lib/gen/HomeKit-TV.ts delete mode 100644 src/lib/gen/HomeKit.ts delete mode 100644 src/lib/gen/import.js delete mode 100644 src/lib/gen/importAsClasses.ts delete mode 100644 src/lib/gen/index.ts create mode 100644 src/types/simple-plist.d.ts diff --git a/jest.config.js b/jest.config.js index 5da30ff81..eee1665b7 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,6 +4,8 @@ module.exports = { coverageReporters: ["lcov"], collectCoverageFrom: [ "src/**", - "!src/accessories/**" + "!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 006cdc958..5fc687fe4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1699,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", diff --git a/package.json b/package.json index 306c8e21b..50a6de630 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "@types/debug": "^4.1.5", "@types/jest": "^26.0.14", "@types/node": "^10.17.20", + "commander": "^6.1.0", "jest": "^26.5.2", "rimraf": "^3.0.2", "semver": "^7.3.2", diff --git a/src/accessories/AirConditioner_accessory.ts b/src/accessories/AirConditioner_accessory.ts index d63dbc015..09c92399f 100644 --- a/src/accessories/AirConditioner_accessory.ts +++ b/src/accessories/AirConditioner_accessory.ts @@ -51,7 +51,6 @@ 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` const FanService = ACTest.addService(Service.Fan, "Blower"); // services exposed to the user should have "names" like "Fake Light" for us FanService.getCharacteristic(Characteristic.On)! diff --git a/src/accessories/Fan_accessory.ts b/src/accessories/Fan_accessory.ts index 028bb9bd1..b7e17b8b0 100644 --- a/src/accessories/Fan_accessory.ts +++ b/src/accessories/Fan_accessory.ts @@ -56,7 +56,6 @@ 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)! diff --git a/src/accessories/Light_accessory.ts b/src/accessories/Light_accessory.ts index 6ab9657c1..fb6d01264 100644 --- a/src/accessories/Light_accessory.ts +++ b/src/accessories/Light_accessory.ts @@ -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 688cad685..2e708e4b0 100644 --- a/src/accessories/Lock_accessory.ts +++ b/src/accessories/Lock_accessory.ts @@ -56,7 +56,6 @@ lock.on(AccessoryEventTypes.IDENTIFY, (paired, callback) => { 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` service.getCharacteristic(Characteristic.LockTargetState) .on(CharacteristicEventTypes.SET, (value, callback) => { diff --git a/src/accessories/Outlet_accessory.ts b/src/accessories/Outlet_accessory.ts index fd4e682fa..3e56634c7 100644 --- a/src/accessories/Outlet_accessory.ts +++ b/src/accessories/Outlet_accessory.ts @@ -67,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)! diff --git a/src/accessories/SmartSpeaker_accessory.ts b/src/accessories/SmartSpeaker_accessory.ts index 70ee33530..079e79b72 100644 --- a/src/accessories/SmartSpeaker_accessory.ts +++ b/src/accessories/SmartSpeaker_accessory.ts @@ -3,11 +3,12 @@ import { Categories, Characteristic, CharacteristicEventTypes, - CharacteristicGetCallback, CharacteristicSetCallback, CharacteristicValue, + 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) diff --git a/src/accessories/Sprinkler_accessory.ts b/src/accessories/Sprinkler_accessory.ts index 5829b518b..027947063 100644 --- a/src/accessories/Sprinkler_accessory.ts +++ b/src/accessories/Sprinkler_accessory.ts @@ -45,7 +45,6 @@ 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` const sprinklerService = sprinkler.addService(Service.Valve, "💦 Sprinkler"); diff --git a/src/accessories/TV_accessory.ts b/src/accessories/TV_accessory.ts index 28e55abac..f6a74696e 100644 --- a/src/accessories/TV_accessory.ts +++ b/src/accessories/TV_accessory.ts @@ -29,7 +29,6 @@ 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` const televisionService = tv.addService(Service.Television, "Television", "Television"); televisionService diff --git a/src/accessories/TemperatureSensor_accessory.ts b/src/accessories/TemperatureSensor_accessory.ts index 1f12a86c3..38574c9bc 100644 --- a/src/accessories/TemperatureSensor_accessory.ts +++ b/src/accessories/TemperatureSensor_accessory.ts @@ -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/index.ts b/src/index.ts index b2266bd3e..614f85fb2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import "source-map-support/register"; // registering node-source-map-support for typescript stack traces -import './lib/gen'; +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'; @@ -17,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'; 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 d5bb881ba..6070426fc 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -42,16 +42,6 @@ import { ControllerType, isSerializableController, } from "./controller"; -import * as HomeKitTypes from "./gen"; -import { - CameraEventRecordingManagement, - CameraOperatingMode, - CameraRTPStreamManagement, - ContactSensorState, - MotionDetected, - ProgrammableSwitchEvent, -} from "./gen/HomeKit"; -import { ButtonEvent } from "./gen/HomeKit-Remote"; import { AccessoriesCallback, AddPairingCallback, @@ -659,8 +649,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 } @@ -1414,7 +1404,7 @@ export class Accessory extends EventEmitter { data.perms = characteristic.props.perms; } if (request.includeType) { - data.type = toShortForm(this.UUID, HomeKitTypes.BASE_UUID); + data.type = toShortForm(this.UUID); } if (request.includeEvent) { data.ev = connection.hasEventNotifications(id.aid, id.iid); @@ -1730,8 +1720,8 @@ export class Accessory extends EventEmitter { } const uuid = change.characteristic.UUID; - const immediateDelivery = uuid === ButtonEvent.UUID || uuid === ProgrammableSwitchEvent.UUID - || uuid === MotionDetected.UUID || uuid === ContactSensorState.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); } diff --git a/src/lib/Characteristic.spec.ts b/src/lib/Characteristic.spec.ts index ae91226c8..89156aaa3 100644 --- a/src/lib/Characteristic.spec.ts +++ b/src/lib/Characteristic.spec.ts @@ -10,7 +10,6 @@ import { Units, uuid } from '..'; -import './gen'; function createCharacteristic(type: Formats, customUUID?: string): Characteristic { return new Characteristic('Test', customUUID || uuid.generate('Foo'), { format: type, perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE] }); diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts index 0e6b498a4..8314e4efa 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -3,7 +3,223 @@ import createDebug from "debug"; import { EventEmitter } from "events"; import { CharacteristicJsonObject } from "../internal-types"; import { CharacteristicValue, Nullable, VoidCallback, } from '../types'; -import * as HomeKitTypes from './gen'; +import { + 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 { clone } from "./util/clone"; @@ -259,231 +475,228 @@ export class Characteristic extends EventEmitter { // @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; - 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; - 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; - 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 CharacteristicValueActiveTransitionCount: typeof HomeKitTypes.Generated.CharacteristicValueActiveTransitionCount; - 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; @@ -1398,7 +1611,7 @@ export class Characteristic extends EventEmitter { internalHAPRepresentation(): CharacteristicJsonObject { assert(this.iid,"iid cannot be undefined for characteristic '" + this.displayName + "'"); return { - type: toShortForm(this.UUID, HomeKitTypes.BASE_UUID), + type: toShortForm(this.UUID), iid: this.iid!, value: null, perms: this.props.perms, diff --git a/src/lib/Service.spec.ts b/src/lib/Service.spec.ts index 0dc295d95..cdc95575f 100644 --- a/src/lib/Service.spec.ts +++ b/src/lib/Service.spec.ts @@ -1,4 +1,3 @@ -import './gen'; import { Characteristic, Service, ServiceEventTypes, uuid } from '..'; const createService = () => { diff --git a/src/lib/Service.ts b/src/lib/Service.ts index 5a6348ff3..289b5e221 100644 --- a/src/lib/Service.ts +++ b/src/lib/Service.ts @@ -8,7 +8,73 @@ import { CharacteristicEventTypes, SerializedCharacteristic } from './Characteristic'; -import * as HomeKitTypes from './gen'; +import { + AccessControl, + AccessoryInformation, + AccessoryRuntimeInformation, + AirPurifier, + AirQualitySensor, + AudioStreamManagement, + Battery, + 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 { HAPConnection } from "./util/eventedhttp"; import { toShortForm } from './util/uuid'; @@ -76,75 +142,94 @@ export declare interface Service { */ 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; - 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; + // 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 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 Please use {@link Service.CloudRelay}. + */ + 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; @@ -467,7 +552,7 @@ export class Service extends EventEmitter { assert(this.characteristics.length, "service '" + this.displayName + "' does not have any characteristics!"); const service: ServiceJsonObject = { - type: toShortForm(this.UUID, HomeKitTypes.BASE_UUID), + type: toShortForm(this.UUID), iid: this.iid!, characteristics: [], hidden: this.isHiddenService? true: undefined, @@ -533,7 +618,7 @@ export class Service extends EventEmitter { assert(this.characteristics.length, "service '" + this.displayName + "' does not have any characteristics!"); const service: ServiceJsonObject = { - type: toShortForm(this.UUID, HomeKitTypes.BASE_UUID), + type: toShortForm(this.UUID), iid: this.iid!, characteristics: this.characteristics.map(characteristic => characteristic.internalHAPRepresentation()), hidden: this.isHiddenService? true: undefined, diff --git a/src/lib/camera/RTPStreamManagement.ts b/src/lib/camera/RTPStreamManagement.ts index 6cd8db2e3..060f874ed 100644 --- a/src/lib/camera/RTPStreamManagement.ts +++ b/src/lib/camera/RTPStreamManagement.ts @@ -6,7 +6,7 @@ import { LegacyCameraSource, LegacyCameraSourceAdapter, once, uuid } from "../.. import { CharacteristicValue, Nullable, SessionIdentifier } from '../../types'; import { Characteristic, CharacteristicEventTypes, CharacteristicSetCallback } from '../Characteristic'; import { CameraController, CameraStreamingDelegate } from "../controller"; -import { CameraRTPStreamManagement } from "../gen/HomeKit"; +import type { CameraRTPStreamManagement } from "../definitions"; import { HAPStatus } from "../HAPServer"; import { Service } from '../Service'; import { HAPConnection, HAPConnectionEvent } from "../util/eventedhttp"; diff --git a/src/lib/controller/CameraController.ts b/src/lib/controller/CameraController.ts index a1656c078..5a07ee92a 100644 --- a/src/lib/controller/CameraController.ts +++ b/src/lib/controller/CameraController.ts @@ -8,17 +8,17 @@ import { CharacteristicGetCallback, CharacteristicSetCallback, CharacteristicValue, + HAPStatus, LegacyCameraSourceAdapter, PrepareStreamRequest, PrepareStreamResponse, RTPStreamManagement, Service, SnapshotRequest, - HAPStatus, StreamingRequest } from "../.."; import { SessionIdentifier } from "../../types"; -import { Doorbell, Microphone, Speaker } from "../gen/HomeKit"; +import type { Doorbell, Microphone, Speaker } from "../definitions"; import { Controller, ControllerServiceMap, ControllerType, DefaultControllerType } from "./Controller"; import Timeout = NodeJS.Timeout; 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 75dfc8b9c..206dcc9dc 100644 --- a/src/lib/controller/RemoteController.ts +++ b/src/lib/controller/RemoteController.ts @@ -24,8 +24,13 @@ import { RequestHandler, Topics } from "../datastream"; -import { DataStreamTransportManagement } from "../gen/HomeKit-DataStream"; -import { AudioStreamManagement, Siri, TargetControl, TargetControlManagement } from "../gen/HomeKit-Remote"; +import type { + AudioStreamManagement, + DataStreamTransportManagement, + Siri, + TargetControl, + TargetControlManagement +} from '../definitions'; import { HAPStatus } from "../HAPServer"; import { Service } from "../Service"; import { HAPConnection, HAPConnectionEvent } from "../util/eventedhttp"; @@ -139,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, } @@ -1147,7 +1147,7 @@ export class RemoteController extends EventEmitter implements SerializableContro 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); diff --git a/src/lib/datastream/DataStreamManagement.ts b/src/lib/datastream/DataStreamManagement.ts index c22388251..f2791cffe 100644 --- a/src/lib/datastream/DataStreamManagement.ts +++ b/src/lib/datastream/DataStreamManagement.ts @@ -1,6 +1,6 @@ import createDebug from "debug"; import { Characteristic, CharacteristicEventTypes, CharacteristicSetCallback } from "../Characteristic"; -import { DataStreamTransportManagement } from "../gen/HomeKit-DataStream"; +import type { DataStreamTransportManagement } from "../definitions"; import { HAPStatus } from "../HAPServer"; import { Service } from "../Service"; import { HAPConnection } from "../util/eventedhttp"; diff --git a/src/lib/definitions/CharacteristicDefinitions.spec.ts b/src/lib/definitions/CharacteristicDefinitions.spec.ts new file mode 100644 index 000000000..d8ad3fd5d --- /dev/null +++ b/src/lib/definitions/CharacteristicDefinitions.spec.ts @@ -0,0 +1,1298 @@ +// THIS FILE IS AUTO-GENERATED - DO NOT MODIFY +import "./"; + +import { Characteristic } from "../Characteristic"; + +describe("CharacteristicDefinitions", () => { + 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..acd03b6b1 --- /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, Perms.ADDITIONAL_AUTHORIZATION], + 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, Perms.ADDITIONAL_AUTHORIZATION], + 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.NOTIFY, 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..6b640e862 --- /dev/null +++ b/src/lib/definitions/ServiceDefinitions.spec.ts @@ -0,0 +1,1452 @@ +// 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("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..942f96fd6 --- /dev/null +++ b/src/lib/definitions/ServiceDefinitions.ts @@ -0,0 +1,1465 @@ +// 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 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..a2b43c124 --- /dev/null +++ b/src/lib/definitions/generate-definitions.ts @@ -0,0 +1,769 @@ +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, + + 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, + + 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 = {}; +const writtenCharacteristicEntries: Record = {}; // Characteristic. = + +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"; + } + } + + generatedCharacteristics[id] = { + 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), + }; + writtenCharacteristicEntries[className] = className; + if (deprecatedClassName) { + writtenCharacteristicEntries[deprecatedClassName] = className; + } + } 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.className; + if (generated.deprecatedClassName) { + writtenCharacteristicEntries[generated.deprecatedClassName] = generated.className; + } +} + +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"); + } + 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 = {}; +const writtenServiceEntries: Record = {}; // Service. = + +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); + } + } + } + } + + generatedServices[id] = { + id: id, + UUID: longUUID, + name: name, + className: className, + deprecatedClassName: deprecatedClassName, + since: ServiceSinceInformation.get(id), + + requiredCharacteristics: requiredCharacteristics, + optionalCharacteristics: optionalCharacteristics, + } + writtenServiceEntries[className] = className; + if (deprecatedClassName) { + writtenServiceEntries[deprecatedClassName] = className; + } + } 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.className; + if (generated.deprecatedClassName) { + writtenServiceEntries[generated.deprecatedClassName] = generated.className; + } +} + +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"); + } + 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 ((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: string][]): void { + const filePath = path.resolve(__dirname, "../" + className + ".ts"); + if (!fs.existsSync(filePath)) { + throw new Error("File '" + filePath + "' does not exists!"); + } + + 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) + .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 = ""; + if (key !== value) { + line += " /**\n"; + line += " * @deprecated Please use {@link " + className + "." + value + "}.\n"; + line += " */ \n"; + } + line += " public static " + key + ": typeof " + value + ";"; + 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..3513d0636 --- /dev/null +++ b/src/lib/definitions/generator-configuration.ts @@ -0,0 +1,155 @@ +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, + }], +]); + +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", { // same as Speaker service just a bit different + id: "og-speaker", + UUID: "00000113-0000-1000-8000-0026BB765291", + name: "Speaker", + className: "Speaker", + since: "10", + + requiredCharacteristics: ["mute"], + optionalCharacteristics: ["active", "volume"], + }] +]); + +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 462dc3d9b..000000000 --- a/src/lib/gen/HomeKit-Bridge.ts +++ /dev/null @@ -1,644 +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, { - format: Formats.TLV8, - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - minStep: 1, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.STRING, - perms: [Perms.PAIRED_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, { - format: Formats.STRING, - perms: [Perms.PAIRED_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, { - format: Formats.UINT16, - maxValue: 16, - minValue: 1, - minStep: 1, - perms: [Perms.PAIRED_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, { - format: Formats.TLV8, - perms: [Perms.PAIRED_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, { - format: Formats.TLV8, - perms: [Perms.PAIRED_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, { - format: Formats.STRING, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 7, - minValue: 1, - minStep: 1, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT16, - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 4, - minValue: 1, - minStep: 1, - perms: [Perms.PAIRED_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, { - format: Formats.BOOL, - perms: [Perms.PAIRED_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, { - format: Formats.TLV8, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.BOOL, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - perms: [Perms.PAIRED_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, { - format: Formats.BOOL, - perms: [Perms.PAIRED_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, { - format: Formats.UINT32, - perms: [Perms.PAIRED_WRITE, Perms.PAIRED_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, { - format: Formats.BOOL, - perms: [Perms.PAIRED_WRITE, Perms.PAIRED_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, { - format: Formats.BOOL, - perms: [Perms.PAIRED_WRITE, Perms.PAIRED_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, { - format: Formats.FLOAT, - perms: [Perms.PAIRED_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 0437dc290..000000000 --- a/src/lib/gen/HomeKit-DataStream.ts +++ /dev/null @@ -1,66 +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, { - 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, { - 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 e6565f05b..000000000 --- a/src/lib/gen/HomeKit-Remote.ts +++ /dev/null @@ -1,190 +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, { - 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, { - 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, { - 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, { - 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, { - 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 f43f5546a..000000000 --- a/src/lib/gen/HomeKit-TV.ts +++ /dev/null @@ -1,543 +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, { - format: Formats.UINT32, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.STRING, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.TLV8, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 5, - minValue: 0, - validValues: [0,1,2,3,4,5], - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0,1,2,3], - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 13, - minValue: 0, - validValues: [0,1,2,3,4,5,6,7,8,9,10,11,12,13], - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Perms.PAIRED_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, { - 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.PAIRED_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, { - format: Formats.UINT8, - maxValue: 10, - minValue: 0, - validValues: [0,1,2,3,4,5,6,7,8,9,10], - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 6, - minValue: 0, - validValues: [0,1,2,3,4,5], - perms: [Perms.PAIRED_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, { - format: Formats.UINT32, - minValue: 0, - minStep: 1, - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 3, - minValue: 0, - validValues: [0,1,2,3], - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 3, - minValue: 0, - validValues: [0,1,2,3], - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Perms.PAIRED_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 41f50adb8..000000000 --- a/src/lib/gen/HomeKit.ts +++ /dev/null @@ -1,5258 +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, { - 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, { - format: Formats.UINT32, - perms: [Perms.PAIRED_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, { - format: Formats.DATA, - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.BOOL, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.FLOAT, - maxValue: 1000, - minValue: 0, - minStep: 1, - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 5, - minValue: 0, - validValues: [0, 1, 2, 3, 4, 5], - perms: [Perms.PAIRED_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, { - format: Formats.BOOL, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - unit: Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Perms.PAIRED_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, { - format: Formats.INT, - unit: Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.PAIRED_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, { - format: Formats.FLOAT, - maxValue: 100000, - minValue: 0, - perms: [Perms.PAIRED_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, { - format: Formats.FLOAT, - maxValue: 100000, - minValue: 0, - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.PAIRED_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, { - format: Formats.FLOAT, - maxValue: 100, - minValue: 0, - perms: [Perms.PAIRED_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, { - format: Formats.FLOAT, - maxValue: 100, - minValue: 0, - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0, 1, 2], - perms: [Perms.PAIRED_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, { - format: Formats.UINT32, - maxValue: 500, - minValue: 140, - minStep: 1, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.PAIRED_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, { - format: Formats.FLOAT, - unit: Units.CELSIUS, - maxValue: 35, - minValue: 10, - minStep: 0.1, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0, 1, 2], - perms: [Perms.PAIRED_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, { - format: Formats.FLOAT, - unit: Units.LUX, - maxValue: 100000, - minValue: 0.0001, - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 4, - minValue: 0, - validValues: [0, 1, 2, 3, 4], - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0, 1, 2], - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 3, - minValue: 0, - validValues: [0, 1, 2, 3], - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0, 1, 2], - perms: [Perms.PAIRED_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, { - format: Formats.INT, - unit: Units.ARC_DEGREE, - maxValue: 90, - minValue: -90, - minStep: 1, - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 3, - minValue: 0, - validValues: [0, 1, 2, 3], - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - unit: Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Perms.PAIRED_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, { - format: Formats.FLOAT, - unit: Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0, 1, 2], - perms: [Perms.PAIRED_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, { - format: Formats.FLOAT, - unit: Units.CELSIUS, - maxValue: 100, - minValue: 0, - minStep: 0.1, - perms: [Perms.PAIRED_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, { - format: Formats.INT, - unit: Units.ARC_DEGREE, - maxValue: 90, - minValue: -90, - minStep: 1, - perms: [Perms.PAIRED_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, { - format: Formats.INT, - unit: Units.ARC_DEGREE, - maxValue: 90, - minValue: -90, - minStep: 1, - perms: [Perms.PAIRED_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, { - format: Formats.FLOAT, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.PAIRED_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, { - format: Formats.FLOAT, - maxValue: 100, - minValue: 0, - perms: [Perms.PAIRED_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, { - format: Formats.STRING, - perms: [Perms.PAIRED_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, { - format: Formats.STRING, - perms: [Perms.PAIRED_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, { - format: Formats.FLOAT, - unit: Units.CELSIUS, - maxValue: 25, - minValue: 0, - minStep: 0.1, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.BOOL, - perms: [Perms.PAIRED_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, { - format: Formats.FLOAT, - unit: Units.ARC_DEGREE, - maxValue: 360, - minValue: 0, - minStep: 1, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.BOOL, - perms: [Perms.PAIRED_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, { - format: Formats.BOOL, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.FLOAT, - unit: Units.ARC_DEGREE, - maxValue: 270, - minValue: 0, - minStep: 90, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.PAIRED_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, { - format: Formats.TLV8, - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 3, - minValue: 0, - validValues: [0, 1, 2, 3], - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 8, - minValue: 0, - validValues: [0, 1, 2, 3, 4, 5, 6, 7, 8], - perms: [Perms.PAIRED_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, { - format: Formats.UINT32, - unit: Units.SECONDS, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.TLV8, - perms: [Perms.PAIRED_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, { - format: Formats.STRING, - perms: [Perms.PAIRED_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, { - format: Formats.STRING, - perms: [Perms.PAIRED_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, { - format: Formats.BOOL, - perms: [Perms.PAIRED_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, { - format: Formats.BOOL, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.STRING, - perms: [Perms.PAIRED_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, { - format: Formats.BOOL, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.FLOAT, - maxValue: 1000, - minValue: 0, - minStep: 1, - perms: [Perms.PAIRED_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, { - format: Formats.BOOL, - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.PAIRED_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, { - format: Formats.BOOL, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.FLOAT, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.BOOL, - perms: [Perms.PAIRED_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, { - format: Formats.FLOAT, - maxValue: 1000, - minValue: 0, - minStep: 1, - perms: [Perms.PAIRED_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, { - 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 { - - 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 "Pairing Features" - */ - -export class PairingFeatures extends Characteristic { - - static readonly UUID: string = '0000004F-0000-1000-8000-0026BB765291'; - - constructor() { - super('Pairing Features', PairingFeatures.UUID, { - format: Formats.UINT8, - perms: [Perms.PAIRED_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, { - format: Formats.TLV8, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - 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, { - format: Formats.FLOAT, - maxValue: 1000, - minValue: 0, - minStep: 1, - perms: [Perms.PAIRED_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, { - format: Formats.FLOAT, - maxValue: 1000, - minValue: 0, - minStep: 1, - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0, 1, 2], - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0, 1, 2], - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0, 1, 2], - perms: [Perms.PAIRED_READ, Perms.NOTIFY] - }); - 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, { - format: Formats.FLOAT, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.FLOAT, - unit: Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT32, - maxValue: 3600, - minValue: 0, - minStep: 1, - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 1, - minStep: 1, - perms: [Perms.PAIRED_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, { - format: Formats.INT, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.FLOAT, - unit: Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.FLOAT, - unit: Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - minStep: 1, - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 4, - minValue: 0, - validValues: [0, 1, 2, 3, 4], - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 3, - minValue: 0, - validValues: [0, 1, 2, 3], - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - 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 { - - static readonly UUID: string = '00000030-0000-1000-8000-0026BB765291'; - - constructor() { - super('Serial Number', SerialNumber.UUID, { - format: Formats.STRING, - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 255, - minValue: 1, - minStep: 1, - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.PAIRED_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, { - format: Formats.UINT32, - maxValue: 3600, - minValue: 0, - minStep: 1, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.TLV8, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.PAIRED_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, { - format: Formats.BOOL, - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.PAIRED_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, { - format: Formats.TLV8, - perms: [Perms.PAIRED_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, { - format: Formats.FLOAT, - maxValue: 1000, - minValue: 0, - minStep: 1, - perms: [Perms.PAIRED_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, { - format: Formats.TLV8, - perms: [Perms.PAIRED_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, { - format: Formats.TLV8, - perms: [Perms.PAIRED_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, { - format: Formats.TLV8, - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0, 1, 2], - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0, 1, 2], - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 3, - minValue: 0, - validValues: [0, 1, 2, 3], - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.INT, - unit: Units.ARC_DEGREE, - maxValue: 90, - minValue: -90, - minStep: 1, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0, 1, 2], - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - unit: Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.FLOAT, - unit: Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.FLOAT, - unit: Units.CELSIUS, - maxValue: 38, - minValue: 10, - minStep: 0.1, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.INT, - unit: Units.ARC_DEGREE, - maxValue: 90, - minValue: -90, - minStep: 1, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.INT, - unit: Units.ARC_DEGREE, - maxValue: 90, - minValue: -90, - minStep: 1, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0, 1], - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 3, - minValue: 0, - validValues: [0, 1, 2, 3], - perms: [Perms.PAIRED_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, { - 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, { - format: Formats.FLOAT, - maxValue: 1000, - minValue: 0, - minStep: 1, - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - unit: Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.FLOAT, - maxValue: 100, - minValue: 0, - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.TLV8, - perms: [Perms.PAIRED_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, { - format: Formats.TLV8, - perms: [Perms.PAIRED_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, { - format: Formats.TLV8, - perms: [Perms.PAIRED_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, { - format: Formats.TLV8, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - } -} - -Characteristic.EventSnapshotsActive = EventSnapshotsActive; - -/** - * Characteristic "Diagonal Field Of View" - */ - -export class DiagonalFieldOfView extends Characteristic { - - static readonly UUID: string = '00000224-0000-1000-8000-0026BB765291'; - - constructor() { - super('Diagonal Field Of View', DiagonalFieldOfView.UUID, { - format: Formats.FLOAT, - unit: Units.ARC_DEGREE, - maxValue: 360, - minValue: 0, - minStep: 1, - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.BOOL, - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.TLV8, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.TLV8, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Perms.PAIRED_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, { - format: Formats.TLV8, - perms: [Perms.PAIRED_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, { - format: Formats.TLV8, - perms: [Perms.PAIRED_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, { - format: Formats.TLV8, - perms: [Perms.PAIRED_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, { - format: Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - format: Formats.TLV8, - perms: [Perms.PAIRED_READ, Perms.PAIRED_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, { - 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" - * @since iOS 13.4 - */ - -export class WakeConfiguration extends Characteristic { - - 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 "Supported Transfer Transport Configuration" - * @since iOS 13.4 - */ - -export class SupportedTransferTransportConfiguration extends Characteristic { - - 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 "Setup Transfer Transport" - * @since iOS 13.4 - */ - -export class SetupTransferTransport extends Characteristic { - - 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 "Activity Interval" - * @since iOS 14 - */ -export class ActivityInterval extends Characteristic { - - static readonly UUID: string = '0000023B-0000-1000-8000-0026BB765291'; - - constructor() { - super("Activity Interval", ActivityInterval.UUID, { - 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" - * @since iOS 14 - */ -export class CCAEnergyDetectThreshold extends Characteristic { - - 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 { - - 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 Transition Control" - * @since iOS 14 - */ -export class CharacteristicValueTransitionControl extends Characteristic { - - 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 "Supported Characteristic Value Transition Configuration" - * @since iOS 14 - */ -export class SupportedCharacteristicValueTransitionConfiguration extends Characteristic { - - 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 "Characteristic Value Active Transition Count" - * @since iOS 14 - */ -export class CharacteristicValueActiveTransitionCount extends Characteristic { - - 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 "Current Transport" - * @since iOS 14 - */ -export class CurrentTransport extends Characteristic { - - 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 "Data Stream HAP Transport" - * @since iOS 14 - */ -export class DataStreamHAPTransport extends Characteristic { - - 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 { - - static readonly UUID: string = '00000139-0000-1000-8000-0026BB765291'; - - constructor() { - super("Data Stream HAP Transport Interrupt", DataStreamHAPTransportInterrupt.UUID, { - format: Formats.TLV8, - perms: [Perms.PAIRED_READ, Perms.NOTIFY], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.DataStreamHAPTransportInterrupt = DataStreamHAPTransportInterrupt; - -/** - * Characteristic "Event Retransmission Maximum" - * @since iOS 14 - */ -export class EventRetransmissionMaximum extends Characteristic { - - 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 Transmission Counters" - * @since iOS 14 - */ -export class EventTransmissionCounters extends Characteristic { - - 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 "Heart Beat" - * @since iOS 14 - */ -export class HeartBeat extends Characteristic { - - 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 "MAC Retransmission Maximum" - * @since iOS 14 - */ -export class MACRetransmissionMaximum extends Characteristic { - - 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" - * @since iOS 14 - */ -export class MACTransmissionCounters extends Characteristic { - - 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 "Operating State Response" - * @since iOS 14 - */ -export class OperatingStateResponse extends Characteristic { - - static readonly UUID: string = '00000232-0000-1000-8000-0026BB765291'; - - constructor() { - super("Operating State Response", OperatingStateResponse.UUID, { - format: Formats.TLV8, - perms: [Perms.PAIRED_READ, Perms.NOTIFY], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.OperatingStateResponse = OperatingStateResponse; - -/** - * Characteristic "Ping" - * @since iOS 14 - */ -export class Ping extends Characteristic { - - 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 "Receiver Sensitivity" - * @since iOS 14 - */ -export class ReceiverSensitivity extends Characteristic { - - 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 "Received Signal Strength Indication" - * @since iOS 14 - */ -export class ReceivedSignalStrengthIndication extends Characteristic { - - 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 "Sleep Interval" - * @since iOS 14 - */ -export class SleepInterval extends Characteristic { - - static readonly UUID: string = '0000023A-0000-1000-8000-0026BB765291'; - - constructor() { - super("Sleep Interval", SleepInterval.UUID, { - 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" - * @since iOS 14 - */ -export class SignalToNoiseRatio extends Characteristic { - - static readonly UUID: string = '00000241-0000-1000-8000-0026BB765291'; - - constructor() { - super("Signal-to-noise Ration", SignalToNoiseRatio.UUID, { - format: Formats.INT, - perms: [Perms.PAIRED_READ], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.SignalToNoiseRatio = SignalToNoiseRatio; - -/** - * Characteristic "Supported Diagnostics Snapshot" - * @since iOS 14 - */ -export class SupportedDiagnosticsSnapshot extends Characteristic { - - 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 "Transmit Power" - * @since iOS 14 - */ -export class TransmitPower extends Characteristic { - - 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 "Transmit Power Maximum" - * @since iOS 14 - */ -export class TransmitPowerMaximum extends Characteristic { - - static readonly UUID: string = '00000243-0000-1000-8000-0026BB765291'; - - constructor() { - super("Transmit Power Maximum", TransmitPowerMaximum.UUID, { - format: Formats.INT, - perms: [Perms.PAIRED_READ], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.TransmitPowerMaximum = TransmitPowerMaximum; - -/** - * Characteristic "Video Analysis Active" - * @since iOS 14 - */ -export class VideoAnalysisActive extends Characteristic { - - static readonly UUID: string = '00000229-0000-1000-8000-0026BB765291'; - - constructor() { - super("Video Analysis Active", VideoAnalysisActive.UUID, { - format: Formats.UINT8, - perms: [Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.NOTIFY], - }); - this.value = this.getDefaultValue(); - } - -} - -Characteristic.VideoAnalysisActive = VideoAnalysisActive; - -/** - * Characteristic "Wi-Fi Capabilities" - * @since iOS 14 - */ -export class WiFiCapabilities extends Characteristic { - - 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 { - - static readonly UUID: string = '0000022D-0000-1000-8000-0026BB765291'; - - constructor() { - super("Wi-Fi Configuration Control", WiFiConfigurationControl.UUID, { - 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); - this.addCharacteristic(Characteristic.Model); - this.addCharacteristic(Characteristic.Name); - this.addCharacteristic(Characteristic.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.CharacteristicValueActiveTransitionCount); // Ambient Lightning - 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" - * @since iOS 13.4 - */ - -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" - * @since iOS 13.4 - */ - -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" - * @since iOS 13.4 - */ - -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" - * @since iOS 14 - */ -export class AccessoryRuntimeInformation extends Service { - - static readonly UUID: string = '00000239-0000-1000-8000-0026BB765291'; - - 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" - * @since iOS 14 - */ -export class Diagnostics extends Service { - - static readonly UUID: string = '00000237-0000-1000-8000-0026BB765291'; - - constructor(displayName?: string, subtype?: string) { - super(displayName, Diagnostics.UUID, subtype); - - // Require Characteristics - this.addCharacteristic(Characteristic.SupportedDiagnosticsSnapshot); - } - -} - -Service.Diagnostics = Diagnostics; - -/** - * Service "Wi-Fi Transport" - * @since iOS 14 - */ -export class WiFiTransport extends Service { - - static readonly UUID: string = '0000022A-0000-1000-8000-0026BB765291'; - - 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 c5dcd50dc..000000000 --- a/src/lib/gen/importAsClasses.ts +++ /dev/null @@ -1,210 +0,0 @@ -/// -import fs from 'fs'; -import path from 'path'; -import plist from 'simple-plist'; -import { 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. - */ -const plistPath = '/Applications/HomeKit Accessory Simulator.app/Contents/Frameworks/HAPAccessoryKit.framework/Versions/A/Resources/default.metadata.plist'; -const metadata = plist.readFileSync(plistPath); - -// begin writing the output file -const outputPath = path.join(__dirname, '..', '..', '..', 'src', 'lib', 'gen', 'HomeKitTypes.generated.ts'); -const 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 (let index in metadata.Characteristics) { - const characteristic = metadata.Characteristics[index]; - let 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 (let value in characteristic.Constraints.ValidValues) { - const name = characteristic.Constraints.ValidValues[value]; - - let 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: ["); - let sep = ""; - for (let i in characteristic.Properties) { - const 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 (let index in metadata.Services) { - const service = metadata.Services[index]; - const 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 (let index in service.RequiredCharacteristics) { - let characteristicUUID = service.RequiredCharacteristics[index]; - - // look up the classyName from the hash we built above - let 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 (let index in service.OptionalCharacteristics) { - let characteristicUUID = service.OptionalCharacteristics[index]; - - // look up the classyName from the hash we built above - let 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 (let key in Formats) { - // @ts-ignore - if (Formats[key as keyof typeof 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 (let key in Units) { - // @ts-ignore - if (Units[key as keyof typeof 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/tv/AccessControlManagement.ts b/src/lib/tv/AccessControlManagement.ts index f18402cb6..349c28eb2 100644 --- a/src/lib/tv/AccessControlManagement.ts +++ b/src/lib/tv/AccessControlManagement.ts @@ -1,13 +1,13 @@ import { EventEmitter } from "events"; -import { AccessControl } from "../gen/HomeKit"; -import { Service } from "../Service"; +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 { 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/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; + +} From 1b47a4c158799f4f4f01b24467753e2edf45dce9 Mon Sep 17 00:00:00 2001 From: Supereg Date: Sun, 18 Oct 2020 20:19:44 +0200 Subject: [PATCH 57/70] Adding debug output for current connection state every minute --- src/lib/util/eventedhttp.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/lib/util/eventedhttp.ts b/src/lib/util/eventedhttp.ts index b45959d4c..e007d26cb 100644 --- a/src/lib/util/eventedhttp.ts +++ b/src/lib/util/eventedhttp.ts @@ -104,6 +104,18 @@ export class EventedHTTPServer extends EventEmitter { constructor() { super(); this.tcpServer = net.createServer(); + const interval = setInterval(() => { + let connectionString = ""; + for (const connection of this.connections) { + if (connectionString) { + connectionString += ", "; + } else { + connectionString += connection.remoteAddress + ":" + connection.remotePort; + } + } + debug("Current " + this.connections.size + " hap connections open: " + connectionString); + }, 60000); + interval.unref(); } public listen(targetPort: number, hostname?: string): void { @@ -247,6 +259,7 @@ export class HAPConnection extends EventEmitter { 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 networkInterface: string; private readonly tcpSocket: Socket; @@ -277,6 +290,7 @@ export class HAPConnection extends EventEmitter { this.server = server; this.sessionID = uuid.generate(clientSocket.remoteAddress + ':' + clientSocket.remotePort); this.remoteAddress = clientSocket.remoteAddress!; // cache because it becomes undefined in 'onClientSocketClose' + this.remotePort = clientSocket.remotePort!; this.networkInterface = HAPConnection.getLocalNetworkInterface(clientSocket); // clientSocket is the socket connected to the actual iOS device From 75452d7edb963091455a629762f64fa02569061b Mon Sep 17 00:00:00 2001 From: Supereg Date: Sun, 18 Oct 2020 22:42:31 +0200 Subject: [PATCH 58/70] Add deprecated CameraControl service again --- src/lib/Service.ts | 5 ++ .../definitions/ServiceDefinitions.spec.ts | 22 +++++++ src/lib/definitions/ServiceDefinitions.ts | 29 +++++++++ src/lib/definitions/generate-definitions.ts | 60 ++++++++++++------- .../definitions/generator-configuration.ts | 15 ++++- 5 files changed, 108 insertions(+), 23 deletions(-) diff --git a/src/lib/Service.ts b/src/lib/Service.ts index 289b5e221..f33b8b01f 100644 --- a/src/lib/Service.ts +++ b/src/lib/Service.ts @@ -16,6 +16,7 @@ import { AirQualitySensor, AudioStreamManagement, Battery, + CameraControl, CameraOperatingMode, CameraRecordingManagement, CameraRTPStreamManagement, @@ -155,6 +156,10 @@ export class Service extends EventEmitter { * @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}. */ diff --git a/src/lib/definitions/ServiceDefinitions.spec.ts b/src/lib/definitions/ServiceDefinitions.spec.ts index 6b640e862..a5ecd5ae6 100644 --- a/src/lib/definitions/ServiceDefinitions.spec.ts +++ b/src/lib/definitions/ServiceDefinitions.spec.ts @@ -162,6 +162,28 @@ describe("ServiceDefinitions", () => { }); }); + 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(); diff --git a/src/lib/definitions/ServiceDefinitions.ts b/src/lib/definitions/ServiceDefinitions.ts index 942f96fd6..ddb472caf 100644 --- a/src/lib/definitions/ServiceDefinitions.ts +++ b/src/lib/definitions/ServiceDefinitions.ts @@ -168,6 +168,35 @@ export class Battery extends Service { 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" */ diff --git a/src/lib/definitions/generate-definitions.ts b/src/lib/definitions/generate-definitions.ts index a2b43c124..e95f368f5 100644 --- a/src/lib/definitions/generate-definitions.ts +++ b/src/lib/definitions/generate-definitions.ts @@ -111,6 +111,7 @@ export interface GeneratedCharacteristic { className: string, deprecatedClassName?: string; since?: string, + deprecatedNotice?: string; format: string, units?: string, @@ -133,6 +134,7 @@ export interface GeneratedService { className: string, deprecatedClassName?: string, since?: string, + deprecatedNotice?: string, requiredCharacteristics: string[]; optionalCharacteristics?: string[]; @@ -216,8 +218,8 @@ characteristicOutput.write("import { Characteristic, Formats, Perms, Units } fro * Characteristics */ -const generatedCharacteristics: Record = {}; -const writtenCharacteristicEntries: Record = {}; // Characteristic. = +const generatedCharacteristics: Record = {}; // indexed by id +const writtenCharacteristicEntries: Record = {}; // indexed by class name for (const [id, definition] of Object.entries(characteristics)) { try { @@ -263,7 +265,7 @@ for (const [id, definition] of Object.entries(characteristics)) { } } - generatedCharacteristics[id] = { + const generatedCharacteristic: GeneratedCharacteristic = { id: id, UUID: longUUID, name: name, @@ -284,9 +286,10 @@ for (const [id, definition] of Object.entries(characteristics)) { validBitMasks: validBitMasks, classAdditions: CharacteristicClassAdditions.get(id), }; - writtenCharacteristicEntries[className] = className; + generatedCharacteristics[id] = generatedCharacteristic; + writtenCharacteristicEntries[className] = generatedCharacteristic; if (deprecatedClassName) { - writtenCharacteristicEntries[deprecatedClassName] = className; + writtenCharacteristicEntries[deprecatedClassName] = generatedCharacteristic; } } catch (error) { throw new Error("Error thrown generating characteristic '" + id + "' (" + definition.DefaultDescription + "): " + error.message); @@ -295,9 +298,9 @@ for (const [id, definition] of Object.entries(characteristics)) { for (const [id, generated] of CharacteristicManualAdditions) { generatedCharacteristics[id] = generated; - writtenCharacteristicEntries[generated.className] = generated.className; + writtenCharacteristicEntries[generated.className] = generated; if (generated.deprecatedClassName) { - writtenCharacteristicEntries[generated.deprecatedClassName] = generated.className; + writtenCharacteristicEntries[generated.deprecatedClassName] = generated; } } @@ -309,6 +312,9 @@ for (const generated of Object.values(generatedCharacteristics) if (generated.since) { characteristicOutput.write(" * @since iOS " + generated.since + "\n"); } + if (generated.deprecatedNotice) { + characteristicOutput.write(" * @deprecated " + generated.deprecatedNotice + "\n"); + } characteristicOutput.write(" */\n"); @@ -386,8 +392,8 @@ serviceOutput.write("\n"); serviceOutput.write("import { Characteristic } from \"../Characteristic\";\n"); serviceOutput.write("import { Service } from \"../Service\";\n\n"); -const generatedServices: Record = {}; -const writtenServiceEntries: Record = {}; // Service. = +const generatedServices: Record = {}; // indexed by id +const writtenServiceEntries: Record = {}; // indexed by class name for (const [id, definition] of Object.entries(services)) { try { @@ -438,7 +444,7 @@ for (const [id, definition] of Object.entries(services)) { } } - generatedServices[id] = { + const generatedService: GeneratedService = { id: id, UUID: longUUID, name: name, @@ -448,10 +454,11 @@ for (const [id, definition] of Object.entries(services)) { requiredCharacteristics: requiredCharacteristics, optionalCharacteristics: optionalCharacteristics, - } - writtenServiceEntries[className] = className; + }; + generatedServices[id] = generatedService; + writtenServiceEntries[className] = generatedService; if (deprecatedClassName) { - writtenServiceEntries[deprecatedClassName] = className; + writtenServiceEntries[deprecatedClassName] = generatedService; } } catch (error) { throw new Error("Error thrown generating service '" + id + "' (" + definition.DefaultDescription + "): " + error.message); @@ -460,9 +467,9 @@ for (const [id, definition] of Object.entries(services)) { for (const [id, generated] of ServiceManualAdditions) { generatedServices[id] = generated; - writtenServiceEntries[generated.className] = generated.className; + writtenServiceEntries[generated.className] = generated; if (generated.deprecatedClassName) { - writtenServiceEntries[generated.deprecatedClassName] = generated.className; + writtenServiceEntries[generated.deprecatedClassName] = generated; } } @@ -474,6 +481,9 @@ for (const generated of Object.values(generatedServices) 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"); @@ -611,7 +621,7 @@ function checkWrittenVersion(filePath: string, parsingVersion: number): boolean return parsingVersion >= version; } -function rewriteProperties(className: string, properties: [key: string, value: string][]): void { +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 exists!"); @@ -664,7 +674,7 @@ function rewriteProperties(className: string, properties: [key: string, value: s const importSize = importEnd - importStart - 1; const newImports = properties - .filter(([key, value]) => key === value) + .filter(([key, value]) => key === value.className) .map(([key]) => " " + key + ","); lines.splice(importStart + 1, importSize, ...newImports); // remove current imports @@ -676,12 +686,20 @@ function rewriteProperties(className: string, properties: [key: string, value: s const amount = stopIndex - startIndex - 1; const newContentLines = properties.map(([key, value]) => { let line = ""; - if (key !== value) { + + 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 Please use {@link " + className + "." + value + "}.\n"; - line += " */ \n"; + line += " * @deprecated " + deprecatedNotice + "\n"; + line += " */\n"; } - line += " public static " + key + ": typeof " + value + ";"; + + line += " public static " + key + ": typeof " + value.className + ";"; return line; }); lines.splice(startIndex + 1, amount, ...newContentLines); // insert new lines diff --git a/src/lib/definitions/generator-configuration.ts b/src/lib/definitions/generator-configuration.ts index 3513d0636..897d542d5 100644 --- a/src/lib/definitions/generator-configuration.ts +++ b/src/lib/definitions/generator-configuration.ts @@ -105,7 +105,7 @@ export const ServiceCharacteristicConfigurationOverrides: Map = new Map([ - ["og-speaker", { // same as Speaker service just a bit different + ["og-speaker", { // the normal speaker is considered to be the "TelevisionSpeaker" id: "og-speaker", UUID: "00000113-0000-1000-8000-0026BB765291", name: "Speaker", @@ -114,7 +114,18 @@ export const ServiceManualAdditions: Map = new Map([ 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([ From 362a5ebc6a8170011274b67aad5641803ac3aa93 Mon Sep 17 00:00:00 2001 From: Supereg Date: Mon, 19 Oct 2020 00:12:03 +0200 Subject: [PATCH 59/70] Use the correct subclass when deserializing characteristics and services from disk --- src/lib/Characteristic.spec.ts | 30 +++++++++++++++++++++ src/lib/Characteristic.ts | 29 ++++++++++++++++---- src/lib/Service.spec.ts | 27 ++++++++++++++++++- src/lib/Service.ts | 23 +++++++++++++++- src/lib/definitions/generate-definitions.ts | 2 +- 5 files changed, 103 insertions(+), 8 deletions(-) diff --git a/src/lib/Characteristic.spec.ts b/src/lib/Characteristic.spec.ts index 89156aaa3..9475c31ac 100644 --- a/src/lib/Characteristic.spec.ts +++ b/src/lib/Characteristic.spec.ts @@ -472,6 +472,21 @@ 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', () => { @@ -511,5 +526,20 @@ describe('Characteristic', () => { expect(characteristic.props).toEqual(json.props); expect(characteristic.value).toEqual(json.value); }); + + 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 8314e4efa..6e79e86a0 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -222,6 +222,7 @@ import { } 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'; @@ -368,9 +369,11 @@ export type CharacteristicChange = { export interface SerializedCharacteristic { displayName: string, UUID: string, - props: CharacteristicProps, - value: Nullable, eventOnlyCharacteristic: boolean, + constructorName?: string, + + value: Nullable, + props: CharacteristicProps, } export const enum CharacteristicEventTypes { @@ -1635,12 +1638,18 @@ export class Characteristic extends EventEmitter { * @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), - value: characteristic.value, eventOnlyCharacteristic: characteristic.UUID === Characteristic.ProgrammableSwitchEvent.UUID, // support downgrades for now + constructorName: constructorName, + value: characteristic.value, + props: clone({}, characteristic.props), } }; @@ -1651,7 +1660,17 @@ export class Characteristic extends EventEmitter { * @internal used to recreate characteristic from disk */ static deserialize(json: SerializedCharacteristic): Characteristic { - const characteristic = new Characteristic(json.displayName, json.UUID, json.props); + 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; diff --git a/src/lib/Service.spec.ts b/src/lib/Service.spec.ts index cdc95575f..12acaa1fa 100644 --- a/src/lib/Service.spec.ts +++ b/src/lib/Service.spec.ts @@ -1,4 +1,4 @@ -import { Characteristic, Service, ServiceEventTypes, uuid } from '..'; +import { Characteristic, SerializedService, Service, ServiceEventTypes, uuid } from '..'; const createService = () => { return new Service('Test', uuid.generate('Foo'), 'subtype'); @@ -94,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', () => { @@ -160,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 f33b8b01f..f6c8ec094 100644 --- a/src/lib/Service.ts +++ b/src/lib/Service.ts @@ -86,10 +86,14 @@ import Timeout = NodeJS.Timeout; */ const MAX_CHARACTERISTICS = 100; +/** + * @internal + */ export interface SerializedService { displayName: string, UUID: string, subtype?: string, + constructorName?: string, hiddenService?: boolean, primaryService?: boolean, @@ -142,6 +146,7 @@ export declare interface Service { * work with these. */ 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 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=- @@ -667,11 +672,18 @@ export class Service extends EventEmitter { * @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, @@ -684,7 +696,16 @@ export class Service extends EventEmitter { * @internal */ static deserialize(json: SerializedService): Service { - const service = new Service(json.displayName, json.UUID, json.subtype); + 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/definitions/generate-definitions.ts b/src/lib/definitions/generate-definitions.ts index e95f368f5..449ab1c12 100644 --- a/src/lib/definitions/generate-definitions.ts +++ b/src/lib/definitions/generate-definitions.ts @@ -624,7 +624,7 @@ function checkWrittenVersion(filePath: string, parsingVersion: number): boolean 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 exists!"); + throw new Error("File '" + filePath + "' does not exist!"); } const file = fs.readFileSync(filePath, { encoding: "utf8"}); From 9b6a3ce68efcf56b772833ea9c777dd6ba7d66dd Mon Sep 17 00:00:00 2001 From: Supereg Date: Mon, 19 Oct 2020 00:29:20 +0200 Subject: [PATCH 60/70] Fixed connection debug output --- src/lib/util/eventedhttp.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/util/eventedhttp.ts b/src/lib/util/eventedhttp.ts index e007d26cb..c6026f38a 100644 --- a/src/lib/util/eventedhttp.ts +++ b/src/lib/util/eventedhttp.ts @@ -109,9 +109,8 @@ export class EventedHTTPServer extends EventEmitter { for (const connection of this.connections) { if (connectionString) { connectionString += ", "; - } else { - connectionString += connection.remoteAddress + ":" + connection.remotePort; } + connectionString += connection.remoteAddress + ":" + connection.remotePort; } debug("Current " + this.connections.size + " hap connections open: " + connectionString); }, 60000); From 581298f6e23c8630906ce75acd6ccd6b5743c521 Mon Sep 17 00:00:00 2001 From: Supereg Date: Mon, 19 Oct 2020 01:47:41 +0200 Subject: [PATCH 61/70] Don't set the value before calling the set handler --- src/lib/Characteristic.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts index 6e79e86a0..fa1db97f4 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -1140,10 +1140,6 @@ export class Characteristic extends EventEmitter { this.emit(CharacteristicEventTypes.CHANGE, { originator: connection, oldValue: oldValue, newValue: value, context: context }); return Promise.resolve(); } else { - // the executor of the promise is called on the next tick, thus we set the updated value immediately until the set - // event is executed. - this.value = value; - return new Promise((resolve, reject) => { try { this.emit(CharacteristicEventTypes.SET, value, once((status?: Error | HAPStatus | null, writeResponse?: Nullable) => { From 0a45bbff76e34ea098ca08fe7a566ec2acdb0657 Mon Sep 17 00:00:00 2001 From: Supereg Date: Mon, 19 Oct 2020 02:19:44 +0200 Subject: [PATCH 62/70] Removed notify perms from Version characteristic again. Additional Authorization should not be added by default. --- src/lib/definitions/CharacteristicDefinitions.ts | 6 +++--- src/lib/definitions/generate-definitions.ts | 4 ++++ src/lib/definitions/generator-configuration.ts | 10 ++++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/lib/definitions/CharacteristicDefinitions.ts b/src/lib/definitions/CharacteristicDefinitions.ts index acd03b6b1..148d6410c 100644 --- a/src/lib/definitions/CharacteristicDefinitions.ts +++ b/src/lib/definitions/CharacteristicDefinitions.ts @@ -1698,7 +1698,7 @@ export class LockTargetState extends Characteristic { constructor() { super("Lock Target State", LockTargetState.UUID, { format: Formats.UINT8, - perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.ADDITIONAL_AUTHORIZATION], + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], minValue: 0, maxValue: 1, minStep: 1, @@ -3576,7 +3576,7 @@ export class TargetDoorState extends Characteristic { constructor() { super("Target Door State", TargetDoorState.UUID, { format: Formats.UINT8, - perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE, Perms.ADDITIONAL_AUTHORIZATION], + perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE], minValue: 0, maxValue: 1, minStep: 1, @@ -4098,7 +4098,7 @@ export class Version extends Characteristic { constructor() { super("Version", Version.UUID, { format: Formats.STRING, - perms: [Perms.NOTIFY, Perms.PAIRED_READ], + perms: [Perms.PAIRED_READ], maxLen: 64, }); this.value = this.getDefaultValue(); diff --git a/src/lib/definitions/generate-definitions.ts b/src/lib/definitions/generate-definitions.ts index 449ab1c12..cb89a0861 100644 --- a/src/lib/definitions/generate-definitions.ts +++ b/src/lib/definitions/generate-definitions.ts @@ -598,6 +598,10 @@ 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); } diff --git a/src/lib/definitions/generator-configuration.ts b/src/lib/definitions/generator-configuration.ts index 897d542d5..ac3eb0ab7 100644 --- a/src/lib/definitions/generator-configuration.ts +++ b/src/lib/definitions/generator-configuration.ts @@ -73,6 +73,16 @@ export const CharacteristicManualAdditions: Map 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([ From bc24a234347050272c92609f8a0ee7f5150d1bb7 Mon Sep 17 00:00:00 2001 From: Supereg Date: Mon, 19 Oct 2020 15:02:05 +0200 Subject: [PATCH 63/70] Only fire characteristic change when value actually changed --- src/lib/Characteristic.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts index fa1db97f4..7cb65652f 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -974,7 +974,9 @@ export class Characteristic extends EventEmitter { callback(); } - this.emit(CharacteristicEventTypes.CHANGE, { originator: undefined, oldValue: oldValue, newValue: value, context: context }); + if (oldValue !== value || this.UUID === Characteristic.ProgrammableSwitchEvent.UUID) { + this.emit(CharacteristicEventTypes.CHANGE, { originator: undefined, oldValue: oldValue, newValue: value, context: context }); + } return this; // for chaining } @@ -1118,9 +1120,12 @@ export class Characteristic extends EventEmitter { console.warn(`[${this.displayName}] 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 }); + } } - this.emit(CharacteristicEventTypes.CHANGE, { originator: connection, oldValue: oldValue, newValue: value, context: context }); return this.value; } catch (error) { if (typeof error === "number") { @@ -1137,7 +1142,9 @@ export class Characteristic extends EventEmitter { if (this.listeners(CharacteristicEventTypes.SET).length === 0) { this.value = value; - this.emit(CharacteristicEventTypes.CHANGE, { originator: connection, oldValue: oldValue, newValue: value, context: context }); + 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) => { @@ -1168,9 +1175,11 @@ export class Characteristic extends EventEmitter { } this.value = value; resolve(); - } - this.emit(CharacteristicEventTypes.CHANGE, { originator: connection, oldValue: oldValue, newValue: value, context: context }); + if (oldValue !== value || this.UUID === Characteristic.ProgrammableSwitchEvent.UUID) { + this.emit(CharacteristicEventTypes.CHANGE, { originator: connection, oldValue: oldValue, newValue: value, context: context }); + } + } }), context, connection); } catch (error) { console.warn(`[${this.displayName}] Unhandled error thrown inside write handler for characteristic: ${error.stack}`); From 14a642435c7c43df51e575f3ffc6b9255e75eab4 Mon Sep 17 00:00:00 2001 From: Supereg Date: Tue, 20 Oct 2020 02:16:56 +0200 Subject: [PATCH 64/70] Adding unit tests for net-utils --- src/lib/util/net-utils.spec.ts | 107 +++++++++++++++++++++++++++++++++ src/lib/util/net-utils.ts | 6 +- 2 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 src/lib/util/net-utils.spec.ts 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 index 24fd4b126..c34fa3844 100644 --- a/src/lib/util/net-utils.ts +++ b/src/lib/util/net-utils.ts @@ -1,6 +1,6 @@ import os from "os"; -function findLoopbackAddress(): string { +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 @@ -39,8 +39,8 @@ function findLoopbackAddress(): string { } return address; } -const loopbackAddress = findLoopbackAddress(); // loopback addressed used for the internal http server (::1 or 127.0.0.1) +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; + return loopbackAddress ?? (loopbackAddress = findLoopbackAddress()); } From 8234e7b3cc9ae3d8895528866cf94ed6924e192c Mon Sep 17 00:00:00 2001 From: Supereg Date: Tue, 20 Oct 2020 02:21:16 +0200 Subject: [PATCH 65/70] Force http socket to connect to loopback address --- src/lib/util/eventedhttp.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/util/eventedhttp.ts b/src/lib/util/eventedhttp.ts index c6026f38a..8f1a32670 100644 --- a/src/lib/util/eventedhttp.ts +++ b/src/lib/util/eventedhttp.ts @@ -496,7 +496,7 @@ export class HAPConnection extends EventEmitter { this.internalHttpServer.on('close', this.onHttpServerClose.bind(this)); // now we can establish a connection to this running HTTP server for proxying data - this.httpSocket = net.createConnection(this.internalHttpServerPort, addressInfo.address); + 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)); From a7d2c5b1bda6e409a898717d89c71c0ed1beaabe Mon Sep 17 00:00:00 2001 From: Supereg Date: Tue, 20 Oct 2020 02:21:29 +0200 Subject: [PATCH 66/70] Correctly transform 0 and 1 to boolean values --- src/lib/Characteristic.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts index 7cb65652f..f34a8dd3f 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -1098,7 +1098,10 @@ export class Characteristic extends EventEmitter { return Promise.reject(HAPStatus.INVALID_VALUE_IN_REQUEST); } - if (isNumericFormat(this.props.format)) { + 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); } From 77a2042ef9872f6168f91c28be0d0a91fc94565d Mon Sep 17 00:00:00 2001 From: Supereg Date: Tue, 20 Oct 2020 10:22:01 +0200 Subject: [PATCH 67/70] Pass more characteristic warnings through the event system for homebridge --- src/lib/Accessory.ts | 102 +++++++++++++++++++------------------- src/lib/Characteristic.ts | 81 ++++++++++++++++++++---------- src/lib/Service.ts | 35 +++++++------ 3 files changed, 125 insertions(+), 93 deletions(-) diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index 6070426fc..10527b9c7 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -143,6 +143,8 @@ export const enum CharacteristicWarningType { TIMEOUT_WRITE = "timeout-write", SLOW_READ = "slow-read", TIMEOUT_READ = "timeout-read", + WARN_MESSAGE = "warn-message", + ERROR_MESSAGE = "error-message", } /** @@ -255,7 +257,7 @@ export const enum AccessoryEventTypes { PAIRED = "paired", UNPAIRED = "unpaired", - CHARACTERISTIC_WARNING = "characteristic-warning", + CHARACTERISTIC_WARNING = "characteristic-warning-v2", } export declare interface Accessory { @@ -268,7 +270,7 @@ export declare interface Accessory { on(event: "paired", listener: () => void): this; on(event: "unpaired", listener: () => void): this; - on(event: "characteristic-warning", listener: (type: CharacteristicWarningType, iid: number) => void): this; + on(event: "characteristic-warning-v2", listener: (characteristic: Characteristic, type: CharacteristicWarningType, message?: string) => void): this; emit(event: "identify", paired: boolean, callback: VoidCallback): boolean; @@ -280,7 +282,7 @@ export declare interface Accessory { emit(event: "paired"): boolean; emit(event: "unpaired"): boolean; - emit(event: "characteristic-warning", type: CharacteristicWarningType, iid: number): boolean; + emit(event: "characteristic-warning-v2", characteristic: Characteristic, type: CharacteristicWarningType, message: string): boolean; } /** @@ -372,7 +374,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' @@ -412,8 +414,7 @@ export class Accessory extends EventEmitter { this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, { service: service }); } - service.on(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, this.handleServiceConfigurationChangeEvent.bind(this, service)); - service.on(ServiceEventTypes.CHARACTERISTIC_CHANGE, this.handleCharacteristicChangeEvent.bind(this, this, service)); + this.setupServiceEventHandlers(service); return service; } @@ -421,9 +422,9 @@ export class Accessory extends EventEmitter { /** * @deprecated use {@link Service.setPrimaryService} directly */ - setPrimaryService = (service: Service) => { + public setPrimaryService(service: Service): void { service.setPrimaryService(); - }; + } public removeService(service: Service): void { const index = this.services.indexOf(service); @@ -452,18 +453,19 @@ export class Accessory extends EventEmitter { } } - getService = >(name: string | T) => { - for (let index in this.services) { - const service = this.services[index]; - - 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]; @@ -483,11 +485,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; @@ -529,7 +534,7 @@ export class Accessory extends EventEmitter { return accessory; } - addBridgedAccessories = (accessories: Accessory[]) => { + public addBridgedAccessories(accessories: Accessory[]): void { for (let index in accessories) { const accessory = accessories[index]; this.addBridgedAccessory(accessory, true); @@ -538,7 +543,7 @@ export class Accessory extends EventEmitter { this.enqueueConfigurationUpdate(); } - removeBridgedAccessory = (accessory: Accessory, deferUpdate: boolean) => { + public removeBridgedAccessory(accessory: Accessory, deferUpdate: boolean): void { if (accessory._isBridge) throw new Error("Cannot Bridge another Bridge!"); @@ -563,7 +568,7 @@ export class Accessory extends EventEmitter { } } - removeBridgedAccessories = (accessories: Accessory[]) => { + public removeBridgedAccessories(accessories: Accessory[]): void { for (let index in accessories) { const accessory = accessories[index]; this.removeBridgedAccessory(accessory, true); @@ -572,7 +577,7 @@ export class Accessory extends EventEmitter { this.enqueueConfigurationUpdate(); } - removeAllBridgedAccessories = () => { + public removeAllBridgedAccessories(): void { for (let i = this.bridgedAccessories.length - 1; i >= 0; i --) { this.removeBridgedAccessory(this.bridgedAccessories[i], true); } @@ -1027,7 +1032,7 @@ export class Accessory extends EventEmitter { if (info.setupID) { this._setupID = info.setupID; } else if (this._accessoryInfo.setupID === undefined || this._accessoryInfo.setupID === "") { - this._setupID = this._generateSetupID(); + this._setupID = Accessory._generateSetupID(); } else { this._setupID = this._accessoryInfo.setupID; } @@ -1286,12 +1291,10 @@ export class Accessory extends EventEmitter { const aid = parseInt(split[0]); const iid = parseInt(split[1]); - const accessory = this.getAccessoryByAID(aid); - let emitted = accessory?.emit(AccessoryEventTypes.CHARACTERISTIC_WARNING, CharacteristicWarningType.SLOW_READ, iid); - if (!emitted) { - const characteristic = accessory?.getCharacteristicByIID(iid); - console.warn(`The read handler for the characteristic '${characteristic?.displayName || iid}' on the accessory '${accessory?.displayName}' was slow to respond!`); - } + 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); } // after a total of 10s we do not longer wait for a request to appear and just return status code timeout @@ -1303,13 +1306,10 @@ export class Accessory extends EventEmitter { const aid = parseInt(split[0]); const iid = parseInt(split[1]); - const accessory = this.getAccessoryByAID(aid); - let emitted = accessory?.emit(AccessoryEventTypes.CHARACTERISTIC_WARNING, CharacteristicWarningType.TIMEOUT_READ, iid); - if (!emitted) { - const characteristic = accessory?.getCharacteristicByIID(iid); - console.error("The read handler for the characteristic '" + (characteristic?.displayName || iid) + "' on the accessory '" + accessory?.displayName + - "' didn't respond at all!. Please check that you properly call the callback!"); - } + 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); characteristics.push({ aid: aid, @@ -1455,12 +1455,10 @@ export class Accessory extends EventEmitter { const aid = parseInt(split[0]); const iid = parseInt(split[1]); - const accessory = this.getAccessoryByAID(aid); - let emitted = accessory?.emit(AccessoryEventTypes.CHARACTERISTIC_WARNING, CharacteristicWarningType.SLOW_WRITE, iid); - if (!emitted) { - const characteristic = accessory?.getCharacteristicByIID(iid); - console.warn(`The write handler for the characteristic '${characteristic?.displayName || iid}' on the accessory '${accessory?.displayName}' was slow to respond!`); - } + 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); } // after a total of 10s we do not longer wait for a request to appear and just return status code timeout @@ -1472,13 +1470,10 @@ export class Accessory extends EventEmitter { const aid = parseInt(split[0]); const iid = parseInt(split[1]); - const accessory = this.getAccessoryByAID(aid); - let emitted = accessory?.emit(AccessoryEventTypes.CHARACTERISTIC_WARNING, CharacteristicWarningType.TIMEOUT_WRITE, iid); - if (!emitted) { - const characteristic = accessory?.getCharacteristicByIID(iid); - console.error("The write handler for the characteristic '" + (characteristic?.displayName || iid) + "' on the accessory '" + accessory?.displayName + - "' didn't respond at all!. Please check that you properly call the callback!"); - } + 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); characteristics.push({ aid: aid, @@ -1727,15 +1722,18 @@ export class Accessory extends EventEmitter { } } - _setupService = (service: Service) => { + 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[]) => { + private _sideloadServices(targetServices: Service[]): void { for (let index in targetServices) { const target = targetServices[index]; - this._setupService(target); + this.setupServiceEventHandlers(target); } this.services = targetServices.slice(); @@ -1752,7 +1750,7 @@ export class Accessory extends EventEmitter { }); } - _generateSetupID = () => { + private static _generateSetupID(): string { const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; const bytes = crypto.randomBytes(4); let setupID = ''; diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts index f34a8dd3f..4905b3de5 100644 --- a/src/lib/Characteristic.ts +++ b/src/lib/Characteristic.ts @@ -3,6 +3,7 @@ import createDebug from "debug"; import { EventEmitter } from "events"; import { CharacteristicJsonObject } from "../internal-types"; import { CharacteristicValue, Nullable, VoidCallback, } from '../types'; +import { CharacteristicWarningType } from "./Accessory"; import { AccessControlLevel, AccessoryFlags, @@ -404,6 +405,10 @@ export const enum CharacteristicEventTypes { * @internal */ UNSUBSCRIBE = "unsubscribe", + /** + * @internal + */ + CHARACTERISTIC_WARNING = "characteristic-warning", } export type CharacteristicGetCallback = (status?: HAPStatus | null | Error, value?: Nullable) => void; @@ -426,6 +431,10 @@ export declare interface Characteristic { * @internal */ on(event: "unsubscribe", listener: VoidCallback): this; + /** + * @internal + */ + on(event: "characteristic-warning", listener: (type: CharacteristicWarningType, message: string) => void): this; /** * @internal @@ -447,6 +456,10 @@ export declare interface Characteristic { * @internal */ emit(event: "unsubscribe"): boolean; + /** + * @internal + */ + emit(event: "characteristic-warning", type: CharacteristicWarningType, message: string): boolean; } @@ -752,7 +765,7 @@ export class Characteristic extends EventEmitter { */ public onGet(handler: CharacteristicGetHandler) { if (typeof handler !== 'function' || handler.length !== 0) { - console.warn(`[${this.displayName}] .onGet handler must be a function with exactly zero input arguments.`); + this.characteristicWarning(`.onGet handler must be a function with exactly zero input arguments.`); return this; } this.getHandler = handler; @@ -775,7 +788,7 @@ export class Characteristic extends EventEmitter { */ public onSet(handler: CharacteristicSetHandler) { if (typeof handler !== 'function' || handler.length !== 1) { - console.warn(`[${this.displayName}] .onSet handler must be a function with exactly one input argument.`); + this.characteristicWarning(`.onSet handler must be a function with exactly one input argument.`); return this; } this.setHandler = handler; @@ -819,7 +832,7 @@ export class Characteristic extends EventEmitter { if (props.maxLen !== undefined) { if (props.maxLen != null) { if (props.maxLen > 256) { - console.warn(""); + this.characteristicWarning("setProps: string maxLen cannot be bigger than 256!"); props.maxLen = 256; } this.props.maxLen = props.maxLen; @@ -1000,7 +1013,7 @@ export class Characteristic extends EventEmitter { if (this.getHandler) { if (this.listeners(CharacteristicEventTypes.GET).length > 0) { - console.warn(`[${this.displayName}] Ignoring on('get') handler as onGet handler was defined instead.`); + this.characteristicWarning(`Ignoring on('get') handler as onGet handler was defined instead.`); } try { @@ -1010,7 +1023,7 @@ export class Characteristic extends EventEmitter { try { value = this.validateUserInput(value); } catch (error) { - console.warn(`[${this.displayName}] An illegal value was supplied by the read handler for characteristic: ${error.message}`); + 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) } @@ -1027,7 +1040,7 @@ export class Characteristic extends EventEmitter { } else if (error instanceof HapStatusError) { this.status = error.hapStatus; } else { - console.warn(`[${this.displayName}] Unhandled error thrown inside read handler for characteristic: ${error.stack}`); + this.characteristicWarning(`Unhandled error thrown inside read handler for characteristic: ${error.stack}`); this.status = HAPStatus.SERVICE_COMMUNICATION_FAILURE; } throw this.status; @@ -1071,7 +1084,7 @@ export class Characteristic extends EventEmitter { } }), context, connection); } catch (error) { - console.warn(`[${this.displayName}] Unhandled error thrown inside read handler for characteristic: ${error.stack}`); + this.characteristicWarning(`Unhandled error thrown inside read handler for characteristic: ${error.stack}`); this.status = HAPStatus.SERVICE_COMMUNICATION_FAILURE; reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE); } @@ -1109,7 +1122,7 @@ export class Characteristic extends EventEmitter { if (this.setHandler) { if (this.listeners(CharacteristicEventTypes.SET).length > 0) { - console.warn(`[${this.displayName}] Ignoring on('set') handler as onSet handler was defined instead.`); + this.characteristicWarning(`Ignoring on('set') handler as onSet handler was defined instead.`); } try { @@ -1120,7 +1133,7 @@ export class Characteristic extends EventEmitter { this.value = writeResponse; } else { if (writeResponse != null) { - console.warn(`[${this.displayName}] SET handler returned write response value, though the characteristic doesn't support write response!`); + this.characteristicWarning(`SET handler returned write response value, though the characteristic doesn't support write response!`); } this.value = value; @@ -1136,7 +1149,7 @@ export class Characteristic extends EventEmitter { } else if (error instanceof HapStatusError) { this.status = error.hapStatus; } else { - console.warn(`[${this.displayName}] Unhandled error thrown inside write handler for characteristic: ${error.stack}`); + this.characteristicWarning(`Unhandled error thrown inside write handler for characteristic: ${error.stack}`); this.status = HAPStatus.SERVICE_COMMUNICATION_FAILURE; } throw this.status; @@ -1174,7 +1187,7 @@ export class Characteristic extends EventEmitter { resolve(writeResponse); } else { if (writeResponse != null) { - console.warn(`[${this.displayName}] SET handler returned write response value, though the characteristic doesn't support write response!`); + this.characteristicWarning(`SET handler returned write response value, though the characteristic doesn't support write response!`); } this.value = value; resolve(); @@ -1185,7 +1198,7 @@ export class Characteristic extends EventEmitter { } }), context, connection); } catch (error) { - console.warn(`[${this.displayName}] Unhandled error thrown inside write handler for characteristic: ${error.stack}`); + this.characteristicWarning(`Unhandled error thrown inside write handler for characteristic: ${error.stack}`); this.status = HAPStatus.SERVICE_COMMUNICATION_FAILURE; reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE); } @@ -1404,11 +1417,11 @@ export class Characteristic extends EventEmitter { */ private validateUserInput(value?: Nullable): Nullable { if (value === undefined) { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: undefined! This might throw errors in the future!`); + 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 - console.error(new Error(`[${this.displayName}] characteristic must have a non null value otherwise HomeKit will reject this accessory. Ignoring new value.`).stack); + 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 } @@ -1436,7 +1449,7 @@ export class Characteristic extends EventEmitter { if (typeof value === "boolean") { value = value? 1: 0; } if (typeof value === "string") { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: string instead of number. Supplying illegal values will throw errors in the future!`); + 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); @@ -1450,7 +1463,7 @@ export class Characteristic extends EventEmitter { if (typeof value === "boolean") { value = value? 1: 0; } if (typeof value === "string") { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: string instead of float. Supplying illegal values will throw errors in the future!`); + 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); @@ -1468,7 +1481,7 @@ export class Characteristic extends EventEmitter { if (typeof value === "boolean") { value = value? 1: 0; } if (typeof value === "string") { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: string instead of number. Supplying illegal values will throw errors in the future!`); + 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); @@ -1482,7 +1495,7 @@ export class Characteristic extends EventEmitter { if (typeof value === "boolean") { value = value? 1: 0; } if (typeof value === "string") { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: string instead of number. Supplying illegal values will throw errors in the future!`); + 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); @@ -1496,7 +1509,7 @@ export class Characteristic extends EventEmitter { if (typeof value === "boolean") { value = value? 1: 0; } if (typeof value === "string") { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: string instead of number. Supplying illegal values will throw errors in the future!`); + 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); @@ -1510,7 +1523,7 @@ export class Characteristic extends EventEmitter { if (typeof value === "boolean") { value = value? 1: 0; } if (typeof value === "string") { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: string instead of number. Supplying illegal values will throw errors in the future!`); + 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); @@ -1522,20 +1535,20 @@ export class Characteristic extends EventEmitter { } case Formats.STRING: { if (typeof value === "number") { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: number instead of string. Supplying illegal values will throw errors in the future!`); + 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 - console.error(new Error(`[${this.displayName}] characteristic must have a length of more than 1 character otherwise HomeKit will reject this accessory. Ignoring new value.`).stack); + 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) { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: string '${value}' exceeded max length of ${maxLength}.`); + this.characteristicWarning(`characteristic was supplied illegal value: string '${value}' exceeded max length of ${maxLength}.`); value = value.substring(0, maxLength); } @@ -1557,11 +1570,11 @@ export class Characteristic extends EventEmitter { if (typeof value === "number") { if (numericMin != null && value < numericMin) { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: number ${value} exceeded minimum of ${numericMin}.`); + this.characteristicWarning(`characteristic was supplied illegal value: number ${value} exceeded minimum of ${numericMin}.`); value = numericMin; } if (numericMax != null && value > numericMax) { - console.warn(`[${this.displayName}] characteristic was supplied illegal value: number ${value} exceeded maximum of ${numericMax}.`); + this.characteristicWarning(`characteristic was supplied illegal value: number ${value} exceeded maximum of ${numericMax}.`); value = numericMax; } @@ -1571,10 +1584,10 @@ export class Characteristic extends EventEmitter { if (this.props.validValueRanges && this.props.validValueRanges.length === 2) { if (value < this.props.validValueRanges[0]) { - console.warn(`[${this.displayName}] 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!`); + 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]) { - console.warn(`[${this.displayName}] 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!`); + 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]; } } @@ -1593,6 +1606,20 @@ export class Characteristic extends EventEmitter { this.iid = identifierCache.getIID(accessoryName, serviceUUID, serviceSubtype, this.UUID); } + /** + * @internal + */ + 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}`); + } + } + } + /** * Returns a JSON representation of this characteristic suitable for delivering to HAP clients. * @internal used to generate response to /accessories query diff --git a/src/lib/Service.ts b/src/lib/Service.ts index f6c8ec094..e4523f4c7 100644 --- a/src/lib/Service.ts +++ b/src/lib/Service.ts @@ -2,6 +2,7 @@ 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, @@ -115,14 +116,17 @@ export type EventService = ServiceEventTypes.CHARACTERISTIC_CHANGE | ServiceEven export const enum ServiceEventTypes { CHARACTERISTIC_CHANGE = "characteristic-change", SERVICE_CONFIGURATION_CHANGE = "service-configurationChange", + CHARACTERISTIC_WARNING = "characteristic-warning", } 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; emit(event: "characteristic-change", change: ServiceCharacteristicChange): boolean; emit(event: "service-configurationChange"): boolean; + emit(event: "characteristic-warning", characteristic: Characteristic, type: CharacteristicWarningType, message: string): boolean; } /** @@ -316,10 +320,7 @@ 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) => { - this.emit(ServiceEventTypes.CHARACTERISTIC_CHANGE, { ...change, characteristic: characteristic }); - }); + this.setupCharacteristicEventHandlers(characteristic); this.characteristics.push(characteristic); @@ -425,11 +426,14 @@ 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; } } @@ -580,15 +584,15 @@ export class Service extends EventEmitter { const missingCharacteristics: Set = new Set(); let timeout: Timeout | undefined = setTimeout(() => { for (const characteristic of missingCharacteristics) { - console.warn(`The read handler for the characteristic '${characteristic.displayName}' was slow to respond!`); + characteristic.characteristicWarning(`The read handler for the characteristic '${characteristic.displayName}' was slow to respond!`, CharacteristicWarningType.SLOW_READ); } timeout = setTimeout(() => { timeout = undefined; for (const characteristic of missingCharacteristics) { - console.error("The read handler for the characteristic '" + characteristic?.displayName + "' didn't respond at all!. " + - "Please check that you properly call the callback!"); + 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 } @@ -649,20 +653,23 @@ export class Service extends EventEmitter { /** * @internal */ - _setupCharacteristic(characteristic: Characteristic): void { + private setupCharacteristicEventHandlers(characteristic: Characteristic): void { // listen for changes in characteristics and bubble them up characteristic.on(CharacteristicEventTypes.CHANGE, (change: CharacteristicChange) => { this.emit(ServiceEventTypes.CHARACTERISTIC_CHANGE, { ...change, characteristic: characteristic }); }); + + characteristic.on(CharacteristicEventTypes.CHARACTERISTIC_WARNING, (type, message) => { + this.emit(ServiceEventTypes.CHARACTERISTIC_WARNING, characteristic, type, message); + }); } /** * @internal */ - _sideloadCharacteristics(targetCharacteristics: Characteristic[]): void { - for (let index in targetCharacteristics) { - const target = targetCharacteristics[index]; - this._setupCharacteristic(target); + private _sideloadCharacteristics(targetCharacteristics: Characteristic[]): void { + for (const target of targetCharacteristics) { + this.setupCharacteristicEventHandlers(target); } this.characteristics = targetCharacteristics.slice(); From 80ef3344a4cd1f36080203b0f77aa19e1f5ce082 Mon Sep 17 00:00:00 2001 From: Supereg Date: Tue, 20 Oct 2020 10:29:11 +0200 Subject: [PATCH 68/70] Fixed accessory characteristic-warning event signature --- src/lib/Accessory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts index 10527b9c7..195b69c80 100644 --- a/src/lib/Accessory.ts +++ b/src/lib/Accessory.ts @@ -270,7 +270,7 @@ export declare interface Accessory { 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; + on(event: "characteristic-warning-v2", listener: (characteristic: Characteristic, type: CharacteristicWarningType, message: string) => void): this; emit(event: "identify", paired: boolean, callback: VoidCallback): boolean; From 46c6498d5eb2f990ad4bbf8e6851b7a28b102da5 Mon Sep 17 00:00:00 2001 From: Supereg Date: Tue, 20 Oct 2020 13:34:34 +0200 Subject: [PATCH 69/70] Adding idle timeout to hap connections --- src/lib/util/eventedhttp.ts | 49 ++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/src/lib/util/eventedhttp.ts b/src/lib/util/eventedhttp.ts index 8f1a32670..99d352f46 100644 --- a/src/lib/util/eventedhttp.ts +++ b/src/lib/util/eventedhttp.ts @@ -89,6 +89,9 @@ export declare interface EventedHTTPServer { */ export class EventedHTTPServer extends EventEmitter { + private static readonly CONNECTION_TIMEOUT_LIMIT = 8; // if we have more (or equal) connections we start the timeout TODO raise the limit once tested + private static readonly MAX_CONNECTION_IDLE = 60000; // 60 seconds + private readonly tcpServer: net.Server; /** @@ -100,11 +103,12 @@ export class EventedHTTPServer extends EventEmitter { * So there can be multiple sessions open for a single username (multiple devices connected to the same Apple ID). */ private readonly connectionsByUsername: Map = new Map(); + private connectionIdleTimeout?: Timeout; constructor() { super(); this.tcpServer = net.createServer(); - const interval = setInterval(() => { + const interval = setInterval(() => { // TODO to be removed let connectionString = ""; for (const connection of this.connections) { if (connectionString) { @@ -117,6 +121,34 @@ export class EventedHTTPServer extends EventEmitter { interval.unref(); } + private scheduleNextConnectionIdleTimeout(): void { + this.connectionIdleTimeout = undefined; + + if (!this.tcpServer.listening) { + return; + } + + debug("Running idle timeout timer..."); + + const currentTime = new Date().getTime(); + let nextTimeout: number = -1; + + for (const connection of this.connections) { + const timeDelta = currentTime - connection.lastSocketOperation; + + if (timeDelta >= EventedHTTPServer.MAX_CONNECTION_IDLE) { + debug("[%s] Closing connection as it was inactive for " + timeDelta + "ms"); + connection.close(); + } else { + nextTimeout = Math.max(nextTimeout, EventedHTTPServer.MAX_CONNECTION_IDLE - timeDelta); + } + } + + if (this.connections.size >= EventedHTTPServer.CONNECTION_TIMEOUT_LIMIT) { + this.connectionIdleTimeout = setTimeout(this.scheduleNextConnectionIdleTimeout.bind(this), nextTimeout); + } + } + 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 @@ -173,6 +205,10 @@ export class EventedHTTPServer extends EventEmitter { debug("[%s] New connection from client on interface %s", connection.remoteAddress, connection.networkInterface); this.emit(EventedHTTPServerEvent.CONNECTION_OPENED, connection); + + if (this.connections.size >= EventedHTTPServer.CONNECTION_TIMEOUT_LIMIT && !this.connectionIdleTimeout) { + this.scheduleNextConnectionIdleTimeout(); + } } private handleConnectionAuthenticated(connection: HAPConnection, username: HAPUsername): void { @@ -266,6 +302,8 @@ export class HAPConnection extends EventEmitter { 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) @@ -443,7 +481,7 @@ export class HAPConnection extends EventEmitter { // 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.tcpSocket.write(this.encrypt(buffer), this.handleTCPSocketWriteFulfilled.bind(this)); } } @@ -528,6 +566,7 @@ export class HAPConnection extends EventEmitter { } this.handlingRequest = true; // reverted to false once response was sent out + this.lastSocketOperation = new Date().getTime(); try { data = this.decrypt(data); @@ -569,7 +608,7 @@ export class HAPConnection extends EventEmitter { */ private handleHttpServerResponse(data: Buffer): void { data = this.encrypt(data); - this.tcpSocket.write(data); + this.tcpSocket.write(data, this.handleTCPSocketWriteFulfilled.bind(this)); debug("[%s] HTTP Response is finished", this.remoteAddress); this.handlingRequest = false; @@ -582,6 +621,10 @@ export class HAPConnection extends EventEmitter { } } + private handleTCPSocketWriteFulfilled(): void { + this.lastSocketOperation = new Date().getTime(); + } + private onTCPSocketError(err: Error): void { debug("[%s] Client connection error: %s", this.remoteAddress, err.message); // onTCPSocketClose will be called next From 977d5adeda824d8a353eabb64ff3165babb86a4c Mon Sep 17 00:00:00 2001 From: Supereg Date: Tue, 20 Oct 2020 13:54:59 +0200 Subject: [PATCH 70/70] Adjust idle timeout configuration --- src/lib/util/eventedhttp.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/util/eventedhttp.ts b/src/lib/util/eventedhttp.ts index 99d352f46..caac8ab89 100644 --- a/src/lib/util/eventedhttp.ts +++ b/src/lib/util/eventedhttp.ts @@ -89,8 +89,8 @@ export declare interface EventedHTTPServer { */ export class EventedHTTPServer extends EventEmitter { - private static readonly CONNECTION_TIMEOUT_LIMIT = 8; // if we have more (or equal) connections we start the timeout TODO raise the limit once tested - private static readonly MAX_CONNECTION_IDLE = 60000; // 60 seconds + 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; @@ -136,11 +136,11 @@ export class EventedHTTPServer extends EventEmitter { for (const connection of this.connections) { const timeDelta = currentTime - connection.lastSocketOperation; - if (timeDelta >= EventedHTTPServer.MAX_CONNECTION_IDLE) { + 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 - timeDelta); + nextTimeout = Math.max(nextTimeout, EventedHTTPServer.MAX_CONNECTION_IDLE_TIME - timeDelta); } }