diff --git a/.gitignore b/.gitignore index 0dd44327c..b62631824 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ persist node_modules .DS_Store +.idea +dist +coverage diff --git a/README.md b/README.md index 118dc8611..8a140a112 100644 --- a/README.md +++ b/README.md @@ -11,24 +11,24 @@ The implementation may not 100% follow the HAP MFi Specification since the MFi p Remember to run `npm install` before actually running the server. -Users can define their own accessories in: accessories/[name]_accessory.js files, where [name] is a short description of the accessory. All defined accessories get loaded on server start. You can define accessories using an object literal notation (see [Fan_accessory.js](accessories/Fan_accessory.js) for an example) or you can use the API (see below). +Users can define their own accessories in: accessories/[name]_accessory.ts files, where [name] is a short description of the accessory. All defined accessories get loaded on server start. You can define accessories using an object literal notation (see [Fan_accessory.ts](src/accessories/Fan_accessory.ts) for an example) or you can use the API (see below). You can use the following command to start the HAP Server in Bridged mode: ```sh -node BridgedCore.js +ts-node BridgedCore.ts ``` Or, if you wish to host each Accessory as an independent HomeKit device: ```sh -node Core.js +ts-node Core.ts ``` The HAP-NodeJS library uses the [debug](https://github.com/visionmedia/debug) library for log output. You can print some or all of the logs by setting the `DEBUG` environment variable. For instance, to see all debug logs while running the server: ```sh -DEBUG=* node BridgedCore.js +DEBUG=* ts-node BridgedCore.ts ``` HOMEKIT PROTOCOL @@ -39,16 +39,16 @@ Hint: the Homekit Application Protocol (HAP) allows that you can pair a Homekit API === -HAP-NodeJS provides a set of classes you can use to construct Accessories programatically. For an example implementation, see [Lock_accessory.js](accessories/Lock_accessory.js). +HAP-NodeJS provides a set of classes you can use to construct Accessories programatically. For an example implementation, see [Lock_accessory.ts](src/accessories/Lock_accessory.ts). The key classes intended for use by API consumers are: - * [Accessory](lib/Accessory.js): Represents a HomeKit device that can be published on your local network. - * [Bridge](lib/Bridge.js): A kind of Accessory that can host other Accessories "behind" it while only publishing a single device. - * [Service](lib/Service.js): Represents a set of grouped values necessary to provide a logical function. Most of the time, when you think of a supported HomeKit device like "Thermostat" or "Door Lock", you're actualy thinking of a Service. Accessories can expose multiple services. - * [Characteristic](lib/Characteristic.js): Represents a particular typed variable assigned to a Service, for instance the `LockMechanism` Service contains a `CurrentDoorState` Characteristic describing whether the door is currently locked. + * [Accessory](src/lib/Accessory.ts): Represents a HomeKit device that can be published on your local network. + * [Bridge](src/lib/Bridge.ts): A kind of Accessory that can host other Accessories "behind" it while only publishing a single device. + * [Service](src/lib/Service.ts): Represents a set of grouped values necessary to provide a logical function. Most of the time, when you think of a supported HomeKit device like "Thermostat" or "Door Lock", you're actualy thinking of a Service. Accessories can expose multiple services. + * [Characteristic](src/lib/Characteristic.ts): Represents a particular typed variable assigned to a Service, for instance the `LockMechanism` Service contains a `CurrentDoorState` Characteristic describing whether the door is currently locked. -All known built-in Service and Characteristic types that HomeKit supports are exposed as a separate subclass in [HomeKitTypes](lib/gen/HomeKitTypes.js). +All known built-in Service and Characteristic types that HomeKit supports are exposed as a separate subclass in [HomeKitTypes](src/lib/gen/HomeKit.ts). See each of the corresponding class files for more explanation and notes. diff --git a/__mocks__/bonjour-hap.ts b/__mocks__/bonjour-hap.ts new file mode 100644 index 000000000..f0e33f3f3 --- /dev/null +++ b/__mocks__/bonjour-hap.ts @@ -0,0 +1,16 @@ +class Advertisement { + updateTxt = jest.fn(); + stop = jest.fn(); + destroy = jest.fn(); +} + +class BonjourService { + publish = jest.fn(() => { + return new Advertisement(); + }); + destroy = jest.fn(); +} + +export default (opts: any) => { + return new BonjourService(); +} diff --git a/__mocks__/node-persist.ts b/__mocks__/node-persist.ts new file mode 100644 index 000000000..5f325d303 --- /dev/null +++ b/__mocks__/node-persist.ts @@ -0,0 +1,8 @@ +class Storage { + getItem = jest.fn(); + setItemSync = jest.fn(); + persistSync = jest.fn(); + removeItemSync = jest.fn(); +} + +export default new Storage(); diff --git a/accessories/types.js b/accessories/types.js deleted file mode 100644 index dca39c8e5..000000000 --- a/accessories/types.js +++ /dev/null @@ -1,92 +0,0 @@ -var exports = module.exports = {}; - -//HomeKit Types UUID's - -var stPre = "000000"; -var stPost = "-0000-1000-8000-0026BB765291"; - - -//HomeKitTransportCategoryTypes -exports.OTHER_TCTYPE = 1; -exports.FAN_TCTYPE = 3; -exports.GARAGE_DOOR_OPENER_TCTYPE = 4; -exports.LIGHTBULB_TCTYPE = 5; -exports.DOOR_LOCK_TCTYPE = 6; -exports.OUTLET_TCTYPE = 7; -exports.SWITCH_TCTYPE = 8; -exports.THERMOSTAT_TCTYPE = 9; -exports.SENSOR_TCTYPE = 10; -exports.ALARM_SYSTEM_TCTYPE = 11; -exports.DOOR_TCTYPE = 12; -exports.WINDOW_TCTYPE = 13; -exports.WINDOW_COVERING_TCTYPE = 14; -exports.PROGRAMMABLE_SWITCH_TCTYPE = 15; - -//HomeKitServiceTypes - -exports.LIGHTBULB_STYPE = stPre + "43" + stPost; -exports.SWITCH_STYPE = stPre + "49" + stPost; -exports.THERMOSTAT_STYPE = stPre + "4A" + stPost; -exports.GARAGE_DOOR_OPENER_STYPE = stPre + "41" + stPost; -exports.ACCESSORY_INFORMATION_STYPE = stPre + "3E" + stPost; -exports.FAN_STYPE = stPre + "40" + stPost; -exports.OUTLET_STYPE = stPre + "47" + stPost; -exports.LOCK_MECHANISM_STYPE = stPre + "45" + stPost; -exports.LOCK_MANAGEMENT_STYPE = stPre + "44" + stPost; -exports.ALARM_STYPE = stPre + "7E" + stPost; -exports.WINDOW_COVERING_STYPE = stPre + "8C" + stPost; -exports.OCCUPANCY_SENSOR_STYPE = stPre + "86" + stPost; -exports.CONTACT_SENSOR_STYPE = stPre + "80" + stPost; -exports.MOTION_SENSOR_STYPE = stPre + "85" + stPost; -exports.HUMIDITY_SENSOR_STYPE = stPre + "82" + stPost; -exports.TEMPERATURE_SENSOR_STYPE = stPre + "8A" + stPost; - -//HomeKitCharacteristicsTypes - - -exports.ALARM_CURRENT_STATE_CTYPE = stPre + "66" + stPost; -exports.ALARM_TARGET_STATE_CTYPE = stPre + "67" + stPost; -exports.ADMIN_ONLY_ACCESS_CTYPE = stPre + "01" + stPost; -exports.AUDIO_FEEDBACK_CTYPE = stPre + "05" + stPost; -exports.BRIGHTNESS_CTYPE = stPre + "08" + stPost; -exports.BATTERY_LEVEL_CTYPE = stPre + "68" + stPost; -exports.COOLING_THRESHOLD_CTYPE = stPre + "0D" + stPost; -exports.CONTACT_SENSOR_STATE_CTYPE = stPre + "6A" + stPost; -exports.CURRENT_DOOR_STATE_CTYPE = stPre + "0E" + stPost; -exports.CURRENT_LOCK_MECHANISM_STATE_CTYPE = stPre + "1D" + stPost; -exports.CURRENT_RELATIVE_HUMIDITY_CTYPE = stPre + "10" + stPost; -exports.CURRENT_TEMPERATURE_CTYPE = stPre + "11" + stPost; -exports.HEATING_THRESHOLD_CTYPE = stPre + "12" + stPost; -exports.HUE_CTYPE = stPre + "13" + stPost; -exports.IDENTIFY_CTYPE = stPre + "14" + stPost; -exports.LOCK_MANAGEMENT_AUTO_SECURE_TIMEOUT_CTYPE = stPre + "1A" + stPost; -exports.LOCK_MANAGEMENT_CONTROL_POINT_CTYPE = stPre + "19" + stPost; -exports.LOCK_MECHANISM_LAST_KNOWN_ACTION_CTYPE = stPre + "1C" + stPost; -exports.LOGS_CTYPE = stPre + "1F" + stPost; -exports.MANUFACTURER_CTYPE = stPre + "20" + stPost; -exports.MODEL_CTYPE = stPre + "21" + stPost; -exports.MOTION_DETECTED_CTYPE = stPre + "22" + stPost; -exports.NAME_CTYPE = stPre + "23" + stPost; -exports.OBSTRUCTION_DETECTED_CTYPE = stPre + "24" + stPost; -exports.OUTLET_IN_USE_CTYPE = stPre + "26" + stPost; -exports.OCCUPANCY_DETECTED_CTYPE = stPre + "71" + stPost; -exports.POWER_STATE_CTYPE = stPre + "25" + stPost; -exports.PROGRAMMABLE_SWITCH_SWITCH_EVENT_CTYPE = stPre + "73" + stPost; -exports.PROGRAMMABLE_SWITCH_OUTPUT_STATE_CTYPE = stPre + "74" + stPost; -exports.ROTATION_DIRECTION_CTYPE = stPre + "28" + stPost; -exports.ROTATION_SPEED_CTYPE = stPre + "29" + stPost; -exports.SATURATION_CTYPE = stPre + "2F" + stPost; -exports.SERIAL_NUMBER_CTYPE = stPre + "30" + stPost; -exports.STATUS_LOW_BATTERY_CTYPE = stPre + "79" + stPost; -exports.STATUS_FAULT_CTYPE = stPre + "77" + stPost; -exports.TARGET_DOORSTATE_CTYPE = stPre + "32" + stPost; -exports.TARGET_LOCK_MECHANISM_STATE_CTYPE = stPre + "1E" + stPost; -exports.TARGET_RELATIVE_HUMIDITY_CTYPE = stPre + "34" + stPost; -exports.TARGET_TEMPERATURE_CTYPE = stPre + "35" + stPost; -exports.TEMPERATURE_UNITS_CTYPE = stPre + "36" + stPost; -exports.VERSION_CTYPE = stPre + "37" + stPost; -exports.WINDOW_COVERING_TARGET_POSITION_CTYPE = stPre + "7C" + stPost; -exports.WINDOW_COVERING_CURRENT_POSITION_CTYPE = stPre + "6D" + stPost; -exports.WINDOW_COVERING_OPERATION_STATE_CTYPE = stPre + "72" + stPost; -exports.CURRENTHEATINGCOOLING_CTYPE = stPre + "0F" + stPost; -exports.TARGETHEATINGCOOLING_CTYPE = stPre + "33" + stPost; diff --git a/index.d.ts b/index.d.ts deleted file mode 100644 index f50447a5b..000000000 --- a/index.d.ts +++ /dev/null @@ -1,467 +0,0 @@ -declare namespace HAPNodeJS { - - export interface uuid { - generate(data: string): string; - isValid(UUID: string): boolean; - unparse(bug: string, offset: number): string; - } - - type EventService = "characteristic-change" | "service-configurationChange" - - export interface IEventEmitterAccessory { - addListener(event: EventService, listener: Function): this; - on(event: EventService, listener: Function): this; - once(event: EventService, listener: Function): this; - removeListener(event: EventService, listener: Function): this; - removeAllListeners(event?: EventService): this; - setMaxListeners(n: number): this; - getMaxListeners(): number; - listeners(event: EventService): Function[]; - emit(event: EventService, ...args: any[]): boolean; - listenerCount(type: string): number; - } - - export interface Service extends IEventEmitterAccessory { - new (displayName: string, UUID: string, subtype: string): Service; - - displayName: string; - UUID: string; - subtype: string; - iid: string; - characteristics: Characteristic[]; - optionalCharacteristics: Characteristic[]; - - isHiddenService: boolean; - isPrimaryService: boolean; - linkedServices: Service[]; - - addCharacteristic(characteristic: Characteristic | Function): Characteristic; - setHiddenService(isHidden: boolean): void; - addLinkedService(newLinkedService: Service | Function): void; - removeLinkedService(oldLinkedService: Service | Function): void; - removeCharacteristic(characteristic: Characteristic): void; - getCharacteristic(name: string | Function): Characteristic; - testCharacteristic(name: string | Function): boolean; - setCharacteristic(name: string | Function, value: CharacteristicValue): Service; - updateCharacteristic(name: string | Function, value: CharacteristicValue): Service; - addOptionalCharacteristic(characteristic: Characteristic | Function): void; - getCharacteristicByIID(iid: string): Characteristic; - - toHAP(opt: any): JSON; - - AccessoryInformation: PredefinedService; - AirPurifier: PredefinedService; - AirQualitySensor: PredefinedService; - BatteryService: PredefinedService; - BridgeConfiguration: PredefinedService; - BridgingState: PredefinedService; - CameraControl: PredefinedService; - CameraRTPStreamManagement: PredefinedService; - CarbonDioxideSensor: PredefinedService; - CarbonMonoxideSensor: PredefinedService; - ContactSensor: PredefinedService; - Door: PredefinedService; - Doorbell: PredefinedService; - Fan: PredefinedService; - Fanv2: PredefinedService; - Faucet: PredefinedService; - FilterMaintenance: PredefinedService; - GarageDoorOpener: PredefinedService; - HeaterCooler: PredefinedService; - HumidifierDehumidifier: PredefinedService; - HumiditySensor: PredefinedService; - InputSource: PredefinedService; - IrrigationSystem: PredefinedService; - LeakSensor: PredefinedService; - LightSensor: PredefinedService; - Lightbulb: PredefinedService; - LockManagement: PredefinedService; - LockMechanism: PredefinedService; - Microphone: PredefinedService; - MotionSensor: PredefinedService; - OccupancySensor: PredefinedService; - Outlet: PredefinedService; - Pairing: PredefinedService; - ProtocolInformation: PredefinedService; - Relay: PredefinedService; - SecuritySystem: PredefinedService; - ServiceLabel: PredefinedService; - Slat: PredefinedService; - SmokeSensor: PredefinedService; - Speaker: PredefinedService; - StatefulProgrammableSwitch: PredefinedService; - StatelessProgrammableSwitch: PredefinedService; - Switch: PredefinedService; - Television: PredefinedService; - TelevisionSpeaker: PredefinedService; - TemperatureSensor: PredefinedService; - Thermostat: PredefinedService; - TimeInformation: PredefinedService; - TunneledBTLEAccessoryService: PredefinedService; - Valve: PredefinedService; - Window: PredefinedService; - WindowCovering: PredefinedService; - } - - export interface PredefinedService { - new (displayName: string, subtype: string): Service; - } - - export interface CameraSource { - - } - - type EventAccessory = "service-configurationChange" | "service-characteristic-change" | "identify" - - export interface IEventEmitterAccessory { - addListener(event: EventAccessory, listener: Function): this; - on(event: EventAccessory, listener: Function): this; - once(event: EventAccessory, listener: Function): this; - removeListener(event: EventAccessory, listener: Function): this; - removeAllListeners(event?: EventAccessory): this; - setMaxListeners(n: number): this; - getMaxListeners(): number; - listeners(event: EventAccessory): Function[]; - emit(event: EventAccessory, ...args: any[]): boolean; - listenerCount(type: string): number; - } - - export interface CharacteristicProps { - format: Characteristic.Formats; - unit: Characteristic.Units, - minValue: number, - maxValue: number, - minStep: number, - perms: Characteristic.Perms[] - } - - type EventCharacteristic = "get" | "set" - type CharacteristicValue = boolean | string | number - - export type CharacteristicGetCallback = (error: Error | null , value: T) => void - export type CharacteristicSetCallback = (error?: Error | null) => void - export type CharacteristicCallback = CharacteristicGetCallback | CharacteristicSetCallback - export type CallbackGetListener = (cb: CharacteristicGetCallback) => void - export type CallbackSetListener = (value: T, cb: CharacteristicSetCallback) => void - export type CallbackListener = CallbackGetListener | CallbackSetListener - - - export interface IEventEmitterCharacteristic { - addListener(event: EventCharacteristic, listener: CallbackListener): this; - on(event: EventCharacteristic, listener: CallbackListener): this; - once(event: EventCharacteristic, listener: CallbackListener): this; - removeListener(event: EventCharacteristic, listener: CallbackListener): this; - removeAllListeners(event?: EventCharacteristic): this; - setMaxListeners(n: number): this; - getMaxListeners(): number; - listeners(event: EventCharacteristic): CallbackListener[]; - emit(event: EventCharacteristic, ...args: any[]): boolean; - listenerCount(type: string): number; - } - - export interface Characteristic extends IEventEmitterCharacteristic { - new (displayName: string, UUID: string, props?: CharacteristicProps): Characteristic; - - Formats: typeof Characteristic.Formats; - Units: typeof Characteristic.Units; - Perms: typeof Characteristic.Perms; - - setProps(props: CharacteristicProps): Characteristic - getValue(callback?: CharacteristicGetCallback, context?: any, connectionID?: string): void; - setValue(newValue: CharacteristicValue, callback?: CharacteristicSetCallback, context?: any, connectionID?: string): Characteristic; - updateValue(newValue: CharacteristicValue, callback?: () => void, context?: any): Characteristic; - getDefaultValue(): CharacteristicValue; - toHAP(opt: any): JSON; - - AccessoryFlags: Characteristic; - AccessoryIdentifier: Characteristic; - Active: Characteristic; - ActiveIdentifier: Characteristic; - AdministratorOnlyAccess: Characteristic; - AirParticulateDensity: Characteristic; - AirParticulateSize: Characteristic; - AirQuality: Characteristic; - AppMatchingIdentifier: Characteristic; - AudioFeedback: Characteristic; - BatteryLevel: Characteristic; - Brightness: Characteristic; - CarbonDioxideDetected: Characteristic; - CarbonDioxideLevel: Characteristic; - CarbonDioxidePeakLevel: Characteristic; - CarbonMonoxideDetected: Characteristic; - CarbonMonoxideLevel: Characteristic; - CarbonMonoxidePeakLevel: Characteristic; - Category: Characteristic; - ChargingState: Characteristic; - ClosedCaptions: Characteristic; - ColorTemperature: Characteristic; - ConfigureBridgedAccessory: Characteristic; - ConfigureBridgedAccessoryStatus: Characteristic; - ConfiguredName: Characteristic; - ContactSensorState: Characteristic; - CoolingThresholdTemperature: Characteristic; - CurrentAirPurifierState: Characteristic; - CurrentAmbientLightLevel: Characteristic; - CurrentDoorState: Characteristic; - CurrentFanState: Characteristic; - CurrentHeaterCoolerState: Characteristic; - CurrentHeatingCoolingState: Characteristic; - CurrentHorizontalTiltAngle: Characteristic; - CurrentHumidifierDehumidifierState: Characteristic; - CurrentMediaState: Characteristic; - CurrentPosition: Characteristic; - CurrentRelativeHumidity: Characteristic; - CurrentSlatState: Characteristic; - CurrentTemperature: Characteristic; - CurrentTiltAngle: Characteristic; - CurrentTime: Characteristic; - CurrentVerticalTiltAngle: Characteristic; - CurrentVisibilityState: Characteristic; - DayoftheWeek: Characteristic; - DigitalZoom: Characteristic; - DiscoverBridgedAccessories: Characteristic; - DiscoveredBridgedAccessories: Characteristic; - DisplayOrder: Characteristic; - FilterChangeIndication: Characteristic; - FilterLifeLevel: Characteristic; - FirmwareRevision: Characteristic; - HardwareRevision: Characteristic; - HeatingThresholdTemperature: Characteristic; - HoldPosition: Characteristic; - Hue: Characteristic; - Identifier: Characteristic; - Identify: Characteristic; - ImageMirroring: Characteristic; - ImageRotation: Characteristic; - InUse: Characteristic; - InputDeviceType: Characteristic; - InputSourceType: Characteristic; - IsConfigured: Characteristic; - LeakDetected: Characteristic; - LinkQuality: Characteristic; - LockControlPoint: Characteristic; - LockCurrentState: Characteristic; - LockLastKnownAction: Characteristic; - LockManagementAutoSecurityTimeout: Characteristic; - LockPhysicalControls: Characteristic; - LockTargetState: Characteristic; - Logs: Characteristic; - Manufacturer: Characteristic; - Model: Characteristic; - MotionDetected: Characteristic; - Mute: Characteristic; - Name: Characteristic; - NightVision: Characteristic; - NitrogenDioxideDensity: Characteristic; - ObstructionDetected: Characteristic; - OccupancyDetected: Characteristic; - On: Characteristic; - OpticalZoom: Characteristic; - OutletInUse: Characteristic; - OzoneDensity: Characteristic; - PM10Density: Characteristic; - PM2_5Density: Characteristic; - PairSetup: Characteristic; - PairVerify: Characteristic; - PairingFeatures: Characteristic; - PairingPairings: Characteristic; - PictureMode: Characteristic; - PositionState: Characteristic; - PowerModeSelection: Characteristic; - ProgramMode: Characteristic; - ProgrammableSwitchEvent: Characteristic; - ProgrammableSwitchOutputState: Characteristic; - Reachable: Characteristic; - RelativeHumidityDehumidifierThreshold: Characteristic; - RelativeHumidityHumidifierThreshold: Characteristic; - RelayControlPoint: Characteristic; - RelayEnabled: Characteristic; - RelayState: Characteristic; - RemainingDuration: Characteristic; - RemoteKey: Characteristic; - ResetFilterIndication: Characteristic; - RotationDirection: Characteristic; - RotationSpeed: Characteristic; - Saturation: Characteristic; - SecuritySystemAlarmType: Characteristic; - SecuritySystemCurrentState: Characteristic; - SecuritySystemTargetState: Characteristic; - SelectedRTPStreamConfiguration: Characteristic; - SerialNumber: Characteristic; - ServiceLabelIndex: Characteristic; - ServiceLabelNamespace: Characteristic; - SetDuration: Characteristic; - SetupEndpoints: Characteristic; - SlatType: Characteristic; - SleepDiscoveryMode: Characteristic; - SmokeDetected: Characteristic; - SoftwareRevision: Characteristic; - StatusActive: Characteristic; - StatusFault: Characteristic; - StatusJammed: Characteristic; - StatusLowBattery: Characteristic; - StatusTampered: Characteristic; - StreamingStatus: Characteristic; - SulphurDioxideDensity: Characteristic; - SupportedAudioStreamConfiguration: Characteristic; - SupportedRTPConfiguration: Characteristic; - SupportedVideoStreamConfiguration: Characteristic; - SwingMode: Characteristic; - TargetAirPurifierState: Characteristic; - TargetAirQuality: Characteristic; - TargetDoorState: Characteristic; - TargetFanState: Characteristic; - TargetHeaterCoolerState: Characteristic; - TargetHeatingCoolingState: Characteristic; - TargetHorizontalTiltAngle: Characteristic; - TargetHumidifierDehumidifierState: Characteristic; - TargetMediaState: Characteristic; - TargetPosition: Characteristic; - TargetRelativeHumidity: Characteristic; - TargetSlatState: Characteristic; - TargetTemperature: Characteristic; - TargetTiltAngle: Characteristic; - TargetVerticalTiltAngle: Characteristic; - TargetVisibilityState: Characteristic; - TemperatureDisplayUnits: Characteristic; - TimeUpdate: Characteristic; - TunnelConnectionTimeout: Characteristic; - TunneledAccessoryAdvertising: Characteristic; - TunneledAccessoryConnected: Characteristic; - TunneledAccessoryStateNumber: Characteristic; - VOCDensity: Characteristic; - ValveType: Characteristic; - Version: Characteristic; - Volume: Characteristic; - VolumeControlType: Characteristic; - VolumeSelector: Characteristic; - WaterLevel: Characteristic; - } - - - module Characteristic { - export enum Formats { - BOOL, - INT, - FLOAT, - STRING, - ARRAY, // unconfirmed - DICTIONARY, // unconfirmed - UINT8, - UINT16, - UINT32, - UINT64, - DATA, // unconfirmed - TLV8 - } - - export enum Units { - // HomeKit only defines Celsius, for Fahrenheit, it requires iOS app to do the conversion. - CELSIUS, - PERCENTAGE, - ARC_DEGREE, - LUX, - SECONDS - } - - export enum Perms { - READ, - WRITE, - NOTIFY, - HIDDEN - } - } - - export interface PublishInfo { - port: number; - username: string; - pincode: string; - category: number; - } - - export interface Accessory extends IEventEmitterAccessory { - new (displayName: string, UUID: string): Accessory; - displayName: string; - username: string; - pincode: string; - UUID: string; - aid: string; - bridged: boolean; - bridgedAccessories: Accessory[]; - reachable: boolean; - category: Accessory.Categories; - services: Service[]; - cameraSource: CameraSource; - Categories: typeof Accessory.Categories - addService(service: Service | Function): Service; - removeService(service: Service): void; - getService(name: string | Function): Service; - updateReachability(reachable: boolean): void; - addBridgedAccessory(accessory: Accessory, deferUpdate: boolean): Accessory; - addBridgedAccessories(accessories: Accessory[]): void - removeBridgedAccessory(accessory: Accessory, deferUpdate: boolean): void; - removeBridgedAccessories(accessories: Accessory[]): void; - getCharacteristicByIID(iid: string): Characteristic; - getBridgedAccessoryByAID(aid: string): Accessory; - findCharacteristic(aid: string, iid: string): Accessory; - configureCameraSource(cameraSource: CameraSource): void; - toHAP(opt: any): JSON; - publish(info: PublishInfo, allowInsecureRequest: boolean): void; - destroy(): void; - setupURI(): string; - } - - module Accessory { - export enum Categories { - OTHER = 1, - BRIDGE = 2, - FAN = 3, - GARAGE_DOOR_OPENER = 4, - LIGHTBULB = 5, - DOOR_LOCK = 6, - OUTLET = 7, - SWITCH = 8, - THERMOSTAT = 9, - SENSOR = 10, - ALARM_SYSTEM = 11, - SECURITY_SYSTEM = 11, - DOOR = 12, - WINDOW = 13, - WINDOW_COVERING = 14, - PROGRAMMABLE_SWITCH = 15, - RANGE_EXTENDER = 16, - CAMERA = 17, - IP_CAMERA = 17, - VIDEO_DOORBELL = 18, - AIR_PURIFIER = 19, - AIR_HEATER = 20, - AIR_CONDITIONER = 21, - AIR_HUMIDIFIER = 22, - AIR_DEHUMIDIFIER = 23, - APPLE_TV = 24, - SPEAKER = 26, - AIRPORT = 27, - SPRINKLER = 28, - FAUCET = 29, - SHOWER_HEAD = 30, - TELEVISION = 31, - TARGET_CONTROLLER = 32 - } - } - - export interface HAPNodeJS { - init(storagePath?: string): void, - uuid: uuid, - Accessory: Accessory, - Service: Service, - Characteristic: Characteristic - } - - -} - -declare var hapNodeJS: HAPNodeJS.HAPNodeJS; - -declare module "hap-nodejs" { - export = hapNodeJS; -} diff --git a/index.js b/index.js deleted file mode 100644 index 6a2eb12d6..000000000 --- a/index.js +++ /dev/null @@ -1,34 +0,0 @@ -var Accessory = require('./lib/Accessory.js').Accessory; -var Bridge = require('./lib/Bridge.js').Bridge; -var Camera = require('./lib/Camera.js').Camera; -var Service = require('./lib/Service.js').Service; -var Characteristic = require('./lib/Characteristic.js').Characteristic; -var uuid = require('./lib/util/uuid'); -var AccessoryLoader = require('./lib/AccessoryLoader.js'); -var StreamController = require('./lib/StreamController.js').StreamController; -var storage = require('node-persist'); -var HAPServer = require('./lib/HAPServer').HAPServer; - -// ensure Characteristic subclasses are defined -var HomeKitTypes = require('./lib/gen/HomeKitTypes'); - -module.exports = { - init: init, - Accessory: Accessory, - Bridge: Bridge, - Camera: Camera, - Service: Service, - Characteristic: Characteristic, - uuid: uuid, - AccessoryLoader: AccessoryLoader, - StreamController: StreamController, - HAPServer: HAPServer -} - -function init(storagePath) { - // initialize our underlying storage system, passing on the directory if needed - if (typeof storagePath !== 'undefined') - storage.initSync({ dir: storagePath }); - else - storage.initSync(); // use whatever is default -} diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 000000000..91a2d2c0d --- /dev/null +++ b/jest.config.js @@ -0,0 +1,4 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', +}; \ No newline at end of file diff --git a/lib/Accessory.js b/lib/Accessory.js deleted file mode 100644 index 6c14133a7..000000000 --- a/lib/Accessory.js +++ /dev/null @@ -1,1066 +0,0 @@ -'use strict'; - -var debug = require('debug')('Accessory'); -var crypto = require('crypto'); -var inherits = require('util').inherits; -var EventEmitter = require('events').EventEmitter; -var clone = require('./util/clone').clone; -var uuid = require('./util/uuid'); -var Service = require('./Service').Service; -var Characteristic = require('./Characteristic').Characteristic; -var HomeKitTypes = require('./gen/HomeKitTypes'); -var Advertiser = require('./Advertiser').Advertiser; -var HAPServer = require('./HAPServer').HAPServer; -var AccessoryInfo = require('./model/AccessoryInfo').AccessoryInfo; -var IdentifierCache = require('./model/IdentifierCache').IdentifierCache; -var bufferShim = require('buffer-shims'); -// var RelayServer = require("./util/relayserver").RelayServer; - -const MAX_ACCESSORIES = 149; // Maximum number of bridged accessories per bridge. - -module.exports = { - Accessory: Accessory -}; - - -/** - * 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. - * - * Bridged Accessories in this implementation must have a UUID that is unique among all other Accessories that - * are hosted by the Bridge. This UUID must be "stable" and unchanging, even when the server is restarted. This - * is required so that the Bridge can provide consistent "Accessory IDs" (aid) and "Instance IDs" (iid) for all - * Accessories, Services, and Characteristics for iOS clients to reference later. - * - * @event 'identify' => function(paired, callback(err)) { } - * Emitted when an iOS device wishes for this Accessory to identify itself. If `paired` is false, then - * this device is currently browsing for Accessories in the system-provided "Add Accessory" screen. If - * `paired` is true, then this is a device that has already paired with us. Note that if `paired` is true, - * listening for this event is a shortcut for the underlying mechanism of setting the `Identify` Characteristic: - * `getService(Service.AccessoryInformation).getCharacteristic(Characteristic.Identify).on('set', ...)` - * You must call the callback for identification to be successful. - * - * @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. - */ - -function Accessory(displayName, UUID) { - - if (!displayName) throw new Error("Accessories must be created with a non-empty displayName."); - if (!UUID) throw new Error("Accessories must be created with a valid UUID."); - if (!uuid.isValid(UUID)) throw new Error("UUID '" + UUID + "' is not a valid UUID. Try using the provided 'generateUUID' function to create a valid UUID from any arbitrary string, like a serial number."); - - this.displayName = displayName; - this.UUID = UUID; - this.aid = null; // assigned by us in assignIDs() or by a Bridge - this._isBridge = false; // true if we are a Bridge (creating a new instance of the Bridge subclass sets this to true) - this.bridged = false; // true if we are hosted "behind" a Bridge Accessory - this.bridgedAccessories = []; // If we are a Bridge, these are the Accessories we are bridging - this.reachable = true; - this.category = Accessory.Categories.OTHER; - this.services = []; // of Service - this.cameraSource = null; - this.shouldPurgeUnusedIDs = true; // Purge unused ids by default - - // create our initial "Accessory Information" Service that all Accessories are expected to have - this - .addService(Service.AccessoryInformation) - .setCharacteristic(Characteristic.Name, displayName) - .setCharacteristic(Characteristic.Manufacturer, "Default-Manufacturer") - .setCharacteristic(Characteristic.Model, "Default-Model") - .setCharacteristic(Characteristic.SerialNumber, "Default-SerialNumber") - .setCharacteristic(Characteristic.FirmwareRevision, "1.0"); - - // sign up for when iOS attempts to "set" the Identify characteristic - this means a paired device wishes - // for us to identify ourselves (as opposed to an unpaired device - that case is handled by HAPServer 'identify' event) - this - .getService(Service.AccessoryInformation) - .getCharacteristic(Characteristic.Identify) - .on('set', function(value, callback) { - if (value) { - var paired = true; - this._identificationRequest(paired, callback); - } - }.bind(this)); -} - -inherits(Accessory, EventEmitter); - -// Known category values. Category is a hint to iOS clients about what "type" of Accessory this represents, for UI only. -Accessory.Categories = { - OTHER: 1, - BRIDGE: 2, - FAN: 3, - GARAGE_DOOR_OPENER: 4, - LIGHTBULB: 5, - DOOR_LOCK: 6, - OUTLET: 7, - SWITCH: 8, - THERMOSTAT: 9, - SENSOR: 10, - ALARM_SYSTEM: 11, - SECURITY_SYSTEM: 11, //Added to conform to HAP naming - DOOR: 12, - WINDOW: 13, - WINDOW_COVERING: 14, - PROGRAMMABLE_SWITCH: 15, - RANGE_EXTENDER: 16, - CAMERA: 17, - IP_CAMERA: 17, //Added to conform to HAP naming - VIDEO_DOORBELL: 18, - AIR_PURIFIER: 19, - AIR_HEATER: 20, //Not in HAP Spec - AIR_CONDITIONER: 21, //Not in HAP Spec - AIR_HUMIDIFIER: 22, //Not in HAP Spec - AIR_DEHUMIDIFIER: 23, // Not in HAP Spec - APPLE_TV: 24, - SPEAKER: 26, - AIRPORT: 27, - SPRINKLER: 28, - FAUCET: 29, - SHOWER_HEAD: 30, - TELEVISION: 31, - TARGET_CONTROLLER: 32 // Remote Control -} - -Accessory.prototype._identificationRequest = function(paired, callback) { - debug("[%s] Identification request", this.displayName); - - if (this.listeners('identify').length > 0) { - // allow implementors to identify this Accessory in whatever way is appropriate, and pass along - // the standard callback for completion. - this.emit('identify', paired, callback); - } - else { - debug("[%s] Identification request ignored; no listeners to 'identify' event", this.displayName); - callback(); - } -} - -Accessory.prototype.addService = function(service) { - // service might be a constructor like `Service.AccessoryInformation` instead of an instance - // of Service. Coerce if necessary. - if (typeof service === 'function') - service = new (Function.prototype.bind.apply(service, arguments)); - - // check for UUID+subtype conflict - for (var index in this.services) { - var 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) - throw new Error("Cannot add a Service with the same UUID '" + existing.UUID + "' as another Service in this Accessory without also defining a unique 'subtype' property."); - - if (service.subtype.toString() === existing.subtype.toString()) - throw new Error("Cannot add a Service with the same UUID '" + existing.UUID + "' and subtype '" + existing.subtype + "' as another Service in this Accessory."); - } - } - - this.services.push(service); - - if (!this.bridged) { - this._updateConfiguration(); - } else { - this.emit('service-configurationChange', clone({accessory:this, service:service})); - } - - service.on('service-configurationChange', function(change) { - if (!this.bridged) { - this._updateConfiguration(); - } else { - this.emit('service-configurationChange', clone({accessory:this, service:service})); - } - }.bind(this)); - - // listen for changes in characteristics and bubble them up - service.on('characteristic-change', function(change) { - this.emit('service-characteristic-change', clone(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})); - - }.bind(this)); - - return service; -} - -Accessory.prototype.setPrimaryService = function (service) { - //find this service in the services list - var targetServiceIndex; - for (var index in this.services) { - var existingService = this.services[index]; - - if (existingService === service) { - targetServiceIndex = index; - break; - } - } - - if (targetServiceIndex) { - //If the service is found, set isPrimaryService to false for everything. - for (var index in this.services) - this.services[index].isPrimaryService = false; - - //Make this service the primary - existingService.isPrimaryService = true - - if (!this.bridged) { - this._updateConfiguration(); - } else { - this.emit('service-configurationChange', clone({ accessory: this, service: service })); - } - } -} - -Accessory.prototype.removeService = function(service) { - var targetServiceIndex; - - for (var index in this.services) { - var existingService = this.services[index]; - - if (existingService === service) { - targetServiceIndex = index; - break; - } - } - - if (targetServiceIndex) { - this.services.splice(targetServiceIndex, 1); - - if (!this.bridged) { - this._updateConfiguration(); - } else { - this.emit('service-configurationChange', clone({accessory:this, service:service})); - } - - service.removeAllListeners(); - } -} - -Accessory.prototype.getService = function(name) { - for (var index in this.services) { - var service = this.services[index]; - - 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))) - return service; - } -} - -Accessory.prototype.updateReachability = function(reachable) { - if (!this.bridged) - throw new Error("Cannot update reachability on non-bridged accessory!"); - this.reachable = reachable; - - debug('Reachability update is no longer being supported.'); -} - -Accessory.prototype.addBridgedAccessory = function(accessory, deferUpdate) { - if (accessory._isBridge) - throw new Error("Cannot Bridge another Bridge!"); - - // check for UUID conflict - for (var index in this.bridgedAccessories) { - var existing = this.bridgedAccessories[index]; - if (existing.UUID === accessory.UUID) - throw new Error("Cannot add a bridged Accessory with the same UUID as another bridged Accessory: " + existing.UUID); - } - - // A bridge too far... - if (this.bridgedAccessories.length >= MAX_ACCESSORIES) { - throw new Error("Cannot Bridge more than " + MAX_ACCESSORIES + " Accessories"); - } - - // listen for changes in ANY characteristics of ANY services on this Accessory - accessory.on('service-characteristic-change', function(change) { - this._handleCharacteristicChange(clone(change, {accessory:accessory})); - }.bind(this)); - - accessory.on('service-configurationChange', function(change) { - this._updateConfiguration(); - }.bind(this)); - - accessory.bridged = true; - - this.bridgedAccessories.push(accessory); - - if(!deferUpdate) { - this._updateConfiguration(); - } - - return accessory; -} - -Accessory.prototype.addBridgedAccessories = function(accessories) { - for (var index in accessories) { - var accessory = accessories[index]; - this.addBridgedAccessory(accessory, true); - } - - this._updateConfiguration(); -} - -Accessory.prototype.removeBridgedAccessory = function(accessory, deferUpdate) { - if (accessory._isBridge) - throw new Error("Cannot Bridge another Bridge!"); - - var foundMatchAccessory = false; - // check for UUID conflict - for (var index in this.bridgedAccessories) { - var existing = this.bridgedAccessories[index]; - if (existing.UUID === accessory.UUID) { - foundMatchAccessory = true; - this.bridgedAccessories.splice(index, 1); - break; - } - } - - if (!foundMatchAccessory) - throw new Error("Cannot find the bridged Accessory to remove."); - - accessory.removeAllListeners(); - - if(!deferUpdate) { - this._updateConfiguration(); - } -} - -Accessory.prototype.removeBridgedAccessories = function(accessories) { - for (var index in accessories) { - var accessory = accessories[index]; - this.removeBridgedAccessory(accessory, true); - } - - this._updateConfiguration(); -} - -Accessory.prototype.removeAllBridgedAccessories = function() { - for (var i = this.bridgedAccessories.length - 1; i >= 0; i --) { - this.removeBridgedAccessory(this.bridgedAccessories[i], true); - } - this._updateConfiguration(); -} - -Accessory.prototype.getCharacteristicByIID = function(iid) { - for (var index in this.services) { - var service = this.services[index]; - var characteristic = service.getCharacteristicByIID(iid); - if (characteristic) return characteristic; - } -} - -Accessory.prototype.getBridgedAccessoryByAID = function(aid) { - for (var index in this.bridgedAccessories) { - var accessory = this.bridgedAccessories[index]; - if (accessory.aid === aid) return accessory; - } -} - -Accessory.prototype.findCharacteristic = function(aid, iid) { - - // if aid === 1, the accessory is us (because we are the server), otherwise find it among our bridged - // accessories (if any) - var accessory = (aid === 1) ? this : this.getBridgedAccessoryByAID(aid); - - return accessory && accessory.getCharacteristicByIID(iid); -} - -Accessory.prototype.configureCameraSource = function(cameraSource) { - this.cameraSource = cameraSource; - for (var index in cameraSource.services) { - var service = cameraSource.services[index]; - this.addService(service); - } -} - -Accessory.prototype.setupURI = function() { - if (this._setupURI) { - return this._setupURI; - } - - var buffer = bufferShim.alloc(8); - var setupCode = parseInt(this._accessoryInfo.pincode.replace(/-/g, ''), 10); - - var value_low = setupCode; - var value_high = this._accessoryInfo.category >> 1; - - value_low |= 1 << 28; // Supports IP; - - buffer.writeUInt32BE(value_low, 4); - - if (this._accessoryInfo.category & 1) { - buffer[4] = buffer[4] | 1 << 7; - } - - buffer.writeUInt32BE(value_high, 0); - - var 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++) { - encodedPayload = "0" + encodedPayload; - } - } - - this._setupURI = "X-HM://" + encodedPayload + this._setupID; - return this._setupURI; -} - -/** - * Assigns aid/iid to ourselves, any Accessories we are bridging, and all associated Services+Characteristics. Uses - * the provided identifierCache to keep IDs stable. - */ -Accessory.prototype._assignIDs = function(identifierCache) { - - // if we are responsible for our own identifierCache, start the expiration process - // also check weather we want to have an expiration process - if (this._identifierCache && this.shouldPurgeUnusedIDs) { - this._identifierCache.startTrackingUsage(); - } - - if (this.bridged) { - // This Accessory is bridged, so it must have an aid > 1. Use the provided identifierCache to - // fetch or assign one based on our UUID. - this.aid = identifierCache.getAID(this.UUID) - } - else { - // Since this Accessory is the server (as opposed to any Accessories that may be bridged behind us), - // we must have aid = 1 - this.aid = 1; - } - - for (var index in this.services) { - var service = this.services[index]; - if (this._isBridge) { - service._assignIDs(identifierCache, this.UUID, 2000000000); - } else { - service._assignIDs(identifierCache, this.UUID); - } - } - - // now assign IDs for any Accessories we are bridging - for (var index in this.bridgedAccessories) { - var accessory = this.bridgedAccessories[index]; - - accessory._assignIDs(identifierCache); - } - - // expire any now-unused cache keys (for Accessories, Services, or Characteristics - // that have been removed since the last call to assignIDs()) - if (this._identifierCache) { - //Check weather we want to purge the unused ids - if (this.shouldPurgeUnusedIDs) - this._identifierCache.stopTrackingUsageAndExpireUnused(); - //Save in case we have new ones - this._identifierCache.save(); - } -} - -Accessory.prototype.disableUnusedIDPurge = function() { - this.shouldPurgeUnusedIDs = false; -} - -Accessory.prototype.enableUnusedIDPurge = function() { - this.shouldPurgeUnusedIDs = true; -} - -/** - * Manually purge the unused ids if you like, comes handy - * when you have disabled auto purge so you can do it manually - */ -Accessory.prototype.purgeUnusedIDs = function() { - //Cache the state of the purge mechanisam and set it to true - var oldValue = this.shouldPurgeUnusedIDs; - this.shouldPurgeUnusedIDs = true; - - //Reassign all ids - this._assignIDs(this._identifierCache); - - //Revert back the purge mechanisam state - this.shouldPurgeUnusedIDs = oldValue; -} - -/** - * Returns a JSON representation of this Accessory suitable for delivering to HAP clients. - */ -Accessory.prototype.toHAP = function(opt) { - - var servicesHAP = []; - - for (var index in this.services) { - var service = this.services[index]; - servicesHAP.push(service.toHAP(opt)); - } - - var 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); - - // bridgedAccessoryHAP is an array of accessories with one item - extract it - // and add it to our own array - accessoriesHAP.push(bridgedAccessoryHAP[0]) - } - - return accessoriesHAP; -} - -/** - * Publishes this Accessory on the local network for iOS clients to communicate with. - * - * @param {Object} info - Required info for publishing. - * @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 - * as a string like "031-45-154". - * @param {string} info.category - One of the values of the Accessory.Category enum, like Accessory.Category.SWITCH. - * This is a hint to iOS clients about what "type" of Accessory this represents, so - * that for instance an appropriate icon can be drawn for the user while adding a - * new Accessory. - */ -Accessory.prototype.publish = function(info, allowInsecureRequest) { - // attempt to load existing AccessoryInfo from disk - this._accessoryInfo = AccessoryInfo.load(info.username); - - // if we don't have one, create a new one. - if (!this._accessoryInfo) { - debug("[%s] Creating new AccessoryInfo for our HAP server", this.displayName); - this._accessoryInfo = AccessoryInfo.create(info.username); - } - - if (info.setupID) { - this._setupID = info.setupID; - } else if (this._accessoryInfo.setupID === undefined || this._accessoryInfo.setupID === "") { - this._setupID = this._generateSetupID(); - } else { - this._setupID = this._accessoryInfo.setupID; - } - - this._accessoryInfo.setupID = this._setupID; - - // make sure we have up-to-date values in AccessoryInfo, then save it in case they changed (or if we just created it) - this._accessoryInfo.displayName = this.displayName; - this._accessoryInfo.category = info.category || Accessory.Categories.OTHER; - this._accessoryInfo.pincode = info.pincode; - this._accessoryInfo.save(); - - // if (this._isBridge) { - // this.relayServer = new RelayServer(this._accessoryInfo); - // this.addService(this.relayServer.relayService()); - // } - - // create our IdentifierCache so we can provide clients with stable aid/iid's - this._identifierCache = IdentifierCache.load(info.username); - - // if we don't have one, create a new one. - if (!this._identifierCache) { - debug("[%s] Creating new IdentifierCache", this.displayName); - this._identifierCache = new IdentifierCache(info.username); - } - - //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 - //accessories. - if (this._isBridge && this.bridgedAccessories.length == 0) - this.disableUnusedIDPurge(); - - // assign aid/iid - this._assignIDs(this._identifierCache); - - // get our accessory information in HAP format and determine if our configuration (that is, our - // Accessories/Services/Characteristics) has changed since the last time we were published. make - // sure to omit actual values since these are not part of the "configuration". - var config = this.toHAP({omitValues:true}); - - // now convert it into a hash code and check it against the last one we made, if we have one - var shasum = crypto.createHash('sha1'); - shasum.update(JSON.stringify(config)); - var configHash = shasum.digest('hex'); - - if (configHash !== this._accessoryInfo.configHash) { - - // our configuration has changed! we'll need to bump our config version number - this._accessoryInfo.configVersion++; - this._accessoryInfo.configHash = configHash; - this._accessoryInfo.save(); - } - - // create our Advertiser which broadcasts our presence over mdns - this._advertiser = new Advertiser(this._accessoryInfo, info.mdns); - - // create our HAP server which handles all communication between iOS devices and us - this._server = new HAPServer(this._accessoryInfo, this.relayServer); - this._server.allowInsecureRequest = allowInsecureRequest - this._server.on('listening', this._onListening.bind(this)); - this._server.on('identify', this._handleIdentify.bind(this)); - this._server.on('pair', this._handlePair.bind(this)); - this._server.on('unpair', this._handleUnpair.bind(this)); - this._server.on('accessories', this._handleAccessories.bind(this)); - this._server.on('get-characteristics', this._handleGetCharacteristics.bind(this)); - this._server.on('set-characteristics', this._handleSetCharacteristics.bind(this)); - this._server.on('session-close', this._handleSessionClose.bind(this)); - - if (this.cameraSource) { - this._server.on('request-resource', this._handleResource.bind(this)); - } - - var targetPort = info.port || 0; - this._server.listen(targetPort); -} - -/** - * Removes this Accessory from the local network - * Accessory object will no longer vaild after invoking this method - * Trying to invoke publish() on the object will result undefined behavior - */ -Accessory.prototype.destroy = function() { - this.unpublish(); - if (this._accessoryInfo) { - this._accessoryInfo.remove(); - this._accessoryInfo = undefined; - } - if (this._identifierCache) { - this._identifierCache.remove(); - this._identifierCache = undefined; - } -} - -Accessory.prototype.unpublish = function() { - if (this._server) { - this._server.stop(); - this._server = undefined; - } - if (this._advertiser) { - this._advertiser.stopAdvertising(); - this._advertiser = undefined; - } -} - -Accessory.prototype._updateConfiguration = function() { - if (this._advertiser && this._advertiser.isAdvertising()) { - // get our accessory information in HAP format and determine if our configuration (that is, our - // Accessories/Services/Characteristics) has changed since the last time we were published. make - // sure to omit actual values since these are not part of the "configuration". - var config = this.toHAP({omitValues:true}); - - // now convert it into a hash code and check it against the last one we made, if we have one - var shasum = crypto.createHash('sha1'); - shasum.update(JSON.stringify(config)); - var configHash = shasum.digest('hex'); - - if (configHash !== this._accessoryInfo.configHash) { - - // our configuration has changed! we'll need to bump our config version number - this._accessoryInfo.configVersion++; - this._accessoryInfo.configHash = configHash; - this._accessoryInfo.save(); - } - - // update our advertisement so HomeKit on iOS can pickup new accessory - this._advertiser.updateAdvertisement(); - } -} - -Accessory.prototype._onListening = function(port) { - // the HAP server is listening, so we can now start advertising our presence. - this._advertiser.startAdvertising(port); - this.emit('listening', port); -} - -// Called when an unpaired client wishes for us to identify ourself -Accessory.prototype._handleIdentify = function(callback) { - var paired = false; - this._identificationRequest(paired, callback); -} - -// Called when HAPServer has completed the pairing process with a client -Accessory.prototype._handlePair = function(username, publicKey, callback) { - - debug("[%s] Paired with client %s", this.displayName, username); - - this._accessoryInfo.addPairedClient(username, publicKey); - this._accessoryInfo.save(); - - // update our advertisement so it can pick up on the paired status of AccessoryInfo - this._advertiser.updateAdvertisement(); - - callback(); -} - -// Called when HAPServer wishes to remove/unpair the pairing information of a client -Accessory.prototype._handleUnpair = function(username, callback) { - - debug("[%s] Unpairing with client %s", this.displayName, username); - - // Unpair - this._accessoryInfo.removePairedClient(username); - this._accessoryInfo.save(); - - // update our advertisement so it can pick up on the paired status of AccessoryInfo - if (this._advertiser) { - this._advertiser.updateAdvertisement(); - } - - callback(); -} - -// Called when an iOS client wishes to know all about our accessory via JSON payload -Accessory.prototype._handleAccessories = function(callback) { - - // 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 - }); -} - -// Called when an iOS client wishes to query the state of one or more characteristics, like "door open?", "light on?", etc. -Accessory.prototype._handleGetCharacteristics = function(data, events, callback, remote, connectionID) { - - // build up our array of responses to the characteristics requested asynchronously - var characteristics = []; - var statusKey = remote ? 's' : 'status'; - var valueKey = remote ? 'v' : 'value'; - - data.forEach(function(characteristicData) { - var aid = characteristicData.aid; - var iid = characteristicData.iid; - - var includeEvent = characteristicData.e; - - var characteristic = this.findCharacteristic(characteristicData.aid, characteristicData.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 = { - aid: aid, - iid: iid - }; - response[statusKey] = HAPServer.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; - } - - // Found the Characteristic! Get the value! - debug('[%s] Getting value for Characteristic "%s"', this.displayName, characteristic.displayName); - - // we want to remember "who" made this request, so that we don't send them an event notification - // about any changes that occurred as a result of the request. For instance, if after querying - // the current value of a characteristic, the value turns out to be different than the previously - // cached Characteristic value, an internal 'change' event will be emitted which will cause us to - // notify all connected clients about that new value. But this client is about to get the new value - // anyway, so we don't want to notify it twice. - var context = events; - - // set the value and wait for success - characteristic.getValue(function(err, value) { - - debug('[%s] Got Characteristic "%s" value: %s', this.displayName, characteristic.displayName, value); - - if (err) { - debug('[%s] Error getting value for Characteristic "%s": %s', this.displayName, characteristic.displayName, err.message); - var response = { - aid: aid, - iid: iid - }; - response[statusKey] = hapStatus(err); - characteristics.push(response); - } - else { - var response = { - aid: aid, - iid: iid - }; - response[valueKey] = value; - response[statusKey] = 0; - - if (includeEvent) { - var eventName = aid + '.' + iid; - response['e'] = (events[eventName] === true); - } - - // compose the response and add it to the list - characteristics.push(response); - } - - // have we collected all responses yet? - if (characteristics.length === data.length) - callback(null, characteristics); - - }.bind(this), context, connectionID); - - }.bind(this)); -} - -// 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. -Accessory.prototype._handleSetCharacteristics = function(data, events, callback, remote, connectionID) { - - // 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)); - - // build up our array of responses to the characteristics requested asynchronously - var characteristics = []; - - data.forEach(function(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; - - var statusKey = remote ? 's' : 'status'; - - var 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 = { - aid: aid, - iid: iid - }; - response[statusKey] = HAPServer.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; - } - - // we want to remember "who" initiated this change, so that we don't send them an event notification - // about the change they just made. We do this by leveraging the arbitrary "context" object supported - // by Characteristic and passed on to the corresponding 'change' events bubbled up from Characteristic - // through Service and Accessory. We'll assign it to the events object since it essentially represents - // the connection requesting the change. - var context = events; - - // if "ev" is present, that means we need to register or unregister this client for change events for - // this characteristic. - if (typeof ev !== 'undefined') { - debug('[%s] %s Characteristic "%s" for events', this.displayName, ev ? "Registering" : "Unregistering", characteristic.displayName); - - // store event registrations in the supplied "events" dict which is associated with the connection making - // the request. - var eventName = aid + '.' + iid; - - if (ev === true && events[eventName] != true) { - events[eventName] = true; // value is arbitrary, just needs to be non-falsey - characteristic.subscribe(); - } - - if (ev === false && events[eventName] != undefined) { - characteristic.unsubscribe(); - delete events[eventName]; // unsubscribe by deleting name from dict - } - } - - // Found the characteristic - set the value if there is one - if (typeof value !== 'undefined') { - - debug('[%s] Setting Characteristic "%s" to value %s', this.displayName, characteristic.displayName, value); - - // set the value and wait for success - characteristic.setValue(value, function(err) { - - if (err) { - debug('[%s] Error setting Characteristic "%s" to value %s: ', this.displayName, characteristic.displayName, value, err.message); - - var response = { - aid: aid, - iid: iid - }; - response[statusKey] = hapStatus(err); - characteristics.push(response); - } - else { - var response = { - aid: aid, - iid: iid - }; - response[statusKey] = 0; - - if (includeValue) - response['value'] = characteristic.value; - - characteristics.push(response); - } - - // have we collected all responses yet? - if (characteristics.length === data.length) - callback(null, characteristics); - - }.bind(this), context, connectionID); - - } - else { - // no value to set, so we're done (success) - var response = { - aid: aid, - iid: iid - }; - response[statusKey] = 0; - characteristics.push(response); - - // have we collected all responses yet? - if (characteristics.length === data.length) - callback(null, characteristics); - } - - }.bind(this)); -} - -Accessory.prototype._handleResource = function(data, callback) { - if (data["resource-type"] == "image") { - if (this.cameraSource) { - this.cameraSource.handleSnapshotRequest({ - width: data["image-width"], - height: data["image-height"] - }, callback); - return; - } - } - - callback('resource not found'); -} - -Accessory.prototype._handleSessionClose = function(sessionID, events) { - if (this.cameraSource && this.cameraSource.handleCloseConnection) { - this.cameraSource.handleCloseConnection(sessionID); - } - - this._unsubscribeEvents(events); -} - -Accessory.prototype._unsubscribeEvents = function (events) { - for (var key in events) { - if (key.indexOf('.') !== -1) { - try { - var id = key.split('.'); - var aid = Number.parseInt(id[0]); - var iid = Number.parseInt(id[1]); - - var characteristic = this.findCharacteristic(aid, iid); - if (characteristic) { - characteristic.unsubscribe(); - } - } - catch (e) { - } - } - } -} - -// Called internally above when a change was detected in one of our hosted Characteristics somewhere in our hierarchy. -Accessory.prototype._handleCharacteristicChange = function(change) { - if (!this._server) - return; // we're not running a HAPServer, so there's no one to notify about this event - - var data = { - characteristics: [{ - aid: change.accessory.aid, - iid: change.characteristic.iid, - value: change.newValue - }] - }; - - // name for this event that corresponds to what we stored when the client signed up (in handleSetCharacteristics) - var eventName = change.accessory.aid + '.' + change.characteristic.iid; - - // 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; - - // pass it along to notifyClients() so that it can omit the connection where events === excludeEvents. - this._server.notifyClients(eventName, data, excludeEvents); -} - -Accessory.prototype._setupService = function(service) { - service.on('service-configurationChange', function(change) { - if (!this.bridged) { - this._updateConfiguration(); - } else { - this.emit('service-configurationChange', clone({accessory:this, service:service})); - } - }.bind(this)); - - // listen for changes in characteristics and bubble them up - service.on('characteristic-change', function(change) { - this.emit('service-characteristic-change', clone(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})); - - }.bind(this)); -} - -Accessory.prototype._sideloadServices = function(targetServices) { - for (var index in targetServices) { - var target = targetServices[index]; - this._setupService(target); - } - - this.services = targetServices.slice(); - - // Fix Identify - this - .getService(Service.AccessoryInformation) - .getCharacteristic(Characteristic.Identify) - .on('set', function(value, callback) { - if (value) { - var paired = true; - this._identificationRequest(paired, callback); - } - }.bind(this)); -} - -Accessory.prototype._generateSetupID = function() { - var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; - var bytes = crypto.randomBytes(4); - var setupID = ''; - - for (var i = 0; i < 4; i++) { - var index = bytes.readUInt8(i) % 26; - setupID += chars.charAt(index); - } - - return setupID; -} - -function hapStatus(err) { - - // Validate that the message is a valid HAPServer.Status - var value = 0; // default if not found or - - for( const k in HAPServer.Status ) { - if (HAPServer.Status[k] == err.message) - { - value = err.message; - break; - } - } - - if ( value == 0 ) - value = HAPServer.Status.SERVICE_COMMUNICATION_FAILURE; // default if not found or 0 - - return(parseInt(value)); -} diff --git a/lib/Advertiser.js b/lib/Advertiser.js deleted file mode 100644 index 282eab09f..000000000 --- a/lib/Advertiser.js +++ /dev/null @@ -1,113 +0,0 @@ -'use strict'; - -var bonjour = require('bonjour-hap'); -var os = require('os'); -var crypto = require('crypto'); - -module.exports = { - Advertiser: Advertiser -}; - - -/** - * Advertiser uses mdns to broadcast the presence of an Accessory to the local network. - * - * Note that as of iOS 9, an accessory can only pair with a single client. Instead of pairing your - * accessories with multiple iOS devices in your home, Apple intends for you to use Home Sharing. - * To support this requirement, we provide the ability to be "discoverable" or not (via a "service flag" on the - * mdns payload). - */ - -function Advertiser(accessoryInfo, mdnsConfig) { - this.accessoryInfo = accessoryInfo; - this._bonjourService = bonjour(mdnsConfig); - this._advertisement = null; - - this._setupHash = this._computeSetupHash(); -} - -Advertiser.prototype.startAdvertising = function(port) { - - // stop advertising if necessary - if (this._advertisement) { - this.stopAdvertising(); - } - - var txtRecord = { - md: this.accessoryInfo.displayName, - pv: "1.0", - id: this.accessoryInfo.username, - "c#": this.accessoryInfo.configVersion + "", // "accessory conf" - represents the "configuration version" of an Accessory. Increasing this "version number" signals iOS devices to re-fetch /accessories data. - "s#": "1", // "accessory state" - "ff": "0", - "ci": this.accessoryInfo.category, - "sf": this.accessoryInfo.paired() ? "0" : "1", // "sf == 1" means "discoverable by HomeKit iOS clients" - "sh": this._setupHash - }; - - /** - * The host name of the component is probably better to be - * the username of the hosted accessory + '.local'. - * By default 'bonjour' doesnt add '.local' at the end of the os.hostname - * this causes to return 'raspberrypi' on raspberry pi / raspbian - * then when the phone queryies for A/AAAA record it is being queried - * on normal dns, not on mdns. By Adding the username of the accessory - * probably the problem will also fix a possible problem - * of having multiple pi's on same network - */ - var host = this.accessoryInfo.username.replace(/\:/ig, "_") + '.local'; - var advertiseName = this.accessoryInfo.displayName - + "-" - + crypto.createHash('sha512').update(this.accessoryInfo.username, 'utf8').digest('hex').slice(0, 4).toUpperCase(); - - // create/recreate our advertisement - this._advertisement = this._bonjourService.publish({ - name: advertiseName, - type: "hap", - port: port, - txt: txtRecord, - host: host - }); -} - -Advertiser.prototype.isAdvertising = function() { - return (this._advertisement != null); -} - -Advertiser.prototype.updateAdvertisement = function() { - if (this._advertisement) { - - var txtRecord = { - md: this.accessoryInfo.displayName, - pv: "1.0", - id: this.accessoryInfo.username, - "c#": this.accessoryInfo.configVersion + "", // "accessory conf" - represents the "configuration version" of an Accessory. Increasing this "version number" signals iOS devices to re-fetch /accessories data. - "s#": "1", // "accessory state" - "ff": "0", - "ci": this.accessoryInfo.category, - "sf": this.accessoryInfo.paired() ? "0" : "1", // "sf == 1" means "discoverable by HomeKit iOS clients" - "sh": this._setupHash - }; - - this._advertisement.updateTxt(txtRecord); - } -} - -Advertiser.prototype.stopAdvertising = function() { - if (this._advertisement) { - this._advertisement.stop(); - this._advertisement.destroy(); - this._advertisement = null; - } - - this._bonjourService.destroy(); -} - -Advertiser.prototype._computeSetupHash = function() { - var setupHashMaterial = this.accessoryInfo.setupID + this.accessoryInfo.username; - var hash = crypto.createHash('sha512'); - hash.update(setupHashMaterial); - var setupHash = hash.digest().slice(0, 4).toString('base64'); - - return setupHash; -} \ No newline at end of file diff --git a/lib/Camera.js b/lib/Camera.js deleted file mode 100644 index f6daba4e4..000000000 --- a/lib/Camera.js +++ /dev/null @@ -1,233 +0,0 @@ -'use strict'; - -var debug = require('debug')('Camera'); -var inherits = require('util').inherits; -var EventEmitter = require('events').EventEmitter; -var clone = require('./util/clone').clone; -var uuid = require('./util/uuid'); -var Service = require('./Service').Service; -var Characteristic = require('./Characteristic').Characteristic; -var StreamController = require('./StreamController').StreamController; -var HomeKitTypes = require('./gen/HomeKitTypes'); - -var crypto = require('crypto'); -var fs = require('fs'); -var ip = require('ip'); -var spawn = require('child_process').spawn; - -module.exports = { - Camera: Camera -}; - -function Camera() { - this.services = []; - this.streamControllers = []; - - this.pendingSessions = {}; - this.ongoingSessions = {}; - - let options = { - proxy: false, // Requires RTP/RTCP MUX Proxy - disable_audio_proxy: false, // If proxy = true, you can opt out audio proxy via this - srtp: true, // Supports SRTP AES_CM_128_HMAC_SHA1_80 encryption - video: { - resolutions: [ - [1920, 1080, 30], // Width, Height, framerate - [320, 240, 15], // Apple Watch requires this configuration - [1280, 960, 30], - [1280, 720, 30], - [1024, 768, 30], - [640, 480, 30], - [640, 360, 30], - [480, 360, 30], - [480, 270, 30], - [320, 240, 30], - [320, 180, 30] - ], - codec: { - profiles: [0, 1, 2], // Enum, please refer StreamController.VideoCodecParamProfileIDTypes - levels: [0, 1, 2] // Enum, please refer StreamController.VideoCodecParamLevelTypes - } - }, - audio: { - comfort_noise: false, - codecs: [ - { - type: "OPUS", // Audio Codec - samplerate: 24 // 8, 16, 24 KHz - }, - { - type: "AAC-eld", - samplerate: 16 - } - ] - } - } - - this.createCameraControlService(); - this._createStreamControllers(2, options); -} - -Camera.prototype.handleSnapshotRequest = function(request, callback) { - // Image request: {width: number, height: number} - // Please override this and invoke callback(error, image buffer) when the snapshot is ready - - var snapshot = fs.readFileSync(__dirname + '/res/snapshot.jpg'); - callback(undefined, snapshot); -} - -Camera.prototype.handleCloseConnection = function(connectionID) { - this.streamControllers.forEach(function(controller) { - controller.handleCloseConnection(connectionID); - }); -} - -Camera.prototype.prepareStream = function(request, callback) { - // Invoked when iOS device requires stream - - var sessionInfo = {}; - - let sessionID = request["sessionID"]; - let targetAddress = request["targetAddress"]; - - sessionInfo["address"] = targetAddress; - - var response = {}; - - let videoInfo = request["video"]; - if (videoInfo) { - let targetPort = videoInfo["port"]; - let srtp_key = videoInfo["srtp_key"]; - let srtp_salt = videoInfo["srtp_salt"]; - - // SSRC is a 32 bit integer that is unique per stream - let ssrcSource = crypto.randomBytes(4); - ssrcSource[0] = 0; - let ssrc = ssrcSource.readInt32BE(0, true); - - let videoResp = { - port: targetPort, - ssrc: ssrc, - srtp_key: srtp_key, - srtp_salt: srtp_salt - }; - - response["video"] = videoResp; - - sessionInfo["video_port"] = targetPort; - sessionInfo["video_srtp"] = Buffer.concat([srtp_key, srtp_salt]); - sessionInfo["video_ssrc"] = ssrc; - } - - let audioInfo = request["audio"]; - if (audioInfo) { - let targetPort = audioInfo["port"]; - let srtp_key = audioInfo["srtp_key"]; - let srtp_salt = audioInfo["srtp_salt"]; - - // SSRC is a 32 bit integer that is unique per stream - let ssrcSource = crypto.randomBytes(4); - ssrcSource[0] = 0; - let ssrc = ssrcSource.readInt32BE(0, true); - - let audioResp = { - port: targetPort, - ssrc: ssrc, - srtp_key: srtp_key, - srtp_salt: srtp_salt - }; - - response["audio"] = audioResp; - - sessionInfo["audio_port"] = targetPort; - sessionInfo["audio_srtp"] = Buffer.concat([srtp_key, srtp_salt]); - sessionInfo["audio_ssrc"] = ssrc; - } - - let currentAddress = ip.address(); - var addressResp = { - address: currentAddress - }; - - if (ip.isV4Format(currentAddress)) { - addressResp["type"] = "v4"; - } else { - addressResp["type"] = "v6"; - } - - response["address"] = addressResp; - this.pendingSessions[uuid.unparse(sessionID)] = sessionInfo; - - callback(response); -} - -Camera.prototype.handleStreamRequest = function(request) { - // Invoked when iOS device asks stream to start/stop/reconfigure - var sessionID = request["sessionID"]; - var requestType = request["type"]; - if (sessionID) { - let sessionIdentifier = uuid.unparse(sessionID); - - if (requestType == "start") { - var sessionInfo = this.pendingSessions[sessionIdentifier]; - if (sessionInfo) { - var width = 1280; - var height = 720; - var fps = 30; - var bitrate = 300; - - let videoInfo = request["video"]; - if (videoInfo) { - width = videoInfo["width"]; - height = videoInfo["height"]; - - let expectedFPS = videoInfo["fps"]; - if (expectedFPS < fps) { - fps = expectedFPS; - } - - bitrate = videoInfo["max_bit_rate"]; - } - - let targetAddress = sessionInfo["address"]; - let targetVideoPort = sessionInfo["video_port"]; - let videoKey = sessionInfo["video_srtp"]; - let videoSsrc = sessionInfo["video_ssrc"]; - - let ffmpegCommand = '-re -f avfoundation -r 29.970000 -i 0:0 -threads 0 -vcodec libx264 -an -pix_fmt yuv420p -r '+ fps +' -f rawvideo -tune zerolatency -vf scale='+ width +':'+ height +' -b:v '+ bitrate +'k -bufsize '+ bitrate +'k -payload_type 99 -ssrc '+ videoSsrc +' -f rtp -srtp_out_suite AES_CM_128_HMAC_SHA1_80 -srtp_out_params '+videoKey.toString('base64')+' srtp://'+targetAddress+':'+targetVideoPort+'?rtcpport='+targetVideoPort+'&localrtcpport='+targetVideoPort+'&pkt_size=1378'; - let ffmpeg = spawn('ffmpeg', ffmpegCommand.split(' '), {env: process.env}); - this.ongoingSessions[sessionIdentifier] = ffmpeg; - } - - delete this.pendingSessions[sessionIdentifier]; - } else if (requestType == "stop") { - var ffmpegProcess = this.ongoingSessions[sessionIdentifier]; - if (ffmpegProcess) { - ffmpegProcess.kill('SIGKILL'); - } - - delete this.ongoingSessions[sessionIdentifier]; - } - } -} - -Camera.prototype.createCameraControlService = function() { - var controlService = new Service.CameraControl(); - - // Developer can add control characteristics like rotation, night vision at here. - - this.services.push(controlService); -} - -// Private - -Camera.prototype._createStreamControllers = function(maxStreams, options) { - let self = this; - - for (var i = 0; i < maxStreams; i++) { - var streamController = new StreamController(i, options, self); - - self.services.push(streamController.service); - self.streamControllers.push(streamController); - } -} \ No newline at end of file diff --git a/lib/Characteristic.js b/lib/Characteristic.js deleted file mode 100644 index 3e64f9a77..000000000 --- a/lib/Characteristic.js +++ /dev/null @@ -1,492 +0,0 @@ -'use strict'; - -var inherits = require('util').inherits; -var EventEmitter = require('events').EventEmitter; -var once = require('./util/once').once; -var Decimal = require('decimal.js'); -var bufferShim = require('buffer-shims'); - -module.exports = { - Characteristic: 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 - * UUID. HomeKit provides a set of known Characteristic UUIDs defined in HomeKitTypes.js 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). - */ - -function Characteristic(displayName, UUID, props) { - this.displayName = displayName; - this.UUID = UUID; - this.iid = null; // assigned by our containing Service - this.value = null; - this.status = null; - this.eventOnlyCharacteristic = false; - this.props = props || { - format: null, - unit: null, - minValue: null, - maxValue: null, - minStep: null, - perms: [] - }; - - this.subscriptions = 0; -} - -inherits(Characteristic, EventEmitter); - -// Known HomeKit formats -Characteristic.Formats = { - BOOL: 'bool', - INT: 'int', - FLOAT: 'float', - STRING: 'string', - UINT8: 'uint8', - UINT16: 'uint16', - UINT32: 'uint32', - UINT64: 'uint64', - DATA: 'data', - TLV8: 'tlv8', - ARRAY: 'array', //Not in HAP Spec - DICTIONARY: 'dict' //Not in HAP Spec -} - -// Known HomeKit unit types -Characteristic.Units = { - // HomeKit only defines Celsius, for Fahrenheit, it requires iOS app to do the conversion. - CELSIUS: 'celsius', - PERCENTAGE: 'percentage', - ARC_DEGREE: 'arcdegrees', - LUX: 'lux', - SECONDS: 'seconds' -} - -// Known HomeKit permission types -Characteristic.Perms = { - READ: 'pr', //Kept for backwards compatability - PAIRED_READ: 'pr', //Added to match HAP's terminology - WRITE: 'pw', //Kept for backwards compatability - PAIRED_WRITE: 'pw', //Added to match HAP's terminology - NOTIFY: 'ev', //Kept for backwards compatability - EVENTS: 'ev', //Added to match HAP's terminology - ADDITIONAL_AUTHORIZATION: 'aa', - TIMED_WRITE: 'tw', //Not currently supported by IP - HIDDEN: 'hd', - WRITE_RESPONSE: 'wr' -} - -/** - * Copies the given properties to our props member variable, - * and returns 'this' for chaining. - * - * @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) - * } - */ - -Characteristic.prototype.setProps = function(props) { - for (var key in (props || {})) - if (Object.prototype.hasOwnProperty.call(props, key)) - this.props[key] = props[key]; - return this; -} - - -Characteristic.prototype.subscribe = function () { - if (this.subscriptions === 0) { - this.emit('subscribe'); - } - this.subscriptions++; -} - -Characteristic.prototype.unsubscribe = function() { - var wasOne = this.subscriptions === 1; - this.subscriptions--; - this.subscriptions = Math.max(this.subscriptions, 0); - if (wasOne) { - this.emit('unsubscribe'); - } -} - -Characteristic.prototype.getValue = function(callback, context, connectionID) { - // Handle special event only characteristics. - if (this.eventOnlyCharacteristic === true) { - if (callback) { - callback(null, null); - } - - return; - } - - if (this.listeners('get').length > 0) { - - // allow a listener to handle the fetching of this value, and wait for completion - this.emit('get', once(function(err, newValue) { - this.status = err; - if (err) { - // pass the error along to our callback - if (callback) callback(err); - } - else { - newValue = this.validateValue(newValue); //validateValue returns a value that has be cooerced into a valid value. - if (newValue === undefined || newValue === null) - newValue = this.getDefaultValue(); - - // getting the value was a success; we can pass it along and also update our cached value - var oldValue = this.value; - this.value = newValue; - if (callback) callback(null, newValue); - - // emit a change event if necessary - if (oldValue !== newValue) - this.emit('change', { oldValue:oldValue, newValue:newValue, context:context }); - } - - }.bind(this)), context, connectionID); - } - else { - - // no one is listening to the 'get' event, so just return the cached value - if (callback) - callback(this.status, this.value); - } -} - -Characteristic.prototype.validateValue = function(newValue) { - - var isNumericType = false; - var minValue_resolved = 0; - var maxValue_resolved = 0; - var minStep_resolved = undefined; - var stepDecimals = 0; - - switch(this.props.format) { - case 'int': - minStep_resolved=1; - minValue_resolved=-2147483648; - maxValue_resolved=2147483647; - isNumericType=true; - break; - case 'float': - minStep_resolved=undefined; - minValue_resolved=undefined; - maxValue_resolved=undefined; - isNumericType=true; - break; - case 'uint8': - minStep_resolved=1; - minValue_resolved=0; - maxValue_resolved=255; - isNumericType=true; - break; - case 'uint16': - minStep_resolved=1; - minValue_resolved=0; - maxValue_resolved=65535; - isNumericType=true; - break; - case 'uint32': - minStep_resolved=1; - minValue_resolved=0; - maxValue_resolved=4294967295; - isNumericType=true; - break; - case 'uint64': - minStep_resolved=1; - minValue_resolved=0; - maxValue_resolved=18446744073709551615; - isNumericType=true; - break; - //All of the following datatypes return from this switch. - case 'bool': - return (newValue == true); //We don't need to make sure this returns true or false - break; - case 'string': - var myString = newValue || ''; //If null or undefined or anything odd, make it a blank string - myString = String(myString); - var maxLength = this.props.maxLen; - if (maxLength === undefined) maxLength=64; //Default Max Length is 64. - if (myString.length>maxLength) myString = myString.substring(0,maxLength); //Truncate strings that are too long - return myString; //We don't need to do any validation after having truncated the string - break; - case 'data': - var maxLength = this.props.maxDataLen; - if (maxLength===undefined) maxLength=2097152; //Default Max Length is 2097152. - //if (newValue.length>maxLength) //I don't know the best way to handle this since it's unknown binary data. - //I suspect that it will crash HomeKit for this bridge if the length is too long. - return newValue; - break; - case '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 (isNaN(newValue)) return this.value; //This is not a number so we'll just pass out the last value. - if (newValue === false) return 0; - if (newValue === true) return 1; - if ((!isNaN(this.props.maxValue))&&(this.props.maxValue!==null)) maxValue_resolved=this.props.maxValue; - if ((!isNaN(this.props.minValue))&&(this.props.minValue!==null)) minValue_resolved=this.props.minValue; - if ((!isNaN(this.props.minStep))&&(this.props.minStep!==null)) minStep_resolved=this.props.minStep; - - if (newValuemaxValue_resolved) newValue = maxValue_resolved; //Fails Maximum Value Test - if (minStep_resolved!==undefined) { - //Determine how many decimals we need to display - if (Math.floor(minStep_resolved) === minStep_resolved) - stepDecimals = 0; - else - stepDecimals = minStep_resolved.toString().split(".")[1].length || 0; - - //Use Decimal to detemine the lowest value within the step. - try { - var decimalVal = new Decimal(newValue); - var decimalDiff = decimalVal.mod(minStep_resolved); - decimalVal = decimalVal.minus(decimalDiff); - if (stepDecimals === 0) { - newValue = parseInt(decimalVal.toFixed(0)); - } else { - newValue = parseFloat(decimalVal.toFixed(stepDecimals)); //Convert it to a fixed decimal - } - } catch (e) { - return this.value; //If we had an error, return the current value. - } - } - - if (this['valid-values']!==undefined) - if (!this['valid-values'].includes(newValue)) return this.value; //Fails Valid Values Test - if (this['valid-values-range']!==undefined) { //This is another way Apple has to handle min/max - if (newValuethis['valid-values-range'][1]) newValue=this['valid-values-range'][1]; - } - } - return newValue; -} - -Characteristic.prototype.setValue = function(newValue, callback, context, connectionID) { - - if ( newValue instanceof Error ) { - this.status = newValue - } else { - this.status = null; - } - - newValue = this.validateValue(newValue); //validateValue returns a value that has be cooerced into a valid value. - - var oldValue = this.value; - - if (this.listeners('set').length > 0) { - - // allow a listener to handle the setting of this value, and wait for completion - this.emit('set', newValue, once(function(err) { - this.status = err; - if (err) { - // pass the error along to our callback - if (callback) callback(err); - } - else { - if (newValue === undefined || newValue === null) - newValue = this.getDefaultValue(); - // setting the value was a success; so we can cache it now - this.value = newValue; - if (callback) callback(); - - if (this.eventOnlyCharacteristic === true || oldValue !== newValue) - this.emit('change', { oldValue:oldValue, newValue:newValue, context:context }); - } - - }.bind(this)), context, connectionID); - - } - else { - if (newValue === undefined || newValue === null) - newValue = this.getDefaultValue(); - // no one is listening to the 'set' event, so just assign the value blindly - this.value = newValue; - if (callback) callback(); - - if (this.eventOnlyCharacteristic === true || oldValue !== newValue) - this.emit('change', { oldValue:oldValue, newValue:newValue, context:context }); - } - - return this; // for chaining -} - -Characteristic.prototype.updateValue = function(newValue, callback, context) { - - if ( newValue instanceof Error ) { - this.status = newValue - } else { - this.status = null; - } - - newValue = this.validateValue(newValue); //validateValue returns a value that has be cooerced into a valid value. - - if (newValue === undefined || newValue === null) - newValue = this.getDefaultValue(); - // no one is listening to the 'set' event, so just assign the value blindly - var oldValue = this.value; - this.value = newValue; - if (callback) callback(); - - if (this.eventOnlyCharacteristic === true || oldValue !== newValue) - this.emit('change', { oldValue:oldValue, newValue:newValue, context:context }); - return this; // for chaining -} - -Characteristic.prototype.getDefaultValue = function() { - switch (this.props.format) { - case Characteristic.Formats.BOOL: return false; - case Characteristic.Formats.STRING: return ""; - case Characteristic.Formats.DATA: return null; // who knows! - case Characteristic.Formats.TLV8: return null; // who knows! - case Characteristic.Formats.DICTIONARY: return {}; - case Characteristic.Formats.ARRAY: return []; - default: return this.props.minValue || 0; - } -} - -Characteristic.prototype._assignID = function(identifierCache, accessoryName, serviceUUID, serviceSubtype) { - - // 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. - */ -Characteristic.prototype.toHAP = function(opt) { - - // ensure our value fits within our constraints if present - var value = this.value; - if (this.props.minValue != null && value < this.props.minValue) value = this.props.minValue; - if (this.props.maxValue != null && value > this.props.maxValue) value = this.props.maxValue; - if (this.props.format != null) { - if (this.props.format === Characteristic.Formats.INT) - value = parseInt(value); - else if (this.props.format === Characteristic.Formats.UINT8) - value = parseInt(value); - else if (this.props.format === Characteristic.Formats.UINT16) - value = parseInt(value); - else if (this.props.format === Characteristic.Formats.UINT32) - value = parseInt(value); - else if (this.props.format === Characteristic.Formats.UINT64) - value = parseInt(value); - else if (this.props.format === Characteristic.Formats.FLOAT) { - value = parseFloat(value); - if (this.props.minStep != null) { - var pow = Math.pow(10, decimalPlaces(this.props.minStep)); - value = Math.round(value * pow) / pow; - } - } - } - - if (this.eventOnlyCharacteristic === true) { - value = null; - } - - var hap = { - iid: this.iid, - type: this.UUID, - perms: this.props.perms, - 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 === Characteristic.Formats.STRING) { - var str = bufferShim.from(value, 'utf8'), - len = str.byteLength; - if (len > 256) { // 256 bytes is the max allowed length - hap.value = str.toString('utf8', 0, 256); - hap.maxLen = 256; - } else if (len > 64) { // values below can be ommited - hap.maxLen = len; - } - } - - // if we're not readable, omit the "value" property - otherwise iOS will complain about non-compliance - if (this.props.perms.indexOf(Characteristic.Perms.READ) == -1) - delete hap.value; - - // delete the "value" property anyway if we were asked to - if (opt && opt.omitValues) - delete hap.value; - - return hap; -} - -// Mike Samuel -// http://stackoverflow.com/questions/10454518/javascript-how-to-retrieve-the-number-of-decimals-of-a-string-number -function decimalPlaces(num) { - var match = (''+num).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/); - if (!match) { return 0; } - return Math.max( - 0, - // Number of digits right of decimal point. - (match[1] ? match[1].length : 0) - // Adjust for scientific notation. - - (match[2] ? +match[2] : 0)); -} diff --git a/lib/HAPServer.js b/lib/HAPServer.js deleted file mode 100644 index 684492923..000000000 --- a/lib/HAPServer.js +++ /dev/null @@ -1,1119 +0,0 @@ -'use strict'; - -var debug = require('debug')('HAPServer'); -var crypto = require('crypto'); -var srp = require('fast-srp-hap'); -var url = require('url'); -var inherits = require('util').inherits; -var tweetnacl = require('tweetnacl'); -var hkdf = require('./util/hkdf'); -var tlv = require('./util/tlv'); -var encryption = require('./util/encryption'); -var EventEmitter = require('events').EventEmitter; -var EventedHTTPServer = require('./util/eventedhttp').EventedHTTPServer; -var once = require('./util/once').once; -var bufferShim = require('buffer-shims'); - -module.exports = { - HAPServer: HAPServer -}; - - -/** - * The actual HAP server that iOS devices talk to. - * - * Notes - * ----- - * It turns out that the IP-based version of HomeKit's HAP protocol operates over a sort of pseudo-HTTP. - * Accessories are meant to host a TCP socket server that initially behaves exactly as an HTTP/1.1 server. - * So iOS devices will open up a long-lived connection to this server and begin issuing HTTP requests. - * So far, this conforms with HTTP/1.1 Keepalive. However, after the "pairing" process is complete, the - * connection is expected to be "upgraded" to support full-packet encryption of both HTTP headers and data. - * This encryption is NOT SSL. It is a customized ChaCha20+Poly1305 encryption layer. - * - * Additionally, this "HTTP Server" supports sending "event" responses at any time without warning. The iOS - * device simply keeps the connection open after it's finished with HTTP request/response traffic, and while - * the connection is open, the server can elect to issue "EVENT/1.0 200 OK" HTTP-style responses. These are - * 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. - */ - - -function HAPServer(accessoryInfo, relayServer) { - this.accessoryInfo = accessoryInfo; - this.allowInsecureRequest = false; - - // internal server that does all the actual communication - this._httpServer = new EventedHTTPServer(); - this._httpServer.on('listening', this._onListening.bind(this)); - this._httpServer.on('request', this._onRequest.bind(this)); - this._httpServer.on('encrypt', this._onEncrypt.bind(this)); - this._httpServer.on('decrypt', this._onDecrypt.bind(this)); - this._httpServer.on('session-close', this._onSessionClose.bind(this)); - - if (relayServer) { - this._relayServer = relayServer; - this._relayServer.on('request', this._onRemoteRequest.bind(this)); - this._relayServer.on('encrypt', this._onEncrypt.bind(this)); - this._relayServer.on('decrypt', this._onDecrypt.bind(this)); - } - - // so iOS is very reluctant to actually disconnect HAP connections (as in, sending a FIN packet). - // For instance, if you turn off wifi on your phone, it will not close the connection, instead - // it will leave it open and hope that it's still valid when it returns to the network. And Node, - // by itself, does not ever "discover" that the connection has been closed behind it, until a - // potentially very long system-level socket timeout (like, days). To work around this, we have - // invented a manual "keepalive" mechanism where we send "empty" events perodicially, such that - // when Node attempts to write to the socket, it discovers that it's been disconnected after - // an additional one-minute timeout (this timeout appears to be hardcoded). - this._keepAliveTimerID = setInterval(this._onKeepAliveTimerTick.bind(this), 1000 * 60 * 10); // send keepalive every 10 minutes -} - -inherits(HAPServer, EventEmitter); - -HAPServer.handlers = { - '/identify': '_handleIdentify', - '/pair-setup': '_handlePair', - '/pair-verify': '_handlePairVerify', - '/pairings': '_handlePairings', - '/accessories': '_handleAccessories', - '/characteristics': '_handleCharacteristics', - '/resource': '_handleResource' -} - -// Various "type" constants for HAP's TLV encoding. -HAPServer.Types = { - REQUEST_TYPE: 0x00, - USERNAME: 0x01, - SALT: 0x02, - PUBLIC_KEY: 0x03, - PASSWORD_PROOF: 0x04, - ENCRYPTED_DATA: 0x05, - SEQUENCE_NUM: 0x06, - ERROR_CODE: 0x07, - PROOF: 0x0a -} - -// Error codes and the like, guessed by packet inspection -HAPServer.Codes = { - INVALID_REQUEST: 0x02, - INVALID_SIGNATURE: 0x04 -} - -// Status codes for underlying HAP calls -HAPServer.Status = { - SUCCESS: 0, - INSUFFICIENT_PRIVILEGES: -70401, - SERVICE_COMMUNICATION_FAILURE: -70402, - RESOURCE_BUSY: -70403, - READ_ONLY_CHARACTERISTIC: -70404, - WRITE_ONLY_CHARACTERISTIC: -70405, - NOTIFICATION_NOT_SUPPORTED: -70406, - OUT_OF_RESOURCE: -70407, - OPERATION_TIMED_OUT: -70408, - RESOURCE_DOES_NOT_EXIST: -70409, - INVALID_VALUE_IN_REQUEST: -70410 -} - -HAPServer.prototype.listen = function(port) { - this._httpServer.listen(port); -} - -HAPServer.prototype.stop = function() { - this._httpServer.stop(); - clearInterval(this._keepAliveTimerID); -} - -HAPServer.prototype._onKeepAliveTimerTick = function() { - // 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: []}); -} - -/** - * Notifies connected clients who have subscribed to a particular event. - * - * @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 - */ -HAPServer.prototype.notifyClients = function(event, data, excludeEvents) { - // 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); - - if (this._relayServer) { - if (event !== 'keepalive') { - this._relayServer.sendEvent(event, data, excludeEvents); - } - } -} - -HAPServer.prototype._onListening = function(port) { - this.emit('listening', port); -} - -// Called when an HTTP request was detected. -HAPServer.prototype._onRequest = function(request, response, session, events) { - - debug("[%s] HAP Request: %s %s", this.accessoryInfo.username, request.method, request.url); - - // collect request data, if any - var requestData = bufferShim.alloc(0); - request.on('data', function(data) { requestData = Buffer.concat([requestData, data]); }); - request.on('end', function() { - - // parse request.url (which can contain querystring, etc.) into components, then extract just the path - var pathname = url.parse(request.url).pathname; - - // all request data received; now process this request - for (var path in HAPServer.handlers) - if (new RegExp('^'+path+'/?$').test(pathname)) { // match exact string and allow trailing slash - this[HAPServer.handlers[path]](request, response, session, events, requestData); - return; - } - - // 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(); - - }.bind(this)); -} - -HAPServer.prototype._onRemoteRequest = function(request, remoteSession, session, events) { - debug('[%s] Remote Request: %s', this.accessoryInfo.username, request.messageType); - if (request.messageType === 'pair-verify') - this._handleRemotePairVerify(request, remoteSession, session); - else if (request.messageType === 'discovery') - this._handleRemoteAccessories(request, remoteSession, session); - else if (request.messageType === 'write-characteristics') - this._handleRemoteCharacteristicsWrite(request, remoteSession, session, events); - else if (request.messageType === 'read-characteristics') - this._handleRemoteCharacteristicsRead(request, remoteSession, session, events); - else - debug('[%s] Remote Request Detail: %s', this.accessoryInfo.username, require('util').inspect(request, {showHidden: false, depth: null})); -} - -HAPServer.prototype._onEncrypt = function(data, encrypted, session) { - - // instance of HAPEncryption (created in handlePairVerifyStepOne) - var enc = session.encryption; - - // if accessoryToControllerKey is not empty, then encryption is enabled for this connection. However, we'll - // need to be careful to ensure that we don't encrypt the last few bytes of the response from handlePairVerifyStepTwo. - // Since all communication calls are asynchronous, we could easily receive this 'encrypt' event for those bytes. - // So we want to make sure that we aren't encrypting data until we have *received* some encrypted data from the - // client first. - if (enc && enc.accessoryToControllerKey.length > 0 && enc.controllerToAccessoryCount.value > 0) { - encrypted.data = encryption.layerEncrypt(data, enc.accessoryToControllerCount, enc.accessoryToControllerKey); - } -} - -HAPServer.prototype._onDecrypt = function(data, decrypted, session) { - - // possibly an instance of HAPEncryption (created in handlePairVerifyStepOne) - var enc = session.encryption; - - // if controllerToAccessoryKey is not empty, then encryption is enabled for this connection. - if (enc && enc.controllerToAccessoryKey.length > 0) { - decrypted.data = encryption.layerDecrypt(data, enc.controllerToAccessoryCount, enc.controllerToAccessoryKey, enc.extraInfo); - } -} - -HAPServer.prototype._onSessionClose = function(sessionID, events) { - this.emit("session-close", sessionID, events); -} - -/** - * Unpaired Accessory identification. - */ - -HAPServer.prototype._handleIdentify = function(request, response, session, events, requestData) { - // /identify only works if the accesory is not paired - if (!this.allowInsecureRequest && this.accessoryInfo.paired()) { - response.writeHead(400, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({status:HAPServer.Status.INSUFFICIENT_PRIVILEGES})); - - return; - } - - this.emit('identify', once(function(err) { - - if (!err) { - debug("[%s] Identification success", this.accessoryInfo.username); - response.writeHead(204); - response.end(); - } - else { - debug("[%s] Identification error: %s", this.accessoryInfo.username, err.message); - response.writeHead(500); - response.end(); - } - - }.bind(this))); -} - -/** - * iOS <-> Accessory pairing process. - */ - -HAPServer.prototype._handlePair = function(request, response, session, events, requestData) { - // Can only be directly paired with one iOS device - if (!this.allowInsecureRequest && this.accessoryInfo.paired()) { - response.writeHead(403); - response.end(); - - return; - } - - var objects = tlv.decode(requestData); - var sequence = objects[HAPServer.Types.SEQUENCE_NUM][0]; // value is single byte with sequence number - - if (sequence == 0x01) - this._handlePairStepOne(request, response, session); - else if (sequence == 0x03) - this._handlePairStepTwo(request, response, session, objects); - else if (sequence == 0x05) - this._handlePairStepThree(request, response, session, objects); -} - -// M1 + M2 -HAPServer.prototype._handlePairStepOne = function(request, response, session) { - debug("[%s] Pair step 1/5", this.accessoryInfo.username); - - var salt = crypto.randomBytes(16); - var srpParams = srp.params["3072"]; - - srp.genKey(32, function (error, key) { - - // create a new SRP server - var srpServer = new srp.Server(srpParams, bufferShim.from(salt), bufferShim.from("Pair-Setup"), bufferShim.from(this.accessoryInfo.pincode), key); - var srpB = srpServer.computeB(); - - // attach it to the current TCP session - session.srpServer = srpServer; - - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode( - HAPServer.Types.SEQUENCE_NUM, 0x02, - HAPServer.Types.SALT, salt, - HAPServer.Types.PUBLIC_KEY, srpB - )); - - }.bind(this)); -} - -// M3 + M4 -HAPServer.prototype._handlePairStepTwo = function(request, response, session, objects) { - debug("[%s] Pair step 2/5", this.accessoryInfo.username); - - var A = objects[HAPServer.Types.PUBLIC_KEY]; // "A is a public key that exists only for a single login session." - var M1 = objects[HAPServer.Types.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; - srpServer.setA(A); - - try { - srpServer.checkM1(M1); - } - catch (err) { - // most likely the client supplied an incorrect pincode. - debug("[%s] Error while checking pincode: %s", this.accessoryInfo.username, err.message); - - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode( - HAPServer.Types.SEQUENCE_NUM, 0x04, - HAPServer.Types.ERROR_CODE, HAPServer.Codes.INVALID_REQUEST - )); - - return; - } - - // "M2 is the proof that the server actually knows your password." - var M2 = srpServer.computeM2(); - - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode( - HAPServer.Types.SEQUENCE_NUM, 0x04, - HAPServer.Types.PASSWORD_PROOF, M2 - )); -} - -// M5-1 -HAPServer.prototype._handlePairStepThree = function(request, response, session, objects) { - 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[HAPServer.Types.ENCRYPTED_DATA]; - - var messageData = bufferShim.alloc(encryptedData.length - 16); - var authTagData = bufferShim.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 = bufferShim.from("Pair-Setup-Encrypt-Salt"); - var encInfo = bufferShim.from("Pair-Setup-Encrypt-Info"); - - var outputKey = hkdf.HKDF("sha512", encSalt, S_private, encInfo, 32); - - var plaintextBuffer = bufferShim.alloc(messageData.length); - encryption.verifyAndDecrypt(outputKey, bufferShim.from("PS-Msg05"), messageData, authTagData, null, plaintextBuffer); - - // decode the client payload and pass it on to the next step - var M5Packet = tlv.decode(plaintextBuffer); - - var clientUsername = M5Packet[HAPServer.Types.USERNAME]; - var clientLTPK = M5Packet[HAPServer.Types.PUBLIC_KEY]; - var clientProof = M5Packet[HAPServer.Types.PROOF]; - var hkdfEncKey = outputKey; - - this._handlePairStepFour(request, response, session, clientUsername, clientLTPK, clientProof, hkdfEncKey); -} - -// M5-2 -HAPServer.prototype._handlePairStepFour = function(request, response, session, clientUsername, clientLTPK, clientProof, hkdfEncKey) { - debug("[%s] Pair step 4/5", this.accessoryInfo.username); - - var S_private = session.srpServer.computeK(); - var controllerSalt = bufferShim.from("Pair-Setup-Controller-Sign-Salt"); - var controllerInfo = bufferShim.from("Pair-Setup-Controller-Sign-Info"); - var outputKey = hkdf.HKDF("sha512", controllerSalt, S_private, controllerInfo, 32); - - var completeData = Buffer.concat([outputKey, clientUsername, clientLTPK]); - - if (!tweetnacl.sign.detached.verify(completeData, clientProof, clientLTPK)) { - debug("[%s] Invalid signature", this.accessoryInfo.username); - - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode( - HAPServer.Types.SEQUENCE_NUM, 0x06, - HAPServer.Types.ERROR_CODE, HAPServer.Codes.INVALID_REQUEST - )); - - return; - } - - this._handlePairStepFive(request, response, session, clientUsername, clientLTPK, hkdfEncKey); -} - -// M5 - F + M6 -HAPServer.prototype._handlePairStepFive = function(request, response, session, clientUsername, clientLTPK, hkdfEncKey) { - debug("[%s] Pair step 5/5", this.accessoryInfo.username); - - var S_private = session.srpServer.computeK(); - var accessorySalt = bufferShim.from("Pair-Setup-Accessory-Sign-Salt"); - var accessoryInfo = bufferShim.from("Pair-Setup-Accessory-Sign-Info"); - var outputKey = hkdf.HKDF("sha512", accessorySalt, S_private, accessoryInfo, 32); - - var serverLTPK = this.accessoryInfo.signPk; - var usernameData = bufferShim.from(this.accessoryInfo.username); - - var material = Buffer.concat([outputKey, usernameData, serverLTPK]); - var privateKey = bufferShim.from(this.accessoryInfo.signSk); - - var serverProof = tweetnacl.sign.detached(material, privateKey); - - var message = tlv.encode( - HAPServer.Types.USERNAME, usernameData, - HAPServer.Types.PUBLIC_KEY, serverLTPK, - HAPServer.Types.PROOF, serverProof - ); - - var ciphertextBuffer = bufferShim.alloc(message.length); - var macBuffer = bufferShim.alloc(16); - encryption.encryptAndSeal(hkdfEncKey, bufferShim.from("PS-Msg06"), message, null, ciphertextBuffer, macBuffer); - - // finally, notify listeners that we have been paired with a client - this.emit('pair', clientUsername.toString(), clientLTPK, once(function(err) { - - if (err) { - debug("[%s] Error adding pairing info: %s", this.accessoryInfo.username, err.message); - response.writeHead(500, "Server Error"); - response.end(); - return; - } - - // send final pairing response to client - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode( - HAPServer.Types.SEQUENCE_NUM, 0x06, - HAPServer.Types.ENCRYPTED_DATA, Buffer.concat([ciphertextBuffer,macBuffer]) - )); - })); -} - -/** - * iOS <-> Accessory pairing verification. - */ - -HAPServer.prototype._handlePairVerify = function(request, response, session, events, requestData) { - // Don't allow pair-verify without being paired first - if (!this.allowInsecureRequest && !this.accessoryInfo.paired()) { - response.writeHead(403); - response.end(); - - return; - } - - var objects = tlv.decode(requestData); - var sequence = objects[HAPServer.Types.SEQUENCE_NUM][0]; // value is single byte with sequence number - - if (sequence == 0x01) - this._handlePairVerifyStepOne(request, response, session, objects); - else if (sequence == 0x03) - this._handlePairVerifyStepTwo(request, response, session, events, objects); -} - -HAPServer.prototype._handlePairVerifyStepOne = function(request, response, session, objects) { - debug("[%s] Pair verify step 1/2", this.accessoryInfo.username); - - var clientPublicKey = objects[HAPServer.Types.PUBLIC_KEY]; // Buffer - - // generate new encryption keys for this session - var keyPair = encryption.generateCurve25519KeyPair(); - var secretKey = bufferShim.from(keyPair.secretKey); - var publicKey = bufferShim.from(keyPair.publicKey); - var sharedSec = bufferShim.from(encryption.generateCurve25519SharedSecKey(secretKey, clientPublicKey)); - - var usernameData = bufferShim.from(this.accessoryInfo.username); - var material = Buffer.concat([publicKey, usernameData, clientPublicKey]); - var privateKey = bufferShim.from(this.accessoryInfo.signSk); - var serverProof = tweetnacl.sign.detached(material, privateKey); - - var encSalt = bufferShim.from("Pair-Verify-Encrypt-Salt"); - var encInfo = bufferShim.from("Pair-Verify-Encrypt-Info"); - - var outputKey = hkdf.HKDF("sha512", encSalt, sharedSec, encInfo, 32).slice(0,32); - - // store keys in a new instance of HAPEncryption - var enc = new HAPEncryption(); - enc.clientPublicKey = clientPublicKey; - enc.secretKey = secretKey; - enc.publicKey = publicKey; - enc.sharedSec = sharedSec; - enc.hkdfPairEncKey = outputKey; - - // store this in the current TCP session - session.encryption = enc; - - // compose the response data in TLV format - var message = tlv.encode( - HAPServer.Types.USERNAME, usernameData, - HAPServer.Types.PROOF, serverProof - ); - - // encrypt the response - var ciphertextBuffer = bufferShim.alloc(message.length); - var macBuffer = bufferShim.alloc(16); - encryption.encryptAndSeal(outputKey, bufferShim.from("PV-Msg02"), message, null, ciphertextBuffer, macBuffer); - - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode( - HAPServer.Types.SEQUENCE_NUM, 0x02, - HAPServer.Types.ENCRYPTED_DATA, Buffer.concat([ciphertextBuffer, macBuffer]), - HAPServer.Types.PUBLIC_KEY, publicKey - )); -} - -HAPServer.prototype._handlePairVerifyStepTwo = function(request, response, session, events, objects) { - debug("[%s] Pair verify step 2/2", this.accessoryInfo.username); - - var encryptedData = objects[HAPServer.Types.ENCRYPTED_DATA]; - - var messageData = bufferShim.alloc(encryptedData.length - 16); - var authTagData = bufferShim.alloc(16); - encryptedData.copy(messageData, 0, 0, encryptedData.length - 16); - encryptedData.copy(authTagData, 0, encryptedData.length - 16, encryptedData.length); - - var plaintextBuffer = bufferShim.alloc(messageData.length); - - // instance of HAPEncryption (created in handlePairVerifyStepOne) - var enc = session.encryption; - - if (!encryption.verifyAndDecrypt(enc.hkdfPairEncKey, bufferShim.from("PV-Msg03"), messageData, authTagData, null, plaintextBuffer)) { - debug("[%s] M3: Invalid signature", this.accessoryInfo.username); - - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode( - HAPServer.Types.ERROR_CODE, HAPServer.Codes.INVALID_REQUEST - )); - - return; - } - - var decoded = tlv.decode(plaintextBuffer); - var clientUsername = decoded[HAPServer.Types.USERNAME]; - var proof = decoded[HAPServer.Types.PROOF]; - - var 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()); - - // if we're not actually paired, then there's nothing to verify - this client thinks it's paired with us but we - // disagree. Respond with invalid request (seems to match HomeKit Accessory Simulator behavior) - if (!clientPublicKey) { - debug("[%s] Client %s attempting to verify, but we are not paired; rejecting client", this.accessoryInfo.username, clientUsername); - - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode( - HAPServer.Types.ERROR_CODE, HAPServer.Codes.INVALID_REQUEST - )); - - return; - } - - if (!tweetnacl.sign.detached.verify(material, proof, clientPublicKey)) { - debug("[%s] Client %s provided an invalid signature", this.accessoryInfo.username, clientUsername); - - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode( - HAPServer.Types.ERROR_CODE, HAPServer.Codes.INVALID_REQUEST - )); - - return; - } - - debug("[%s] Client %s verification complete", this.accessoryInfo.username, clientUsername); - - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode( - HAPServer.Types.SEQUENCE_NUM, 0x04 - )); - - // now that the client has been verified, we must "upgrade" our pesudo-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 = bufferShim.from("Control-Salt"); - var infoRead = bufferShim.from("Control-Read-Encryption-Key"); - var infoWrite = bufferShim.from("Control-Write-Encryption-Key"); - - enc.accessoryToControllerKey = hkdf.HKDF("sha512", encSalt, enc.sharedSec, infoRead, 32); - enc.controllerToAccessoryKey = hkdf.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; -} - -HAPServer.prototype._handleRemotePairVerify = function(request, remoteSession, session) { - var objects = tlv.decode(request.requestBody); - var sequence = objects[HAPServer.Types.SEQUENCE_NUM][0]; // value is single byte with sequence number - - if (sequence == 0x01) - this._handleRemotePairVerifyStepOne(request, remoteSession, session, objects); - else if (sequence == 0x03) - this._handleRemotePairVerifyStepTwo(request, remoteSession, session, objects); -} - -HAPServer.prototype._handleRemotePairVerifyStepOne = function(request, remoteSession, session, objects) { - debug("[%s] Remote Pair verify step 1/2", this.accessoryInfo.username); - - var clientPublicKey = objects[HAPServer.Types.PUBLIC_KEY]; // Buffer - - // generate new encryption keys for this session - var keyPair = encryption.generateCurve25519KeyPair(); - var secretKey = bufferShim.from(keyPair.secretKey); - var publicKey = bufferShim.from(keyPair.publicKey); - var sharedSec = bufferShim.from(encryption.generateCurve25519SharedSecKey(secretKey, clientPublicKey)); - - var usernameData = bufferShim.from(this.accessoryInfo.username); - var material = Buffer.concat([publicKey, usernameData, clientPublicKey]); - var privateKey = bufferShim.from(this.accessoryInfo.signSk); - var serverProof = tweetnacl.sign.detached(material, privateKey); - - var encSalt = bufferShim.from("Pair-Verify-Encrypt-Salt"); - var encInfo = bufferShim.from("Pair-Verify-Encrypt-Info"); - - var outputKey = hkdf.HKDF("sha512", encSalt, sharedSec, encInfo, 32).slice(0,32); - - // store keys in a new instance of HAPEncryption - var enc = new HAPEncryption(); - enc.clientPublicKey = clientPublicKey; - enc.secretKey = secretKey; - enc.publicKey = publicKey; - enc.sharedSec = sharedSec; - enc.hkdfPairEncKey = outputKey; - - // store this in the current TCP session - session.encryption = enc; - - // compose the response data in TLV format - var message = tlv.encode( - HAPServer.Types.USERNAME, usernameData, - HAPServer.Types.PROOF, serverProof - ); - - // encrypt the response - var ciphertextBuffer = bufferShim.alloc(message.length); - var macBuffer = bufferShim.alloc(16); - encryption.encryptAndSeal(outputKey, bufferShim.from("PV-Msg02"), message, null, ciphertextBuffer, macBuffer); - - var response = tlv.encode( - HAPServer.Types.SEQUENCE_NUM, 0x02, - HAPServer.Types.ENCRYPTED_DATA, Buffer.concat([ciphertextBuffer, macBuffer]), - HAPServer.Types.PUBLIC_KEY, publicKey - ); - - remoteSession.responseMessage(request, response); -} - -HAPServer.prototype._handleRemotePairVerifyStepTwo = function(request, remoteSession, session, objects) { - debug("[%s] Remote Pair verify step 2/2", this.accessoryInfo.username); - - var encryptedData = objects[HAPServer.Types.ENCRYPTED_DATA]; - - var messageData = bufferShim.alloc(encryptedData.length - 16); - var authTagData = bufferShim.alloc(16); - encryptedData.copy(messageData, 0, 0, encryptedData.length - 16); - encryptedData.copy(authTagData, 0, encryptedData.length - 16, encryptedData.length); - - var plaintextBuffer = bufferShim.alloc(messageData.length); - - // instance of HAPEncryption (created in handlePairVerifyStepOne) - var enc = session.encryption; - - if (!encryption.verifyAndDecrypt(enc.hkdfPairEncKey, bufferShim.from("PV-Msg03"), messageData, authTagData, null, plaintextBuffer)) { - debug("[%s] M3: Invalid signature", this.accessoryInfo.username); - - var response = tlv.encode( - HAPServer.Types.ERROR_CODE, HAPServer.Codes.INVALID_REQUEST - ); - - remoteSession.responseMessage(request, response); - return; - } - - var decoded = tlv.decode(plaintextBuffer); - var clientUsername = decoded[HAPServer.Types.USERNAME]; - var proof = decoded[HAPServer.Types.PROOF]; - - var 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()); - - // if we're not actually paired, then there's nothing to verify - this client thinks it's paired with us but we - // disagree. Respond with invalid request (seems to match HomeKit Accessory Simulator behavior) - if (!clientPublicKey) { - debug("[%s] Client %s attempting to verify, but we are not paired; rejecting client", this.accessoryInfo.username, clientUsername); - - var response = tlv.encode( - HAPServer.Types.ERROR_CODE, HAPServer.Codes.INVALID_REQUEST - ); - - remoteSession.responseMessage(request, response); - return; - } - - if (!tweetnacl.sign.detached.verify(material, proof, clientPublicKey)) { - debug("[%s] Client %s provided an invalid signature", this.accessoryInfo.username, clientUsername); - - var response = tlv.encode( - HAPServer.Types.ERROR_CODE, HAPServer.Codes.INVALID_REQUEST - ); - - remoteSession.responseMessage(request, response); - return; - } - - debug("[%s] Client %s verification complete", this.accessoryInfo.username, clientUsername); - - var encSalt = bufferShim.from("Control-Salt"); - var infoRead = bufferShim.from("Control-Read-Encryption-Key"); - var infoWrite = bufferShim.from("Control-Write-Encryption-Key"); - - enc.accessoryToControllerKey = hkdf.HKDF("sha512", encSalt, enc.sharedSec, infoRead, 32); - enc.controllerToAccessoryKey = hkdf.HKDF("sha512", encSalt, enc.sharedSec, infoWrite, 32); - - var response = tlv.encode( - HAPServer.Types.SEQUENCE_NUM, 0x04 - ); - - remoteSession.responseMessage(request, response); -} - -/** - * Pair add/remove - */ - -HAPServer.prototype._handlePairings = function(request, response, session, events, requestData) { - - // Only accept /pairing request if there is a secure session - if (!this.allowInsecureRequest && !session.encryption) { - response.writeHead(401, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({status:HAPServer.Status.INSUFFICIENT_PRIVILEGES})); - - return; - } - - var objects = tlv.decode(requestData); - var requestType = objects[HAPServer.Types.REQUEST_TYPE][0]; // value is single byte with request type - - if (requestType == 3) { - - // technically we're already paired and communicating securely if the client is able to call /pairings at all! - // but maybe the client wants to change their public key? so we'll emit 'pair' like we just paired again - - debug("[%s] Adding pairing info for client", this.accessoryInfo.username); - var clientUsername = objects[HAPServer.Types.USERNAME]; - var clientLTPK = objects[HAPServer.Types.PUBLIC_KEY]; - - this.emit('pair', clientUsername.toString(), clientLTPK, once(function(err) { - - if (err) { - debug("[%s] Error adding pairing info: %s", this.accessoryInfo.username, err.message); - response.writeHead(500, "Server Error"); - response.end(); - return; - } - - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(HAPServer.Types.SEQUENCE_NUM, 0x02)); - - }.bind(this))); - } - else if (requestType == 4) { - - debug("[%s] Removing pairing info for client", this.accessoryInfo.username); - var clientUsername = objects[HAPServer.Types.USERNAME]; - - this.emit('unpair', clientUsername.toString(), once(function(err) { - - if (err) { - debug("[%s] Error removing pairing info: %s", this.accessoryInfo.username, err.message); - response.writeHead(500, "Server Error"); - response.end(); - return; - } - - response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); - response.end(tlv.encode(HAPServer.Types.SEQUENCE_NUM, 0x02)); - - }.bind(this))); - } -} - - -/* - * Handlers for all after-pairing communication, or the bulk of HAP. - */ - -// Called when the client wishes to fetch all data regarding our published Accessories. -HAPServer.prototype._handleAccessories = function(request, response, session, events, requestData) { - if (!this.allowInsecureRequest && !session.encryption) { - response.writeHead(401, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({status:HAPServer.Status.INSUFFICIENT_PRIVILEGES})); - - return; - } - - // call out to listeners to retrieve the latest accessories JSON - this.emit('accessories', once(function(err, accessories) { - - 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)); - })); -} - -HAPServer.prototype._handleRemoteAccessories = function(request, remoteSession, session) { - var deserializedRequest = JSON.parse(request.requestBody); - - if (!deserializedRequest['attribute-database']) { - var response = { - 'configuration-number': this.accessoryInfo.configVersion - } - remoteSession.responseMessage(request, bufferShim.from(JSON.stringify(response))); - } else { - var self = this; - // call out to listeners to retrieve the latest accessories JSON - this.emit('accessories', once(function(err, accessories) { - - if (err) { - debug("[%s] Error getting accessories: %s", this.accessoryInfo.username, err.message); - return; - } - - var response = { - 'configuration-number': self.accessoryInfo.configVersion, - 'attribute-database': accessories - } - remoteSession.responseMessage(request, bufferShim.from(JSON.stringify(response))); - })); - } -} - -// Called when the client wishes to get or set particular characteristics -HAPServer.prototype._handleCharacteristics = function(request, response, session, events, requestData) { - if (!this.allowInsecureRequest && !session.encryption) { - response.writeHead(401, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({status:HAPServer.Status.INSUFFICIENT_PRIVILEGES})); - - return; - } - - if (request.method == "GET") { - - // Extract the query params from the URL which looks like: /characteristics?id=1.9,2.14,... - var parseQueryString = true; - var query = url.parse(request.url, parseQueryString).query; // { id: '1.9,2.14' } - - if(query == undefined || query.id == undefined) { - response.writeHead(500); - response.end(); - return; - } - - var sets = query.id.split(','); // ["1.9","2.14"] - var data = []; // [{aid:1,iid:9},{aid:2,iid:14}] - - for (var i in sets) { - var ids = sets[i].split('.'); // ["1","9"] - var aid = parseInt(ids[0]); // accessory ID - var iid = parseInt(ids[1]); // instance ID (for characteristic) - data.push({aid:aid,iid:iid}); - } - - this.emit('get-characteristics', data, events, once(function(err, characteristics) { - - if (!characteristics && !err) - err = new Error("characteristics not supplied by the get-characteristics event callback"); - - if (err) { - debug("[%s] Error getting characteristics: %s", this.accessoryInfo.username, err.stack); - - // rewrite characteristics array to include error status for each characteristic requested - characteristics = []; - for (var i in data) { - characteristics.push({ - aid: data[i].aid, - iid: data[i].iid, - status: HAPServer.Status.SERVICE_COMMUNICATION_FAILURE - }); - } - } - - // 207 is "multi-status" since HomeKit may be requesting multiple things and any one can fail independently - response.writeHead(207, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({characteristics:characteristics})); - - }.bind(this)), false, session.sessionID); - } - else if (request.method == "PUT") { - if (!session.encryption) { - if (!request.headers || (request.headers && request.headers["authorization"] !== this.accessoryInfo.pincode)) { - response.writeHead(401, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({status:HAPServer.Status.INSUFFICIENT_PRIVILEGES})); - - return; - } - } - - if(requestData.length == 0) { - response.writeHead(400, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({status:HAPServer.Status.INVALID_VALUE_IN_REQUEST})); - - return; - } - - // requestData is a JSON payload like { characteristics: [ { aid: 1, iid: 8, value: true, ev: true } ] } - var data = JSON.parse(requestData.toString()).characteristics; // pull out characteristics array - - // call out to listeners to retrieve the latest accessories JSON - this.emit('set-characteristics', data, events, once(function(err, characteristics) { - - if (err) { - debug("[%s] Error setting characteristics: %s", this.accessoryInfo.username, err.message); - - // rewrite characteristics array to include error status for each characteristic requested - characteristics = []; - for (var i in data) { - characteristics.push({ - aid: data[i].aid, - iid: data[i].iid, - status: HAPServer.Status.SERVICE_COMMUNICATION_FAILURE - }); - } - } - - // 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})); - - }.bind(this)), false, session.sessionID); - } -} - -// Called when controller request snapshot -HAPServer.prototype._handleResource = function(request, response, session, events, requestData) { - if (!this.allowInsecureRequest && !session.encryption) { - response.writeHead(401, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({status:HAPServer.Status.INSUFFICIENT_PRIVILEGES})); - - return; - } - - if (this.listeners('request-resource').length == 0) { - response.writeHead(405); - response.end(); - - return; - } - - if (request.method == "POST") { - if (!session.encryption) { - if (!request.headers || (request.headers && request.headers["authorization"] !== this.accessoryInfo.pincode)) { - response.writeHead(401, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({status:HAPServer.Status.INSUFFICIENT_PRIVILEGES})); - - return; - } - } - - if(requestData.length == 0) { - response.writeHead(400, {"Content-Type": "application/hap+json"}); - response.end(JSON.stringify({status:HAPServer.Status.INVALID_VALUE_IN_REQUEST})); - - return; - } - - // requestData is a JSON payload - var data = JSON.parse(requestData.toString()); - - // call out to listeners to retrieve the resource, snapshot only right now - this.emit('request-resource', data, once(function(err, resource) { - - if (err) { - debug("[%s] Error getting snapshot: %s", this.accessoryInfo.username, err.message); - - response.writeHead(500); - response.end(); - } else { - response.writeHead(200, {"Content-Type": "image/jpeg"}); - response.end(resource); - } - - }.bind(this))); - } else { - response.writeHead(405); - response.end(); - } -} - -HAPServer.prototype._handleRemoteCharacteristicsWrite = function(request, remoteSession, session, events) { - var data = JSON.parse(request.requestBody.toString()); - - // call out to listeners to retrieve the latest accessories JSON - this.emit('set-characteristics', data, events, once(function(err, characteristics) { - - if (err) { - debug("[%s] Error setting characteristics: %s", this.accessoryInfo.username, err.message); - - // rewrite characteristics array to include error status for each characteristic requested - characteristics = []; - for (var i in data) { - characteristics.push({ - aid: data[i].aid, - iid: data[i].iid, - s: HAPServer.Status.SERVICE_COMMUNICATION_FAILURE - }); - } - } - - remoteSession.responseMessage(request, bufferShim.from(JSON.stringify(characteristics))); - }.bind(this)), true); -} - -HAPServer.prototype._handleRemoteCharacteristicsRead = function(request, remoteSession, session, events) { - var data = JSON.parse(request.requestBody.toString()); - - this.emit('get-characteristics', data, events, function(err, characteristics) { - - if (!characteristics && !err) - err = new Error("characteristics not supplied by the get-characteristics event callback"); - - if (err) { - debug("[%s] Error getting characteristics: %s", this.accessoryInfo.username, err.stack); - - // rewrite characteristics array to include error status for each characteristic requested - characteristics = []; - for (var i in data) { - characteristics.push({ - aid: data[i].aid, - iid: data[i].iid, - s: HAPServer.Status.SERVICE_COMMUNICATION_FAILURE - }); - } - } - remoteSession.responseMessage(request, bufferShim.from(JSON.stringify(characteristics))); - }.bind(this), true); -} - -/** - * Simple struct to hold vars needed to support HAP encryption. - */ - -function HAPEncryption() { - // initialize member vars with null-object values - this.clientPublicKey = bufferShim.alloc(0); - this.secretKey = bufferShim.alloc(0); - this.publicKey = bufferShim.alloc(0); - this.sharedSec = bufferShim.alloc(0); - this.hkdfPairEncKey = bufferShim.alloc(0); - this.accessoryToControllerCount = { value: 0 }; - this.controllerToAccessoryCount = { value: 0 }; - this.accessoryToControllerKey = bufferShim.alloc(0); - this.controllerToAccessoryKey = bufferShim.alloc(0); - this.extraInfo = {}; -} diff --git a/lib/Service.js b/lib/Service.js deleted file mode 100644 index 749e93daf..000000000 --- a/lib/Service.js +++ /dev/null @@ -1,282 +0,0 @@ -'use strict'; - -var inherits = require('util').inherits; -var clone = require('./util/clone').clone; -var EventEmitter = require('events').EventEmitter; -var Characteristic = require('./Characteristic').Characteristic; - -module.exports = { - Service: Service -}; - -/** - * 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 - * "current lock state". A particular Service is distinguished from others by its "type", which is a UUID. - * HomeKit provides a set of known Service UUIDs defined in HomeKitTypes.js along with a corresponding - * concrete subclass that you can instantiate directly to setup the necessary values. These natively-supported - * Services are expected to contain a particular set of Characteristics. - * - * Unlike Characteristics, where you cannot have two Characteristics with the same UUID in the same Service, - * you can actually have multiple Services with the same UUID in a single Accessory. For instance, imagine - * a Garage Door Opener with both a "security light" and a "backlight" for the display. Each light could be - * a "Lightbulb" Service with the same UUID. To account for this situation, we define an extra "subtype" - * property on Service, that can be a string or other string-convertible object that uniquely identifies the - * Service among its peers in an Accessory. For instance, you might have `service1.subtype = 'security_light'` - * for one and `service2.subtype = 'backlight'` for the other. - * - * 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. - */ - -function Service(displayName, UUID, subtype) { - - if (!UUID) throw new Error("Services must be created with a valid UUID."); - - this.displayName = displayName; - this.UUID = UUID; - this.subtype = subtype; - this.iid = null; // assigned later by our containing Accessory - this.characteristics = []; - this.optionalCharacteristics = []; - - this.isHiddenService = false; - this.isPrimaryService = false; - this.linkedServices = []; - - // every service has an optional Characteristic.Name property - we'll set it to our displayName - // if one was given - // if you don't provide a display name, some HomeKit apps may choose to hide the device. - if (displayName) { - // create the characteristic if necessary - var nameCharacteristic = - this.getCharacteristic(Characteristic.Name) || - this.addCharacteristic(Characteristic.Name); - - nameCharacteristic.setValue(displayName); - } -} - -inherits(Service, EventEmitter); - -Service.prototype.addCharacteristic = function (characteristic) { - // characteristic might be a constructor like `Characteristic.Brightness` instead of an instance - // of Characteristic. Coerce if necessary. - if (typeof characteristic === 'function') { - characteristic = new (Function.prototype.bind.apply(characteristic, arguments)); - } - // check for UUID conflict - for (var index in this.characteristics) { - var 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. - return existing; - } - throw new Error("Cannot add a Characteristic with the same UUID as another Characteristic in this Service: " + existing.UUID); - } - } - - // listen for changes in characteristics and bubble them up - characteristic.on('change', function (change) { - // make a new object with the relevant characteristic added, and bubble it up - this.emit('characteristic-change', clone(change, { characteristic: characteristic })); - }.bind(this)); - - this.characteristics.push(characteristic); - - this.emit('service-configurationChange', clone({ service: this })); - - return characteristic; -} - -//Defines this service as hidden -Service.prototype.setHiddenService = function (isHidden) { - this.isHiddenService = isHidden; - this.emit('service-configurationChange', clone({ service: this })); -} - -//Allows setting other services that link to this one. -Service.prototype.addLinkedService = function (newLinkedService) { - //TODO: Add a check if the service is on the same accessory. - if (!this.linkedServices.includes(newLinkedService)) - this.linkedServices.push(newLinkedService); - this.emit('service-configurationChange', clone({ service: this })); -} - -Service.prototype.removeLinkedService = function (oldLinkedService) { - //TODO: Add a check if the service is on the same accessory. - if (this.linkedServices.includes(oldLinkedService)) - this.linkedServices.splice(this.linkedServices.indexOf(oldLinkedService), 1); - this.emit('service-configurationChange', clone({ service: this })); -} - -Service.prototype.removeCharacteristic = function (characteristic) { - var targetCharacteristicIndex; - - for (var index in this.characteristics) { - var existingCharacteristic = this.characteristics[index]; - - if (existingCharacteristic === characteristic) { - targetCharacteristicIndex = index; - break; - } - } - - if (targetCharacteristicIndex) { - this.characteristics.splice(targetCharacteristicIndex, 1); - characteristic.removeAllListeners(); - - this.emit('service-configurationChange', clone({ service: this })); - } -} - -Service.prototype.getCharacteristic = function (name) { - // returns a characteristic object from the service - // If Service.prototype.getCharacteristic(Characteristic.Type) does not find the characteristic, - // but the type is in optionalCharacteristics, it adds the characteristic.type to the service and returns it. - var index, characteristic; - for (index in this.characteristics) { - characteristic = this.characteristics[index]; - if (typeof name === 'string' && characteristic.displayName === name) { - return characteristic; - } - else if (typeof name === 'function' && ((characteristic instanceof name) || (name.UUID === characteristic.UUID))) { - return characteristic; - } - } - if (typeof name === 'function') { - for (index in this.optionalCharacteristics) { - characteristic = this.optionalCharacteristics[index]; - if ((characteristic instanceof name) || (name.UUID === characteristic.UUID)) { - return this.addCharacteristic(name); - } - } - //Not found in optional Characteristics. Adding anyway, but warning about it if it isn't the Name. - if (name !== Characteristic.Name) { - console.warn("HAP Warning: Characteristic %s not in required or optional characteristics for service %s. Adding anyway.", name.UUID, this.UUID); - return this.addCharacteristic(name); - } - } -}; - -Service.prototype.testCharacteristic = function (name) { - // checks for the existence of a characteristic object in the service - var index, characteristic; - for (index in this.characteristics) { - characteristic = this.characteristics[index]; - if (typeof name === 'string' && characteristic.displayName === name) { - return true; - } - else if (typeof name === 'function' && ((characteristic instanceof name) || (name.UUID === characteristic.UUID))) { - return true; - } - } - return false; -} - -Service.prototype.setCharacteristic = function (name, value) { - this.getCharacteristic(name).setValue(value); - return this; // for chaining -} - -// A function to only updating the remote value, but not firiring the 'set' event. -Service.prototype.updateCharacteristic = function (name, value) { - this.getCharacteristic(name).updateValue(value); - return this; -} - -Service.prototype.addOptionalCharacteristic = function (characteristic) { - // characteristic might be a constructor like `Characteristic.Brightness` instead of an instance - // of Characteristic. Coerce if necessary. - if (typeof characteristic === 'function') - characteristic = new characteristic(); - - this.optionalCharacteristics.push(characteristic); -} - -Service.prototype.getCharacteristicByIID = function (iid) { - for (var index in this.characteristics) { - var characteristic = this.characteristics[index]; - if (characteristic.iid === iid) - return characteristic; - } -} - -Service.prototype._assignIDs = function (identifierCache, accessoryName, baseIID) { - if (baseIID === undefined) { - baseIID = 0; - } - // the Accessory Information service must have a (reserved by IdentifierCache) ID of 1 - if (this.UUID === '0000003E-0000-1000-8000-0026BB765291') { - this.iid = 1; - } - else { - // assign our own ID based on our UUID - this.iid = baseIID + identifierCache.getIID(accessoryName, this.UUID, this.subtype); - } - - // assign IIDs to our Characteristics - for (var index in this.characteristics) { - var characteristic = this.characteristics[index]; - characteristic._assignID(identifierCache, accessoryName, this.UUID, this.subtype); - } -} - -/** - * Returns a JSON representation of this Accessory suitable for delivering to HAP clients. - */ -Service.prototype.toHAP = function (opt) { - - var characteristicsHAP = []; - - for (var index in this.characteristics) { - var characteristic = this.characteristics[index]; - characteristicsHAP.push(characteristic.toHAP(opt)); - } - - var hap = { - iid: this.iid, - type: this.UUID, - characteristics: characteristicsHAP - }; - - if (this.isPrimaryService !== undefined) { - hap['primary'] = this.isPrimaryService; - } - - if (this.isHiddenService !== undefined) { - hap['hidden'] = this.isHiddenService; - } - - if (this.linkedServices.length > 0) { - hap['linked'] = []; - for (var index in this.linkedServices) { - var otherService = this.linkedServices[index]; - hap['linked'].push(otherService.iid); - } - } - - return hap; -} - -Service.prototype._setupCharacteristic = function (characteristic) { - // listen for changes in characteristics and bubble them up - characteristic.on('change', function (change) { - // make a new object with the relevant characteristic added, and bubble it up - this.emit('characteristic-change', clone(change, { characteristic: characteristic })); - }.bind(this)); -} - -Service.prototype._sideloadCharacteristics = function (targetCharacteristics) { - for (var index in targetCharacteristics) { - var target = targetCharacteristics[index]; - this._setupCharacteristic(target); - } - - this.characteristics = targetCharacteristics.slice(); -} diff --git a/lib/StreamController.js b/lib/StreamController.js deleted file mode 100644 index 50f9cc420..000000000 --- a/lib/StreamController.js +++ /dev/null @@ -1,895 +0,0 @@ -'use strict'; - -var debug = require('debug')('StreamController'); -var inherits = require('util').inherits; -var EventEmitter = require('events').EventEmitter; -var clone = require('./util/clone').clone; -var uuid = require('./util/uuid'); -var tlv = require('./util/tlv'); -var Service = require('./Service').Service; -var Characteristic = require('./Characteristic').Characteristic; -var HomeKitTypes = require('./gen/HomeKitTypes'); - -var RTPProxy = require('./camera/RTPProxy'); -var crypto = require('crypto'); -var ip = require('ip'); -var bufferShim = require('buffer-shims'); - -module.exports = { - StreamController: StreamController -}; - -function StreamController(identifier, options, cameraSource) { - if (identifier === undefined) { - throw new Error('Identifier cannot be undefined'); - } - - if (!options) { - throw new Error('Options cannot be undefined'); - } - - if (!cameraSource) { - throw new Error('CameraSource cannot be undefined'); - } - - let self = this; - self.identifier = identifier; - self.cameraSource = cameraSource; - - self.requireProxy = options["proxy"] || false; - self.disableAudioProxy = options["disable_audio_proxy"] || false; - - self.supportSRTP = options["srtp"] || false; - - self.supportedRTPConfiguration = self._supportedRTPConfiguration(self.supportSRTP); - - let videoParams = options["video"]; - if (!videoParams) { - throw new Error('Video parameters cannot be undefined in options'); - } - - self.supportedVideoStreamConfiguration = self._supportedVideoStreamConfiguration(videoParams); - - let audioParams = options["audio"]; - if (!audioParams) { - throw new Error('Audio parameters cannot be undefined in options'); - } - - self.supportedAudioStreamConfiguration = self._supportedAudioStreamConfiguration(audioParams); - - self.selectedConfiguration = null; - self.sessionIdentifier = null; - self.streamStatus = StreamController.StreamingStatus.AVAILABLE; - self.videoOnly = false; - - self._createService(); -} - -StreamController.prototype.forceStop = function() { - this.connectionID = undefined; - this._handleStopStream(undefined, true); -} - -StreamController.prototype.handleCloseConnection = function(connectionID) { - if (this.connectionID && this.connectionID == connectionID) { - this.connectionID = undefined; - this._handleStopStream(); - } -} - -StreamController.SetupTypes = { - SESSION_ID: 0x01, - STATUS: 0x02, - ADDRESS: 0x03, - VIDEO_SRTP_PARAM: 0x04, - AUDIO_SRTP_PARAM: 0x05, - VIDEO_SSRC: 0x06, - AUDIO_SSRC: 0x07 -} - -StreamController.SetupStatus = { - SUCCESS: 0x00, - BUSY: 0x01, - ERROR: 0x02 -} - -StreamController.SetupAddressVer = { - IPV4: 0x00, - IPV6: 0x01 -} - -StreamController.SetupAddressInfo = { - ADDRESS_VER: 0x01, - ADDRESS: 0x02, - VIDEO_RTP_PORT: 0x03, - AUDIO_RTP_PORT: 0x04 -} - -StreamController.SetupSRTP_PARAM = { - CRYPTO: 0x01, - MASTER_KEY: 0x02, - MASTER_SALT: 0x03 -} - -StreamController.StreamingStatus = { - AVAILABLE: 0x00, - STREAMING: 0x01, - BUSY: 0x02 -} - -StreamController.RTPConfigTypes = { - CRYPTO: 0x02 -} - -StreamController.SRTPCryptoSuites = { - AES_CM_128_HMAC_SHA1_80: 0x00, - AES_CM_256_HMAC_SHA1_80: 0x01, - NONE: 0x02 -} - -StreamController.VideoTypes = { - CODEC: 0x01, - CODEC_PARAM: 0x02, - ATTRIBUTES: 0x03, - RTP_PARAM: 0x04 -} - -StreamController.VideoCodecTypes = { - H264: 0x00 -} - -StreamController.VideoCodecParamTypes = { - PROFILE_ID: 0x01, - LEVEL: 0x02, - PACKETIZATION_MODE: 0x03, - CVO_ENABLED: 0x04, - CVO_ID: 0x05 -} - -StreamController.VideoCodecParamCVOTypes = { - UNSUPPORTED: 0x01, - SUPPORTED: 0x02 -} - -StreamController.VideoCodecParamProfileIDTypes = { - BASELINE: 0x00, - MAIN: 0x01, - HIGH: 0x02 -} - -StreamController.VideoCodecParamLevelTypes = { - TYPE3_1: 0x00, - TYPE3_2: 0x01, - TYPE4_0: 0x02 -} - -StreamController.VideoCodecParamPacketizationModeTypes = { - NON_INTERLEAVED: 0x00 -} - -StreamController.VideoAttributesTypes = { - IMAGE_WIDTH: 0x01, - IMAGE_HEIGHT: 0x02, - FRAME_RATE: 0x03 -} - -StreamController.SelectedStreamConfigurationTypes = { - SESSION: 0x01, - VIDEO: 0x02, - AUDIO: 0x03 -} - -StreamController.RTPParamTypes = { - PAYLOAD_TYPE: 0x01, - SYNCHRONIZATION_SOURCE: 0x02, - MAX_BIT_RATE: 0x03, - RTCP_SEND_INTERVAL: 0x04, - MAX_MTU: 0x05, - COMFORT_NOISE_PAYLOAD_TYPE: 0x06 -} - -StreamController.AudioTypes = { - CODEC: 0x01, - CODEC_PARAM: 0x02, - RTP_PARAM: 0x03, - COMFORT_NOISE: 0x04 -} - -StreamController.AudioCodecTypes = { - PCMU: 0x00, - PCMA: 0x01, - AACELD: 0x02, - OPUS: 0x03 -} - -StreamController.AudioCodecParamTypes = { - CHANNEL: 0x01, - BIT_RATE: 0x02, - SAMPLE_RATE: 0x03, - PACKET_TIME: 0x04 -} - -StreamController.AudioCodecParamBitRateTypes = { - VARIABLE: 0x00, - CONSTANT: 0x01 -} - -StreamController.AudioCodecParamSampleRateTypes = { - KHZ_8: 0x00, - KHZ_16: 0x01, - KHZ_24: 0x02 -} - -// Private - -StreamController.prototype._createService = function() { - var self = this; - var managementService = new Service.CameraRTPStreamManagement(undefined, this.identifier.toString()); - - managementService - .getCharacteristic(Characteristic.StreamingStatus) - .on('get', function(callback) { - var data = tlv.encode( 0x01, self.streamStatus ); - callback(null, data.toString('base64')); - }); - - managementService - .getCharacteristic(Characteristic.SupportedRTPConfiguration) - .on('get', function(callback) { - callback(null, self.supportedRTPConfiguration); - }); - - managementService - .getCharacteristic(Characteristic.SupportedVideoStreamConfiguration) - .on('get', function(callback) { - callback(null, self.supportedVideoStreamConfiguration); - }); - - managementService - .getCharacteristic(Characteristic.SupportedAudioStreamConfiguration) - .on('get', function(callback) { - callback(null, self.supportedAudioStreamConfiguration); - }); - - managementService - .getCharacteristic(Characteristic.SelectedStreamConfiguration) - .on('get', function(callback) { - debug('Read SelectedStreamConfiguration'); - callback(null, self.selectedConfiguration); - }) - .on('set',function(value, callback, context, connectionID) { - debug('Write SelectedStreamConfiguration'); - self._handleSelectedStreamConfigurationWrite(value, callback, connectionID); - }); - - managementService - .getCharacteristic(Characteristic.SetupEndpoints) - .on('get', function(callback) { - self._handleSetupRead(callback); - }) - .on('set', function(value, callback) { - self._handleSetupWrite(value, callback); - }); - - self.service = managementService; -} - -StreamController.prototype._handleSelectedStreamConfigurationWrite = function(value, callback, connectionID) { - var self = this; - self.selectedConfiguration = value; - - var data = bufferShim.from(value, 'base64'); - var objects = tlv.decode(data); - - var session; - - if(objects[StreamController.SelectedStreamConfigurationTypes.SESSION]) { - session = tlv.decode(objects[StreamController.SelectedStreamConfigurationTypes.SESSION]); - self.sessionIdentifier = session[0x01]; - - let requestType = session[0x02][0]; - if (requestType == 1) { - if (self.connectionID && self.connectionID != connectionID) { - debug("Received start stream request from a different connection."); - } else { - self.connectionID = connectionID; - } - - self._handleStartStream(objects, session, false, callback); - } else if (requestType == 0) { - if (self.connectionID && self.connectionID != connectionID) { - debug("Received stop stream request from a different connection.") - } else { - self.connectionID = undefined; - } - - self._handleStopStream(callback); - } else if (requestType == 4) { - self._handleStartStream(objects, session, true, callback); - } else { - debug("Unhandled request type: ", requestType); - callback(); - } - } else { - debug("Unexpected request for Selected Stream Configuration"); - callback(); - } -} - -StreamController.prototype._handleStartStream = function(objects, session, reconfigure, callback) { - var self = this; - - var request = { - "sessionID": self.sessionIdentifier, - "type": !reconfigure ? "start" : "reconfigure" - }; - - let videoPT = null; - let audioPT = null; - - if(objects[StreamController.SelectedStreamConfigurationTypes.VIDEO]) { - var videoInfo = {}; - - var video = tlv.decode(objects[StreamController.SelectedStreamConfigurationTypes.VIDEO]); - var codec = video[StreamController.VideoTypes.CODEC]; - - if (video[StreamController.VideoTypes.CODEC_PARAM]) { - var videoCodecParamsTLV = tlv.decode(video[StreamController.VideoTypes.CODEC_PARAM]); - videoInfo["profile"] = videoCodecParamsTLV[StreamController.VideoCodecParamTypes.PROFILE_ID].readUInt8(0); - videoInfo["level"] = videoCodecParamsTLV[StreamController.VideoCodecParamTypes.LEVEL].readUInt8(0); - } - - if (video[StreamController.VideoTypes.ATTRIBUTES]) { - var videoAttrTLV = tlv.decode(video[StreamController.VideoTypes.ATTRIBUTES]); - - videoInfo["width"] = videoAttrTLV[StreamController.VideoAttributesTypes.IMAGE_WIDTH].readUInt16LE(0); - videoInfo["height"] = videoAttrTLV[StreamController.VideoAttributesTypes.IMAGE_HEIGHT].readUInt16LE(0); - videoInfo["fps"] = videoAttrTLV[StreamController.VideoAttributesTypes.FRAME_RATE].readUInt8(0); - } - - if (video[StreamController.VideoTypes.RTP_PARAM]) { - var videoRTPParamsTLV = tlv.decode(video[StreamController.VideoTypes.RTP_PARAM]); - - if (videoRTPParamsTLV[StreamController.RTPParamTypes.SYNCHRONIZATION_SOURCE]) { - videoInfo["ssrc"] = videoRTPParamsTLV[StreamController.RTPParamTypes.SYNCHRONIZATION_SOURCE].readUInt32LE(0); - } - - if (videoRTPParamsTLV[StreamController.RTPParamTypes.PAYLOAD_TYPE]) { - videoPT = videoRTPParamsTLV[StreamController.RTPParamTypes.PAYLOAD_TYPE].readUInt8(0); - videoInfo["pt"] = videoPT; - } - - if (videoRTPParamsTLV[StreamController.RTPParamTypes.MAX_BIT_RATE]) { - videoInfo["max_bit_rate"] = videoRTPParamsTLV[StreamController.RTPParamTypes.MAX_BIT_RATE].readUInt16LE(0); - } - - if (videoRTPParamsTLV[StreamController.RTPParamTypes.RTCP_SEND_INTERVAL]) { - videoInfo["rtcp_interval"] = videoRTPParamsTLV[StreamController.RTPParamTypes.RTCP_SEND_INTERVAL].readUInt32LE(0); - } - - if (videoRTPParamsTLV[StreamController.RTPParamTypes.MAX_MTU]) { - videoInfo["mtu"] = videoRTPParamsTLV[StreamController.RTPParamTypes.MAX_MTU].readUInt16LE(0); - } - } - - request["video"] = videoInfo; - } - - if(objects[StreamController.SelectedStreamConfigurationTypes.AUDIO]) { - var audioInfo = {}; - - var audio = tlv.decode(objects[StreamController.SelectedStreamConfigurationTypes.AUDIO]); - - var codec = audio[StreamController.AudioTypes.CODEC]; - var audioCodecParamsTLV = tlv.decode(audio[StreamController.AudioTypes.CODEC_PARAM]); - var audioRTPParamsTLV = tlv.decode(audio[StreamController.AudioTypes.RTP_PARAM]); - var comfortNoise = tlv.decode(audio[StreamController.AudioTypes.COMFORT_NOISE]); - - let audioCodec = codec.readUInt8(0); - if (audioCodec !== undefined) { - if (audioCodec == StreamController.AudioCodecTypes.OPUS) { - audioInfo["codec"] = "OPUS"; - } else if (audioCodec == StreamController.AudioCodecTypes.AACELD) { - audioInfo["codec"] = "AAC-eld"; - } else { - debug("Unexpected audio codec: %s", audioCodec); - audioInfo["codec"] = audioCodec; - } - } - - audioInfo["channel"] = audioCodecParamsTLV[StreamController.AudioCodecParamTypes.CHANNEL].readUInt8(0); - audioInfo["bit_rate"] = audioCodecParamsTLV[StreamController.AudioCodecParamTypes.BIT_RATE].readUInt8(0); - - let sample_rate_enum = audioCodecParamsTLV[StreamController.AudioCodecParamTypes.SAMPLE_RATE].readUInt8(0); - if (sample_rate_enum !== undefined) { - if (sample_rate_enum == StreamController.AudioCodecParamSampleRateTypes.KHZ_8) { - audioInfo["sample_rate"] = 8; - } else if (sample_rate_enum == StreamController.AudioCodecParamSampleRateTypes.KHZ_16) { - audioInfo["sample_rate"] = 16; - } else if (sample_rate_enum == StreamController.AudioCodecParamSampleRateTypes.KHZ_24) { - audioInfo["sample_rate"] = 24; - } else { - debug("Unexpected audio sample rate: %s", sample_rate_enum); - } - } - - audioInfo["packet_time"] = audioCodecParamsTLV[StreamController.AudioCodecParamTypes.PACKET_TIME].readUInt8(0); - - var ssrc = audioRTPParamsTLV[StreamController.RTPParamTypes.SYNCHRONIZATION_SOURCE].readUInt32LE(0); - audioPT = audioRTPParamsTLV[StreamController.RTPParamTypes.PAYLOAD_TYPE].readUInt8(0); - - audioInfo["pt"] = audioPT; - audioInfo["ssrc"] = ssrc; - audioInfo["max_bit_rate"] = audioRTPParamsTLV[StreamController.RTPParamTypes.MAX_BIT_RATE].readUInt16LE(0); - audioInfo["rtcp_interval"] = audioRTPParamsTLV[StreamController.RTPParamTypes.RTCP_SEND_INTERVAL].readUInt32LE(0); - audioInfo["comfort_pt"] = audioRTPParamsTLV[StreamController.RTPParamTypes.COMFORT_NOISE_PAYLOAD_TYPE].readUInt8(0); - - request["audio"] = audioInfo; - } - - if (!reconfigure && self.requireProxy) { - self.videoProxy.setOutgoingPayloadType(videoPT); - if (!self.disableAudioProxy) { - self.audioProxy.setOutgoingPayloadType(audioPT); - } - } - - self.cameraSource.handleStreamRequest(request); - - self._updateStreamStatus(StreamController.StreamingStatus.STREAMING); - callback(); -} - -StreamController.prototype._handleStopStream = function(callback, silent) { - var self = this; - - let request = { - "sessionID": self.sessionIdentifier, - "type": "stop" - }; - - if (!silent) { - self.cameraSource.handleStreamRequest(request); - } - - if (self.requireProxy) { - self.videoProxy.destroy(); - if (!self.disableAudioProxy) { - self.audioProxy.destroy(); - } - - self.videoProxy = undefined; - self.audioProxy = undefined; - } - - self._updateStreamStatus(StreamController.StreamingStatus.AVAILABLE); - - if (callback) { - callback(); - } -} - -StreamController.prototype._handleSetupWrite = function(value, callback) { - var self = this; - - var data = bufferShim.from(value, 'base64'); - var objects = tlv.decode(data); - - self.sessionIdentifier = objects[StreamController.SetupTypes.SESSION_ID]; - - // Address - var targetAddressPayload = objects[StreamController.SetupTypes.ADDRESS]; - var processedAddressInfo = tlv.decode(targetAddressPayload); - var isIPv6 = processedAddressInfo[StreamController.SetupAddressInfo.ADDRESS_VER][0]; - var targetAddress = processedAddressInfo[StreamController.SetupAddressInfo.ADDRESS].toString('utf8'); - var targetVideoPort = processedAddressInfo[StreamController.SetupAddressInfo.VIDEO_RTP_PORT].readUInt16LE(0); - var targetAudioPort = processedAddressInfo[StreamController.SetupAddressInfo.AUDIO_RTP_PORT].readUInt16LE(0); - - // Video SRTP Params - var videoSRTPPayload = objects[StreamController.SetupTypes.VIDEO_SRTP_PARAM]; - var processedVideoInfo = tlv.decode(videoSRTPPayload); - var videoCryptoSuite = processedVideoInfo[StreamController.SetupSRTP_PARAM.CRYPTO][0]; - var videoMasterKey = processedVideoInfo[StreamController.SetupSRTP_PARAM.MASTER_KEY]; - var videoMasterSalt = processedVideoInfo[StreamController.SetupSRTP_PARAM.MASTER_SALT]; - - // Audio SRTP Params - var audioSRTPPayload = objects[StreamController.SetupTypes.AUDIO_SRTP_PARAM]; - var processedAudioInfo = tlv.decode(audioSRTPPayload); - var audioCryptoSuite = processedAudioInfo[StreamController.SetupSRTP_PARAM.CRYPTO][0]; - var audioMasterKey = processedAudioInfo[StreamController.SetupSRTP_PARAM.MASTER_KEY]; - var audioMasterSalt = processedAudioInfo[StreamController.SetupSRTP_PARAM.MASTER_SALT]; - - debug( - '\nSession: ', this.sessionIdentifier, - '\nControllerAddress: ', targetAddress, - '\nVideoPort: ', targetVideoPort, - '\nAudioPort: ', targetAudioPort, - '\nVideo Crypto: ', videoCryptoSuite, - '\nVideo Master Key: ', videoMasterKey, - '\nVideo Master Salt: ', videoMasterSalt, - '\nAudio Crypto: ', audioCryptoSuite, - '\nAudio Master Key: ', audioMasterKey, - '\nAudio Master Salt: ', audioMasterSalt - ); - - var request = { - "sessionID": self.sessionIdentifier, - }; - - var videoInfo = {}; - - var audioInfo = {}; - - if (self.supportSRTP) { - videoInfo["srtp_key"] = videoMasterKey; - videoInfo["srtp_salt"] = videoMasterSalt; - - audioInfo["srtp_key"] = audioMasterKey; - audioInfo["srtp_salt"] = audioMasterSalt; - } - - if (!self.requireProxy) { - request["targetAddress"] = targetAddress; - - videoInfo["port"] = targetVideoPort; - audioInfo["port"] = targetAudioPort; - - request["video"] = videoInfo; - request["audio"] = audioInfo; - - self.cameraSource.prepareStream(request, function(response) { - self._generateSetupResponse(self.sessionIdentifier, response, callback); - }); - } else { - request["targetAddress"] = ip.address(); - var promises = []; - - var videoSSRCNumber = crypto.randomBytes(4).readUInt32LE(0); - self.videoProxy = new RTPProxy({ - outgoingAddress: targetAddress, - outgoingPort: targetVideoPort, - outgoingSSRC: videoSSRCNumber, - disabled: false - }); - - promises.push(self.videoProxy.setup()); - - if (!self.disableAudioProxy) { - var audioSSRCNumber = crypto.randomBytes(4).readUInt32LE(0); - - self.audioProxy = new RTPProxy({ - outgoingAddress: targetAddress, - outgoingPort: targetAudioPort, - outgoingSSRC: audioSSRCNumber, - disabled: self.videoOnly - }); - - promises.push(self.audioProxy.setup()); - } else { - audioInfo["port"] = targetAudioPort; - audioInfo["targetAddress"] = targetAddress; - } - - Promise.all(promises).then(function() { - videoInfo["proxy_rtp"] = self.videoProxy.incomingRTPPort(); - videoInfo["proxy_rtcp"] = self.videoProxy.incomingRTCPPort(); - - if (!self.disableAudioProxy) { - audioInfo["proxy_rtp"] = self.audioProxy.incomingRTPPort(); - audioInfo["proxy_rtcp"] = self.audioProxy.incomingRTCPPort(); - } - - request["video"] = videoInfo; - request["audio"] = audioInfo; - - self.cameraSource.prepareStream(request, function(response) { - self._generateSetupResponse(self.sessionIdentifier, response, callback); - }); - }); - } -} - -StreamController.prototype._generateSetupResponse = function(identifier, response, callback) { - var self = this; - - var ipVer = 0; - var ipAddress = null; - var videoPort = bufferShim.alloc(2); - var audioPort = bufferShim.alloc(2); - - var videoSSRC = bufferShim.alloc(4); - var audioSSRC = bufferShim.alloc(4); - - var videoSRTP = bufferShim.from([0x01, 0x01, 0x02, 0x02, 0x00, 0x03, 0x00]); - var audioSRTP = bufferShim.from([0x01, 0x01, 0x02, 0x02, 0x00, 0x03, 0x00]); - - if (self.requireProxy) { - let currentAddress = ip.address(); - - ipVer = 1; - - if (ip.isV4Format(currentAddress)) { - ipVer = 0; - } - - ipAddress = bufferShim.from(currentAddress); - videoPort.writeUInt16LE(self.videoProxy.outgoingLocalPort(), 0); - - if (!self.disableAudioProxy) { - audioPort.writeUInt16LE(self.audioProxy.outgoingLocalPort(), 0); - } - - let videoInfo = response["video"]; - - let video_pt = videoInfo["proxy_pt"]; - let video_serverAddr = videoInfo["proxy_server_address"]; - let video_serverRTP = videoInfo["proxy_server_rtp"]; - let video_serverRTCP = videoInfo["proxy_server_rtcp"]; - - self.videoProxy.setIncomingPayloadType(video_pt); - self.videoProxy.setServerAddress(video_serverAddr); - self.videoProxy.setServerRTPPort(video_serverRTP); - self.videoProxy.setServerRTCPPort(video_serverRTCP); - - videoSSRC.writeUInt32LE(self.videoProxy.outgoingSSRC, 0); - - let audioInfo = response["audio"]; - - if (!self.disableAudioProxy) { - let audio_pt = audioInfo["proxy_pt"]; - let audio_serverAddr = audioInfo["proxy_server_address"]; - let audio_serverRTP = audioInfo["proxy_server_rtp"]; - let audio_serverRTCP = audioInfo["proxy_server_rtcp"]; - - self.audioProxy.setIncomingPayloadType(audio_pt); - self.audioProxy.setServerAddress(audio_serverAddr); - self.audioProxy.setServerRTPPort(audio_serverRTP); - self.audioProxy.setServerRTCPPort(audio_serverRTCP); - - audioSSRC.writeUInt32LE(self.audioProxy.outgoingSSRC, 0); - } else { - audioPort.writeUInt16LE(audioInfo["port"], 0); - audioSSRC.writeUInt32LE(audioInfo["ssrc"], 0); - } - - } else { - let addressInfo = response["address"]; - - if (addressInfo["type"] == "v6") { - ipVer = 1; - } else { - ipVer = 0; - } - - ipAddress = addressInfo["address"]; - - let videoInfo = response["video"]; - videoPort.writeUInt16LE(videoInfo["port"], 0); - videoSSRC.writeUInt32LE(videoInfo["ssrc"], 0); - - let audioInfo = response["audio"]; - audioPort.writeUInt16LE(audioInfo["port"], 0); - audioSSRC.writeUInt32LE(audioInfo["ssrc"], 0); - - if (self.supportSRTP) { - let videoKey = videoInfo["srtp_key"]; - let videoSalt = videoInfo["srtp_salt"]; - - let audioKey = audioInfo["srtp_key"]; - let audioSalt = audioInfo["srtp_salt"]; - - videoSRTP = tlv.encode( - StreamController.SetupSRTP_PARAM.CRYPTO, StreamController.SRTPCryptoSuites.AES_CM_128_HMAC_SHA1_80, - StreamController.SetupSRTP_PARAM.MASTER_KEY, videoKey, - StreamController.SetupSRTP_PARAM.MASTER_SALT, videoSalt - ); - - audioSRTP = tlv.encode( - StreamController.SetupSRTP_PARAM.CRYPTO, StreamController.SRTPCryptoSuites.AES_CM_128_HMAC_SHA1_80, - StreamController.SetupSRTP_PARAM.MASTER_KEY, audioKey, - StreamController.SetupSRTP_PARAM.MASTER_SALT, audioSalt - ); - } - } - - var addressTLV = tlv.encode( - StreamController.SetupAddressInfo.ADDRESS_VER, ipVer, - StreamController.SetupAddressInfo.ADDRESS, ipAddress, - StreamController.SetupAddressInfo.VIDEO_RTP_PORT, videoPort, - StreamController.SetupAddressInfo.AUDIO_RTP_PORT, audioPort - ); - - var responseTLV = tlv.encode( - StreamController.SetupTypes.SESSION_ID, identifier, - StreamController.SetupTypes.STATUS, StreamController.SetupStatus.SUCCESS, - StreamController.SetupTypes.ADDRESS, addressTLV, - StreamController.SetupTypes.VIDEO_SRTP_PARAM, videoSRTP, - StreamController.SetupTypes.AUDIO_SRTP_PARAM, audioSRTP, - StreamController.SetupTypes.VIDEO_SSRC, videoSSRC, - StreamController.SetupTypes.AUDIO_SSRC, audioSSRC - ); - - self.setupResponse = responseTLV.toString('base64'); - callback(); -} - -StreamController.prototype._updateStreamStatus = function(status) { - var self = this; - - self.streamStatus = status; - - self.service - .getCharacteristic(Characteristic.StreamingStatus) - .setValue(tlv.encode( 0x01, self.streamStatus ).toString('base64')); -} - -StreamController.prototype._handleSetupRead = function(callback) { - debug('Setup Read'); - callback(null, this.setupResponse); -} - -StreamController.prototype._supportedRTPConfiguration = function(supportSRTP) { - var cryptoSuite = StreamController.SRTPCryptoSuites.AES_CM_128_HMAC_SHA1_80; - - if (!supportSRTP) { - cryptoSuite = StreamController.SRTPCryptoSuites.NONE; - debug("Client claims it doesn't support SRTP. The stream may stops working with future iOS releases."); - } - - return tlv.encode( - StreamController.RTPConfigTypes.CRYPTO, cryptoSuite - ).toString('base64'); -} - -StreamController.prototype._supportedVideoStreamConfiguration = function(videoParams) { - var self = this; - - let codec = videoParams["codec"]; - if (!codec) { - throw new Error('Video codec cannot be undefined'); - } - - var videoCodecParamsTLV = tlv.encode( - StreamController.VideoCodecParamTypes.PACKETIZATION_MODE, StreamController.VideoCodecParamPacketizationModeTypes.NON_INTERLEAVED - ); - - let profiles = codec["profiles"]; - profiles.forEach(function(value) { - let tlvBuffer = tlv.encode(StreamController.VideoCodecParamTypes.PROFILE_ID, value); - videoCodecParamsTLV = Buffer.concat([videoCodecParamsTLV, tlvBuffer]); - }); - - let levels = codec["levels"]; - levels.forEach(function(value) { - let tlvBuffer = tlv.encode(StreamController.VideoCodecParamTypes.LEVEL, value); - videoCodecParamsTLV = Buffer.concat([videoCodecParamsTLV, tlvBuffer]); - }); - - let resolutions = videoParams["resolutions"]; - if (!resolutions) { - throw new Error('Video resolutions cannot be undefined'); - } - - var videoAttrsTLV = bufferShim.alloc(0); - resolutions.forEach(function(resolution) { - if (resolution.length != 3) { - throw new Error('Unexpected video resolution'); - } - - var imageWidth = bufferShim.alloc(2); - imageWidth.writeUInt16LE(resolution[0], 0); - var imageHeight = bufferShim.alloc(2); - imageHeight.writeUInt16LE(resolution[1], 0); - var frameRate = bufferShim.alloc(1); - frameRate.writeUInt8(resolution[2]); - - var videoAttrTLV = tlv.encode( - StreamController.VideoAttributesTypes.IMAGE_WIDTH, imageWidth, - StreamController.VideoAttributesTypes.IMAGE_HEIGHT, imageHeight, - StreamController.VideoAttributesTypes.FRAME_RATE, frameRate - ); - var videoAttrBuffer = tlv.encode(StreamController.VideoTypes.ATTRIBUTES, videoAttrTLV); - videoAttrsTLV = Buffer.concat([videoAttrsTLV, videoAttrBuffer]); - }); - - var configurationTLV = tlv.encode( - StreamController.VideoTypes.CODEC, StreamController.VideoCodecTypes.H264, - StreamController.VideoTypes.CODEC_PARAM, videoCodecParamsTLV - ); - - return tlv.encode( - 0x01, Buffer.concat([configurationTLV, videoAttrsTLV]) - ).toString('base64'); -} - -StreamController.prototype._supportedAudioStreamConfiguration = function(audioParams) { - // Only AACELD and OPUS are accepted by iOS currently, and we need to give it something it will accept - // for it to start the video stream. - - var self = this; - var comfortNoiseValue = 0x00; - - if (audioParams["comfort_noise"] === true) { - comfortNoiseValue = 0x01; - } - - let codecs = audioParams["codecs"]; - if (!codecs) { - throw new Error('Audio codecs cannot be undefined'); - } - - var audioConfigurationsBuffer = bufferShim.alloc(0); - var hasSupportedCodec = false; - - codecs.forEach(function(codecParam){ - var codec = StreamController.AudioCodecTypes.OPUS; - var bitrate = StreamController.AudioCodecParamBitRateTypes.CONSTANT; - var samplerate = StreamController.AudioCodecParamSampleRateTypes.KHZ_24; - - let param_type = codecParam["type"]; - let param_samplerate = codecParam["samplerate"]; - - if (param_type == 'OPUS') { - hasSupportedCodec = true; - bitrate = StreamController.AudioCodecParamBitRateTypes.VARIABLE; - } else if (param_type == "AAC-eld") { - hasSupportedCodec = true; - codec = StreamController.AudioCodecTypes.AACELD; - bitrate = StreamController.AudioCodecParamBitRateTypes.VARIABLE; - } else { - debug("Unsupported codec: ", param_type); - return; - } - - if (param_samplerate == 8) { - samplerate = StreamController.AudioCodecParamSampleRateTypes.KHZ_8; - } else if (param_samplerate == 16) { - samplerate = StreamController.AudioCodecParamSampleRateTypes.KHZ_16; - } else if (param_samplerate == 24) { - samplerate = StreamController.AudioCodecParamSampleRateTypes.KHZ_24; - } else { - debug("Unsupported sample rate: ", param_samplerate); - return; - } - - var audioParamTLV = tlv.encode( - StreamController.AudioCodecParamTypes.CHANNEL, 1, - StreamController.AudioCodecParamTypes.BIT_RATE, bitrate, - StreamController.AudioCodecParamTypes.SAMPLE_RATE, samplerate - ); - - var audioConfiguration = tlv.encode( - StreamController.AudioTypes.CODEC, codec, - StreamController.AudioTypes.CODEC_PARAM, audioParamTLV - ); - - audioConfigurationsBuffer = Buffer.concat([audioConfigurationsBuffer, tlv.encode(0x01, audioConfiguration)]); - }); - - // If we're not one of the supported codecs - if(!hasSupportedCodec) { - debug("Client doesn't support any audio codec that HomeKit supports."); - - var codec = StreamController.AudioCodecTypes.OPUS; - var bitrate = StreamController.AudioCodecParamBitRateTypes.VARIABLE; - var samplerate = StreamController.AudioCodecParamSampleRateTypes.KHZ_24; - - var audioParamTLV = tlv.encode( - StreamController.AudioCodecParamTypes.CHANNEL, 1, - StreamController.AudioCodecParamTypes.BIT_RATE, bitrate, - StreamController.AudioCodecParamTypes.SAMPLE_RATE, StreamController.AudioCodecParamSampleRateTypes.KHZ_24 - ); - - - var audioConfiguration = tlv.encode( - StreamController.AudioTypes.CODEC, codec, - StreamController.AudioTypes.CODEC_PARAM, audioParamTLV - ); - - audioConfigurationsBuffer = tlv.encode(0x01, audioConfiguration); - - self.videoOnly = true; - } - - return Buffer.concat([audioConfigurationsBuffer, tlv.encode(0x02, comfortNoiseValue)]).toString('base64'); -} diff --git a/lib/camera/RTPProxy.js b/lib/camera/RTPProxy.js deleted file mode 100644 index 061e4c560..000000000 --- a/lib/camera/RTPProxy.js +++ /dev/null @@ -1,298 +0,0 @@ -'use strict'; - -const dgram = require('dgram'); -const EventEmitter = require('events').EventEmitter; - -class RTPProxy extends EventEmitter { - constructor(options) { - super(); - - let self = this; - self.type = options.isIPV6 ? 'udp6' : 'udp4'; - - self.options = options; - self.startingPort = 10000; - - self.outgoingAddress = options.outgoingAddress; - self.outgoingPort = options.outgoingPort; - self.incomingPayloadType = 0; - self.outgoingSSRC = options.outgoingSSRC; - self.disabled = options.disabled; - self.incomingSSRC = null; - self.outgoingPayloadType = null; - } - - setup() { - let self = this; - return self.createSocketPair(self.type) - .then(function(sockets) { - self.incomingRTPSocket = sockets[0]; - self.incomingRTCPSocket = sockets[1]; - - return self.createSocket(self.type); - }).then(function(socket) { - self.outgoingSocket = socket; - self.onBound(); - }); - } - - destroy() { - let self = this; - if (self.incomingRTPSocket) { - self.incomingRTPSocket.close(); - } - - if (self.incomingRTCPSocket) { - self.incomingRTCPSocket.close(); - } - - if (self.outgoingSocket) { - self.outgoingSocket.close(); - } - } - - incomingRTPPort() { - let self = this; - return self.incomingRTPSocket.address().port; - } - - incomingRTCPPort() { - let self = this; - return self.incomingRTCPSocket.address().port; - } - - outgoingLocalPort() { - let self = this; - return self.outgoingSocket.address().port; - } - - setServerAddress(address) { - let self = this; - self.serverAddress = address; - } - - setServerRTPPort(port) { - let self = this; - self.serverRTPPort = port; - } - - setServerRTCPPort(port) { - let self = this; - self.serverRTCPPort = port; - } - - setIncomingPayloadType(pt) { - let self = this; - self.incomingPayloadType = pt; - } - - setOutgoingPayloadType(pt) { - let self = this; - self.outgoingPayloadType = pt; - } - - sendOut(msg) { - let self = this; - // Just drop it if we're not setup yet, I guess. - if(!self.outgoingAddress || !self.outgoingPort) - return; - - self.outgoingSocket.send(msg, self.outgoingPort, self.outgoingAddress); - } - - sendBack(msg) { - let self = this; - // Just drop it if we're not setup yet, I guess. - if(!self.serverAddress || !self.serverRTCPPort) - return; - - self.outgoingSocket.send(msg, self.serverRTCPPort, self.serverAddress); - } - - onBound() { - let self = this; - if(self.disabled) - return; - - self.incomingRTPSocket.on('message', function(msg, rinfo) { - self.rtpMessage(msg); - }); - - self.incomingRTCPSocket.on('message', function(msg, rinfo) { - self.rtcpMessage(msg); - }); - - self.outgoingSocket.on('message', function(msg, rinfo) { - self.rtcpReply(msg); - }); - } - - rtpMessage(msg) { - let self = this; - - if(msg.length < 12) { - // Not a proper RTP packet. Just forward it. - self.sendOut(msg); - return; - } - - let mpt = msg.readUInt8(1); - let pt = mpt & 0x7F; - if(pt == self.incomingPayloadType) { - mpt = (mpt & 0x80) | self.outgoingPayloadType; - msg.writeUInt8(mpt, 1); - } - - if(self.incomingSSRC === null) - self.incomingSSRC = msg.readUInt32BE(4); - - msg.writeUInt32BE(self.outgoingSSRC, 8); - self.sendOut(msg); - } - - processRTCPMessage(msg, transform) { - let self = this; - let rtcpPackets = []; - let offset = 0; - while((offset + 4) <= msg.length) { - let pt = msg.readUInt8(offset + 1); - let len = msg.readUInt16BE(offset + 2) * 4; - if((offset + 4 + len) > msg.length) - break; - let packet = msg.slice(offset, offset + 4 + len); - - packet = transform(pt, packet); - - if(packet) - rtcpPackets.push(packet); - - offset += 4 + len; - } - - if(rtcpPackets.length > 0) - return Buffer.concat(rtcpPackets); - - return null; - } - - rtcpMessage(msg) { - let self = this; - - let processed = self.processRTCPMessage(msg, function(pt, packet) { - if(pt != 200 || packet.length < 8) - return packet; - - if(self.incomingSSRC === null) - self.incomingSSRC = packet.readUInt32BE(4); - packet.writeUInt32BE(self.outgoingSSRC, 4); - return packet; - }); - - if(processed) - self.sendOut(processed); - } - - rtcpReply(msg) { - let self = this; - - let processed = self.processRTCPMessage(msg, function(pt, packet) { - if(pt != 201 || packet.length < 12) - return packet; - - // Assume source 1 is the one we want to edit. - packet.writeUInt32BE(self.incomingSSRC, 8); - return packet; - }); - - - if(processed) - self.sendOut(processed); - } - - createSocket(type) { - let self = this; - return new Promise(function(resolve, reject) { - let retry = function() { - let socket = dgram.createSocket(type); - - let bindErrorHandler = function() { - if(self.startingPort == 65535) - self.startingPort = 10000; - else - ++self.startingPort; - - socket.close(); - retry(); - }; - - socket.once('error', bindErrorHandler); - - socket.on('listening', function() { - resolve(socket); - }); - - socket.bind(self.startingPort); - }; - - retry(); - }); - } - - createSocketPair(type) { - let self = this; - return new Promise(function(resolve, reject) { - let retry = function() { - let socket1 = dgram.createSocket(type); - let socket2 = dgram.createSocket(type); - let state = {socket1: 0, socket2: 0}; - - let recheck = function() { - if(state.socket1 == 0 || state.socket2 == 0) - return; - - if(state.socket1 == 2 && state.socket2 == 2) { - resolve([socket1, socket2]); - return; - } - - if(self.startingPort == 65534) - self.startingPort = 10000; - else - ++self.startingPort; - - socket1.close(); - socket2.close(); - - retry(self.startingPort); - } - - socket1.once('error', function() { - state.socket1 = 1; - recheck(); - }); - - socket2.once('error', function() { - state.socket2 = 1; - recheck(); - }); - - socket1.once('listening', function() { - state.socket1 = 2; - recheck(); - }); - - socket2.once('listening', function() { - state.socket2 = 2; - recheck(); - }); - - socket1.bind(self.startingPort); - socket2.bind(self.startingPort + 1); - } - - retry(); - }); - } -} - -module.exports = RTPProxy; diff --git a/lib/gen/HomeKitTypes-Bridge.js b/lib/gen/HomeKitTypes-Bridge.js deleted file mode 100644 index ecba81f0d..000000000 --- a/lib/gen/HomeKitTypes-Bridge.js +++ /dev/null @@ -1,611 +0,0 @@ -'use strict'; -// Removed from new HAS - -var inherits = require('util').inherits; -var Characteristic = require('../Characteristic').Characteristic; -var Service = require('../Service').Service; - -/** - * - * Removed in IOS 11 - * - */ - -/** - * Characteristic "App Matching Identifier" - */ - -Characteristic.AppMatchingIdentifier = function() { - Characteristic.call(this, 'App Matching Identifier', '000000A4-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.TLV8, - perms: [Characteristic.Perms.READ] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.AppMatchingIdentifier, Characteristic); - -Characteristic.AppMatchingIdentifier.UUID = '000000A4-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Programmable Switch Output State" - */ - -Characteristic.ProgrammableSwitchOutputState = function() { - Characteristic.call(this, 'Programmable Switch Output State', '00000074-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.ProgrammableSwitchOutputState, Characteristic); - -Characteristic.ProgrammableSwitchOutputState.UUID = '00000074-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Software Revision" - */ - -Characteristic.SoftwareRevision = function() { - Characteristic.call(this, 'Software Revision', '00000054-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.STRING, - perms: [Characteristic.Perms.READ] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.SoftwareRevision, Characteristic); - -Characteristic.SoftwareRevision.UUID = '00000054-0000-1000-8000-0026BB765291'; - -/** - * Service "Camera Control" - */ - -Service.CameraControl = function(displayName, subtype) { - Service.call(this, displayName, '00000111-0000-1000-8000-0026BB765291', 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); -}; - -inherits(Service.CameraControl, Service); - -Service.CameraControl.UUID = '00000111-0000-1000-8000-0026BB765291'; - -/** - * Service "Stateful Programmable Switch" - */ - -Service.StatefulProgrammableSwitch = function(displayName, subtype) { - Service.call(this, displayName, '00000088-0000-1000-8000-0026BB765291', subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.ProgrammableSwitchEvent); - this.addCharacteristic(Characteristic.ProgrammableSwitchOutputState); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Name); -}; - -inherits(Service.StatefulProgrammableSwitch, Service); - -Service.StatefulProgrammableSwitch.UUID = '00000088-0000-1000-8000-0026BB765291'; - - -// Aliases -Characteristic.SelectedStreamConfiguration = Characteristic.SelectedRTPStreamConfiguration; -Service.Label = Service.ServiceLabel; -Characteristic.LabelNamespace = Characteristic.ServiceLabelNamespace; -Characteristic.LabelIndex = Characteristic.ServiceLabelIndex; - -//Renamed Enums: -Characteristic.TargetHumidifierDehumidifierState.AUTO = 0; //Is Now HUMIDIFIER_OR_DEHUMIDIFIER - - - - - - - -/** - * - * Removed in IOS 10 - * - */ - -/** - * Characteristic "Accessory Identifier" - */ - -Characteristic.AccessoryIdentifier = function() { - Characteristic.call(this, 'Accessory Identifier', '00000057-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.STRING, - perms: [Characteristic.Perms.READ] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.AccessoryIdentifier, Characteristic); - -Characteristic.AccessoryIdentifier.UUID = '00000057-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Category" - */ - -Characteristic.Category = function() { - Characteristic.call(this, 'Category', '000000A3-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT16, - maxValue: 16, - minValue: 1, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.Category, Characteristic); - -Characteristic.Category.UUID = '000000A3-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Configure Bridged Accessory" - */ - -Characteristic.ConfigureBridgedAccessory = function() { - Characteristic.call(this, 'Configure Bridged Accessory', '000000A0-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.TLV8, - perms: [Characteristic.Perms.WRITE] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.ConfigureBridgedAccessory, Characteristic); - -Characteristic.ConfigureBridgedAccessory.UUID = '000000A0-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Configure Bridged Accessory Status" - */ - -Characteristic.ConfigureBridgedAccessoryStatus = function() { - Characteristic.call(this, 'Configure Bridged Accessory Status', '0000009D-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.TLV8, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.ConfigureBridgedAccessoryStatus, Characteristic); - -Characteristic.ConfigureBridgedAccessoryStatus.UUID = '0000009D-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Current Time" - */ - -Characteristic.CurrentTime = function() { - Characteristic.call(this, 'Current Time', '0000009B-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.STRING, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.CurrentTime, Characteristic); - -Characteristic.CurrentTime.UUID = '0000009B-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Day of the Week" - */ - -Characteristic.DayoftheWeek = function() { - Characteristic.call(this, 'Day of the Week', '00000098-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 7, - minValue: 1, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.DayoftheWeek, Characteristic); - -Characteristic.DayoftheWeek.UUID = '00000098-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Discover Bridged Accessories" - */ - -Characteristic.DiscoverBridgedAccessories = function() { - Characteristic.call(this, 'Discover Bridged Accessories', '0000009E-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.DiscoverBridgedAccessories, Characteristic); - -Characteristic.DiscoverBridgedAccessories.UUID = '0000009E-0000-1000-8000-0026BB765291'; - -// The value property of DiscoverBridgedAccessories must be one of the following: -Characteristic.DiscoverBridgedAccessories.START_DISCOVERY = 0; -Characteristic.DiscoverBridgedAccessories.STOP_DISCOVERY = 1; - -/** - * Characteristic "Discovered Bridged Accessories" - */ - -Characteristic.DiscoveredBridgedAccessories = function() { - Characteristic.call(this, 'Discovered Bridged Accessories', '0000009F-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT16, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.DiscoveredBridgedAccessories, Characteristic); - -Characteristic.DiscoveredBridgedAccessories.UUID = '0000009F-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Link Quality" - */ - -Characteristic.LinkQuality = function() { - Characteristic.call(this, 'Link Quality', '0000009C-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 4, - minValue: 1, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.LinkQuality, Characteristic); - -Characteristic.LinkQuality.UUID = '0000009C-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Reachable" - */ - -Characteristic.Reachable = function() { - Characteristic.call(this, 'Reachable', '00000063-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.BOOL, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.Reachable, Characteristic); - -Characteristic.Reachable.UUID = '00000063-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Relay Control Point" - */ - -Characteristic.RelayControlPoint = function() { - Characteristic.call(this, 'Relay Control Point', '0000005E-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.TLV8, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.RelayControlPoint, Characteristic); - -Characteristic.RelayControlPoint.UUID = '0000005E-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Relay Enabled" - */ - -Characteristic.RelayEnabled = function() { - Characteristic.call(this, 'Relay Enabled', '0000005B-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.BOOL, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.RelayEnabled, Characteristic); - -Characteristic.RelayEnabled.UUID = '0000005B-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Relay State" - */ - -Characteristic.RelayState = function() { - Characteristic.call(this, 'Relay State', '0000005C-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.RelayState, Characteristic); - -Characteristic.RelayState.UUID = '0000005C-0000-1000-8000-0026BB765291'; - - -/** - * Characteristic "Time Update" - */ - -Characteristic.TimeUpdate = function() { - Characteristic.call(this, 'Time Update', '0000009A-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.BOOL, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.TimeUpdate, Characteristic); - -Characteristic.TimeUpdate.UUID = '0000009A-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Tunnel Connection Timeout " - */ - -Characteristic.TunnelConnectionTimeout = function() { - Characteristic.call(this, 'Tunnel Connection Timeout ', '00000061-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT32, - perms: [Characteristic.Perms.WRITE, Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.TunnelConnectionTimeout, Characteristic); - -Characteristic.TunnelConnectionTimeout.UUID = '00000061-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Tunneled Accessory Advertising" - */ - -Characteristic.TunneledAccessoryAdvertising = function() { - Characteristic.call(this, 'Tunneled Accessory Advertising', '00000060-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.BOOL, - perms: [Characteristic.Perms.WRITE, Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.TunneledAccessoryAdvertising, Characteristic); - -Characteristic.TunneledAccessoryAdvertising.UUID = '00000060-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Tunneled Accessory Connected" - */ - -Characteristic.TunneledAccessoryConnected = function() { - Characteristic.call(this, 'Tunneled Accessory Connected', '00000059-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.BOOL, - perms: [Characteristic.Perms.WRITE, Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.TunneledAccessoryConnected, Characteristic); - -Characteristic.TunneledAccessoryConnected.UUID = '00000059-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Tunneled Accessory State Number" - */ - -Characteristic.TunneledAccessoryStateNumber = function() { - Characteristic.call(this, 'Tunneled Accessory State Number', '00000058-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.TunneledAccessoryStateNumber, Characteristic); - -Characteristic.TunneledAccessoryStateNumber.UUID = '00000058-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Version" - */ - -Characteristic.Version = function() { - Characteristic.call(this, 'Version', '00000037-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.STRING, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.Version, Characteristic); - -Characteristic.Version.UUID = '00000037-0000-1000-8000-0026BB765291'; - -/** - * Service "Bridge Configuration" - */ - -Service.BridgeConfiguration = function(displayName, subtype) { - Service.call(this, displayName, '000000A1-0000-1000-8000-0026BB765291', 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); -}; - -inherits(Service.BridgeConfiguration, Service); - -Service.BridgeConfiguration.UUID = '000000A1-0000-1000-8000-0026BB765291'; - -/** - * Service "Bridging State" - */ - -Service.BridgingState = function(displayName, subtype) { - Service.call(this, displayName, '00000062-0000-1000-8000-0026BB765291', 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); -}; - -inherits(Service.BridgingState, Service); - -Service.BridgingState.UUID = '00000062-0000-1000-8000-0026BB765291'; - -/** - * Service "Pairing" - */ - -Service.Pairing = function(displayName, subtype) { - Service.call(this, displayName, '00000055-0000-1000-8000-0026BB765291', subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.PairSetup); - this.addCharacteristic(Characteristic.PairVerify); - this.addCharacteristic(Characteristic.PairingFeatures); - this.addCharacteristic(Characteristic.PairingPairings); - - // Optional Characteristics -}; - -inherits(Service.Pairing, Service); - -Service.Pairing.UUID = '00000055-0000-1000-8000-0026BB765291'; - -/** - * Service "Protocol Information" - */ - -Service.ProtocolInformation = function(displayName, subtype) { - Service.call(this, displayName, '000000A2-0000-1000-8000-0026BB765291', subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.Version); - - // Optional Characteristics -}; - -inherits(Service.ProtocolInformation, Service); - -Service.ProtocolInformation.UUID = '000000A2-0000-1000-8000-0026BB765291'; - -/** - * Service "Relay" - */ - -Service.Relay = function(displayName, subtype) { - Service.call(this, displayName, '0000005A-0000-1000-8000-0026BB765291', subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.RelayEnabled); - this.addCharacteristic(Characteristic.RelayState); - this.addCharacteristic(Characteristic.RelayControlPoint); - - // Optional Characteristics -}; - -inherits(Service.Relay, Service); - -Service.Relay.UUID = '0000005A-0000-1000-8000-0026BB765291'; - -/** - * Service "Time Information" - */ - -Service.TimeInformation = function(displayName, subtype) { - Service.call(this, displayName, '00000099-0000-1000-8000-0026BB765291', subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.CurrentTime); - this.addCharacteristic(Characteristic.DayoftheWeek); - this.addCharacteristic(Characteristic.TimeUpdate); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Name); -}; - -inherits(Service.TimeInformation, Service); - -Service.TimeInformation.UUID = '00000099-0000-1000-8000-0026BB765291'; - -/** - * Service "Tunneled BTLE Accessory Service" - */ - -Service.TunneledBTLEAccessoryService = function(displayName, subtype) { - Service.call(this, displayName, '00000056-0000-1000-8000-0026BB765291', subtype); - - // Required Characteristics - 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 -}; - -inherits(Service.TunneledBTLEAccessoryService, Service); - -Service.TunneledBTLEAccessoryService.UUID = '00000056-0000-1000-8000-0026BB765291'; diff --git a/lib/gen/HomeKitTypes-Television.js b/lib/gen/HomeKitTypes-Television.js deleted file mode 100644 index fb1719d23..000000000 --- a/lib/gen/HomeKitTypes-Television.js +++ /dev/null @@ -1,491 +0,0 @@ -'use strict'; -// Manually created from metadata in HomeKitDaemon - -var inherits = require('util').inherits; -var Characteristic = require('../Characteristic').Characteristic; -var Service = require('../Service').Service; - -/** - * Characteristic "Active Identifier" - */ - -Characteristic.ActiveIdentifier = function() { - Characteristic.call(this, 'Active Identifier', '000000E7-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT32, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.ActiveIdentifier, Characteristic); - -Characteristic.ActiveIdentifier.UUID = '000000E7-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Configured Name" - */ - -Characteristic.ConfiguredName = function() { - Characteristic.call(this, 'Configured Name', '000000E3-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.STRING, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.ConfiguredName, Characteristic); - -Characteristic.ConfiguredName.UUID = '000000E3-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Sleep Discovery Mode" - */ - -Characteristic.SleepDiscoveryMode = function() { - Characteristic.call(this, 'Sleep Discovery Mode', '000000E8-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.SleepDiscoveryMode, Characteristic); - -Characteristic.SleepDiscoveryMode.UUID = '000000E8-0000-1000-8000-0026BB765291'; - -// The value property of SleepDiscoveryMode must be one of the following: -Characteristic.SleepDiscoveryMode.NOT_DISCOVERABLE = 0; -Characteristic.SleepDiscoveryMode.ALWAYS_DISCOVERABLE = 1; - -/** - * Characteristic "Closed Captions" - */ - -Characteristic.ClosedCaptions = function() { - Characteristic.call(this, 'Closed Captions', '000000DD-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.ClosedCaptions, Characteristic); - -Characteristic.ClosedCaptions.UUID = '000000DD-0000-1000-8000-0026BB765291'; - -// The value property of ClosedCaptions must be one of the following: -Characteristic.ClosedCaptions.DISABLED = 0; -Characteristic.ClosedCaptions.ENABLED = 1; - -/** - * Characteristic "Display Order" - */ - -Characteristic.DisplayOrder = function() { - Characteristic.call(this, 'Display Order', '00000136-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.TLV8, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.DisplayOrder, Characteristic); - -Characteristic.DisplayOrder.UUID = '00000136-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Current Media State" - */ - -Characteristic.CurrentMediaState = function() { - Characteristic.call(this, 'Current Media State', '000000E0-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 3, - minValue: 0, - validValues: [0,1,2,3], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.CurrentMediaState, Characteristic); - -Characteristic.CurrentMediaState.UUID = '000000E0-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Target Media State" - */ - -Characteristic.TargetMediaState = function() { - Characteristic.call(this, 'Target Media State', '00000137-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0,1,2,3], - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.TargetMediaState, Characteristic); - -Characteristic.TargetMediaState.UUID = '00000137-0000-1000-8000-0026BB765291'; - -// The value property of TargetMediaState must be one of the following: -Characteristic.TargetMediaState.PLAY = 0; -Characteristic.TargetMediaState.PAUSE = 1; -Characteristic.TargetMediaState.STOP = 2; - -/** - * Characteristic "Picture Mode" - */ - -Characteristic.PictureMode = function() { - Characteristic.call(this, 'Picture Mode', '000000E2-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT16, - maxValue: 13, - minValue: 0, - validValues: [0,1,2,3,4,5,6,7,8,9,10,11,12,13], - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.PictureMode, Characteristic); - -Characteristic.PictureMode.UUID = '000000E2-0000-1000-8000-0026BB765291'; - -// The value property of PictureMode must be one of the following: -Characteristic.PictureMode.OTHER = 0; -Characteristic.PictureMode.STANDARD = 1; -Characteristic.PictureMode.CALIBRATED = 2; -Characteristic.PictureMode.CALIBRATED_DARK = 3; -Characteristic.PictureMode.VIVID = 4; -Characteristic.PictureMode.GAME = 5; -Characteristic.PictureMode.COMPUTER = 6; -Characteristic.PictureMode.CUSTOM = 7; - -/** - * Characteristic "Power Mode Selection" - */ - -Characteristic.PowerModeSelection = function() { - Characteristic.call(this, 'Power Mode Selection', '000000DF-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.WRITE] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.PowerModeSelection, Characteristic); - -Characteristic.PowerModeSelection.UUID = '000000DF-0000-1000-8000-0026BB765291'; - -// The value property of PowerModeSelection must be one of the following: -Characteristic.PowerModeSelection.SHOW = 0; -Characteristic.PowerModeSelection.HIDE = 1; - -/** - * Characteristic "Remote Key" - */ - -Characteristic.RemoteKey = function() { - Characteristic.call(this, 'Remote Key', '000000E1-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.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: [Characteristic.Perms.WRITE] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.RemoteKey, Characteristic); - -Characteristic.RemoteKey.UUID = '000000E1-0000-1000-8000-0026BB765291'; - -// The value property of RemoteKey must be one of the following: -Characteristic.RemoteKey.REWIND = 0; -Characteristic.RemoteKey.FAST_FORWARD = 1; -Characteristic.RemoteKey.NEXT_TRACK = 2; -Characteristic.RemoteKey.PREVIOUS_TRACK = 3; -Characteristic.RemoteKey.ARROW_UP = 4; -Characteristic.RemoteKey.ARROW_DOWN = 5; -Characteristic.RemoteKey.ARROW_LEFT = 6; -Characteristic.RemoteKey.ARROW_RIGHT = 7; -Characteristic.RemoteKey.SELECT = 8; -Characteristic.RemoteKey.BACK = 9; -Characteristic.RemoteKey.EXIT = 10; -Characteristic.RemoteKey.PLAY_PAUSE = 11; -Characteristic.RemoteKey.INFORMATION = 15; - -/** - * Characteristic "Input Source Type" - */ - -Characteristic.InputSourceType = function() { - Characteristic.call(this, 'Input Source Type', '000000DB-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 10, - minValue: 0, - validValues: [0,1,2,3,4,5,6,7,8,9,10], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.InputSourceType, Characteristic); - -Characteristic.InputSourceType.UUID = '000000DB-0000-1000-8000-0026BB765291'; - -// The value property of InputSourceType must be one of the following: -Characteristic.InputSourceType.OTHER = 0; -Characteristic.InputSourceType.HOME_SCREEN = 1; -Characteristic.InputSourceType.TUNER = 2; -Characteristic.InputSourceType.HDMI = 3; -Characteristic.InputSourceType.COMPOSITE_VIDEO = 4; -Characteristic.InputSourceType.S_VIDEO = 5; -Characteristic.InputSourceType.COMPONENT_VIDEO = 6; -Characteristic.InputSourceType.DVI = 7; -Characteristic.InputSourceType.AIRPLAY = 8; -Characteristic.InputSourceType.USB = 9; -Characteristic.InputSourceType.APPLICATION = 10; - -/** - * Characteristic "Input Device Type" - */ - -Characteristic.InputDeviceType = function() { - Characteristic.call(this, 'Input Device Type', '000000DC-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 5, - minValue: 0, - validValues: [0,1,2,3,4,5], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.InputDeviceType, Characteristic); - -Characteristic.InputDeviceType.UUID = '000000DC-0000-1000-8000-0026BB765291'; - -// The value property of InputDeviceType must be one of the following: -Characteristic.InputDeviceType.OTHER = 0; -Characteristic.InputDeviceType.TV = 1; -Characteristic.InputDeviceType.RECORDING = 2; -Characteristic.InputDeviceType.TUNER = 3; -Characteristic.InputDeviceType.PLAYBACK = 4; -Characteristic.InputDeviceType.AUDIO_SYSTEM = 5; - -/** - * Characteristic "Identifier" - */ - -Characteristic.Identifier = function() { - Characteristic.call(this, 'Identifier', '000000E6-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT32, - minValue: 0, - minStep: 1, - perms: [Characteristic.Perms.READ] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.Identifier, Characteristic); - -Characteristic.Identifier.UUID = '000000E6-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Current Visibility State" - */ - -Characteristic.CurrentVisibilityState = function() { - Characteristic.call(this, 'Current Visibility State', '00000135-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 3, - minValue: 0, - validValues: [0,1,2,3], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.CurrentVisibilityState, Characteristic); - -Characteristic.CurrentVisibilityState.UUID = '00000135-0000-1000-8000-0026BB765291'; - -// The value property of CurrentVisibilityState must be one of the following: -Characteristic.CurrentVisibilityState.SHOWN = 0; -Characteristic.CurrentVisibilityState.HIDDEN = 1; - -/** - * Characteristic "Target Visibility State" - */ - -Characteristic.TargetVisibilityState = function() { - Characteristic.call(this, 'Target Visibility State', '00000134-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.TargetVisibilityState, Characteristic); - -Characteristic.TargetVisibilityState.UUID = '00000134-0000-1000-8000-0026BB765291'; - -// The value property of TargetVisibilityState must be one of the following: -Characteristic.TargetVisibilityState.SHOWN = 0; -Characteristic.TargetVisibilityState.HIDDEN = 1; - -/** - * Characteristic "Volume Control Type" - */ - -Characteristic.VolumeControlType = function() { - Characteristic.call(this, 'Volume Control Type', '000000E9-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 3, - minValue: 0, - validValues: [0,1,2,3], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.VolumeControlType, Characteristic); - -Characteristic.VolumeControlType.UUID = '000000E9-0000-1000-8000-0026BB765291'; - -// The value property of VolumeControlType must be one of the following: -Characteristic.VolumeControlType.NONE = 0; -Characteristic.VolumeControlType.RELATIVE = 1; -Characteristic.VolumeControlType.RELATIVE_WITH_CURRENT = 2; -Characteristic.VolumeControlType.ABSOLUTE = 3; - -/** - * Characteristic "Volume Selector" - */ - -Characteristic.VolumeSelector = function() { - Characteristic.call(this, 'Volume Selector', '000000EA-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.WRITE] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.VolumeSelector, Characteristic); - -Characteristic.VolumeSelector.UUID = '000000EA-0000-1000-8000-0026BB765291'; - -// The value property of VolumeSelector must be one of the following: -Characteristic.VolumeSelector.INCREMENT = 0; -Characteristic.VolumeSelector.DECREMENT = 1; - -/** - * Service "Television" - */ - -Service.Television = function(displayName, subtype) { - Service.call(this, displayName, '000000D8-0000-1000-8000-0026BB765291', subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.Active); - this.addCharacteristic(Characteristic.ActiveIdentifier); - this.addCharacteristic(Characteristic.ConfiguredName); - 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.RemoteKey); -}; - -inherits(Service.Television, Service); - -Service.Television.UUID = '000000D8-0000-1000-8000-0026BB765291'; - -/** - * Service "Input Source" - */ - -Service.InputSource = function(displayName, subtype) { - Service.call(this, displayName, '000000D9-0000-1000-8000-0026BB765291', subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.ConfiguredName); - this.addCharacteristic(Characteristic.InputSourceType); - this.addCharacteristic(Characteristic.IsConfigured); - this.addCharacteristic(Characteristic.CurrentVisibilityState); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Identifier); - this.addOptionalCharacteristic(Characteristic.InputDeviceType); - this.addOptionalCharacteristic(Characteristic.TargetVisibilityState); - this.addOptionalCharacteristic(Characteristic.Name); -}; - -inherits(Service.InputSource, Service); - -Service.InputSource.UUID = '000000D9-0000-1000-8000-0026BB765291'; - -/** - * Service "Television Speaker" - */ - -Service.TelevisionSpeaker = function(displayName, subtype) { - Service.call(this, displayName, '00000113-0000-1000-8000-0026BB765291', 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); - this.addOptionalCharacteristic(Characteristic.Name); -}; - -inherits(Service.TelevisionSpeaker, Service); - -Service.TelevisionSpeaker.UUID = '00000113-0000-1000-8000-0026BB765291'; \ No newline at end of file diff --git a/lib/gen/HomeKitTypes.js b/lib/gen/HomeKitTypes.js deleted file mode 100755 index 266b05b06..000000000 --- a/lib/gen/HomeKitTypes.js +++ /dev/null @@ -1,3539 +0,0 @@ -'use strict'; -// THIS FILE IS AUTO-GENERATED - DO NOT MODIFY - -var inherits = require('util').inherits; -var Characteristic = require('../Characteristic').Characteristic; -var Service = require('../Service').Service; - -/** - * Characteristic "Accessory Flags" - */ - -Characteristic.AccessoryFlags = function() { - Characteristic.call(this, 'Accessory Flags', '000000A6-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT32, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.AccessoryFlags, Characteristic); - -Characteristic.AccessoryFlags.UUID = '000000A6-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Active" - */ - -Characteristic.Active = function() { - Characteristic.call(this, 'Active', '000000B0-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.Active, Characteristic); - -Characteristic.Active.UUID = '000000B0-0000-1000-8000-0026BB765291'; - -// The value property of Active must be one of the following: -Characteristic.Active.INACTIVE = 0; -Characteristic.Active.ACTIVE = 1; - -/** - * Characteristic "Administrator Only Access" - */ - -Characteristic.AdministratorOnlyAccess = function() { - Characteristic.call(this, 'Administrator Only Access', '00000001-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.BOOL, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.AdministratorOnlyAccess, Characteristic); - -Characteristic.AdministratorOnlyAccess.UUID = '00000001-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Air Particulate Density" - */ - -Characteristic.AirParticulateDensity = function() { - Characteristic.call(this, 'Air Particulate Density', '00000064-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - maxValue: 1000, - minValue: 0, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.AirParticulateDensity, Characteristic); - -Characteristic.AirParticulateDensity.UUID = '00000064-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Air Particulate Size" - */ - -Characteristic.AirParticulateSize = function() { - Characteristic.call(this, 'Air Particulate Size', '00000065-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.AirParticulateSize, Characteristic); - -Characteristic.AirParticulateSize.UUID = '00000065-0000-1000-8000-0026BB765291'; - -// The value property of AirParticulateSize must be one of the following: -Characteristic.AirParticulateSize._2_5_M = 0; -Characteristic.AirParticulateSize._10_M = 1; - -/** - * Characteristic "Air Quality" - */ - -Characteristic.AirQuality = function() { - Characteristic.call(this, 'Air Quality', '00000095-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 5, - minValue: 0, - validValues: [0,1,2,3,4,5], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.AirQuality, Characteristic); - -Characteristic.AirQuality.UUID = '00000095-0000-1000-8000-0026BB765291'; - -// The value property of AirQuality must be one of the following: -Characteristic.AirQuality.UNKNOWN = 0; -Characteristic.AirQuality.EXCELLENT = 1; -Characteristic.AirQuality.GOOD = 2; -Characteristic.AirQuality.FAIR = 3; -Characteristic.AirQuality.INFERIOR = 4; -Characteristic.AirQuality.POOR = 5; - -/** - * Characteristic "Audio Feedback" - */ - -Characteristic.AudioFeedback = function() { - Characteristic.call(this, 'Audio Feedback', '00000005-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.BOOL, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.AudioFeedback, Characteristic); - -Characteristic.AudioFeedback.UUID = '00000005-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Battery Level" - */ - -Characteristic.BatteryLevel = function() { - Characteristic.call(this, 'Battery Level', '00000068-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - unit: Characteristic.Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.BatteryLevel, Characteristic); - -Characteristic.BatteryLevel.UUID = '00000068-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Brightness" - */ - -Characteristic.Brightness = function() { - Characteristic.call(this, 'Brightness', '00000008-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.INT, - unit: Characteristic.Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.Brightness, Characteristic); - -Characteristic.Brightness.UUID = '00000008-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Carbon Dioxide Detected" - */ - -Characteristic.CarbonDioxideDetected = function() { - Characteristic.call(this, 'Carbon Dioxide Detected', '00000092-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.CarbonDioxideDetected, Characteristic); - -Characteristic.CarbonDioxideDetected.UUID = '00000092-0000-1000-8000-0026BB765291'; - -// The value property of CarbonDioxideDetected must be one of the following: -Characteristic.CarbonDioxideDetected.CO2_LEVELS_NORMAL = 0; -Characteristic.CarbonDioxideDetected.CO2_LEVELS_ABNORMAL = 1; - -/** - * Characteristic "Carbon Dioxide Level" - */ - -Characteristic.CarbonDioxideLevel = function() { - Characteristic.call(this, 'Carbon Dioxide Level', '00000093-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - maxValue: 100000, - minValue: 0, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.CarbonDioxideLevel, Characteristic); - -Characteristic.CarbonDioxideLevel.UUID = '00000093-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Carbon Dioxide Peak Level" - */ - -Characteristic.CarbonDioxidePeakLevel = function() { - Characteristic.call(this, 'Carbon Dioxide Peak Level', '00000094-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - maxValue: 100000, - minValue: 0, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.CarbonDioxidePeakLevel, Characteristic); - -Characteristic.CarbonDioxidePeakLevel.UUID = '00000094-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Carbon Monoxide Detected" - */ - -Characteristic.CarbonMonoxideDetected = function() { - Characteristic.call(this, 'Carbon Monoxide Detected', '00000069-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.CarbonMonoxideDetected, Characteristic); - -Characteristic.CarbonMonoxideDetected.UUID = '00000069-0000-1000-8000-0026BB765291'; - -// The value property of CarbonMonoxideDetected must be one of the following: -Characteristic.CarbonMonoxideDetected.CO_LEVELS_NORMAL = 0; -Characteristic.CarbonMonoxideDetected.CO_LEVELS_ABNORMAL = 1; - -/** - * Characteristic "Carbon Monoxide Level" - */ - -Characteristic.CarbonMonoxideLevel = function() { - Characteristic.call(this, 'Carbon Monoxide Level', '00000090-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - maxValue: 100, - minValue: 0, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.CarbonMonoxideLevel, Characteristic); - -Characteristic.CarbonMonoxideLevel.UUID = '00000090-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Carbon Monoxide Peak Level" - */ - -Characteristic.CarbonMonoxidePeakLevel = function() { - Characteristic.call(this, 'Carbon Monoxide Peak Level', '00000091-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - maxValue: 100, - minValue: 0, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.CarbonMonoxidePeakLevel, Characteristic); - -Characteristic.CarbonMonoxidePeakLevel.UUID = '00000091-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Charging State" - */ - -Characteristic.ChargingState = function() { - Characteristic.call(this, 'Charging State', '0000008F-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0,1,2], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.ChargingState, Characteristic); - -Characteristic.ChargingState.UUID = '0000008F-0000-1000-8000-0026BB765291'; - -// The value property of ChargingState must be one of the following: -Characteristic.ChargingState.NOT_CHARGING = 0; -Characteristic.ChargingState.CHARGING = 1; -Characteristic.ChargingState.NOT_CHARGEABLE = 2; - -/** - * Characteristic "Color Temperature" - */ - -Characteristic.ColorTemperature = function() { - Characteristic.call(this, 'Color Temperature', '000000CE-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT32, - maxValue: 500, - minValue: 140, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.ColorTemperature, Characteristic); - -Characteristic.ColorTemperature.UUID = '000000CE-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Contact Sensor State" - */ - -Characteristic.ContactSensorState = function() { - Characteristic.call(this, 'Contact Sensor State', '0000006A-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.ContactSensorState, Characteristic); - -Characteristic.ContactSensorState.UUID = '0000006A-0000-1000-8000-0026BB765291'; - -// The value property of ContactSensorState must be one of the following: -Characteristic.ContactSensorState.CONTACT_DETECTED = 0; -Characteristic.ContactSensorState.CONTACT_NOT_DETECTED = 1; - -/** - * Characteristic "Cooling Threshold Temperature" - */ - -Characteristic.CoolingThresholdTemperature = function() { - Characteristic.call(this, 'Cooling Threshold Temperature', '0000000D-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - unit: Characteristic.Units.CELSIUS, - maxValue: 35, - minValue: 10, - minStep: 0.1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.CoolingThresholdTemperature, Characteristic); - -Characteristic.CoolingThresholdTemperature.UUID = '0000000D-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Current Air Purifier State" - */ - -Characteristic.CurrentAirPurifierState = function() { - Characteristic.call(this, 'Current Air Purifier State', '000000A9-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0,1,2], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.CurrentAirPurifierState, Characteristic); - -Characteristic.CurrentAirPurifierState.UUID = '000000A9-0000-1000-8000-0026BB765291'; - -// The value property of CurrentAirPurifierState must be one of the following: -Characteristic.CurrentAirPurifierState.INACTIVE = 0; -Characteristic.CurrentAirPurifierState.IDLE = 1; -Characteristic.CurrentAirPurifierState.PURIFYING_AIR = 2; - -/** - * Characteristic "Current Ambient Light Level" - */ - -Characteristic.CurrentAmbientLightLevel = function() { - Characteristic.call(this, 'Current Ambient Light Level', '0000006B-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - unit: Characteristic.Units.LUX, - maxValue: 100000, - minValue: 0.0001, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.CurrentAmbientLightLevel, Characteristic); - -Characteristic.CurrentAmbientLightLevel.UUID = '0000006B-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Current Door State" - */ - -Characteristic.CurrentDoorState = function() { - Characteristic.call(this, 'Current Door State', '0000000E-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 4, - minValue: 0, - validValues: [0,1,2,3,4], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.CurrentDoorState, Characteristic); - -Characteristic.CurrentDoorState.UUID = '0000000E-0000-1000-8000-0026BB765291'; - -// The value property of CurrentDoorState must be one of the following: -Characteristic.CurrentDoorState.OPEN = 0; -Characteristic.CurrentDoorState.CLOSED = 1; -Characteristic.CurrentDoorState.OPENING = 2; -Characteristic.CurrentDoorState.CLOSING = 3; -Characteristic.CurrentDoorState.STOPPED = 4; - -/** - * Characteristic "Current Fan State" - */ - -Characteristic.CurrentFanState = function() { - Characteristic.call(this, 'Current Fan State', '000000AF-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0,1,2], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.CurrentFanState, Characteristic); - -Characteristic.CurrentFanState.UUID = '000000AF-0000-1000-8000-0026BB765291'; - -// The value property of CurrentFanState must be one of the following: -Characteristic.CurrentFanState.INACTIVE = 0; -Characteristic.CurrentFanState.IDLE = 1; -Characteristic.CurrentFanState.BLOWING_AIR = 2; - -/** - * Characteristic "Current Heater Cooler State" - */ - -Characteristic.CurrentHeaterCoolerState = function() { - Characteristic.call(this, 'Current Heater Cooler State', '000000B1-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 3, - minValue: 0, - validValues: [0,1,2,3], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.CurrentHeaterCoolerState, Characteristic); - -Characteristic.CurrentHeaterCoolerState.UUID = '000000B1-0000-1000-8000-0026BB765291'; - -// The value property of CurrentHeaterCoolerState must be one of the following: -Characteristic.CurrentHeaterCoolerState.INACTIVE = 0; -Characteristic.CurrentHeaterCoolerState.IDLE = 1; -Characteristic.CurrentHeaterCoolerState.HEATING = 2; -Characteristic.CurrentHeaterCoolerState.COOLING = 3; - -/** - * Characteristic "Current Heating Cooling State" - */ - -Characteristic.CurrentHeatingCoolingState = function() { - Characteristic.call(this, 'Current Heating Cooling State', '0000000F-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0,1,2], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.CurrentHeatingCoolingState, Characteristic); - -Characteristic.CurrentHeatingCoolingState.UUID = '0000000F-0000-1000-8000-0026BB765291'; - -// The value property of CurrentHeatingCoolingState must be one of the following: -Characteristic.CurrentHeatingCoolingState.OFF = 0; -Characteristic.CurrentHeatingCoolingState.HEAT = 1; -Characteristic.CurrentHeatingCoolingState.COOL = 2; - -/** - * Characteristic "Current Horizontal Tilt Angle" - */ - -Characteristic.CurrentHorizontalTiltAngle = function() { - Characteristic.call(this, 'Current Horizontal Tilt Angle', '0000006C-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.INT, - unit: Characteristic.Units.ARC_DEGREE, - maxValue: 90, - minValue: -90, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.CurrentHorizontalTiltAngle, Characteristic); - -Characteristic.CurrentHorizontalTiltAngle.UUID = '0000006C-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Current Humidifier Dehumidifier State" - */ - -Characteristic.CurrentHumidifierDehumidifierState = function() { - Characteristic.call(this, 'Current Humidifier Dehumidifier State', '000000B3-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 3, - minValue: 0, - validValues: [0,1,2,3], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.CurrentHumidifierDehumidifierState, Characteristic); - -Characteristic.CurrentHumidifierDehumidifierState.UUID = '000000B3-0000-1000-8000-0026BB765291'; - -// The value property of CurrentHumidifierDehumidifierState must be one of the following: -Characteristic.CurrentHumidifierDehumidifierState.INACTIVE = 0; -Characteristic.CurrentHumidifierDehumidifierState.IDLE = 1; -Characteristic.CurrentHumidifierDehumidifierState.HUMIDIFYING = 2; -Characteristic.CurrentHumidifierDehumidifierState.DEHUMIDIFYING = 3; - -/** - * Characteristic "Current Position" - */ - -Characteristic.CurrentPosition = function() { - Characteristic.call(this, 'Current Position', '0000006D-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - unit: Characteristic.Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.CurrentPosition, Characteristic); - -Characteristic.CurrentPosition.UUID = '0000006D-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Current Relative Humidity" - */ - -Characteristic.CurrentRelativeHumidity = function() { - Characteristic.call(this, 'Current Relative Humidity', '00000010-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - unit: Characteristic.Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.CurrentRelativeHumidity, Characteristic); - -Characteristic.CurrentRelativeHumidity.UUID = '00000010-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Current Slat State" - */ - -Characteristic.CurrentSlatState = function() { - Characteristic.call(this, 'Current Slat State', '000000AA-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0,1,2], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.CurrentSlatState, Characteristic); - -Characteristic.CurrentSlatState.UUID = '000000AA-0000-1000-8000-0026BB765291'; - -// The value property of CurrentSlatState must be one of the following: -Characteristic.CurrentSlatState.FIXED = 0; -Characteristic.CurrentSlatState.JAMMED = 1; -Characteristic.CurrentSlatState.SWINGING = 2; - -/** - * Characteristic "Current Temperature" - */ - -Characteristic.CurrentTemperature = function() { - Characteristic.call(this, 'Current Temperature', '00000011-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - unit: Characteristic.Units.CELSIUS, - maxValue: 100, - minValue: 0, - minStep: 0.1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.CurrentTemperature, Characteristic); - -Characteristic.CurrentTemperature.UUID = '00000011-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Current Tilt Angle" - */ - -Characteristic.CurrentTiltAngle = function() { - Characteristic.call(this, 'Current Tilt Angle', '000000C1-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.INT, - unit: Characteristic.Units.ARC_DEGREE, - maxValue: 90, - minValue: -90, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.CurrentTiltAngle, Characteristic); - -Characteristic.CurrentTiltAngle.UUID = '000000C1-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Current Vertical Tilt Angle" - */ - -Characteristic.CurrentVerticalTiltAngle = function() { - Characteristic.call(this, 'Current Vertical Tilt Angle', '0000006E-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.INT, - unit: Characteristic.Units.ARC_DEGREE, - maxValue: 90, - minValue: -90, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.CurrentVerticalTiltAngle, Characteristic); - -Characteristic.CurrentVerticalTiltAngle.UUID = '0000006E-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Digital Zoom" - */ - -Characteristic.DigitalZoom = function() { - Characteristic.call(this, 'Digital Zoom', '0000011D-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.DigitalZoom, Characteristic); - -Characteristic.DigitalZoom.UUID = '0000011D-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Filter Change Indication" - */ - -Characteristic.FilterChangeIndication = function() { - Characteristic.call(this, 'Filter Change Indication', '000000AC-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.FilterChangeIndication, Characteristic); - -Characteristic.FilterChangeIndication.UUID = '000000AC-0000-1000-8000-0026BB765291'; - -// The value property of FilterChangeIndication must be one of the following: -Characteristic.FilterChangeIndication.FILTER_OK = 0; -Characteristic.FilterChangeIndication.CHANGE_FILTER = 1; - -/** - * Characteristic "Filter Life Level" - */ - -Characteristic.FilterLifeLevel = function() { - Characteristic.call(this, 'Filter Life Level', '000000AB-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - maxValue: 100, - minValue: 0, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.FilterLifeLevel, Characteristic); - -Characteristic.FilterLifeLevel.UUID = '000000AB-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Firmware Revision" - */ - -Characteristic.FirmwareRevision = function() { - Characteristic.call(this, 'Firmware Revision', '00000052-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.STRING, - perms: [Characteristic.Perms.READ] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.FirmwareRevision, Characteristic); - -Characteristic.FirmwareRevision.UUID = '00000052-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Hardware Revision" - */ - -Characteristic.HardwareRevision = function() { - Characteristic.call(this, 'Hardware Revision', '00000053-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.STRING, - perms: [Characteristic.Perms.READ] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.HardwareRevision, Characteristic); - -Characteristic.HardwareRevision.UUID = '00000053-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Heating Threshold Temperature" - */ - -Characteristic.HeatingThresholdTemperature = function() { - Characteristic.call(this, 'Heating Threshold Temperature', '00000012-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - unit: Characteristic.Units.CELSIUS, - maxValue: 25, - minValue: 0, - minStep: 0.1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.HeatingThresholdTemperature, Characteristic); - -Characteristic.HeatingThresholdTemperature.UUID = '00000012-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Hold Position" - */ - -Characteristic.HoldPosition = function() { - Characteristic.call(this, 'Hold Position', '0000006F-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.BOOL, - perms: [Characteristic.Perms.WRITE] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.HoldPosition, Characteristic); - -Characteristic.HoldPosition.UUID = '0000006F-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Hue" - */ - -Characteristic.Hue = function() { - Characteristic.call(this, 'Hue', '00000013-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - unit: Characteristic.Units.ARC_DEGREE, - maxValue: 360, - minValue: 0, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.Hue, Characteristic); - -Characteristic.Hue.UUID = '00000013-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Identify" - */ - -Characteristic.Identify = function() { - Characteristic.call(this, 'Identify', '00000014-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.BOOL, - perms: [Characteristic.Perms.WRITE] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.Identify, Characteristic); - -Characteristic.Identify.UUID = '00000014-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Image Mirroring" - */ - -Characteristic.ImageMirroring = function() { - Characteristic.call(this, 'Image Mirroring', '0000011F-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.BOOL, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.ImageMirroring, Characteristic); - -Characteristic.ImageMirroring.UUID = '0000011F-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Image Rotation" - */ - -Characteristic.ImageRotation = function() { - Characteristic.call(this, 'Image Rotation', '0000011E-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - unit: Characteristic.Units.ARC_DEGREE, - maxValue: 270, - minValue: 0, - minStep: 90, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.ImageRotation, Characteristic); - -Characteristic.ImageRotation.UUID = '0000011E-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "In Use" - */ - -Characteristic.InUse = function() { - Characteristic.call(this, 'In Use', '000000D2-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.InUse, Characteristic); - -Characteristic.InUse.UUID = '000000D2-0000-1000-8000-0026BB765291'; - -// The value property of InUse must be one of the following: -Characteristic.InUse.NOT_IN_USE = 0; -Characteristic.InUse.IN_USE = 1; - -/** - * Characteristic "Is Configured" - */ - -Characteristic.IsConfigured = function() { - Characteristic.call(this, 'Is Configured', '000000D6-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.IsConfigured, Characteristic); - -Characteristic.IsConfigured.UUID = '000000D6-0000-1000-8000-0026BB765291'; - -// The value property of IsConfigured must be one of the following: -Characteristic.IsConfigured.NOT_CONFIGURED = 0; -Characteristic.IsConfigured.CONFIGURED = 1; - -/** - * Characteristic "Leak Detected" - */ - -Characteristic.LeakDetected = function() { - Characteristic.call(this, 'Leak Detected', '00000070-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.LeakDetected, Characteristic); - -Characteristic.LeakDetected.UUID = '00000070-0000-1000-8000-0026BB765291'; - -// The value property of LeakDetected must be one of the following: -Characteristic.LeakDetected.LEAK_NOT_DETECTED = 0; -Characteristic.LeakDetected.LEAK_DETECTED = 1; - -/** - * Characteristic "Lock Control Point" - */ - -Characteristic.LockControlPoint = function() { - Characteristic.call(this, 'Lock Control Point', '00000019-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.TLV8, - perms: [Characteristic.Perms.WRITE] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.LockControlPoint, Characteristic); - -Characteristic.LockControlPoint.UUID = '00000019-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Lock Current State" - */ - -Characteristic.LockCurrentState = function() { - Characteristic.call(this, 'Lock Current State', '0000001D-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 3, - minValue: 0, - validValues: [0,1,2,3], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.LockCurrentState, Characteristic); - -Characteristic.LockCurrentState.UUID = '0000001D-0000-1000-8000-0026BB765291'; - -// The value property of LockCurrentState must be one of the following: -Characteristic.LockCurrentState.UNSECURED = 0; -Characteristic.LockCurrentState.SECURED = 1; -Characteristic.LockCurrentState.JAMMED = 2; -Characteristic.LockCurrentState.UNKNOWN = 3; - -/** - * Characteristic "Lock Last Known Action" - */ - -Characteristic.LockLastKnownAction = function() { - Characteristic.call(this, 'Lock Last Known Action', '0000001C-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 8, - minValue: 0, - validValues: [0,1,2,3,4,5,6,7,8], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.LockLastKnownAction, Characteristic); - -Characteristic.LockLastKnownAction.UUID = '0000001C-0000-1000-8000-0026BB765291'; - -// The value property of LockLastKnownAction must be one of the following: -Characteristic.LockLastKnownAction.SECURED_PHYSICALLY_INTERIOR = 0; -Characteristic.LockLastKnownAction.UNSECURED_PHYSICALLY_INTERIOR = 1; -Characteristic.LockLastKnownAction.SECURED_PHYSICALLY_EXTERIOR = 2; -Characteristic.LockLastKnownAction.UNSECURED_PHYSICALLY_EXTERIOR = 3; -Characteristic.LockLastKnownAction.SECURED_BY_KEYPAD = 4; -Characteristic.LockLastKnownAction.UNSECURED_BY_KEYPAD = 5; -Characteristic.LockLastKnownAction.SECURED_REMOTELY = 6; -Characteristic.LockLastKnownAction.UNSECURED_REMOTELY = 7; -Characteristic.LockLastKnownAction.SECURED_BY_AUTO_SECURE_TIMEOUT = 8; - -/** - * Characteristic "Lock Management Auto Security Timeout" - */ - -Characteristic.LockManagementAutoSecurityTimeout = function() { - Characteristic.call(this, 'Lock Management Auto Security Timeout', '0000001A-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT32, - unit: Characteristic.Units.SECONDS, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.LockManagementAutoSecurityTimeout, Characteristic); - -Characteristic.LockManagementAutoSecurityTimeout.UUID = '0000001A-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Lock Physical Controls" - */ - -Characteristic.LockPhysicalControls = function() { - Characteristic.call(this, 'Lock Physical Controls', '000000A7-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.LockPhysicalControls, Characteristic); - -Characteristic.LockPhysicalControls.UUID = '000000A7-0000-1000-8000-0026BB765291'; - -// The value property of LockPhysicalControls must be one of the following: -Characteristic.LockPhysicalControls.CONTROL_LOCK_DISABLED = 0; -Characteristic.LockPhysicalControls.CONTROL_LOCK_ENABLED = 1; - -/** - * Characteristic "Lock Target State" - */ - -Characteristic.LockTargetState = function() { - Characteristic.call(this, 'Lock Target State', '0000001E-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.LockTargetState, Characteristic); - -Characteristic.LockTargetState.UUID = '0000001E-0000-1000-8000-0026BB765291'; - -// The value property of LockTargetState must be one of the following: -Characteristic.LockTargetState.UNSECURED = 0; -Characteristic.LockTargetState.SECURED = 1; - -/** - * Characteristic "Logs" - */ - -Characteristic.Logs = function() { - Characteristic.call(this, 'Logs', '0000001F-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.TLV8, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.Logs, Characteristic); - -Characteristic.Logs.UUID = '0000001F-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Manufacturer" - */ - -Characteristic.Manufacturer = function() { - Characteristic.call(this, 'Manufacturer', '00000020-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.STRING, - perms: [Characteristic.Perms.READ] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.Manufacturer, Characteristic); - -Characteristic.Manufacturer.UUID = '00000020-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Model" - */ - -Characteristic.Model = function() { - Characteristic.call(this, 'Model', '00000021-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.STRING, - perms: [Characteristic.Perms.READ] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.Model, Characteristic); - -Characteristic.Model.UUID = '00000021-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Motion Detected" - */ - -Characteristic.MotionDetected = function() { - Characteristic.call(this, 'Motion Detected', '00000022-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.BOOL, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.MotionDetected, Characteristic); - -Characteristic.MotionDetected.UUID = '00000022-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Mute" - */ - -Characteristic.Mute = function() { - Characteristic.call(this, 'Mute', '0000011A-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.BOOL, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.Mute, Characteristic); - -Characteristic.Mute.UUID = '0000011A-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Name" - */ - -Characteristic.Name = function() { - Characteristic.call(this, 'Name', '00000023-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.STRING, - perms: [Characteristic.Perms.READ] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.Name, Characteristic); - -Characteristic.Name.UUID = '00000023-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Night Vision" - */ - -Characteristic.NightVision = function() { - Characteristic.call(this, 'Night Vision', '0000011B-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.BOOL, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.NightVision, Characteristic); - -Characteristic.NightVision.UUID = '0000011B-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Nitrogen Dioxide Density" - */ - -Characteristic.NitrogenDioxideDensity = function() { - Characteristic.call(this, 'Nitrogen Dioxide Density', '000000C4-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - maxValue: 1000, - minValue: 0, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.NitrogenDioxideDensity, Characteristic); - -Characteristic.NitrogenDioxideDensity.UUID = '000000C4-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Obstruction Detected" - */ - -Characteristic.ObstructionDetected = function() { - Characteristic.call(this, 'Obstruction Detected', '00000024-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.BOOL, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.ObstructionDetected, Characteristic); - -Characteristic.ObstructionDetected.UUID = '00000024-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Occupancy Detected" - */ - -Characteristic.OccupancyDetected = function() { - Characteristic.call(this, 'Occupancy Detected', '00000071-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.OccupancyDetected, Characteristic); - -Characteristic.OccupancyDetected.UUID = '00000071-0000-1000-8000-0026BB765291'; - -// The value property of OccupancyDetected must be one of the following: -Characteristic.OccupancyDetected.OCCUPANCY_NOT_DETECTED = 0; -Characteristic.OccupancyDetected.OCCUPANCY_DETECTED = 1; - -/** - * Characteristic "On" - */ - -Characteristic.On = function() { - Characteristic.call(this, 'On', '00000025-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.BOOL, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.On, Characteristic); - -Characteristic.On.UUID = '00000025-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Optical Zoom" - */ - -Characteristic.OpticalZoom = function() { - Characteristic.call(this, 'Optical Zoom', '0000011C-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.OpticalZoom, Characteristic); - -Characteristic.OpticalZoom.UUID = '0000011C-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Outlet In Use" - */ - -Characteristic.OutletInUse = function() { - Characteristic.call(this, 'Outlet In Use', '00000026-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.BOOL, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.OutletInUse, Characteristic); - -Characteristic.OutletInUse.UUID = '00000026-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Ozone Density" - */ - -Characteristic.OzoneDensity = function() { - Characteristic.call(this, 'Ozone Density', '000000C3-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - maxValue: 1000, - minValue: 0, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.OzoneDensity, Characteristic); - -Characteristic.OzoneDensity.UUID = '000000C3-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Pair Setup" - */ - -Characteristic.PairSetup = function() { - Characteristic.call(this, 'Pair Setup', '0000004C-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.TLV8, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.PairSetup, Characteristic); - -Characteristic.PairSetup.UUID = '0000004C-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Pair Verify" - */ - -Characteristic.PairVerify = function() { - Characteristic.call(this, 'Pair Verify', '0000004E-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.TLV8, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.PairVerify, Characteristic); - -Characteristic.PairVerify.UUID = '0000004E-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Pairing Features" - */ - -Characteristic.PairingFeatures = function() { - Characteristic.call(this, 'Pairing Features', '0000004F-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - perms: [Characteristic.Perms.READ] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.PairingFeatures, Characteristic); - -Characteristic.PairingFeatures.UUID = '0000004F-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Pairing Pairings" - */ - -Characteristic.PairingPairings = function() { - Characteristic.call(this, 'Pairing Pairings', '00000050-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.TLV8, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.PairingPairings, Characteristic); - -Characteristic.PairingPairings.UUID = '00000050-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "PM10 Density" - */ - -Characteristic.PM10Density = function() { - Characteristic.call(this, 'PM10 Density', '000000C7-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - maxValue: 1000, - minValue: 0, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.PM10Density, Characteristic); - -Characteristic.PM10Density.UUID = '000000C7-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "PM2.5 Density" - */ - -Characteristic.PM2_5Density = function() { - Characteristic.call(this, 'PM2.5 Density', '000000C6-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - maxValue: 1000, - minValue: 0, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.PM2_5Density, Characteristic); - -Characteristic.PM2_5Density.UUID = '000000C6-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Position State" - */ - -Characteristic.PositionState = function() { - Characteristic.call(this, 'Position State', '00000072-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0,1,2], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.PositionState, Characteristic); - -Characteristic.PositionState.UUID = '00000072-0000-1000-8000-0026BB765291'; - -// The value property of PositionState must be one of the following: -Characteristic.PositionState.DECREASING = 0; -Characteristic.PositionState.INCREASING = 1; -Characteristic.PositionState.STOPPED = 2; - -/** - * Characteristic "Program Mode" - */ - -Characteristic.ProgramMode = function() { - Characteristic.call(this, 'Program Mode', '000000D1-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0,1,2], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.ProgramMode, Characteristic); - -Characteristic.ProgramMode.UUID = '000000D1-0000-1000-8000-0026BB765291'; - -// The value property of ProgramMode must be one of the following: -Characteristic.ProgramMode.NO_PROGRAM_SCHEDULED = 0; -Characteristic.ProgramMode.PROGRAM_SCHEDULED = 1; -Characteristic.ProgramMode.PROGRAM_SCHEDULED_MANUAL_MODE_ = 2; - -/** - * Characteristic "Programmable Switch Event" - */ - -Characteristic.ProgrammableSwitchEvent = function() { - Characteristic.call(this, 'Programmable Switch Event', '00000073-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0,1,2], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.eventOnlyCharacteristic = true; //Manual addition. - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.ProgrammableSwitchEvent, Characteristic); - -Characteristic.ProgrammableSwitchEvent.UUID = '00000073-0000-1000-8000-0026BB765291'; - -// The value property of ProgrammableSwitchEvent must be one of the following: -Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS = 0; -Characteristic.ProgrammableSwitchEvent.DOUBLE_PRESS = 1; -Characteristic.ProgrammableSwitchEvent.LONG_PRESS = 2; - -/** - * Characteristic "Relative Humidity Dehumidifier Threshold" - */ - -Characteristic.RelativeHumidityDehumidifierThreshold = function() { - Characteristic.call(this, 'Relative Humidity Dehumidifier Threshold', '000000C9-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.RelativeHumidityDehumidifierThreshold, Characteristic); - -Characteristic.RelativeHumidityDehumidifierThreshold.UUID = '000000C9-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Relative Humidity Humidifier Threshold" - */ - -Characteristic.RelativeHumidityHumidifierThreshold = function() { - Characteristic.call(this, 'Relative Humidity Humidifier Threshold', '000000CA-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - unit: Characteristic.Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.RelativeHumidityHumidifierThreshold, Characteristic); - -Characteristic.RelativeHumidityHumidifierThreshold.UUID = '000000CA-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Remaining Duration" - */ - -Characteristic.RemainingDuration = function() { - Characteristic.call(this, 'Remaining Duration', '000000D4-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT32, - maxValue: 3600, - minValue: 0, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.RemainingDuration, Characteristic); - -Characteristic.RemainingDuration.UUID = '000000D4-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Reset Filter Indication" - */ - -Characteristic.ResetFilterIndication = function() { - Characteristic.call(this, 'Reset Filter Indication', '000000AD-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 1, - minStep: 1, - perms: [Characteristic.Perms.WRITE] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.ResetFilterIndication, Characteristic); - -Characteristic.ResetFilterIndication.UUID = '000000AD-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Rotation Direction" - */ - -Characteristic.RotationDirection = function() { - Characteristic.call(this, 'Rotation Direction', '00000028-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.INT, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.RotationDirection, Characteristic); - -Characteristic.RotationDirection.UUID = '00000028-0000-1000-8000-0026BB765291'; - -// The value property of RotationDirection must be one of the following: -Characteristic.RotationDirection.CLOCKWISE = 0; -Characteristic.RotationDirection.COUNTER_CLOCKWISE = 1; - -/** - * Characteristic "Rotation Speed" - */ - -Characteristic.RotationSpeed = function() { - Characteristic.call(this, 'Rotation Speed', '00000029-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - unit: Characteristic.Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.RotationSpeed, Characteristic); - -Characteristic.RotationSpeed.UUID = '00000029-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Saturation" - */ - -Characteristic.Saturation = function() { - Characteristic.call(this, 'Saturation', '0000002F-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - unit: Characteristic.Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.Saturation, Characteristic); - -Characteristic.Saturation.UUID = '0000002F-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Security System Alarm Type" - */ - -Characteristic.SecuritySystemAlarmType = function() { - Characteristic.call(this, 'Security System Alarm Type', '0000008E-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.SecuritySystemAlarmType, Characteristic); - -Characteristic.SecuritySystemAlarmType.UUID = '0000008E-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Security System Current State" - */ - -Characteristic.SecuritySystemCurrentState = function() { - Characteristic.call(this, 'Security System Current State', '00000066-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 4, - minValue: 0, - validValues: [0,1,2,3,4], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.SecuritySystemCurrentState, Characteristic); - -Characteristic.SecuritySystemCurrentState.UUID = '00000066-0000-1000-8000-0026BB765291'; - -// The value property of SecuritySystemCurrentState must be one of the following: -Characteristic.SecuritySystemCurrentState.STAY_ARM = 0; -Characteristic.SecuritySystemCurrentState.AWAY_ARM = 1; -Characteristic.SecuritySystemCurrentState.NIGHT_ARM = 2; -Characteristic.SecuritySystemCurrentState.DISARMED = 3; -Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED = 4; - -/** - * Characteristic "Security System Target State" - */ - -Characteristic.SecuritySystemTargetState = function() { - Characteristic.call(this, 'Security System Target State', '00000067-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 3, - minValue: 0, - validValues: [0,1,2,3], - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.SecuritySystemTargetState, Characteristic); - -Characteristic.SecuritySystemTargetState.UUID = '00000067-0000-1000-8000-0026BB765291'; - -// The value property of SecuritySystemTargetState must be one of the following: -Characteristic.SecuritySystemTargetState.STAY_ARM = 0; -Characteristic.SecuritySystemTargetState.AWAY_ARM = 1; -Characteristic.SecuritySystemTargetState.NIGHT_ARM = 2; -Characteristic.SecuritySystemTargetState.DISARM = 3; - -/** - * Characteristic "Selected RTP Stream Configuration" - */ - -Characteristic.SelectedRTPStreamConfiguration = function() { - Characteristic.call(this, 'Selected RTP Stream Configuration', '00000117-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.TLV8, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.SelectedRTPStreamConfiguration, Characteristic); - -Characteristic.SelectedRTPStreamConfiguration.UUID = '00000117-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Serial Number" - */ - -Characteristic.SerialNumber = function() { - Characteristic.call(this, 'Serial Number', '00000030-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.STRING, - perms: [Characteristic.Perms.READ] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.SerialNumber, Characteristic); - -Characteristic.SerialNumber.UUID = '00000030-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Service Label Index" - */ - -Characteristic.ServiceLabelIndex = function() { - Characteristic.call(this, 'Service Label Index', '000000CB-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 255, - minValue: 1, - minStep: 1, - perms: [Characteristic.Perms.READ] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.ServiceLabelIndex, Characteristic); - -Characteristic.ServiceLabelIndex.UUID = '000000CB-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Service Label Namespace" - */ - -Characteristic.ServiceLabelNamespace = function() { - Characteristic.call(this, 'Service Label Namespace', '000000CD-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.ServiceLabelNamespace, Characteristic); - -Characteristic.ServiceLabelNamespace.UUID = '000000CD-0000-1000-8000-0026BB765291'; - -// The value property of ServiceLabelNamespace must be one of the following: -Characteristic.ServiceLabelNamespace.DOTS = 0; -Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS = 1; - -/** - * Characteristic "Set Duration" - */ - -Characteristic.SetDuration = function() { - Characteristic.call(this, 'Set Duration', '000000D3-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT32, - maxValue: 3600, - minValue: 0, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.SetDuration, Characteristic); - -Characteristic.SetDuration.UUID = '000000D3-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Setup Endpoints" - */ - -Characteristic.SetupEndpoints = function() { - Characteristic.call(this, 'Setup Endpoints', '00000118-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.TLV8, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.SetupEndpoints, Characteristic); - -Characteristic.SetupEndpoints.UUID = '00000118-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Slat Type" - */ - -Characteristic.SlatType = function() { - Characteristic.call(this, 'Slat Type', '000000C0-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.SlatType, Characteristic); - -Characteristic.SlatType.UUID = '000000C0-0000-1000-8000-0026BB765291'; - -// The value property of SlatType must be one of the following: -Characteristic.SlatType.HORIZONTAL = 0; -Characteristic.SlatType.VERTICAL = 1; - -/** - * Characteristic "Smoke Detected" - */ - -Characteristic.SmokeDetected = function() { - Characteristic.call(this, 'Smoke Detected', '00000076-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.SmokeDetected, Characteristic); - -Characteristic.SmokeDetected.UUID = '00000076-0000-1000-8000-0026BB765291'; - -// The value property of SmokeDetected must be one of the following: -Characteristic.SmokeDetected.SMOKE_NOT_DETECTED = 0; -Characteristic.SmokeDetected.SMOKE_DETECTED = 1; - -/** - * Characteristic "Status Active" - */ - -Characteristic.StatusActive = function() { - Characteristic.call(this, 'Status Active', '00000075-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.BOOL, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.StatusActive, Characteristic); - -Characteristic.StatusActive.UUID = '00000075-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Status Fault" - */ - -Characteristic.StatusFault = function() { - Characteristic.call(this, 'Status Fault', '00000077-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.StatusFault, Characteristic); - -Characteristic.StatusFault.UUID = '00000077-0000-1000-8000-0026BB765291'; - -// The value property of StatusFault must be one of the following: -Characteristic.StatusFault.NO_FAULT = 0; -Characteristic.StatusFault.GENERAL_FAULT = 1; - -/** - * Characteristic "Status Jammed" - */ - -Characteristic.StatusJammed = function() { - Characteristic.call(this, 'Status Jammed', '00000078-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.StatusJammed, Characteristic); - -Characteristic.StatusJammed.UUID = '00000078-0000-1000-8000-0026BB765291'; - -// The value property of StatusJammed must be one of the following: -Characteristic.StatusJammed.NOT_JAMMED = 0; -Characteristic.StatusJammed.JAMMED = 1; - -/** - * Characteristic "Status Low Battery" - */ - -Characteristic.StatusLowBattery = function() { - Characteristic.call(this, 'Status Low Battery', '00000079-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.StatusLowBattery, Characteristic); - -Characteristic.StatusLowBattery.UUID = '00000079-0000-1000-8000-0026BB765291'; - -// The value property of StatusLowBattery must be one of the following: -Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL = 0; -Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW = 1; - -/** - * Characteristic "Status Tampered" - */ - -Characteristic.StatusTampered = function() { - Characteristic.call(this, 'Status Tampered', '0000007A-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.StatusTampered, Characteristic); - -Characteristic.StatusTampered.UUID = '0000007A-0000-1000-8000-0026BB765291'; - -// The value property of StatusTampered must be one of the following: -Characteristic.StatusTampered.NOT_TAMPERED = 0; -Characteristic.StatusTampered.TAMPERED = 1; - -/** - * Characteristic "Streaming Status" - */ - -Characteristic.StreamingStatus = function() { - Characteristic.call(this, 'Streaming Status', '00000120-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.TLV8, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.StreamingStatus, Characteristic); - -Characteristic.StreamingStatus.UUID = '00000120-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Sulphur Dioxide Density" - */ - -Characteristic.SulphurDioxideDensity = function() { - Characteristic.call(this, 'Sulphur Dioxide Density', '000000C5-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - maxValue: 1000, - minValue: 0, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.SulphurDioxideDensity, Characteristic); - -Characteristic.SulphurDioxideDensity.UUID = '000000C5-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Supported Audio Stream Configuration" - */ - -Characteristic.SupportedAudioStreamConfiguration = function() { - Characteristic.call(this, 'Supported Audio Stream Configuration', '00000115-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.TLV8, - perms: [Characteristic.Perms.READ] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.SupportedAudioStreamConfiguration, Characteristic); - -Characteristic.SupportedAudioStreamConfiguration.UUID = '00000115-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Supported RTP Configuration" - */ - -Characteristic.SupportedRTPConfiguration = function() { - Characteristic.call(this, 'Supported RTP Configuration', '00000116-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.TLV8, - perms: [Characteristic.Perms.READ] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.SupportedRTPConfiguration, Characteristic); - -Characteristic.SupportedRTPConfiguration.UUID = '00000116-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Supported Video Stream Configuration" - */ - -Characteristic.SupportedVideoStreamConfiguration = function() { - Characteristic.call(this, 'Supported Video Stream Configuration', '00000114-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.TLV8, - perms: [Characteristic.Perms.READ] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.SupportedVideoStreamConfiguration, Characteristic); - -Characteristic.SupportedVideoStreamConfiguration.UUID = '00000114-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Swing Mode" - */ - -Characteristic.SwingMode = function() { - Characteristic.call(this, 'Swing Mode', '000000B6-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.SwingMode, Characteristic); - -Characteristic.SwingMode.UUID = '000000B6-0000-1000-8000-0026BB765291'; - -// The value property of SwingMode must be one of the following: -Characteristic.SwingMode.SWING_DISABLED = 0; -Characteristic.SwingMode.SWING_ENABLED = 1; - -/** - * Characteristic "Target Air Purifier State" - */ - -Characteristic.TargetAirPurifierState = function() { - Characteristic.call(this, 'Target Air Purifier State', '000000A8-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.TargetAirPurifierState, Characteristic); - -Characteristic.TargetAirPurifierState.UUID = '000000A8-0000-1000-8000-0026BB765291'; - -// The value property of TargetAirPurifierState must be one of the following: -Characteristic.TargetAirPurifierState.MANUAL = 0; -Characteristic.TargetAirPurifierState.AUTO = 1; - -/** - * Characteristic "Target Air Quality" - */ - -Characteristic.TargetAirQuality = function() { - Characteristic.call(this, 'Target Air Quality', '000000AE-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0,1,2], - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.TargetAirQuality, Characteristic); - -Characteristic.TargetAirQuality.UUID = '000000AE-0000-1000-8000-0026BB765291'; - -// The value property of TargetAirQuality must be one of the following: -Characteristic.TargetAirQuality.EXCELLENT = 0; -Characteristic.TargetAirQuality.GOOD = 1; -Characteristic.TargetAirQuality.FAIR = 2; - -/** - * Characteristic "Target Door State" - */ - -Characteristic.TargetDoorState = function() { - Characteristic.call(this, 'Target Door State', '00000032-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.TargetDoorState, Characteristic); - -Characteristic.TargetDoorState.UUID = '00000032-0000-1000-8000-0026BB765291'; - -// The value property of TargetDoorState must be one of the following: -Characteristic.TargetDoorState.OPEN = 0; -Characteristic.TargetDoorState.CLOSED = 1; - -/** - * Characteristic "Target Fan State" - */ - -Characteristic.TargetFanState = function() { - Characteristic.call(this, 'Target Fan State', '000000BF-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.TargetFanState, Characteristic); - -Characteristic.TargetFanState.UUID = '000000BF-0000-1000-8000-0026BB765291'; - -// The value property of TargetFanState must be one of the following: -Characteristic.TargetFanState.MANUAL = 0; -Characteristic.TargetFanState.AUTO = 1; - -/** - * Characteristic "Target Heater Cooler State" - */ - -Characteristic.TargetHeaterCoolerState = function() { - Characteristic.call(this, 'Target Heater Cooler State', '000000B2-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0,1,2], - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.TargetHeaterCoolerState, Characteristic); - -Characteristic.TargetHeaterCoolerState.UUID = '000000B2-0000-1000-8000-0026BB765291'; - -// The value property of TargetHeaterCoolerState must be one of the following: -Characteristic.TargetHeaterCoolerState.AUTO = 0; -Characteristic.TargetHeaterCoolerState.HEAT = 1; -Characteristic.TargetHeaterCoolerState.COOL = 2; - -/** - * Characteristic "Target Heating Cooling State" - */ - -Characteristic.TargetHeatingCoolingState = function() { - Characteristic.call(this, 'Target Heating Cooling State', '00000033-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 3, - minValue: 0, - validValues: [0,1,2,3], - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.TargetHeatingCoolingState, Characteristic); - -Characteristic.TargetHeatingCoolingState.UUID = '00000033-0000-1000-8000-0026BB765291'; - -// The value property of TargetHeatingCoolingState must be one of the following: -Characteristic.TargetHeatingCoolingState.OFF = 0; -Characteristic.TargetHeatingCoolingState.HEAT = 1; -Characteristic.TargetHeatingCoolingState.COOL = 2; -Characteristic.TargetHeatingCoolingState.AUTO = 3; - -/** - * Characteristic "Target Horizontal Tilt Angle" - */ - -Characteristic.TargetHorizontalTiltAngle = function() { - Characteristic.call(this, 'Target Horizontal Tilt Angle', '0000007B-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.INT, - unit: Characteristic.Units.ARC_DEGREE, - maxValue: 90, - minValue: -90, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.TargetHorizontalTiltAngle, Characteristic); - -Characteristic.TargetHorizontalTiltAngle.UUID = '0000007B-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Target Humidifier Dehumidifier State" - */ - -Characteristic.TargetHumidifierDehumidifierState = function() { - Characteristic.call(this, 'Target Humidifier Dehumidifier State', '000000B4-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 2, - minValue: 0, - validValues: [0,1,2], - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.TargetHumidifierDehumidifierState, Characteristic); - -Characteristic.TargetHumidifierDehumidifierState.UUID = '000000B4-0000-1000-8000-0026BB765291'; - -// The value property of TargetHumidifierDehumidifierState must be one of the following: -Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER_OR_DEHUMIDIFIER = 0; -Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER = 1; -Characteristic.TargetHumidifierDehumidifierState.DEHUMIDIFIER = 2; - -/** - * Characteristic "Target Position" - */ - -Characteristic.TargetPosition = function() { - Characteristic.call(this, 'Target Position', '0000007C-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - unit: Characteristic.Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.TargetPosition, Characteristic); - -Characteristic.TargetPosition.UUID = '0000007C-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Target Relative Humidity" - */ - -Characteristic.TargetRelativeHumidity = function() { - Characteristic.call(this, 'Target Relative Humidity', '00000034-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - unit: Characteristic.Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.TargetRelativeHumidity, Characteristic); - -Characteristic.TargetRelativeHumidity.UUID = '00000034-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Target Slat State" - */ - -Characteristic.TargetSlatState = function() { - Characteristic.call(this, 'Target Slat State', '000000BE-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.TargetSlatState, Characteristic); - -Characteristic.TargetSlatState.UUID = '000000BE-0000-1000-8000-0026BB765291'; - -// The value property of TargetSlatState must be one of the following: -Characteristic.TargetSlatState.MANUAL = 0; -Characteristic.TargetSlatState.AUTO = 1; - -/** - * Characteristic "Target Temperature" - */ - -Characteristic.TargetTemperature = function() { - Characteristic.call(this, 'Target Temperature', '00000035-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - unit: Characteristic.Units.CELSIUS, - maxValue: 38, - minValue: 10, - minStep: 0.1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.TargetTemperature, Characteristic); - -Characteristic.TargetTemperature.UUID = '00000035-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Target Tilt Angle" - */ - -Characteristic.TargetTiltAngle = function() { - Characteristic.call(this, 'Target Tilt Angle', '000000C2-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.INT, - unit: Characteristic.Units.ARC_DEGREE, - maxValue: 90, - minValue: -90, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.TargetTiltAngle, Characteristic); - -Characteristic.TargetTiltAngle.UUID = '000000C2-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Target Vertical Tilt Angle" - */ - -Characteristic.TargetVerticalTiltAngle = function() { - Characteristic.call(this, 'Target Vertical Tilt Angle', '0000007D-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.INT, - unit: Characteristic.Units.ARC_DEGREE, - maxValue: 90, - minValue: -90, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.TargetVerticalTiltAngle, Characteristic); - -Characteristic.TargetVerticalTiltAngle.UUID = '0000007D-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Temperature Display Units" - */ - -Characteristic.TemperatureDisplayUnits = function() { - Characteristic.call(this, 'Temperature Display Units', '00000036-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 1, - minValue: 0, - validValues: [0,1], - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.TemperatureDisplayUnits, Characteristic); - -Characteristic.TemperatureDisplayUnits.UUID = '00000036-0000-1000-8000-0026BB765291'; - -// The value property of TemperatureDisplayUnits must be one of the following: -Characteristic.TemperatureDisplayUnits.CELSIUS = 0; -Characteristic.TemperatureDisplayUnits.FAHRENHEIT = 1; - -/** - * Characteristic "Valve Type" - */ - -Characteristic.ValveType = function() { - Characteristic.call(this, 'Valve Type', '000000D5-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - maxValue: 3, - minValue: 0, - validValues: [0,1,2,3], - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.ValveType, Characteristic); - -Characteristic.ValveType.UUID = '000000D5-0000-1000-8000-0026BB765291'; - -// The value property of ValveType must be one of the following: -Characteristic.ValveType.GENERIC_VALVE = 0; -Characteristic.ValveType.IRRIGATION = 1; -Characteristic.ValveType.SHOWER_HEAD = 2; -Characteristic.ValveType.WATER_FAUCET = 3; - -/** - * Characteristic "Version" - */ - -Characteristic.Version = function() { - Characteristic.call(this, 'Version', '00000037-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.STRING, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.Version, Characteristic); - -Characteristic.Version.UUID = '00000037-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "VOC Density" - */ - -Characteristic.VOCDensity = function() { - Characteristic.call(this, 'VOC Density', '000000C8-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - maxValue: 1000, - minValue: 0, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.VOCDensity, Characteristic); - -Characteristic.VOCDensity.UUID = '000000C8-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Volume" - */ - -Characteristic.Volume = function() { - Characteristic.call(this, 'Volume', '00000119-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.UINT8, - unit: Characteristic.Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.Volume, Characteristic); - -Characteristic.Volume.UUID = '00000119-0000-1000-8000-0026BB765291'; - -/** - * Characteristic "Water Level" - */ - -Characteristic.WaterLevel = function() { - Characteristic.call(this, 'Water Level', '000000B5-0000-1000-8000-0026BB765291'); - this.setProps({ - format: Characteristic.Formats.FLOAT, - maxValue: 100, - minValue: 0, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; - -inherits(Characteristic.WaterLevel, Characteristic); - -Characteristic.WaterLevel.UUID = '000000B5-0000-1000-8000-0026BB765291'; - -/** - * Service "Accessory Information" - */ - -Service.AccessoryInformation = function(displayName, subtype) { - Service.call(this, displayName, '0000003E-0000-1000-8000-0026BB765291', subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.Identify); - this.addCharacteristic(Characteristic.Manufacturer); - this.addCharacteristic(Characteristic.Model); - this.addCharacteristic(Characteristic.Name); - this.addCharacteristic(Characteristic.SerialNumber); - this.addCharacteristic(Characteristic.FirmwareRevision); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.HardwareRevision); - this.addOptionalCharacteristic(Characteristic.AccessoryFlags); -}; - -inherits(Service.AccessoryInformation, Service); - -Service.AccessoryInformation.UUID = '0000003E-0000-1000-8000-0026BB765291'; - -/** - * Service "Air Purifier" - */ - -Service.AirPurifier = function(displayName, subtype) { - Service.call(this, displayName, '000000BB-0000-1000-8000-0026BB765291', 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); -}; - -inherits(Service.AirPurifier, Service); - -Service.AirPurifier.UUID = '000000BB-0000-1000-8000-0026BB765291'; - -/** - * Service "Air Quality Sensor" - */ - -Service.AirQualitySensor = function(displayName, subtype) { - Service.call(this, displayName, '0000008D-0000-1000-8000-0026BB765291', 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); - this.addOptionalCharacteristic(Characteristic.CarbonMonoxideLevel); - this.addOptionalCharacteristic(Characteristic.CarbonDioxideLevel); -}; - -inherits(Service.AirQualitySensor, Service); - -Service.AirQualitySensor.UUID = '0000008D-0000-1000-8000-0026BB765291'; - -/** - * Service "Battery Service" - */ - -Service.BatteryService = function(displayName, subtype) { - Service.call(this, displayName, '00000096-0000-1000-8000-0026BB765291', subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.BatteryLevel); - this.addCharacteristic(Characteristic.ChargingState); - this.addCharacteristic(Characteristic.StatusLowBattery); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Name); -}; - -inherits(Service.BatteryService, Service); - -Service.BatteryService.UUID = '00000096-0000-1000-8000-0026BB765291'; - -/** - * Service "Camera RTP Stream Management" - */ - -Service.CameraRTPStreamManagement = function(displayName, subtype) { - Service.call(this, displayName, '00000110-0000-1000-8000-0026BB765291', 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.Name); -}; - -inherits(Service.CameraRTPStreamManagement, Service); - -Service.CameraRTPStreamManagement.UUID = '00000110-0000-1000-8000-0026BB765291'; - -/** - * Service "Carbon Dioxide Sensor" - */ - -Service.CarbonDioxideSensor = function(displayName, subtype) { - Service.call(this, displayName, '00000097-0000-1000-8000-0026BB765291', 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); -}; - -inherits(Service.CarbonDioxideSensor, Service); - -Service.CarbonDioxideSensor.UUID = '00000097-0000-1000-8000-0026BB765291'; - -/** - * Service "Carbon Monoxide Sensor" - */ - -Service.CarbonMonoxideSensor = function(displayName, subtype) { - Service.call(this, displayName, '0000007F-0000-1000-8000-0026BB765291', 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); -}; - -inherits(Service.CarbonMonoxideSensor, Service); - -Service.CarbonMonoxideSensor.UUID = '0000007F-0000-1000-8000-0026BB765291'; - -/** - * Service "Contact Sensor" - */ - -Service.ContactSensor = function(displayName, subtype) { - Service.call(this, displayName, '00000080-0000-1000-8000-0026BB765291', 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); -}; - -inherits(Service.ContactSensor, Service); - -Service.ContactSensor.UUID = '00000080-0000-1000-8000-0026BB765291'; - -/** - * Service "Door" - */ - -Service.Door = function(displayName, subtype) { - Service.call(this, displayName, '00000081-0000-1000-8000-0026BB765291', 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); -}; - -inherits(Service.Door, Service); - -Service.Door.UUID = '00000081-0000-1000-8000-0026BB765291'; - -/** - * Service "Doorbell" - */ - -Service.Doorbell = function(displayName, subtype) { - Service.call(this, displayName, '00000121-0000-1000-8000-0026BB765291', subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.ProgrammableSwitchEvent); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Brightness); - this.addOptionalCharacteristic(Characteristic.Volume); - this.addOptionalCharacteristic(Characteristic.Name); -}; - -inherits(Service.Doorbell, Service); - -Service.Doorbell.UUID = '00000121-0000-1000-8000-0026BB765291'; - -/** - * Service "Fan" - */ - -Service.Fan = function(displayName, subtype) { - Service.call(this, displayName, '00000040-0000-1000-8000-0026BB765291', subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.On); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.RotationDirection); - this.addOptionalCharacteristic(Characteristic.RotationSpeed); - this.addOptionalCharacteristic(Characteristic.Name); -}; - -inherits(Service.Fan, Service); - -Service.Fan.UUID = '00000040-0000-1000-8000-0026BB765291'; - -/** - * Service "Fan v2" - */ - -Service.Fanv2 = function(displayName, subtype) { - Service.call(this, displayName, '000000B7-0000-1000-8000-0026BB765291', 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); -}; - -inherits(Service.Fanv2, Service); - -Service.Fanv2.UUID = '000000B7-0000-1000-8000-0026BB765291'; - -/** - * Service "Filter Maintenance" - */ - -Service.FilterMaintenance = function(displayName, subtype) { - Service.call(this, displayName, '000000BA-0000-1000-8000-0026BB765291', subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.FilterChangeIndication); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.FilterLifeLevel); - this.addOptionalCharacteristic(Characteristic.ResetFilterIndication); - this.addOptionalCharacteristic(Characteristic.Name); -}; - -inherits(Service.FilterMaintenance, Service); - -Service.FilterMaintenance.UUID = '000000BA-0000-1000-8000-0026BB765291'; - -/** - * Service "Faucet" - */ - -Service.Faucet = function(displayName, subtype) { - Service.call(this, displayName, '000000D7-0000-1000-8000-0026BB765291', subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.Active); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Name); - this.addOptionalCharacteristic(Characteristic.StatusFault); -}; - -inherits(Service.Faucet, Service); - -Service.Faucet.UUID = '000000D7-0000-1000-8000-0026BB765291'; - -/** - * Service "Garage Door Opener" - */ - -Service.GarageDoorOpener = function(displayName, subtype) { - Service.call(this, displayName, '00000041-0000-1000-8000-0026BB765291', 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); -}; - -inherits(Service.GarageDoorOpener, Service); - -Service.GarageDoorOpener.UUID = '00000041-0000-1000-8000-0026BB765291'; - -/** - * Service "Heater Cooler" - */ - -Service.HeaterCooler = function(displayName, subtype) { - Service.call(this, displayName, '000000BC-0000-1000-8000-0026BB765291', 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); -}; - -inherits(Service.HeaterCooler, Service); - -Service.HeaterCooler.UUID = '000000BC-0000-1000-8000-0026BB765291'; - -/** - * Service "Humidifier Dehumidifier" - */ - -Service.HumidifierDehumidifier = function(displayName, subtype) { - Service.call(this, displayName, '000000BD-0000-1000-8000-0026BB765291', 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); -}; - -inherits(Service.HumidifierDehumidifier, Service); - -Service.HumidifierDehumidifier.UUID = '000000BD-0000-1000-8000-0026BB765291'; - -/** - * Service "Humidity Sensor" - */ - -Service.HumiditySensor = function(displayName, subtype) { - Service.call(this, displayName, '00000082-0000-1000-8000-0026BB765291', 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); -}; - -inherits(Service.HumiditySensor, Service); - -Service.HumiditySensor.UUID = '00000082-0000-1000-8000-0026BB765291'; - -/** - * Service "Irrigation System" - */ - -Service.IrrigationSystem = function(displayName, subtype) { - Service.call(this, displayName, '000000CF-0000-1000-8000-0026BB765291', 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); -}; - -inherits(Service.IrrigationSystem, Service); - -Service.IrrigationSystem.UUID = '000000CF-0000-1000-8000-0026BB765291'; - -/** - * Service "Leak Sensor" - */ - -Service.LeakSensor = function(displayName, subtype) { - Service.call(this, displayName, '00000083-0000-1000-8000-0026BB765291', 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); -}; - -inherits(Service.LeakSensor, Service); - -Service.LeakSensor.UUID = '00000083-0000-1000-8000-0026BB765291'; - -/** - * Service "Light Sensor" - */ - -Service.LightSensor = function(displayName, subtype) { - Service.call(this, displayName, '00000084-0000-1000-8000-0026BB765291', 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); -}; - -inherits(Service.LightSensor, Service); - -Service.LightSensor.UUID = '00000084-0000-1000-8000-0026BB765291'; - -/** - * Service "Lightbulb" - */ - -Service.Lightbulb = function(displayName, subtype) { - Service.call(this, displayName, '00000043-0000-1000-8000-0026BB765291', 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); //Manual fix to add temperature -}; - -inherits(Service.Lightbulb, Service); - -Service.Lightbulb.UUID = '00000043-0000-1000-8000-0026BB765291'; - -/** - * Service "Lock Management" - */ - -Service.LockManagement = function(displayName, subtype) { - Service.call(this, displayName, '00000044-0000-1000-8000-0026BB765291', 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); -}; - -inherits(Service.LockManagement, Service); - -Service.LockManagement.UUID = '00000044-0000-1000-8000-0026BB765291'; - -/** - * Service "Lock Mechanism" - */ - -Service.LockMechanism = function(displayName, subtype) { - Service.call(this, displayName, '00000045-0000-1000-8000-0026BB765291', subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.LockCurrentState); - this.addCharacteristic(Characteristic.LockTargetState); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Name); -}; - -inherits(Service.LockMechanism, Service); - -Service.LockMechanism.UUID = '00000045-0000-1000-8000-0026BB765291'; - -/** - * Service "Microphone" - */ - -Service.Microphone = function(displayName, subtype) { - Service.call(this, displayName, '00000112-0000-1000-8000-0026BB765291', subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.Mute); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Volume); - this.addOptionalCharacteristic(Characteristic.Name); -}; - -inherits(Service.Microphone, Service); - -Service.Microphone.UUID = '00000112-0000-1000-8000-0026BB765291'; - -/** - * Service "Motion Sensor" - */ - -Service.MotionSensor = function(displayName, subtype) { - Service.call(this, displayName, '00000085-0000-1000-8000-0026BB765291', 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); -}; - -inherits(Service.MotionSensor, Service); - -Service.MotionSensor.UUID = '00000085-0000-1000-8000-0026BB765291'; - -/** - * Service "Occupancy Sensor" - */ - -Service.OccupancySensor = function(displayName, subtype) { - Service.call(this, displayName, '00000086-0000-1000-8000-0026BB765291', 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); -}; - -inherits(Service.OccupancySensor, Service); - -Service.OccupancySensor.UUID = '00000086-0000-1000-8000-0026BB765291'; - -/** - * Service "Outlet" - */ - -Service.Outlet = function(displayName, subtype) { - Service.call(this, displayName, '00000047-0000-1000-8000-0026BB765291', subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.On); - this.addCharacteristic(Characteristic.OutletInUse); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Name); -}; - -inherits(Service.Outlet, Service); - -Service.Outlet.UUID = '00000047-0000-1000-8000-0026BB765291'; - -/** - * Service "Security System" - */ - -Service.SecuritySystem = function(displayName, subtype) { - Service.call(this, displayName, '0000007E-0000-1000-8000-0026BB765291', 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); -}; - -inherits(Service.SecuritySystem, Service); - -Service.SecuritySystem.UUID = '0000007E-0000-1000-8000-0026BB765291'; - -/** - * Service "Service Label" - */ - -Service.ServiceLabel = function(displayName, subtype) { - Service.call(this, displayName, '000000CC-0000-1000-8000-0026BB765291', subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.ServiceLabelNamespace); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Name); -}; - -inherits(Service.ServiceLabel, Service); - -Service.ServiceLabel.UUID = '000000CC-0000-1000-8000-0026BB765291'; - -/** - * Service "Slat" - */ - -Service.Slat = function(displayName, subtype) { - Service.call(this, displayName, '000000B9-0000-1000-8000-0026BB765291', 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); -}; - -inherits(Service.Slat, Service); - -Service.Slat.UUID = '000000B9-0000-1000-8000-0026BB765291'; - -/** - * Service "Smoke Sensor" - */ - -Service.SmokeSensor = function(displayName, subtype) { - Service.call(this, displayName, '00000087-0000-1000-8000-0026BB765291', 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); -}; - -inherits(Service.SmokeSensor, Service); - -Service.SmokeSensor.UUID = '00000087-0000-1000-8000-0026BB765291'; - -/** - * Service "Speaker" - */ - -Service.Speaker = function(displayName, subtype) { - Service.call(this, displayName, '00000113-0000-1000-8000-0026BB765291', subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.Mute); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Volume); - this.addOptionalCharacteristic(Characteristic.Name); -}; - -inherits(Service.Speaker, Service); - -Service.Speaker.UUID = '00000113-0000-1000-8000-0026BB765291'; - -/** - * Service "Stateless Programmable Switch" - */ - -Service.StatelessProgrammableSwitch = function(displayName, subtype) { - Service.call(this, displayName, '00000089-0000-1000-8000-0026BB765291', subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.ProgrammableSwitchEvent); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Name); - this.addOptionalCharacteristic(Characteristic.ServiceLabelIndex); -}; - -inherits(Service.StatelessProgrammableSwitch, Service); - -Service.StatelessProgrammableSwitch.UUID = '00000089-0000-1000-8000-0026BB765291'; - -/** - * Service "Switch" - */ - -Service.Switch = function(displayName, subtype) { - Service.call(this, displayName, '00000049-0000-1000-8000-0026BB765291', subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.On); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.Name); -}; - -inherits(Service.Switch, Service); - -Service.Switch.UUID = '00000049-0000-1000-8000-0026BB765291'; - -/** - * Service "Temperature Sensor" - */ - -Service.TemperatureSensor = function(displayName, subtype) { - Service.call(this, displayName, '0000008A-0000-1000-8000-0026BB765291', 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); -}; - -inherits(Service.TemperatureSensor, Service); - -Service.TemperatureSensor.UUID = '0000008A-0000-1000-8000-0026BB765291'; - -/** - * Service "Thermostat" - */ - -Service.Thermostat = function(displayName, subtype) { - Service.call(this, displayName, '0000004A-0000-1000-8000-0026BB765291', 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); -}; - -inherits(Service.Thermostat, Service); - -Service.Thermostat.UUID = '0000004A-0000-1000-8000-0026BB765291'; - -/** - * Service "Valve" - */ - -Service.Valve = function(displayName, subtype) { - Service.call(this, displayName, '000000D0-0000-1000-8000-0026BB765291', 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); -}; - -inherits(Service.Valve, Service); - -Service.Valve.UUID = '000000D0-0000-1000-8000-0026BB765291'; - -/** - * Service "Window" - */ - -Service.Window = function(displayName, subtype) { - Service.call(this, displayName, '0000008B-0000-1000-8000-0026BB765291', 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); -}; - -inherits(Service.Window, Service); - -Service.Window.UUID = '0000008B-0000-1000-8000-0026BB765291'; - -/** - * Service "Window Covering" - */ - -Service.WindowCovering = function(displayName, subtype) { - Service.call(this, displayName, '0000008C-0000-1000-8000-0026BB765291', 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); -}; - -inherits(Service.WindowCovering, Service); - -Service.WindowCovering.UUID = '0000008C-0000-1000-8000-0026BB765291'; - -var HomeKitTypesBridge = require('./HomeKitTypes-Bridge'); -var HomeKitTypesTelevision = require('./HomeKitTypes-Television'); diff --git a/lib/model/AccessoryInfo.js b/lib/model/AccessoryInfo.js deleted file mode 100644 index 0df2e5217..000000000 --- a/lib/model/AccessoryInfo.js +++ /dev/null @@ -1,171 +0,0 @@ -'use strict'; - -var storage = require('node-persist'); -var util = require('util'); -var tweetnacl = require('tweetnacl'); -var bufferShim = require('buffer-shims'); - -module.exports = { - AccessoryInfo: AccessoryInfo -}; - -/** - * AccessoryInfo is a model class containing a subset of Accessory data relevant to the internal HAP server, - * such as encryption keys and username. It is persisted to disk. - */ - -function AccessoryInfo(username) { - this.username = username; - this.displayName = ""; - this.category = ""; - this.pincode = ""; - this.signSk = bufferShim.alloc(0); - this.signPk = bufferShim.alloc(0); - this.pairedClients = {}; // pairedClients[clientUsername:string] = clientPublicKey:Buffer - this.configVersion = 1; - this.configHash = ""; - - this.setupID = ""; - - this.relayEnabled = false; - this.relayState = 2; - this.relayAccessoryID = ""; - this.relayAdminID = ""; - this.relayPairedControllers = {}; - this.accessoryBagURL = ""; -} - -// Add a paired client to our memory. 'publicKey' should be an instance of Buffer. -AccessoryInfo.prototype.addPairedClient = function(username, publicKey) { - this.pairedClients[username] = publicKey; -}; - -// Add a paired client to our memory. -AccessoryInfo.prototype.removePairedClient = function(username) { - delete this.pairedClients[username]; - - if (Object.keys(this.pairedClients).length == 0) { - this.relayEnabled = false; - this.relayState = 2; - this.relayAccessoryID = ""; - this.relayAdminID = ""; - this.relayPairedControllers = {}; - this.accessoryBagURL = ""; - } -}; - -// Gets the public key for a paired client as a Buffer, or falsey value if not paired. -AccessoryInfo.prototype.getClientPublicKey = function(username) { - return this.pairedClients[username]; -}; - -// Returns a boolean indicating whether this accessory has been paired with a client. -AccessoryInfo.prototype.paired = function() { - return Object.keys(this.pairedClients).length > 0; // if we have any paired clients, we're paired. -}; - -AccessoryInfo.prototype.updateRelayEnableState = function(state) { - this.relayEnabled = state; -}; - -AccessoryInfo.prototype.updateRelayState = function(newState) { - this.relayState = newState; -}; - -AccessoryInfo.prototype.addPairedRelayClient = function(username, accessToken) { - this.relayPairedControllers[username] = accessToken; -}; - -AccessoryInfo.prototype.removePairedRelayClient = function(username) { - delete this.relayPairedControllers[username]; -}; - -// Gets a key for storing this AccessoryInfo in the filesystem, like "AccessoryInfo.CC223DE3CEF3.json" -AccessoryInfo.persistKey = function(username) { - return util.format("AccessoryInfo.%s.json", username.replace(/:/g,"").toUpperCase()); -}; - -AccessoryInfo.create = function(username) { - var accessoryInfo = new AccessoryInfo(username); - - // Create a new unique key pair for this accessory. - var keyPair = tweetnacl.sign.keyPair(); - - accessoryInfo.signSk = bufferShim.from(keyPair.secretKey); - accessoryInfo.signPk = bufferShim.from(keyPair.publicKey); - - return accessoryInfo; -} - -AccessoryInfo.load = function(username) { - var key = AccessoryInfo.persistKey(username); - var saved = storage.getItem(key); - - if (saved) { - var info = new AccessoryInfo(username); - info.displayName = saved.displayName || ""; - info.category = saved.category || ""; - info.pincode = saved.pincode || ""; - info.signSk = bufferShim.from(saved.signSk || '', 'hex'); - info.signPk = bufferShim.from(saved.signPk || '', 'hex'); - - info.pairedClients = {}; - for (var username in saved.pairedClients || {}) { - var publicKey = saved.pairedClients[username]; - info.pairedClients[username] = bufferShim.from(publicKey, 'hex'); - } - - info.configVersion = saved.configVersion || 1; - info.configHash = saved.configHash || ""; - - info.setupID = saved.setupID || ""; - - info.relayEnabled = saved.relayEnabled || false; - info.relayState = saved.relayState || 2; - info.relayAccessoryID = saved.relayAccessoryID || ""; - info.relayAdminID = saved.relayAdminID || ""; - info.relayPairedControllers = saved.relayPairedControllers || {}; - info.accessoryBagURL = saved.accessoryBagURL || ""; - - return info; - } - else { - return null; - } -}; - -AccessoryInfo.prototype.save = function() { - var saved = { - displayName: this.displayName, - category: this.category, - pincode: this.pincode, - signSk: this.signSk.toString('hex'), - signPk: this.signPk.toString('hex'), - pairedClients: {}, - configVersion: this.configVersion, - configHash: this.configHash, - setupID: this.setupID, - relayEnabled: this.relayEnabled, - relayState: this.relayState, - relayAccessoryID: this.relayAccessoryID, - relayAdminID: this.relayAdminID, - relayPairedControllers: this.relayPairedControllers, - accessoryBagURL: this.accessoryBagURL - }; - - for (var username in this.pairedClients) { - var publicKey = this.pairedClients[username]; - saved.pairedClients[username] = publicKey.toString('hex'); - } - - var key = AccessoryInfo.persistKey(this.username); - - storage.setItemSync(key, saved); - storage.persistSync(); -}; - -AccessoryInfo.prototype.remove = function () { - var key = AccessoryInfo.persistKey(this.username); - - storage.removeItemSync(key); -}; diff --git a/lib/model/IdentifierCache.js b/lib/model/IdentifierCache.js deleted file mode 100644 index c35e4e559..000000000 --- a/lib/model/IdentifierCache.js +++ /dev/null @@ -1,137 +0,0 @@ -'use strict'; - -var util = require('util'); -var storage = require('node-persist'); -var crypto = require('crypto'); - -module.exports = { - IdentifierCache: IdentifierCache -}; - - -/** - * IdentifierCache is a model class that manages a system of associating HAP "Accessory IDs" and "Instance IDs" - * with other values that don't usually change. HomeKit Clients use Accessory/Instance IDs as a primary key of - * sorts, so the IDs need to remain "stable". For instance, if you create a HomeKit "Scene" called "Leaving Home" - * that sets your Alarm System's "Target Alarm State" Characteristic to "Arm Away", that Scene will store whatever - * "Instance ID" it was given for the "Target Alarm State" Characteristic. If the ID changes later on this server, - * the scene will stop working. - */ - -function IdentifierCache(username) { - this.username = username; - this._cache = {}; // cache[key:string] = id:number - this._usedCache = null; // for usage tracking and expiring old keys - this._savedCacheHash = ""; // for checking if new cache neeed to be saved -} - -IdentifierCache.prototype.startTrackingUsage = function() { - this._usedCache = {}; -}; - -IdentifierCache.prototype.stopTrackingUsageAndExpireUnused = function() { - // simply rotate in the new cache that was built during our normal getXYZ() calls. - this._cache = this._usedCache; - this._usedCache = null; -}; - -IdentifierCache.prototype.getCache = function(key) { - var value = this._cache[key]; - - // track this cache item if needed - if (this._usedCache && typeof value !== 'undefined') - this._usedCache[key] = value; - - return value; -}; - -IdentifierCache.prototype.setCache = function(key, value) { - this._cache[key] = value; - - // track this cache item if needed - if (this._usedCache) - this._usedCache[key] = value; - - return value; -}; - -IdentifierCache.prototype.getAID = function(accessoryUUID) { - var key = accessoryUUID; - - // ensure that our "next AID" field is not expired - this.getCache('|nextAID'); - - return this.getCache(key) || this.setCache(key, this.getNextAID()); -}; - -IdentifierCache.prototype.getIID = function(accessoryUUID, serviceUUID, serviceSubtype, characteristicUUID) { - - var key = accessoryUUID - + '|' + serviceUUID - + (serviceSubtype ? '|' + serviceSubtype : '') - + (characteristicUUID ? '|' + characteristicUUID : ''); - - // ensure that our "next IID" field for this accessory is not expired - this.getCache(accessoryUUID + '|nextIID'); - - return this.getCache(key) || this.setCache(key, this.getNextIID(accessoryUUID)); -}; - -IdentifierCache.prototype.getNextAID = function() { - var key = '|nextAID'; - var 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; -}; - -IdentifierCache.prototype.getNextIID = function(accessoryUUID) { - var key = accessoryUUID + '|nextIID'; - var nextIID = this.getCache(key) || 2; // iid 1 is reserved for the Accessory Information service - this.setCache(key, nextIID + 1); // increment - return nextIID; -}; - -/** - * Persisting to File System - */ - -// Gets a key for storing this IdentifierCache in the filesystem, like "IdentifierCache.CC223DE3CEF3.json" -IdentifierCache.persistKey = function(username) { - return util.format("IdentifierCache.%s.json", username.replace(/:/g,"").toUpperCase()); -}; - -IdentifierCache.load = function(username) { - var key = IdentifierCache.persistKey(username); - var saved = storage.getItem(key); - - if (saved) { - var 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 - return info; - } - else { - return null; - } -}; - -IdentifierCache.prototype.save = function() { - var 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 = { - cache: this._cache - }; - - var key = IdentifierCache.persistKey(this.username); - - storage.setItemSync(key, saved); - storage.persistSync(); - this._savedCacheHash = newCacheHash; //update hash of saved cache for future use - } -}; - -IdentifierCache.prototype.remove = function () { - var key = IdentifierCache.persistKey(this.username); - - storage.removeItemSync(key); -}; diff --git a/lib/util/eventedhttp.js b/lib/util/eventedhttp.js deleted file mode 100644 index 5d020c9f2..000000000 --- a/lib/util/eventedhttp.js +++ /dev/null @@ -1,333 +0,0 @@ -'use strict'; - -var debug = require('debug')('EventedHTTPServer'); -var EventEmitter = require('events').EventEmitter; -var inherits = require('util').inherits; -var net = require('net'); -var http = require('http'); -var uuid = require('./uuid'); -var bufferShim = require('buffer-shims'); - -module.exports = { - EventedHTTPServer: EventedHTTPServer -}; - - -/** - * EventedHTTPServer provides an HTTP-like server that supports HAP "extensions" for security and events. - * - * Implementation - * -------------- - * In order to implement the "custom HTTP" server required by the HAP protocol (see HAPServer.js) without completely - * reinventing the wheel, we create both a generic TCP socket server as well as a standard Node HTTP server. - * The TCP socket server acts as a proxy, allowing users of this class to transform data (for encryption) as necessary - * and passing through bytes directly to the HTTP server for processing. This way we get Node to do all - * the "heavy lifting" of HTTP like parsing headers and formatting responses. - * - * Events are sent by simply waiting for current HTTP traffic to subside and then sending a custom response packet - * directly down the wire via the socket. - * - * Each connection to the main TCP server gets its own internal HTTP server, so we can track ongoing requests/responses - * for safe event insertion. - * - * @event 'listening' => function() { } - * Emitted when the server is fully set up and ready to receive connections. - * - * @event 'request' => function(request, response, session, events) { } - * Just like the 'http' module, request is http.IncomingMessage and response is http.ServerResponse. - * The 'session' param is an arbitrary object that you can use to store data associated with this connection; - * it will not be used by this class. The 'events' param is an object where the keys are the names of - * events that this connection has signed up for. It is initially empty and listeners are expected to manage it. - * - * @event 'decrypt' => function(data, {decrypted.data}, session) { } - * Fired when we receive data from the client device. You may detemine whether the data is encrypted, and if - * so, you can decrypt the data and store it into a new 'data' property of the 'decrypted' argument. If data is not - * encrypted, you can simply leave 'data' as null and the original data will be passed through as-is. - * - * @event 'encrypt' => function(data, {encrypted.data}, session) { } - * Fired when we wish to send data to the client device. If necessary, set the 'data' property of the - * 'encrypted' argument to be the encrypted data and it will be sent instead. - */ - -function EventedHTTPServer() { - this._tcpServer = net.createServer(); - this._connections = []; // track all open connections (for sending events) -} - -inherits(EventedHTTPServer, EventEmitter); - -EventedHTTPServer.prototype.listen = function(targetPort) { - this._tcpServer.listen(targetPort); - - this._tcpServer.on('listening', function() { - var port = this._tcpServer.address().port; - debug("Server listening on port %s", port); - this.emit('listening', port); - }.bind(this)); - - this._tcpServer.on('connection', this._onConnection.bind(this)); -} - -EventedHTTPServer.prototype.stop = function() { - this._tcpServer.close(); - this._connections = []; -} - -EventedHTTPServer.prototype.sendEvent = function(event, data, contentType, exclude) { - for (var index in this._connections) { - var connection = this._connections[index]; - 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. -EventedHTTPServer.prototype._onConnection = function(socket) { - var connection = new EventedHTTPServerConnection(socket); - - // pass on session events to our listeners directly - connection.on('request', function(request, response, session, events) { this.emit('request', request, response, session, events); }.bind(this)); - connection.on('encrypt', function(data, encrypted, session) { this.emit('encrypt', data, encrypted, session); }.bind(this)); - connection.on('decrypt', function(data, decrypted, session) { this.emit('decrypt', data, decrypted, session); }.bind(this)); - connection.on('close', function(events) { this._handleConnectionClose(connection, events); }.bind(this)); - this._connections.push(connection); -} - -EventedHTTPServer.prototype._handleConnectionClose = function(connection, events) { - this.emit('session-close', connection.sessionID, events); - - // remove it from our array of connections for events - this._connections.splice(this._connections.indexOf(connection), 1); -} - - -/** - * 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() { } - */ - -function EventedHTTPServerConnection(clientSocket) { - this.sessionID = uuid.generate(clientSocket.remoteAddress + ':' + clientSocket.remotePort); - - this._remoteAddress = clientSocket.remoteAddress; // cache because it becomes undefined in 'onClientSocketClose' - this._pendingClientSocketData = bufferShim.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._pendingEventData = bufferShim.alloc(0); // event data waiting to be sent until after an in-progress HTTP response is being written - - // clientSocket is the socket connected to the actual iOS device - this._clientSocket = clientSocket; - this._clientSocket.on('data', this._onClientSocketData.bind(this)); - this._clientSocket.on('close', this._onClientSocketClose.bind(this)); - this._clientSocket.on('error', this._onClientSocketError.bind(this)); // we MUST register for this event, otherwise the error will bubble up to the top and crash the node process entirely. - - // serverSocket is our connection to our own internal httpServer - this._serverSocket = null; // created after httpServer 'listening' event - - // 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.bind(this)); - this._httpServer.on('request', this._onHttpServerRequest.bind(this)); - this._httpServer.on('error', this._onHttpServerError.bind(this)); - this._httpServer.listen(0); - - // an arbitrary dict that users of this class can store values in to associate with this particular connection - this._session = { - sessionID: this.sessionID - }; - - // 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", this._remoteAddress); -} - -inherits(EventedHTTPServerConnection, EventEmitter); - -EventedHTTPServerConnection.prototype.sendEvent = function(event, data, contentType, excludeEvents) { - - // 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); - return; - } - - debug("[%s] Sending HTTP event '%s' with data: %s", this._remoteAddress, event, data.toString('utf8')); - - // ensure data is a Buffer - data = bufferShim.from(data); - - // format this payload as an HTTP response - var linebreak = bufferShim.from("0D0A","hex"); - data = Buffer.concat([ - bufferShim.from('EVENT/1.0 200 OK'), linebreak, - bufferShim.from('Content-Type: ' + contentType), linebreak, - bufferShim.from('Content-Length: ' + data.length), linebreak, - linebreak, - data - ]); - - // give listeners an opportunity to encrypt this data before sending it to the client - var encrypted = { data: null }; - this.emit('encrypt', data, encrypted, this._session); - if (encrypted.data) data = encrypted.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 = Buffer.concat([this._pendingEventData, data]); - else - this._clientSocket.write(data); -} - -EventedHTTPServerConnection.prototype._sendPendingEvents = function() { - - // an existing HTTP response was finished, so let's flush our pending event buffer if necessary! - if (this._pendingEventData.length > 0) { - debug("[%s] Writing pending HTTP event data", this._remoteAddress); - this._clientSocket.write(this._pendingEventData); - } - - // clear the buffer - this._pendingEventData = bufferShim.alloc(0); -} - -// Called only once right after constructor finishes -EventedHTTPServerConnection.prototype._onHttpServerListening = function() { - this._httpPort = this._httpServer.address().port; - debug("[%s] HTTP server listening on port %s", this._remoteAddress, this._httpPort); - - // closes before this are due to retrying listening, which don't need to be handled - this._httpServer.on('close', this._onHttpServerClose.bind(this)); - - // now we can establish a connection to this running HTTP server for proxying data - this._serverSocket = net.createConnection(this._httpPort); - this._serverSocket.on('connect', this._onServerSocketConnect.bind(this)); - this._serverSocket.on('data', this._onServerSocketData.bind(this)); - this._serverSocket.on('close', this._onServerSocketClose.bind(this)); - this._serverSocket.on('error', this._onServerSocketError.bind(this)); // we MUST register for this event, otherwise the error will bubble up to the top and crash the node process entirely. -} - -// Called only once right after onHttpServerListening -EventedHTTPServerConnection.prototype._onServerSocketConnect = function() { - - // 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.length > 0) { - this._serverSocket.write(this._pendingClientSocketData); - this._pendingClientSocketData = null; - } -} - -// Received data from client (iOS) -EventedHTTPServerConnection.prototype._onClientSocketData = function(data) { - - // give listeners an opportunity to decrypt this data before processing it as HTTP - var decrypted = { data: null }; - this.emit('decrypt', data, decrypted, this._session); - if (decrypted.data) data = decrypted.data; - - if (this._fullySetup) { - // proxy it along to the HTTP server - this._serverSocket.write(data); - } - else { - // we're not setup yet, so add this data to our buffer - this._pendingClientSocketData = Buffer.concat([this._pendingClientSocketData, data]); - } -} - -// Received data from HTTP Server -EventedHTTPServerConnection.prototype._onServerSocketData = function(data) { - - // give listeners an opportunity to encrypt this data before sending it to the client - var encrypted = { data: null }; - this.emit('encrypt', data, encrypted, this._session); - if (encrypted.data) data = encrypted.data; - - // proxy it along to the client (iOS) - this._clientSocket.write(data); -} - -// Our internal HTTP Server has been closed (happens after we call this._httpServer.close() below) -EventedHTTPServerConnection.prototype._onServerSocketClose = function() { - 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(); -} - -// Our internal HTTP Server has been closed (happens after we call this._httpServer.close() below) -EventedHTTPServerConnection.prototype._onServerSocketError = function(err) { - debug("[%s] HTTP connection error: ", this._remoteAddress, err.message); - - // _onServerSocketClose will be called next -} - -EventedHTTPServerConnection.prototype._onHttpServerRequest = function(request, response) { - debug("[%s] HTTP request: %s", this._remoteAddress, request.url); - - this._writingResponse = true; - - // sign up to know when the response is ended, so we can safely send EVENT responses - response.on('finish', function() { - - debug("[%s] HTTP Response is finished", this._remoteAddress); - this._writingResponse = false; - this._sendPendingEvents(); - - }.bind(this)); - - // pass it along to listeners - this.emit('request', request, response, this._session, this._events); -} - -EventedHTTPServerConnection.prototype._onHttpServerClose = function() { - debug("[%s] HTTP server was closed", this._remoteAddress); - - // notify listeners that we are completely closed - this.emit('close', this._events); -} - -EventedHTTPServerConnection.prototype._onHttpServerError = function(err) { - debug("[%s] HTTP server error: %s", this._remoteAddress, err.message); - - if (err.code === 'EADDRINUSE') { - this._httpServer.close(); - this._httpServer.listen(0); - } -} - -EventedHTTPServerConnection.prototype._onClientSocketClose = function() { - debug("[%s] Client connection closed", this._remoteAddress); - - // shutdown the other side - this._serverSocket.destroy(); -} - -EventedHTTPServerConnection.prototype._onClientSocketError = function(err) { - debug("[%s] Client connection error: %s", this._remoteAddress, err.message); - - // _onClientSocketClose will be called next -} diff --git a/lib/util/hkdf.js b/lib/util/hkdf.js deleted file mode 100644 index 063d64758..000000000 --- a/lib/util/hkdf.js +++ /dev/null @@ -1,40 +0,0 @@ -'use strict'; - -var crypto = require('crypto'); -var bufferShim = require('buffer-shims'); - -module.exports = { - HKDF: HKDF -}; - -function HKDF(hashAlg, salt, ikm, info, size) { - // create the hash alg to see if it exists and get its length - var hash = crypto.createHash(hashAlg); - var hashLength = hash.digest().length; - - // now we compute the PRK - var hmac = crypto.createHmac(hashAlg, salt); - hmac.update(ikm); - var prk = hmac.digest(); - - var prev = bufferShim.alloc(0); - var output; - var buffers = []; - var num_blocks = Math.ceil(size / hashLength); - info = bufferShim.from(info); - - for (var i=0; i=4.3.2" }, "dependencies": { + "bonjour-hap": "^3.5.1", "buffer-shims": "^1.0.0", - "tweetnacl": "^1.0.1", "debug": "^2.2.0", + "decimal.js": "^7.2.3", "fast-srp-hap": "^1.0.1", "ip": "^1.1.3", - "bonjour-hap": "^3.5.1", "node-persist": "^0.0.11", - "decimal.js": "^7.2.3" + "tweetnacl": "^1.0.1" }, "devDependencies": { - "simple-plist": "0.0.4" + "@types/debug": "^4.1.4", + "@types/ip": "^1.1.0", + "@types/jest": "^24.0.15", + "@types/node": "^12.6.8", + "jest": "^24.8.0", + "rimraf": "^2.6.3", + "simple-plist": "0.0.4", + "ts-jest": "^24.0.2", + "ts-node": "^8.3.0", + "typescript": "^3.5.3" }, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "start": "node BridgedCore.js" + "build": "rimraf dist/ && tsc", + "prepublishOnly": "yarn build", + "test": "jest", + "start": "node dist/BridgedCore.js" }, "repository": { "type": "git", diff --git a/BridgedCore.js b/src/BridgedCore.ts similarity index 55% rename from BridgedCore.js rename to src/BridgedCore.ts index 693eb6285..24edeef70 100644 --- a/BridgedCore.js +++ b/src/BridgedCore.ts @@ -1,10 +1,8 @@ -var fs = require('fs'); -var path = require('path'); -var storage = require('node-persist'); -var uuid = require('./').uuid; -var Bridge = require('./').Bridge; -var Accessory = require('./').Accessory; -var accessoryLoader = require('./lib/AccessoryLoader'); +import path from 'path'; + +import storage from 'node-persist'; + +import { Accessory, AccessoryEventTypes, AccessoryLoader, Bridge, Categories, uuid, VoidCallback } from './'; console.log("HAP-NodeJS starting..."); @@ -12,20 +10,20 @@ console.log("HAP-NodeJS starting..."); storage.initSync(); // Start by creating our Bridge which will host all loaded Accessories -var bridge = new Bridge('Node Bridge', uuid.generate("Node Bridge")); +const bridge = new Bridge('Node Bridge', uuid.generate("Node Bridge")); // Listen for bridge identification event -bridge.on('identify', function(paired, callback) { +bridge.on(AccessoryEventTypes.IDENTIFY, (paired: boolean, callback: VoidCallback) => { console.log("Node Bridge identify"); callback(); // success }); // Load up all accessories in the /accessories folder var dir = path.join(__dirname, "accessories"); -var accessories = accessoryLoader.loadDirectory(dir); +var accessories = AccessoryLoader.loadDirectory(dir); // Add them all to the bridge -accessories.forEach(function(accessory) { +accessories.forEach((accessory: Accessory) => { bridge.addBridgedAccessory(accessory); }); @@ -34,11 +32,11 @@ bridge.publish({ username: "CC:22:3D:E3:CE:F6", port: 51826, pincode: "031-45-154", - category: Accessory.Categories.BRIDGE + category: Categories.BRIDGE }); -var signals = { 'SIGINT': 2, 'SIGTERM': 15 }; -Object.keys(signals).forEach(function (signal) { +var signals = { 'SIGINT': 2, 'SIGTERM': 15 } as Record; +Object.keys(signals).forEach((signal: any) => { process.on(signal, function () { bridge.unpublish(); setTimeout(function (){ diff --git a/CameraCore.js b/src/CameraCore.ts similarity index 60% rename from CameraCore.js rename to src/CameraCore.ts index e67014c9e..74de0c175 100644 --- a/CameraCore.js +++ b/src/CameraCore.ts @@ -1,7 +1,6 @@ -var storage = require('node-persist'); -var uuid = require('./').uuid; -var Accessory = require('./').Accessory; -var Camera = require('./').Camera; +import storage from 'node-persist'; + +import { Accessory, AccessoryEventTypes, Camera, Categories, uuid, VoidCallback } from './'; console.log("HAP-NodeJS starting..."); @@ -15,7 +14,7 @@ var cameraSource = new Camera(); cameraAccessory.configureCameraSource(cameraSource); -cameraAccessory.on('identify', function(paired, callback) { +cameraAccessory.on(AccessoryEventTypes.IDENTIFY, (paired: boolean, callback: VoidCallback) => { console.log("Node Camera identify"); callback(); // success }); @@ -25,14 +24,14 @@ cameraAccessory.publish({ username: "EC:22:3D:D3:CE:CE", port: 51062, pincode: "031-45-154", - category: Accessory.Categories.CAMERA + category: Categories.CAMERA }, true); -var signals = { 'SIGINT': 2, 'SIGTERM': 15 }; -Object.keys(signals).forEach(function (signal) { - process.on(signal, function () { +var signals = { 'SIGINT': 2, 'SIGTERM': 15 } as Record; +Object.keys(signals).forEach((signal: any) => { + process.on(signal, () => { cameraAccessory.unpublish(); - setTimeout(function (){ + setTimeout(() => { process.exit(128 + signals[signal]); }, 1000) }); diff --git a/Core.js b/src/Core.ts similarity index 73% rename from Core.js rename to src/Core.ts index 908c7245f..d1404da26 100644 --- a/Core.js +++ b/src/Core.ts @@ -1,8 +1,8 @@ -var path = require('path'); -var storage = require('node-persist'); -var uuid = require('./').uuid; -var Accessory = require('./').Accessory; -var accessoryLoader = require('./lib/AccessoryLoader'); +import path from 'path'; + +import storage from 'node-persist'; + +import { AccessoryLoader } from './'; console.log("HAP-NodeJS starting..."); @@ -14,16 +14,18 @@ var targetPort = 51826; // Load up all accessories in the /accessories folder var dir = path.join(__dirname, "accessories"); -var accessories = accessoryLoader.loadDirectory(dir); +var accessories = AccessoryLoader.loadDirectory(dir); // Publish them all separately (as opposed to BridgedCore which publishes them behind a single Bridge accessory) -accessories.forEach(function(accessory) { +accessories.forEach((accessory) => { // To push Accessories separately, we'll need a few extra properties + // @ts-ignore if (!accessory.username) throw new Error("Username not found on accessory '" + accessory.displayName + "'. Core.js requires all accessories to define a unique 'username' property."); + // @ts-ignore if (!accessory.pincode) throw new Error("Pincode not found on accessory '" + accessory.displayName + "'. Core.js requires all accessories to define a 'pincode' property."); @@ -31,19 +33,21 @@ accessories.forEach(function(accessory) { // publish this Accessory on the local network accessory.publish({ port: targetPort++, + // @ts-ignore username: accessory.username, + // @ts-ignore pincode: accessory.pincode }); }); -var signals = { 'SIGINT': 2, 'SIGTERM': 15 }; -Object.keys(signals).forEach(function (signal) { - process.on(signal, function () { +var signals = { 'SIGINT': 2, 'SIGTERM': 15 } as Record; +Object.keys(signals).forEach((signal: any) => { + process.on(signal, () => { for (var i = 0; i < accessories.length; i++) { accessories[i].unpublish(); } - setTimeout(function (){ + setTimeout(() => { process.exit(128 + signals[signal]); }, 1000) }); diff --git a/accessories/AirConditioner_accessory.js b/src/accessories/AirConditioner_accessory.ts similarity index 66% rename from accessories/AirConditioner_accessory.js rename to src/accessories/AirConditioner_accessory.ts index 71383688e..693e439e9 100644 --- a/accessories/AirConditioner_accessory.js +++ b/src/accessories/AirConditioner_accessory.ts @@ -1,14 +1,20 @@ -var Accessory = require('../').Accessory; -var Service = require('../').Service; -var Characteristic = require('../').Characteristic; -var uuid = require('../').uuid; - //In This example we create an Airconditioner 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 // here's a fake hardware device that we'll expose to HomeKit -var ACTest_data = { +import { + Accessory, + AccessoryEventTypes, + Characteristic, + CharacteristicEventTypes, CharacteristicGetCallback, CharacteristicSetCallback, + CharacteristicValue, + Service, + uuid, +} from '..'; +import { NodeCallback, VoidCallback } from '../types'; + +var ACTest_data: Record = { fanPowerOn: false, rSpeed: 100, CurrentHeatingCoolingState: 1, @@ -23,26 +29,28 @@ var ACTest_data = { var 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 ACTest.username = "1A:2B:3C:4D:5E:FF"; +// @ts-ignore ACTest.pincode = "031-45-154"; // set some basic properties (these values are arbitrary and setting them is optional) ACTest - .getService(Service.AccessoryInformation) + .getService(Service.AccessoryInformation)! .setCharacteristic(Characteristic.Manufacturer, "Sample Company") // listen for the "identify" event for this Accessory -ACTest.on('identify', function(paired, callback) { +ACTest.on(AccessoryEventTypes.IDENTIFY, (paired: boolean, callback: VoidCallback) => { console.log("Fan Identified!"); callback(); // success }); // 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/HomeKitTypes.js` +// 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 -FanService.getCharacteristic(Characteristic.On) - .on('set', function(value, callback) { +FanService.getCharacteristic(Characteristic.On)! + .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { console.log("Fan Power Changed To "+value); ACTest_data.fanPowerOn=value callback(); // Our fake Fan is synchronous - this value has been successfully set @@ -50,8 +58,8 @@ FanService.getCharacteristic(Characteristic.On) // We want to intercept requests for our current power state so we can query the hardware itself instead of // allowing HAP-NodeJS to return the cached Characteristic.value. -FanService.getCharacteristic(Characteristic.On) - .on('get', function(callback) { +FanService.getCharacteristic(Characteristic.On)! + .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { // 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 @@ -70,10 +78,10 @@ FanService.getCharacteristic(Characteristic.On) // also add an "optional" Characteristic for speed FanService.addCharacteristic(Characteristic.RotationSpeed) - .on('get', function(callback) { + .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { callback(null, ACTest_data.rSpeed); }) - .on('set', function(value, callback) { + .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { console.log("Setting fan rSpeed to %s", value); ACTest_data.rSpeed=value callback(); @@ -82,65 +90,65 @@ FanService.addCharacteristic(Characteristic.RotationSpeed) var ThermostatService = ACTest.addService(Service.Thermostat,"Thermostat"); ThermostatService.addLinkedService(FanService); -ThermostatService.getCharacteristic(Characteristic.CurrentHeatingCoolingState) +ThermostatService.getCharacteristic(Characteristic.CurrentHeatingCoolingState)! - .on('get', function(callback) { + .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { callback(null, ACTest_data.CurrentHeatingCoolingState); }) - .on('set',function(value, callback) { + .on(CharacteristicEventTypes.SET,(value: CharacteristicValue, callback: CharacteristicSetCallback) => { ACTest_data.CurrentHeatingCoolingState=value; console.log( "Characteristic CurrentHeatingCoolingState changed to %s",value); callback(); }); - ThermostatService.getCharacteristic(Characteristic.TargetHeatingCoolingState) - .on('get', function(callback) { + ThermostatService.getCharacteristic(Characteristic.TargetHeatingCoolingState)! + .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { callback(null, ACTest_data.TargetHeatingCoolingState); }) - .on('set',function(value, callback) { + .on(CharacteristicEventTypes.SET,(value: CharacteristicValue, callback: CharacteristicSetCallback) => { ACTest_data.TargetHeatingCoolingState=value; console.log( "Characteristic TargetHeatingCoolingState changed to %s",value); callback(); }); - ThermostatService.getCharacteristic(Characteristic.CurrentTemperature) - .on('get', function(callback) { + ThermostatService.getCharacteristic(Characteristic.CurrentTemperature)! + .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { callback(null, ACTest_data.CurrentTemperature); }) - .on('set',function(value, callback) { + .on(CharacteristicEventTypes.SET,(value: CharacteristicValue, callback: CharacteristicSetCallback) => { ACTest_data.CurrentTemperature=value; console.log( "Characteristic CurrentTemperature changed to %s",value); callback(); }); - ThermostatService.getCharacteristic(Characteristic.TargetTemperature) - .on('get', function(callback) { + ThermostatService.getCharacteristic(Characteristic.TargetTemperature)! + .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { callback(null, ACTest_data.TargetTemperature); }) - .on('set',function(value, callback) { + .on(CharacteristicEventTypes.SET,(value: CharacteristicValue, callback: CharacteristicSetCallback) => { ACTest_data.TargetTemperature=value; console.log( "Characteristic TargetTemperature changed to %s",value); callback(); }); - ThermostatService.getCharacteristic(Characteristic.TemperatureDisplayUnits) - .on('get', function(callback) { + ThermostatService.getCharacteristic(Characteristic.TemperatureDisplayUnits)! + .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { callback(null, ACTest_data.TemperatureDisplayUnits); }) - .on('set',function(value, callback) { + .on(CharacteristicEventTypes.SET,(value: CharacteristicValue, callback: CharacteristicSetCallback) => { ACTest_data.TemperatureDisplayUnits=value; console.log( "Characteristic TemperatureDisplayUnits changed to %s",value); callback(); }); - + var LightService = ACTest.addService(Service.Lightbulb, 'AC Light'); -LightService.getCharacteristic(Characteristic.On) - .on('get', function(callback) { +LightService.getCharacteristic(Characteristic.On)! + .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { callback(null, ACTest_data.LightOn); }) - .on('set', function(value, callback) { + .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { ACTest_data.LightOn=value; console.log( "Characteristic Light On changed to %s",value); callback(); diff --git a/accessories/Fan_accessory.js b/src/accessories/Fan_accessory.ts similarity index 68% rename from accessories/Fan_accessory.js rename to src/accessories/Fan_accessory.ts index eef1d4e77..547852c3f 100644 --- a/accessories/Fan_accessory.js +++ b/src/accessories/Fan_accessory.ts @@ -1,14 +1,20 @@ -var Accessory = require('../').Accessory; -var Service = require('../').Service; -var Characteristic = require('../').Characteristic; -var uuid = require('../').uuid; - - // here's a fake hardware device that we'll expose to HomeKit -var FAKE_FAN = { +import { + Accessory, + AccessoryEventTypes, + Characteristic, + CharacteristicEventTypes, CharacteristicSetCallback, + CharacteristicValue, + NodeCallback, + Service, + uuid, + VoidCallback +} from '..'; + +var FAKE_FAN: Record = { powerOn: false, rSpeed: 100, - setPowerOn: function(on) { + setPowerOn: (on: CharacteristicValue) => { if(on){ //put your code here to turn on the fan FAKE_FAN.powerOn = on; @@ -18,12 +24,12 @@ var FAKE_FAN = { FAKE_FAN.powerOn = on; } }, - setSpeed: function(value) { + setSpeed: (value: CharacteristicValue) => { console.log("Setting fan rSpeed to %s", value); FAKE_FAN.rSpeed = value; //put your code here to set the fan to a specific value }, - identify: function() { + identify: () => { //put your code here to identify the fan console.log("Fan Identified!"); } @@ -33,26 +39,28 @@ var FAKE_FAN = { var 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 fan.username = "1A:2B:3C:4D:5E:FF"; +// @ts-ignore fan.pincode = "031-45-154"; // set some basic properties (these values are arbitrary and setting them is optional) fan - .getService(Service.AccessoryInformation) + .getService(Service.AccessoryInformation)! .setCharacteristic(Characteristic.Manufacturer, "Sample Company") // listen for the "identify" event for this Accessory -fan.on('identify', function(paired, callback) { +fan.on(AccessoryEventTypes.IDENTIFY, (paired: boolean, callback: VoidCallback) => { FAKE_FAN.identify(); callback(); // success }); // 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/HomeKitTypes.js` +// We can see the complete list of Services and Characteristics in `lib/gen/HomeKit.ts` fan .addService(Service.Fan, "Fan") // services exposed to the user should have "names" like "Fake Light" for us - .getCharacteristic(Characteristic.On) - .on('set', function(value, callback) { + .getCharacteristic(Characteristic.On)! + .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { FAKE_FAN.setPowerOn(value); callback(); // Our fake Fan is synchronous - this value has been successfully set }); @@ -60,9 +68,9 @@ fan // We want to intercept requests for our current power state so we can query the hardware itself instead of // allowing HAP-NodeJS to return the cached Characteristic.value. fan - .getService(Service.Fan) - .getCharacteristic(Characteristic.On) - .on('get', function(callback) { + .getService(Service.Fan)! + .getCharacteristic(Characteristic.On)! + .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { // 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 @@ -80,12 +88,12 @@ fan // also add an "optional" Characteristic for speed fan - .getService(Service.Fan) + .getService(Service.Fan)! .addCharacteristic(Characteristic.RotationSpeed) - .on('get', function(callback) { + .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { callback(null, FAKE_FAN.rSpeed); }) - .on('set', function(value, callback) { + .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { FAKE_FAN.setSpeed(value); callback(); }) diff --git a/accessories/GarageDoorOpener_accessory.js b/src/accessories/GarageDoorOpener_accessory.ts similarity index 71% rename from accessories/GarageDoorOpener_accessory.js rename to src/accessories/GarageDoorOpener_accessory.ts index 97a63c9ac..a40bedf98 100644 --- a/accessories/GarageDoorOpener_accessory.js +++ b/src/accessories/GarageDoorOpener_accessory.ts @@ -1,26 +1,31 @@ -var Accessory = require('../').Accessory; -var Service = require('../').Service; -var Characteristic = require('../').Characteristic; -var uuid = require('../').uuid; - +import { + Accessory, + AccessoryEventTypes, + Characteristic, + CharacteristicEventTypes, CharacteristicSetCallback, CharacteristicValue, + NodeCallback, + Service, + uuid, + VoidCallback +} from '..'; var FAKE_GARAGE = { opened: false, - open: function() { + open: () => { console.log("Opening the Garage!"); //add your code here which allows the garage to open FAKE_GARAGE.opened = true; }, - close: function() { + close: () => { console.log("Closing the Garage!"); //add your code here which allows the garage to close FAKE_GARAGE.opened = false; }, - identify: function() { + identify: () => { //add your code here which allows the garage to be identified console.log("Identify the Garage"); }, - status: function(){ + 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; @@ -31,16 +36,18 @@ var garageUUID = uuid.generate('hap-nodejs:accessories:'+'GarageDoor'); var 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 garage.username = "C1:5D:3F:EE:5E:FA"; //edit this if you use Core.js +// @ts-ignore garage.pincode = "031-45-154"; garage - .getService(Service.AccessoryInformation) + .getService(Service.AccessoryInformation)! .setCharacteristic(Characteristic.Manufacturer, "Liftmaster") .setCharacteristic(Characteristic.Model, "Rev-1") .setCharacteristic(Characteristic.SerialNumber, "TW000165"); -garage.on('identify', function(paired, callback) { +garage.on(AccessoryEventTypes.IDENTIFY, (paired: boolean, callback: VoidCallback) => { FAKE_GARAGE.identify(); callback(); }); @@ -48,30 +55,30 @@ garage.on('identify', function(paired, callback) { garage .addService(Service.GarageDoorOpener, "Garage Door") .setCharacteristic(Characteristic.TargetDoorState, Characteristic.TargetDoorState.CLOSED) // force initial state to CLOSED - .getCharacteristic(Characteristic.TargetDoorState) - .on('set', function(value, callback) { + .getCharacteristic(Characteristic.TargetDoorState)! + .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { if (value == Characteristic.TargetDoorState.CLOSED) { FAKE_GARAGE.close(); callback(); garage - .getService(Service.GarageDoorOpener) + .getService(Service.GarageDoorOpener)! .setCharacteristic(Characteristic.CurrentDoorState, Characteristic.CurrentDoorState.CLOSED); } else if (value == Characteristic.TargetDoorState.OPEN) { FAKE_GARAGE.open(); callback(); garage - .getService(Service.GarageDoorOpener) + .getService(Service.GarageDoorOpener)! .setCharacteristic(Characteristic.CurrentDoorState, Characteristic.CurrentDoorState.OPEN); } }); garage - .getService(Service.GarageDoorOpener) - .getCharacteristic(Characteristic.CurrentDoorState) - .on('get', function(callback) { + .getService(Service.GarageDoorOpener)! + .getCharacteristic(Characteristic.CurrentDoorState)! + .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { var err = null; FAKE_GARAGE.status(); diff --git a/accessories/Light_accessory.js b/src/accessories/Light_accessory.ts similarity index 59% rename from accessories/Light_accessory.js rename to src/accessories/Light_accessory.ts index 19c6ba6a7..c074df282 100644 --- a/accessories/Light_accessory.js +++ b/src/accessories/Light_accessory.ts @@ -1,99 +1,111 @@ -var Accessory = require('../').Accessory; -var Service = require('../').Service; -var Characteristic = require('../').Characteristic; -var uuid = require('../').uuid; - -var LightController = { - name: "Simple Light", //name of accessory - pincode: "031-45-154", - username: "FA:3C:ED:5A:1A:1A", // MAC like address used by HomeKit to differentiate accessories. - manufacturer: "HAP-NodeJS", //manufacturer (optional) - model: "v1.0", //model (optional) - serialNumber: "A12S345KGB", //serial number (optional) - - power: false, //current power status - brightness: 100, //current brightness - hue: 0, //current hue - saturation: 0, //current saturation - - outputLogs: false, //output logs - - setPower: function(status) { //set power of accessory +import { + Accessory, + AccessoryEventTypes, + Characteristic, + CharacteristicEventTypes, CharacteristicSetCallback, + CharacteristicValue, + NodeCallback, + Service, + uuid, + VoidCallback +} from '..'; + +class LightControllerClass { + + name: CharacteristicValue = "Simple Light"; //name of accessory + pincode: CharacteristicValue = "031-45-154"; + username: CharacteristicValue = "FA:3C:ED:5A:1A:1A"; // MAC like address used by HomeKit to differentiate accessories. + manufacturer: CharacteristicValue = "HAP-NodeJS"; //manufacturer (optional) + model: CharacteristicValue = "v1.0"; //model (optional) + serialNumber: CharacteristicValue = "A12S345KGB"; //serial number (optional) + + power: CharacteristicValue = false; //current power status + brightness: CharacteristicValue = 100; //current brightness + hue: CharacteristicValue = 0; //current hue + saturation: CharacteristicValue = 0; //current saturation + + outputLogs = false; //output logs + + setPower(status: CharacteristicValue) { //set power of accessory if(this.outputLogs) console.log("Turning the '%s' %s", this.name, status ? "on" : "off"); this.power = status; - }, + } - getPower: function() { //get power of accessory + getPower() { //get power of accessory if(this.outputLogs) console.log("'%s' is %s.", this.name, this.power ? "on" : "off"); return this.power; - }, + } - setBrightness: function(brightness) { //set brightness + setBrightness(brightness: CharacteristicValue) { //set brightness if(this.outputLogs) console.log("Setting '%s' brightness to %s", this.name, brightness); this.brightness = brightness; - }, + } - getBrightness: function() { //get brightness + getBrightness() { //get brightness if(this.outputLogs) console.log("'%s' brightness is %s", this.name, this.brightness); return this.brightness; - }, + } - setSaturation: function(saturation) { //set brightness + setSaturation(saturation: CharacteristicValue) { //set brightness if(this.outputLogs) console.log("Setting '%s' saturation to %s", this.name, saturation); this.saturation = saturation; - }, + } - getSaturation: function() { //get brightness + getSaturation() { //get brightness if(this.outputLogs) console.log("'%s' saturation is %s", this.name, this.saturation); return this.saturation; - }, + } - setHue: function(hue) { //set brightness + setHue(hue: CharacteristicValue) { //set brightness if(this.outputLogs) console.log("Setting '%s' hue to %s", this.name, hue); this.hue = hue; - }, + } - getHue: function() { //get hue + getHue() { //get hue if(this.outputLogs) console.log("'%s' hue is %s", this.name, this.hue); return this.hue; - }, + } - identify: function() { //identify the accessory + identify() { //identify the accessory if(this.outputLogs) console.log("Identify the '%s'", this.name); } } +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); // This is the Accessory that we'll return to HAP-NodeJS that represents our light. -var lightAccessory = exports.accessory = new Accessory(LightController.name, lightUUID); +var 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 lightAccessory.username = LightController.username; +// @ts-ignore lightAccessory.pincode = LightController.pincode; // set some basic properties (these values are arbitrary and setting them is optional) lightAccessory - .getService(Service.AccessoryInformation) + .getService(Service.AccessoryInformation)! .setCharacteristic(Characteristic.Manufacturer, LightController.manufacturer) .setCharacteristic(Characteristic.Model, LightController.model) .setCharacteristic(Characteristic.SerialNumber, LightController.serialNumber); // listen for the "identify" event for this Accessory -lightAccessory.on('identify', function(paired, callback) { +lightAccessory.on(AccessoryEventTypes.IDENTIFY, (paired: boolean, callback: VoidCallback) => { LightController.identify(); callback(); }); // 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/HomeKitTypes.js` +// 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) - .on('set', function(value, callback) { + .getCharacteristic(Characteristic.On)! + .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { LightController.setPower(value); // Our light is synchronous - this value has been successfully set @@ -105,13 +117,13 @@ lightAccessory }) // We want to intercept requests for our current power state so we can query the hardware itself instead of // allowing HAP-NodeJS to return the cached Characteristic.value. - .on('get', function(callback) { + .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { callback(null, LightController.getPower()); }); // To inform HomeKit about changes occurred outside of HomeKit (like user physically turn on the light) // Please use Characteristic.updateValue -// +// // lightAccessory // .getService(Service.Lightbulb) // .getCharacteristic(Characteristic.On) @@ -119,36 +131,36 @@ lightAccessory // also add an "optional" Characteristic for Brightness lightAccessory - .getService(Service.Lightbulb) + .getService(Service.Lightbulb)! .addCharacteristic(Characteristic.Brightness) - .on('set', function(value, callback) { + .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { LightController.setBrightness(value); callback(); }) - .on('get', function(callback) { + .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { callback(null, LightController.getBrightness()); }); // also add an "optional" Characteristic for Saturation lightAccessory - .getService(Service.Lightbulb) - .addCharacteristic(Characteristic.Saturation) - .on('set', function(value, callback) { + .getService(Service.Lightbulb)! + .addCharacteristic(Characteristic.Saturation)! + .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { LightController.setSaturation(value); callback(); }) - .on('get', function(callback) { + .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { callback(null, LightController.getSaturation()); }); // also add an "optional" Characteristic for Hue lightAccessory - .getService(Service.Lightbulb) + .getService(Service.Lightbulb)! .addCharacteristic(Characteristic.Hue) - .on('set', function(value, callback) { + .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { LightController.setHue(value); callback(); }) - .on('get', function(callback) { + .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { callback(null, LightController.getHue()); }); diff --git a/accessories/Lock_accessory.js b/src/accessories/Lock_accessory.ts similarity index 77% rename from accessories/Lock_accessory.js rename to src/accessories/Lock_accessory.ts index 2de7c52e4..7d3fcffbd 100644 --- a/accessories/Lock_accessory.js +++ b/src/accessories/Lock_accessory.ts @@ -1,20 +1,26 @@ -var Accessory = require('../').Accessory; -var Service = require('../').Service; -var Characteristic = require('../').Characteristic; -var uuid = require('../').uuid; +import { + Accessory, + AccessoryEventTypes, + Characteristic, + CharacteristicEventTypes, CharacteristicSetCallback, + CharacteristicValue, + Service, + uuid +} from '../'; +import { NodeCallback, VoidCallback } from '../types'; // here's a fake hardware device that we'll expose to HomeKit var FAKE_LOCK = { locked: false, - lock: function() { + lock: () => { console.log("Locking the lock!"); FAKE_LOCK.locked = true; }, - unlock: function() { + unlock: () => { console.log("Unlocking the lock!"); FAKE_LOCK.locked = false; }, - identify: function() { + identify: () => { console.log("Identify the lock!"); } } @@ -28,45 +34,47 @@ var lockUUID = uuid.generate('hap-nodejs:accessories:lock'); var lock = exports.accessory = new Accessory('Lock', lockUUID); // Add properties for publishing (in case we're using Core.js and not BridgedCore.js) +// @ts-ignore lock.username = "C1:5D:3A:EE:5E:FA"; +// @ts-ignore lock.pincode = "031-45-154"; // set some basic properties (these values are arbitrary and setting them is optional) lock - .getService(Service.AccessoryInformation) + .getService(Service.AccessoryInformation)! .setCharacteristic(Characteristic.Manufacturer, "Oltica") .setCharacteristic(Characteristic.Model, "Rev-1") .setCharacteristic(Characteristic.SerialNumber, "A1S2NASF88EW"); // listen for the "identify" event for this Accessory -lock.on('identify', function(paired, callback) { +lock.on(AccessoryEventTypes.IDENTIFY, (paired: boolean, callback: VoidCallback) => { FAKE_LOCK.identify(); callback(); // success }); // 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/HomeKitTypes.js` +// 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('set', function(value, callback) { - + .getCharacteristic(Characteristic.LockTargetState)! + .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { + 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) + .getService(Service.LockMechanism)! .setCharacteristic(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) + .getService(Service.LockMechanism)! .setCharacteristic(Characteristic.LockCurrentState, Characteristic.LockCurrentState.SECURED); } }); @@ -74,16 +82,16 @@ lock // 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('get', function(callback) { - + .getService(Service.LockMechanism)! + .getCharacteristic(Characteristic.LockCurrentState)! + .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { + // this event is emitted when you ask Siri directly whether your lock is locked or not. you might query // the lock hardware itself to find this out, then call the callback. But if you take longer than a // few seconds to respond, Siri will give up. - + var err = null; // in case there were any problems - + if (FAKE_LOCK.locked) { console.log("Are we locked? Yes."); callback(err, Characteristic.LockCurrentState.SECURED); diff --git a/accessories/MotionSensor_accessory.js b/src/accessories/MotionSensor_accessory.ts similarity index 75% rename from accessories/MotionSensor_accessory.js rename to src/accessories/MotionSensor_accessory.ts index 5c78cd035..91a939174 100644 --- a/accessories/MotionSensor_accessory.js +++ b/src/accessories/MotionSensor_accessory.ts @@ -1,17 +1,23 @@ -var Accessory = require('../').Accessory; -var Service = require('../').Service; -var Characteristic = require('../').Characteristic; -var uuid = require('../').uuid; - // here's a fake hardware device that we'll expose to HomeKit +import { + Accessory, + AccessoryEventTypes, + Characteristic, + CharacteristicEventTypes, + CharacteristicValue, + NodeCallback, + Service, + uuid, VoidCallback +} from '..'; + var MOTION_SENSOR = { motionDetected: false, - getStatus: function() { + getStatus: () => { //set the boolean here, this will be returned to the device MOTION_SENSOR.motionDetected = false; }, - identify: function() { + identify: () => { console.log("Identify the motion sensor!"); } } @@ -25,26 +31,28 @@ var motionSensorUUID = uuid.generate('hap-nodejs:accessories:motionsensor'); var 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 motionSensor.username = "1A:2B:3D:4D:2E:AF"; +// @ts-ignore motionSensor.pincode = "031-45-154"; // set some basic properties (these values are arbitrary and setting them is optional) motionSensor - .getService(Service.AccessoryInformation) + .getService(Service.AccessoryInformation)! .setCharacteristic(Characteristic.Manufacturer, "Oltica") .setCharacteristic(Characteristic.Model, "Rev-1") .setCharacteristic(Characteristic.SerialNumber, "A1S2NASF88EW"); // listen for the "identify" event for this Accessory -motionSensor.on('identify', function(paired, callback) { +motionSensor.on(AccessoryEventTypes.IDENTIFY, (paired: boolean, callback: VoidCallback) => { MOTION_SENSOR.identify(); callback(); // success }); motionSensor .addService(Service.MotionSensor, "Fake Motion Sensor") // services exposed to the user should have "names" like "Fake Motion Sensor" for us - .getCharacteristic(Characteristic.MotionDetected) - .on('get', function(callback) { + .getCharacteristic(Characteristic.MotionDetected)! + .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { MOTION_SENSOR.getStatus(); callback(null, Boolean(MOTION_SENSOR.motionDetected)); }); diff --git a/accessories/Outlet_accessory.js b/src/accessories/Outlet_accessory.ts similarity index 74% rename from accessories/Outlet_accessory.js rename to src/accessories/Outlet_accessory.ts index 2b1e5c2ab..d94a1ad00 100644 --- a/accessories/Outlet_accessory.js +++ b/src/accessories/Outlet_accessory.ts @@ -1,12 +1,21 @@ -var Accessory = require('../').Accessory; -var Service = require('../').Service; -var Characteristic = require('../').Characteristic; -var uuid = require('../').uuid; -var err = null; // in case there were any problems +import { + Accessory, + AccessoryEventTypes, + Characteristic, + CharacteristicEventTypes, CharacteristicSetCallback, + CharacteristicValue, NodeCallback, + Service, + uuid, + VoidCallback +} from '..'; +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 = { - setPowerOn: function(on) { + powerOn: false, + setPowerOn: (on: CharacteristicValue) => { console.log("Turning the outlet %s!...", on ? "on" : "off"); if (on) { FAKE_OUTLET.powerOn = true; @@ -32,28 +41,30 @@ var outletUUID = uuid.generate('hap-nodejs:accessories:Outlet'); var outlet = exports.accessory = new Accessory('Outlet', outletUUID); // Add properties for publishing (in case we're using Core.js and not BridgedCore.js) +// @ts-ignore outlet.username = "1A:2B:3C:4D:5D:FF"; +// @ts-ignore outlet.pincode = "031-45-154"; // set some basic properties (these values are arbitrary and setting them is optional) outlet - .getService(Service.AccessoryInformation) + .getService(Service.AccessoryInformation)! .setCharacteristic(Characteristic.Manufacturer, "Oltica") .setCharacteristic(Characteristic.Model, "Rev-1") .setCharacteristic(Characteristic.SerialNumber, "A1S2NASF88EW"); // listen for the "identify" event for this Accessory -outlet.on('identify', function(paired, callback) { +outlet.on(AccessoryEventTypes.IDENTIFY, function(paired: boolean, callback: VoidCallback) { FAKE_OUTLET.identify(); callback(); // success }); // 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/HomeKitTypes.js` +// 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) - .on('set', function(value, callback) { + .getCharacteristic(Characteristic.On)! + .on(CharacteristicEventTypes.SET, function(value: CharacteristicValue, callback: CharacteristicSetCallback) { FAKE_OUTLET.setPowerOn(value); callback(); // Our fake Outlet is synchronous - this value has been successfully set }); @@ -61,9 +72,9 @@ outlet // We want to intercept requests for our current power state so we can query the hardware itself instead of // allowing HAP-NodeJS to return the cached Characteristic.value. outlet - .getService(Service.Outlet) - .getCharacteristic(Characteristic.On) - .on('get', function(callback) { + .getService(Service.Outlet)! + .getCharacteristic(Characteristic.On)! + .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { // this event is emitted when you ask Siri directly whether your light is on or not. you might query // the light hardware itself to find this out, then call the callback. But if you take longer than a @@ -79,4 +90,4 @@ outlet console.log("Are we on? No."); callback(err, false); } - }); + }); diff --git a/accessories/Sprinkler_accessory.js b/src/accessories/Sprinkler_accessory.ts similarity index 65% rename from accessories/Sprinkler_accessory.js rename to src/accessories/Sprinkler_accessory.ts index a441d91a3..d6e7d1e63 100644 --- a/accessories/Sprinkler_accessory.js +++ b/src/accessories/Sprinkler_accessory.ts @@ -1,20 +1,27 @@ -var Accessory = require('../').Accessory; -var Service = require('../').Service; -var Characteristic = require('../').Characteristic; -var uuid = require('../').uuid; - // here's a fake hardware device that we'll expose to HomeKit -var SPRINKLER = { +import { + Accessory, + Characteristic, + CharacteristicEventTypes, + CharacteristicSetCallback, + CharacteristicValue, + NodeCallback, + Service, + uuid +} from '..'; + +var SPRINKLER: any = { active: false, name: "Garten Hinten", timerEnd: 0, defaultDuration: 3600, + motionDetected: false, - getStatus: function() { + getStatus: () => { //set the boolean here, this will be returned to the device SPRINKLER.motionDetected = false; }, - identify: function() { + identify: () => { console.log("Identify the sprinkler!"); } } @@ -29,25 +36,27 @@ var sprinklerUUID = uuid.generate('hap-nodejs:accessories:sprinkler'); var sprinkler = exports.accessory = new Accessory('💦 Sprinkler', sprinklerUUID); // Add properties for publishing (in case we're using Core.js and not BridgedCore.js) +// @ts-ignore sprinkler.username = "A3:AB:3D:4D:2E:A3"; +// @ts-ignore sprinkler.pincode = "123-44-567"; // 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/HomeKitTypes.js` +// We can see the complete list of Services and Characteristics in `lib/gen/HomeKit.ts` var sprinklerService = sprinkler.addService(Service.Valve, "💦 Sprinkler") // set some basic properties (these values are arbitrary and setting them is optional) sprinkler - .getService(Service.Valve) + .getService(Service.Valve)! .setCharacteristic(Characteristic.ValveType, "1") // IRRIGATION/SPRINKLER = 1; SHOWER_HEAD = 2; WATER_FAUCET = 3; .setCharacteristic(Characteristic.Name, SPRINKLER.name) ; sprinkler - .getService(Service.Valve) - .getCharacteristic(Characteristic.Active) - .on('get', function(callback) { + .getService(Service.Valve)! + .getCharacteristic(Characteristic.Active)! + .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { console.log("get Active"); var err = null; // in case there were any problems @@ -59,24 +68,24 @@ sprinkler callback(err, false); } }) - .on('set', function(newValue, callback) { + .on(CharacteristicEventTypes.SET, (newValue: CharacteristicValue, callback: CharacteristicSetCallback) => { console.log("set Active => setNewValue: " + newValue); - + if (SPRINKLER.active) { SPRINKLER.active = false; closeVentile(); setTimeout(function() { - console.log("Ausgeschaltet"); - SPRINKLER.timerEnd = SPRINKLER.defaultDuration + Math.floor(new Date() / 1000); + console.log("Ausgeschaltet"); + SPRINKLER.timerEnd = SPRINKLER.defaultDuration + Math.floor(new Date().getTime() / 1000); callback(null); sprinkler - .getService(Service.Valve) - .setCharacteristic(Characteristic.SetDuration, 0); + .getService(Service.Valve)! + .setCharacteristic(Characteristic.SetDuration, 0); sprinkler - .getService(Service.Valve) + .getService(Service.Valve)! .setCharacteristic(Characteristic.InUse, 0); }, 1000); @@ -86,30 +95,30 @@ sprinkler openVentile(); setTimeout(function() { console.log("Eingeschaltet"); - SPRINKLER.timerEnd = SPRINKLER.defaultDuration + Math.floor(new Date() / 1000); + SPRINKLER.timerEnd = SPRINKLER.defaultDuration + Math.floor(new Date().getTime() / 1000); callback(null, SPRINKLER.defaultDuration); - + sprinkler - .getService(Service.Valve) + .getService(Service.Valve)! .setCharacteristic(Characteristic.InUse, 1); sprinkler - .getService(Service.Valve) - .setCharacteristic(Characteristic.RemainingDuration, SPRINKLER.defaultDuration); + .getService(Service.Valve)! + .setCharacteristic(Characteristic.RemainingDuration, SPRINKLER.defaultDuration); sprinkler - .getService(Service.Valve) + .getService(Service.Valve)! .setCharacteristic(Characteristic.SetDuration, SPRINKLER.defaultDuration); - + }, 1000); } }); sprinkler - .getService(Service.Valve) - .getCharacteristic(Characteristic.InUse) - .on('get', function(callback) { + .getService(Service.Valve)! + .getCharacteristic(Characteristic.InUse)! + .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { console.log("get In_Use"); var err = null; // in case there were any problems @@ -120,21 +129,21 @@ sprinkler callback(err, false); } }) - .on('set', function(newValue, callback) { - console.log("set In_Use => NewValue: " + newValue); + .on(CharacteristicEventTypes.SET, (newValue: CharacteristicValue, callback: CharacteristicSetCallback) => { + console.log("set In_Use => NewValue: " + newValue); }); sprinkler - .getService(Service.Valve) - .getCharacteristic(Characteristic.RemainingDuration) - .on('get', function(callback) { + .getService(Service.Valve)! + .getCharacteristic(Characteristic.RemainingDuration)! + .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { var err = null; // in case there were any problems if (SPRINKLER.active) { - - var duration = SPRINKLER.timerEnd - Math.floor(new Date() / 1000); + + var duration = SPRINKLER.timerEnd - Math.floor(new Date().getTime() / 1000); console.log("RemainingDuration: " + duration) callback(err, duration); } @@ -145,14 +154,14 @@ sprinkler sprinkler - .getService(Service.Valve) - .getCharacteristic(Characteristic.SetDuration) - .on('set', function(newValue, callback) { + .getService(Service.Valve)! + .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(); + callback(); }); @@ -164,4 +173,4 @@ sprinkler function closeVentile() { // Add your code here } - + diff --git a/accessories/TV_accessory.js b/src/accessories/TV_accessory.ts similarity index 73% rename from accessories/TV_accessory.js rename to src/accessories/TV_accessory.ts index 64c2b85b6..9c521b582 100644 --- a/accessories/TV_accessory.js +++ b/src/accessories/TV_accessory.ts @@ -1,7 +1,12 @@ -var Accessory = require('../').Accessory; -var Service = require('../').Service; -var Characteristic = require('../').Characteristic; -var uuid = require('../').uuid; +import { + Accessory, + Characteristic, + CharacteristicEventTypes, + CharacteristicSetCallback, + CharacteristicValue, + Service, + uuid +} from '..'; // 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 @@ -12,11 +17,13 @@ var tvUUID = uuid.generate('hap-nodejs:accessories:tv'); var tv = exports.accessory = new Accessory('TV', tvUUID); // Add properties for publishing (in case we're using Core.js and not BridgedCore.js) +// @ts-ignore tv.username = "A3:FB:3D:4D:2E:AC"; +// @ts-ignore tv.pincode = "031-45-154"; // 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/HomeKitTypes.js` +// We can see the complete list of Services and Characteristics in `lib/gen/HomeKit.ts` var televisionService = tv.addService(Service.Television, "Television", "Television"); televisionService @@ -29,8 +36,8 @@ televisionService ); televisionService - .getCharacteristic(Characteristic.Active) - .on('set', function(newValue, callback) { + .getCharacteristic(Characteristic.Active)! + .on(CharacteristicEventTypes.SET, (newValue: CharacteristicValue, callback: CharacteristicSetCallback) => { console.log("set Active => setNewValue: " + newValue); callback(null); }); @@ -39,29 +46,29 @@ televisionService .setCharacteristic(Characteristic.ActiveIdentifier, 1); televisionService - .getCharacteristic(Characteristic.ActiveIdentifier) - .on('set', function(newValue, callback) { + .getCharacteristic(Characteristic.ActiveIdentifier)! + .on(CharacteristicEventTypes.SET, (newValue: CharacteristicValue, callback: CharacteristicSetCallback) => { console.log("set Active Identifier => setNewValue: " + newValue); callback(null); }); televisionService - .getCharacteristic(Characteristic.RemoteKey) - .on('set', function(newValue, callback) { + .getCharacteristic(Characteristic.RemoteKey)! + .on(CharacteristicEventTypes.SET, (newValue: CharacteristicValue, callback: CharacteristicSetCallback) => { console.log("set Remote Key => setNewValue: " + newValue); callback(null); }); televisionService - .getCharacteristic(Characteristic.PictureMode) - .on('set', function(newValue, callback) { + .getCharacteristic(Characteristic.PictureMode)! + .on(CharacteristicEventTypes.SET, (newValue: CharacteristicValue, callback: CharacteristicSetCallback) => { console.log("set PictureMode => setNewValue: " + newValue); callback(null); }); televisionService - .getCharacteristic(Characteristic.PowerModeSelection) - .on('set', function(newValue, callback) { + .getCharacteristic(Characteristic.PowerModeSelection)! + .on(CharacteristicEventTypes.SET, (newValue: CharacteristicValue, callback: CharacteristicSetCallback) => { console.log("set PowerModeSelection => setNewValue: " + newValue); callback(null); }); @@ -74,8 +81,8 @@ speakerService .setCharacteristic(Characteristic.Active, Characteristic.Active.ACTIVE) .setCharacteristic(Characteristic.VolumeControlType, Characteristic.VolumeControlType.ABSOLUTE); -speakerService.getCharacteristic(Characteristic.VolumeSelector) - .on('set', function(newValue, callback) { +speakerService.getCharacteristic(Characteristic.VolumeSelector)! + .on(CharacteristicEventTypes.SET, (newValue: CharacteristicValue, callback: CharacteristicSetCallback) => { console.log("set VolumeSelector => setNewValue: " + newValue); callback(null); }); diff --git a/accessories/TemperatureSensor_accessory.js b/src/accessories/TemperatureSensor_accessory.ts similarity index 78% rename from accessories/TemperatureSensor_accessory.js rename to src/accessories/TemperatureSensor_accessory.ts index ed1e92021..dbc9c3b8d 100644 --- a/accessories/TemperatureSensor_accessory.js +++ b/src/accessories/TemperatureSensor_accessory.ts @@ -1,12 +1,9 @@ -var Accessory = require('../').Accessory; -var Service = require('../').Service; -var Characteristic = require('../').Characteristic; -var uuid = require('../').uuid; - // here's a fake temperature sensor device that we'll expose to HomeKit +import { Accessory, Characteristic, CharacteristicEventTypes, CharacteristicValue, NodeCallback, Service, uuid } from '..'; + var FAKE_SENSOR = { currentTemperature: 50, - getTemperature: function() { + getTemperature: function() { console.log("Getting the current temperature!"); return FAKE_SENSOR.currentTemperature; }, @@ -26,28 +23,30 @@ var sensorUUID = uuid.generate('hap-nodejs:accessories:temperature-sensor'); var 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 sensor.username = "C1:5D:3A:AE:5E:FA"; +// @ts-ignore sensor.pincode = "031-45-154"; // Add the actual TemperatureSensor Service. -// We can see the complete list of Services and Characteristics in `lib/gen/HomeKitTypes.js` +// We can see the complete list of Services and Characteristics in `lib/gen/HomeKit.ts` sensor - .addService(Service.TemperatureSensor) - .getCharacteristic(Characteristic.CurrentTemperature) - .on('get', function(callback) { - + .addService(Service.TemperatureSensor)! + .getCharacteristic(Characteristic.CurrentTemperature)! + .on(CharacteristicEventTypes.GET, (callback: NodeCallback) => { + // return our current value callback(null, FAKE_SENSOR.getTemperature()); }); // randomize our temperature reading every 3 seconds setInterval(function() { - + FAKE_SENSOR.randomizeTemperature(); - + // update the characteristic value so interested iOS devices can get notified sensor - .getService(Service.TemperatureSensor) + .getService(Service.TemperatureSensor)! .setCharacteristic(Characteristic.CurrentTemperature, FAKE_SENSOR.currentTemperature); - + }, 3000); diff --git a/accessories/Thermostat_accessory.js b/src/accessories/Thermostat_accessory.ts similarity index 67% rename from accessories/Thermostat_accessory.js rename to src/accessories/Thermostat_accessory.ts index ccbed9fc3..eee09e53d 100644 --- a/accessories/Thermostat_accessory.js +++ b/src/accessories/Thermostat_accessory.ts @@ -1,17 +1,19 @@ // HomeKit types required -var types = require("./types.js") -var exports = module.exports = {}; +import * as types from "./types"; +import { CharacteristicValue } from '..'; -var execute = function(accessory,characteristic,value){ console.log("executed accessory: " + accessory + ", and characteristic: " + characteristic + ", with value: " + value + "."); } +const execute = (accessory: string, characteristic: string, value: CharacteristicValue) => { + console.log("executed accessory: " + accessory + ", and characteristic: " + characteristic + ", with value: " + value + "."); +} -exports.accessory = { +export const accessory = { displayName: "Thermostat 1", username: "CA:3E:BC:4D:5E:FF", pincode: "031-45-154", services: [{ - sType: types.ACCESSORY_INFORMATION_STYPE, + sType: types.ACCESSORY_INFORMATION_STYPE, characteristics: [{ - cType: types.NAME_CTYPE, + cType: types.NAME_CTYPE, onUpdate: null, perms: ["pr"], format: "string", @@ -19,9 +21,9 @@ exports.accessory = { supportEvents: false, supportBonjour: false, manfDescription: "Bla", - designedMaxLength: 255 + designedMaxLength: 255 },{ - cType: types.MANUFACTURER_CTYPE, + cType: types.MANUFACTURER_CTYPE, onUpdate: null, perms: ["pr"], format: "string", @@ -29,7 +31,7 @@ exports.accessory = { supportEvents: false, supportBonjour: false, manfDescription: "Bla", - designedMaxLength: 255 + designedMaxLength: 255 },{ cType: types.MODEL_CTYPE, onUpdate: null, @@ -39,9 +41,9 @@ exports.accessory = { supportEvents: false, supportBonjour: false, manfDescription: "Bla", - designedMaxLength: 255 + designedMaxLength: 255 },{ - cType: types.SERIAL_NUMBER_CTYPE, + cType: types.SERIAL_NUMBER_CTYPE, onUpdate: null, perms: ["pr"], format: "string", @@ -49,9 +51,9 @@ exports.accessory = { supportEvents: false, supportBonjour: false, manfDescription: "Bla", - designedMaxLength: 255 + designedMaxLength: 255 },{ - cType: types.IDENTIFY_CTYPE, + cType: types.IDENTIFY_CTYPE, onUpdate: null, perms: ["pw"], format: "bool", @@ -59,10 +61,10 @@ exports.accessory = { supportEvents: false, supportBonjour: false, manfDescription: "Identify Accessory", - designedMaxLength: 1 + designedMaxLength: 1 }] },{ - sType: types.THERMOSTAT_STYPE, + sType: types.THERMOSTAT_STYPE, characteristics: [{ cType: types.NAME_CTYPE, onUpdate: null, @@ -72,10 +74,10 @@ exports.accessory = { supportEvents: false, supportBonjour: false, manfDescription: "Bla", - designedMaxLength: 255 + designedMaxLength: 255 },{ cType: types.CURRENTHEATINGCOOLING_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute("Thermostat", "Current HC", value); }, + onUpdate: (value: CharacteristicValue) => { console.log("Change:",value); execute("Thermostat", "Current HC", value); }, perms: ["pr","ev"], format: "int", initialValue: 0, @@ -85,10 +87,10 @@ exports.accessory = { designedMaxLength: 1, designedMinValue: 0, designedMaxValue: 2, - designedMinStep: 1, + designedMinStep: 1, },{ cType: types.TARGETHEATINGCOOLING_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute("Thermostat", "Target HC", value); }, + onUpdate: (value: CharacteristicValue) => { console.log("Change:",value); execute("Thermostat", "Target HC", value); }, perms: ["pw","pr","ev"], format: "int", initialValue: 0, @@ -100,7 +102,7 @@ exports.accessory = { designedMinStep: 1, },{ cType: types.CURRENT_TEMPERATURE_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute("Thermostat", "Current Temperature", value); }, + onUpdate: (value: CharacteristicValue) => { console.log("Change:",value); execute("Thermostat", "Current Temperature", value); }, perms: ["pr","ev"], format: "int", initialValue: 20, @@ -110,7 +112,7 @@ exports.accessory = { unit: "celsius" },{ cType: types.TARGET_TEMPERATURE_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute("Thermostat", "Target Temperature", value); }, + onUpdate: (value: CharacteristicValue) => { console.log("Change:",value); execute("Thermostat", "Target Temperature", value); }, perms: ["pw","pr","ev"], format: "int", initialValue: 20, @@ -123,7 +125,7 @@ exports.accessory = { unit: "celsius" },{ cType: types.TEMPERATURE_UNITS_CTYPE, - onUpdate: function(value) { console.log("Change:",value); execute("Thermostat", "Unit", value); }, + onUpdate: (value: CharacteristicValue) => { console.log("Change:",value); execute("Thermostat", "Unit", value); }, perms: ["pr","ev"], format: "int", initialValue: 0, @@ -132,4 +134,4 @@ exports.accessory = { manfDescription: "Unit" }] }] -} \ No newline at end of file +} diff --git a/src/accessories/types.ts b/src/accessories/types.ts new file mode 100644 index 000000000..52bae186a --- /dev/null +++ b/src/accessories/types.ts @@ -0,0 +1,90 @@ +//HomeKit Types UUID's + +const stPre = "000000"; +const stPost = "-0000-1000-8000-0026BB765291"; + + +//HomeKitTransportCategoryTypes +export const OTHER_TCTYPE = 1; +export const FAN_TCTYPE = 3; +export const GARAGE_DOOR_OPENER_TCTYPE = 4; +export const LIGHTBULB_TCTYPE = 5; +export const DOOR_LOCK_TCTYPE = 6; +export const OUTLET_TCTYPE = 7; +export const SWITCH_TCTYPE = 8; +export const THERMOSTAT_TCTYPE = 9; +export const SENSOR_TCTYPE = 10; +export const ALARM_SYSTEM_TCTYPE = 11; +export const DOOR_TCTYPE = 12; +export const WINDOW_TCTYPE = 13; +export const WINDOW_COVERING_TCTYPE = 14; +export const PROGRAMMABLE_SWITCH_TCTYPE = 15; + +//HomeKitServiceTypes + +export const LIGHTBULB_STYPE = stPre + "43" + stPost; +export const SWITCH_STYPE = stPre + "49" + stPost; +export const THERMOSTAT_STYPE = stPre + "4A" + stPost; +export const GARAGE_DOOR_OPENER_STYPE = stPre + "41" + stPost; +export const ACCESSORY_INFORMATION_STYPE = stPre + "3E" + stPost; +export const FAN_STYPE = stPre + "40" + stPost; +export const OUTLET_STYPE = stPre + "47" + stPost; +export const LOCK_MECHANISM_STYPE = stPre + "45" + stPost; +export const LOCK_MANAGEMENT_STYPE = stPre + "44" + stPost; +export const ALARM_STYPE = stPre + "7E" + stPost; +export const WINDOW_COVERING_STYPE = stPre + "8C" + stPost; +export const OCCUPANCY_SENSOR_STYPE = stPre + "86" + stPost; +export const CONTACT_SENSOR_STYPE = stPre + "80" + stPost; +export const MOTION_SENSOR_STYPE = stPre + "85" + stPost; +export const HUMIDITY_SENSOR_STYPE = stPre + "82" + stPost; +export const TEMPERATURE_SENSOR_STYPE = stPre + "8A" + stPost; + +//HomeKitCharacteristicsTypes + + +export const ALARM_CURRENT_STATE_CTYPE = stPre + "66" + stPost; +export const ALARM_TARGET_STATE_CTYPE = stPre + "67" + stPost; +export const ADMIN_ONLY_ACCESS_CTYPE = stPre + "01" + stPost; +export const AUDIO_FEEDBACK_CTYPE = stPre + "05" + stPost; +export const BRIGHTNESS_CTYPE = stPre + "08" + stPost; +export const BATTERY_LEVEL_CTYPE = stPre + "68" + stPost; +export const COOLING_THRESHOLD_CTYPE = stPre + "0D" + stPost; +export const CONTACT_SENSOR_STATE_CTYPE = stPre + "6A" + stPost; +export const CURRENT_DOOR_STATE_CTYPE = stPre + "0E" + stPost; +export const CURRENT_LOCK_MECHANISM_STATE_CTYPE = stPre + "1D" + stPost; +export const CURRENT_RELATIVE_HUMIDITY_CTYPE = stPre + "10" + stPost; +export const CURRENT_TEMPERATURE_CTYPE = stPre + "11" + stPost; +export const HEATING_THRESHOLD_CTYPE = stPre + "12" + stPost; +export const HUE_CTYPE = stPre + "13" + stPost; +export const IDENTIFY_CTYPE = stPre + "14" + stPost; +export const LOCK_MANAGEMENT_AUTO_SECURE_TIMEOUT_CTYPE = stPre + "1A" + stPost; +export const LOCK_MANAGEMENT_CONTROL_POINT_CTYPE = stPre + "19" + stPost; +export const LOCK_MECHANISM_LAST_KNOWN_ACTION_CTYPE = stPre + "1C" + stPost; +export const LOGS_CTYPE = stPre + "1F" + stPost; +export const MANUFACTURER_CTYPE = stPre + "20" + stPost; +export const MODEL_CTYPE = stPre + "21" + stPost; +export const MOTION_DETECTED_CTYPE = stPre + "22" + stPost; +export const NAME_CTYPE = stPre + "23" + stPost; +export const OBSTRUCTION_DETECTED_CTYPE = stPre + "24" + stPost; +export const OUTLET_IN_USE_CTYPE = stPre + "26" + stPost; +export const OCCUPANCY_DETECTED_CTYPE = stPre + "71" + stPost; +export const POWER_STATE_CTYPE = stPre + "25" + stPost; +export const PROGRAMMABLE_SWITCH_SWITCH_EVENT_CTYPE = stPre + "73" + stPost; +export const PROGRAMMABLE_SWITCH_OUTPUT_STATE_CTYPE = stPre + "74" + stPost; +export const ROTATION_DIRECTION_CTYPE = stPre + "28" + stPost; +export const ROTATION_SPEED_CTYPE = stPre + "29" + stPost; +export const SATURATION_CTYPE = stPre + "2F" + stPost; +export const SERIAL_NUMBER_CTYPE = stPre + "30" + stPost; +export const STATUS_LOW_BATTERY_CTYPE = stPre + "79" + stPost; +export const STATUS_FAULT_CTYPE = stPre + "77" + stPost; +export const TARGET_DOORSTATE_CTYPE = stPre + "32" + stPost; +export const TARGET_LOCK_MECHANISM_STATE_CTYPE = stPre + "1E" + stPost; +export const TARGET_RELATIVE_HUMIDITY_CTYPE = stPre + "34" + stPost; +export const TARGET_TEMPERATURE_CTYPE = stPre + "35" + stPost; +export const TEMPERATURE_UNITS_CTYPE = stPre + "36" + stPost; +export const VERSION_CTYPE = stPre + "37" + stPost; +export const WINDOW_COVERING_TARGET_POSITION_CTYPE = stPre + "7C" + stPost; +export const WINDOW_COVERING_CURRENT_POSITION_CTYPE = stPre + "6D" + stPost; +export const WINDOW_COVERING_OPERATION_STATE_CTYPE = stPre + "72" + stPost; +export const CURRENTHEATINGCOOLING_CTYPE = stPre + "0F" + stPost; +export const TARGETHEATINGCOOLING_CTYPE = stPre + "33" + stPost; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 000000000..24d9a14a9 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,35 @@ +import storage from 'node-persist'; + +import './lib/gen'; +import * as accessoryLoader from './lib/AccessoryLoader'; +import * as uuidFunctions from './lib/util/uuid'; + +export const AccessoryLoader = accessoryLoader; +export const uuid = uuidFunctions; + +export * from './lib/Accessory.js'; +export * from './lib/Bridge.js'; +export * from './lib/Camera.js'; +export * from './lib/Service.js'; +export * from './lib/Characteristic.js'; +export * from './lib/AccessoryLoader.js'; +export * from './lib/StreamController.js'; +export * from './lib/HAPServer'; +export * from './lib/gen'; + +export * from './lib/util/chacha20poly1305'; +export * from './lib/util/clone'; +export * from './lib/util/encryption'; +export * from './lib/util/hkdf'; +export * from './lib/util/once'; +export * from './lib/util/tlv'; + +export * from './types'; + +export function init(storagePath: string) { + // initialize our underlying storage system, passing on the directory if needed + if (typeof storagePath !== 'undefined') + storage.initSync({ dir: storagePath }); + else + storage.initSync(); // use whatever is default +} diff --git a/src/lib/Accessory.spec.ts b/src/lib/Accessory.spec.ts new file mode 100644 index 000000000..be0bee4fd --- /dev/null +++ b/src/lib/Accessory.spec.ts @@ -0,0 +1,41 @@ +import { Accessory } from './Accessory'; +import { Service } from './Service'; +import { Characteristic, CharacteristicEventTypes } from './Characteristic'; +import { generate } from './util/uuid'; +import './gen'; + +describe('Accessory', () => { + + describe('#constructor()', () => { + + it('should identify itself with a valid UUID', () => { + const accessory = new Accessory('Test', generate('Foo')); + + const VALUE = true; + + accessory.getService(Service.AccessoryInformation)! + .getCharacteristic(Characteristic.Identify)! + .on(CharacteristicEventTypes.SET, (value: any, callback: any) => { + expect(value).toEqual(VALUE); + }); + }); + + it('should fail to load with no display name', () => { + expect(() => { + new Accessory('', ''); + }).toThrow('non-empty displayName'); + }); + + it('should fail to load with no UUID', () => { + expect(() => { + new Accessory('Test', ''); + }).toThrow('valid UUID'); + }); + + it('should fail to load with an invalid UUID', () => { + expect(() => { + new Accessory('Test', 'test'); + }).toThrow('not a valid UUID'); + }); + }); +}); diff --git a/src/lib/Accessory.ts b/src/lib/Accessory.ts new file mode 100644 index 000000000..bc524ba26 --- /dev/null +++ b/src/lib/Accessory.ts @@ -0,0 +1,1129 @@ +import bufferShim from 'buffer-shims'; +import crypto from 'crypto'; +import createDebug from 'debug'; + +import * as uuid from './util/uuid'; +import { clone } from './util/clone'; +import { Service, ServiceConfigurationChange, ServiceEventTypes } from './Service'; +import { + Characteristic, + CharacteristicEventTypes, + CharacteristicSetCallback +} from './Characteristic'; +import { Advertiser } from './Advertiser'; +import { HAPServer, HAPServerEventTypes, Status } from './HAPServer'; +import { AccessoryInfo } from './model/AccessoryInfo'; +import { IdentifierCache } from './model/IdentifierCache'; +import { + CharacteristicChange, + CharacteristicData, CharacteristicValue, + NodeCallback, + Nullable, + ToHAPOptions, + VoidCallback, + WithUUID, +} from '../types'; +import { Camera } from './Camera'; +import { EventEmitter } from './EventEmitter'; + +// var HomeKitTypes = require('./gen/HomeKitTypes'); +// var RelayServer = require("./util/relayserver").RelayServer; + +const debug = createDebug('Accessory'); +const MAX_ACCESSORIES = 149; // Maximum number of bridged accessories per bridge. + +// Known category values. Category is a hint to iOS clients about what "type" of Accessory this represents, for UI only. +export enum Categories { + OTHER = 1, + BRIDGE = 2, + FAN = 3, + GARAGE_DOOR_OPENER = 4, + LIGHTBULB = 5, + DOOR_LOCK = 6, + OUTLET = 7, + SWITCH = 8, + THERMOSTAT = 9, + SENSOR = 10, + ALARM_SYSTEM = 11, + SECURITY_SYSTEM = 11, //Added to conform to HAP naming + DOOR = 12, + WINDOW = 13, + WINDOW_COVERING = 14, + PROGRAMMABLE_SWITCH = 15, + RANGE_EXTENDER = 16, + CAMERA = 17, + IP_CAMERA = 17, //Added to conform to HAP naming + VIDEO_DOORBELL = 18, + AIR_PURIFIER = 19, + AIR_HEATER = 20, //Not in HAP Spec + AIR_CONDITIONER = 21, //Not in HAP Spec + AIR_HUMIDIFIER = 22, //Not in HAP Spec + AIR_DEHUMIDIFIER = 23, // Not in HAP Spec + APPLE_TV = 24, + SPEAKER = 26, + AIRPORT = 27, + SPRINKLER = 28, + FAUCET = 29, + SHOWER_HEAD = 30, + TELEVISION = 31, + TARGET_CONTROLLER = 32 // Remote Control +} + +export enum AccessoryEventTypes { + IDENTIFY = "identify", + LISTENING = "listening", + SERVICE_CONFIGURATION_CHANGE = "service-configurationChange", + SERVICE_CHARACTERISTIC_CHANGE = "service-characteristic-change", +} + +type Events = { + identify: (paired:boolean, cb: VoidCallback) => void; + listening: (port: number) => void; + "service-configurationChange": VoidCallback; + "service-characteristic-change": (change: ServiceCharacteristicChange) => void; +} + +/** + * @deprecated Use AccessoryEventTypes instead + */ +export type EventAccessory = "identify" | "listening" | "service-configurationChange" | "service-characteristic-change"; + +export type CharacteristicEvents = Record; + +export interface PublishInfo { + username: string; + pincode: string; + category?: Categories; + setupID?: string; + port?: number; + mdns?: any; +} + +export type ServiceCharacteristicChange = CharacteristicChange & { + accessory: Accessory; + service: Service; +}; + +export enum ResourceTypes { + IMAGE = 'image', +} + +export type Resource = { + 'image-height': number; + 'image-width': number; + 'resource-type': ResourceTypes; +} + +type IdentifyCallback = VoidCallback; +type PairCallback = VoidCallback; +type UnpairCallback = VoidCallback; +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 + * with - or it can run behind another "Bridge" Accessory server. + * + * Bridged Accessories in this implementation must have a UUID that is unique among all other Accessories that + * are hosted by the Bridge. This UUID must be "stable" and unchanging, even when the server is restarted. This + * is required so that the Bridge can provide consistent "Accessory IDs" (aid) and "Instance IDs" (iid) for all + * Accessories, Services, and Characteristics for iOS clients to reference later. + * + * @event 'identify' => function(paired, callback(err)) { } + * Emitted when an iOS device wishes for this Accessory to identify itself. If `paired` is false, then + * this device is currently browsing for Accessories in the system-provided "Add Accessory" screen. If + * `paired` is true, then this is a device that has already paired with us. Note that if `paired` is true, + * listening for this event is a shortcut for the underlying mechanism of setting the `Identify` Characteristic: + * `getService(Service.AccessoryInformation).getCharacteristic(Characteristic.Identify).on('set', ...)` + * You must call the callback for identification to be successful. + * + * @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 { + + static Categories = Categories; + + aid: Nullable = null; // assigned by us in assignIDs() or by a Bridge + _isBridge: boolean = false; // true if we are a Bridge (creating a new instance of the Bridge subclass sets this to true) + bridged: boolean = false; // true if we are hosted "behind" a Bridge Accessory + bridgedAccessories: Accessory[] = []; // If we are a Bridge, these are the Accessories we are bridging + reachable: boolean = true; + cameraSource: Nullable = null; + category: Categories = Categories.OTHER; + services: Service[] = []; + shouldPurgeUnusedIDs: boolean = true; // Purge unused ids by default + + _accessoryInfo?: Nullable; + _setupID: Nullable = null; + _identifierCache?: Nullable; + _advertiser?: Advertiser; + _server?: HAPServer; + _setupURI?: string; + relayServer: any; + + constructor(public displayName: string, public UUID: string) { + super(); + if (!displayName) throw new Error("Accessories must be created with a non-empty displayName."); + if (!UUID) throw new Error("Accessories must be created with a valid UUID."); + if (!uuid.isValid(UUID)) throw new Error("UUID '" + UUID + "' is not a valid UUID. Try using the provided 'generateUUID' function to create a valid UUID from any arbitrary string, like a serial number."); + + // create our initial "Accessory Information" Service that all Accessories are expected to have + this + .addService(Service.AccessoryInformation) + .setCharacteristic(Characteristic.Name, displayName) + .setCharacteristic(Characteristic.Manufacturer, "Default-Manufacturer") + .setCharacteristic(Characteristic.Model, "Default-Model") + .setCharacteristic(Characteristic.SerialNumber, "Default-SerialNumber") + .setCharacteristic(Characteristic.FirmwareRevision, "1.0"); + + // sign up for when iOS attempts to "set" the Identify characteristic - this means a paired device wishes + // for us to identify ourselves (as opposed to an unpaired device - that case is handled by HAPServer 'identify' event) + this.getService(Service.AccessoryInformation)! + .getCharacteristic(Characteristic.Identify)! + .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { + if (value) { + const paired = true; + this._identificationRequest(paired, callback); + } + }); + } + + _identificationRequest = (paired: boolean, callback: CharacteristicSetCallback) => { + debug("[%s] Identification request", this.displayName); + + if (this.listeners(AccessoryEventTypes.IDENTIFY).length > 0) { + // allow implementors to identify this Accessory in whatever way is appropriate, and pass along + // the standard callback for completion. + this.emit(AccessoryEventTypes.IDENTIFY, paired, callback); + } else { + debug("[%s] Identification request ignored; no listeners to 'identify' event", this.displayName); + callback(); + } + } + + addService = (service: Service | typeof Service, ...constructorArgs: any[]) => { + // service might be a constructor like `Service.AccessoryInformation` instead of an instance + // of Service. Coerce if necessary. + if (typeof service === 'function') + service = new service(constructorArgs[0], constructorArgs[1], constructorArgs[2]) as Service; + // service = new (Function.prototype.bind.apply(service, arguments)); + + // check for UUID+subtype conflict + for (var index in this.services) { + var 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) + throw new Error("Cannot add a Service with the same UUID '" + existing.UUID + "' as another Service in this Accessory without also defining a unique 'subtype' property."); + + if (service.subtype.toString() === existing.subtype.toString()) + throw new Error("Cannot add a Service with the same UUID '" + existing.UUID + "' and subtype '" + existing.subtype + "' as another Service in this Accessory."); + } + } + + this.services.push(service); + + if (!this.bridged) { + this._updateConfiguration(); + } else { + this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({accessory:this, service:service})); + } + + service.on(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, (change: ServiceConfigurationChange) => { + if (!this.bridged) { + this._updateConfiguration(); + } else { + this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({accessory:this, service:service})); + } + }); + + // listen for changes in characteristics and bubble them up + service.on(ServiceEventTypes.CHARACTERISTIC_CHANGE, (change: CharacteristicChange) => { + this.emit(AccessoryEventTypes.SERVICE_CHARACTERISTIC_CHANGE, clone(change, {service:service as Service})); + + // if we're not bridged, when we'll want to process this event through our HAPServer + if (!this.bridged) + this._handleCharacteristicChange(clone(change, {accessory:this, service:service as Service})); + + }); + + return service; + } + + setPrimaryService = (service: Service) => { + //find this service in the services list + let targetServiceIndex, existingService: Service; + for (let index in this.services) { + existingService = this.services[index]; + + if (existingService === service) { + targetServiceIndex = index; + break; + } + } + + if (targetServiceIndex) { + //If the service is found, set isPrimaryService to false for everything. + for (let index in this.services) + this.services[index].isPrimaryService = false; + + //Make this service the primary + existingService!.isPrimaryService = true + + if (!this.bridged) { + this._updateConfiguration(); + } else { + this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({ accessory: this, service: service })); + } + } + } + + removeService = (service: Service) => { + let targetServiceIndex: number | undefined = undefined; + + for (let index in this.services) { + var existingService = this.services[index]; + + if (existingService === service) { + targetServiceIndex = Number.parseInt(index); + break; + } + } + + if (targetServiceIndex) { + this.services.splice(targetServiceIndex, 1); + + if (!this.bridged) { + this._updateConfiguration(); + } else { + this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({accessory:this, service:service})); + } + + service.removeAllListeners(); + } + } + + getService = >(name: string | T) => { + for (var index in this.services) { + var service = this.services[index]; + + 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))) + return service; + } + } + + updateReachability = (reachable: boolean) => { + if (!this.bridged) + throw new Error("Cannot update reachability on non-bridged accessory!"); + this.reachable = reachable; + + debug('Reachability update is no longer being supported.'); + } + + addBridgedAccessory = (accessory: Accessory, deferUpdate: boolean = false) => { + if (accessory._isBridge) + throw new Error("Cannot Bridge another Bridge!"); + + // check for UUID conflict + for (var index in this.bridgedAccessories) { + var existing = this.bridgedAccessories[index]; + if (existing.UUID === accessory.UUID) + throw new Error("Cannot add a bridged Accessory with the same UUID as another bridged Accessory: " + existing.UUID); + } + + // A bridge too far... + if (this.bridgedAccessories.length >= MAX_ACCESSORIES) { + throw new Error("Cannot Bridge more than " + MAX_ACCESSORIES + " Accessories"); + } + + // listen for changes in ANY characteristics of ANY services on this Accessory + accessory.on(AccessoryEventTypes.SERVICE_CHARACTERISTIC_CHANGE, (change: ServiceCharacteristicChange) => { + this._handleCharacteristicChange(clone(change, {accessory:accessory})); + }); + + accessory.on(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, () => { + this._updateConfiguration(); + }); + + accessory.bridged = true; + + this.bridgedAccessories.push(accessory); + + if(!deferUpdate) { + this._updateConfiguration(); + } + + return accessory; + } + + addBridgedAccessories = (accessories: Accessory[]) => { + for (var index in accessories) { + var accessory = accessories[index]; + this.addBridgedAccessory(accessory, true); + } + + this._updateConfiguration(); + } + + removeBridgedAccessory = (accessory: Accessory, deferUpdate: boolean) => { + if (accessory._isBridge) + throw new Error("Cannot Bridge another Bridge!"); + + var foundMatchAccessory = false; + // check for UUID conflict + for (var index in this.bridgedAccessories) { + var existing = this.bridgedAccessories[index]; + if (existing.UUID === accessory.UUID) { + foundMatchAccessory = true; + this.bridgedAccessories.splice(Number.parseInt(index), 1); + break; + } + } + + if (!foundMatchAccessory) + throw new Error("Cannot find the bridged Accessory to remove."); + + accessory.removeAllListeners(); + + if(!deferUpdate) { + this._updateConfiguration(); + } + } + + removeBridgedAccessories = (accessories: Accessory[]) => { + for (var index in accessories) { + var accessory = accessories[index]; + this.removeBridgedAccessory(accessory, true); + } + + this._updateConfiguration(); + } + + removeAllBridgedAccessories = () => { + for (var 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); + if (characteristic) return characteristic; + } + } + + getBridgedAccessoryByAID = (aid: number) => { + for (var index in this.bridgedAccessories) { + var accessory = this.bridgedAccessories[index]; + if (accessory.aid === aid) return accessory; + } + } + + findCharacteristic = (aid: number, iid: number) => { + + // if aid === 1, the accessory is us (because we are the server), otherwise find it among our bridged + // accessories (if any) + var accessory = (aid === 1) ? this : this.getBridgedAccessoryByAID(aid); + + return accessory && accessory.getCharacteristicByIID(iid); + } + + configureCameraSource = (cameraSource: Camera) => { + this.cameraSource = cameraSource; + for (var index in cameraSource.services) { + var service = cameraSource.services[index]; + this.addService(service); + } + } + + setupURI = () => { + if (this._setupURI) { + return this._setupURI; + } + + var buffer = bufferShim.alloc(8); + var setupCode = this._accessoryInfo && parseInt(this._accessoryInfo.pincode.replace(/-/g, ''), 10); + + var value_low = setupCode!; + var value_high = this._accessoryInfo && this._accessoryInfo.category >> 1; + + value_low |= 1 << 28; // Supports IP; + + buffer.writeUInt32BE(value_low, 4); + + if (this._accessoryInfo && this._accessoryInfo.category & 1) { + buffer[4] = buffer[4] | 1 << 7; + } + + buffer.writeUInt32BE(value_high!, 0); + + var 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++) { + encodedPayload = "0" + encodedPayload; + } + } + + this._setupURI = "X-HM://" + encodedPayload + this._setupID; + return this._setupURI; + } + + /** + * 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) => { + + // if we are responsible for our own identifierCache, start the expiration process + // also check weather we want to have an expiration process + if (this._identifierCache && this.shouldPurgeUnusedIDs) { + this._identifierCache.startTrackingUsage(); + } + + if (this.bridged) { + // This Accessory is bridged, so it must have an aid > 1. Use the provided identifierCache to + // fetch or assign one based on our UUID. + this.aid = identifierCache.getAID(this.UUID) + } else { + // Since this Accessory is the server (as opposed to any Accessories that may be bridged behind us), + // we must have aid = 1 + this.aid = 1; + } + + for (var index in this.services) { + var service = this.services[index]; + if (this._isBridge) { + service._assignIDs(identifierCache, this.UUID, 2000000000); + } else { + service._assignIDs(identifierCache, this.UUID); + } + } + + // now assign IDs for any Accessories we are bridging + for (var index in this.bridgedAccessories) { + var accessory = this.bridgedAccessories[index]; + + accessory._assignIDs(identifierCache); + } + + // expire any now-unused cache keys (for Accessories, Services, or Characteristics + // that have been removed since the last call to assignIDs()) + if (this._identifierCache) { + //Check weather we want to purge the unused ids + if (this.shouldPurgeUnusedIDs) + this._identifierCache.stopTrackingUsageAndExpireUnused(); + //Save in case we have new ones + this._identifierCache.save(); + } + } + + disableUnusedIDPurge = () => { + this.shouldPurgeUnusedIDs = false; + } + + enableUnusedIDPurge = () => { + this.shouldPurgeUnusedIDs = true; + } + + /** + * Manually purge the unused ids if you like, comes handy + * 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; + this.shouldPurgeUnusedIDs = true; + + //Reassign all ids + this._assignIDs(this._identifierCache!); + + //Revert back the purge mechanisam state + this.shouldPurgeUnusedIDs = oldValue; + } + + /** + * Returns a JSON representation of this Accessory suitable for delivering to HAP clients. + */ + toHAP = (opt?: ToHAPOptions) => { + + var servicesHAP = []; + + for (var index in this.services) { + var service = this.services[index]; + servicesHAP.push(service.toHAP(opt)); + } + + var 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); + + // bridgedAccessoryHAP is an array of accessories with one item - extract it + // and add it to our own array + accessoriesHAP.push(bridgedAccessoryHAP[0]) + } + + return accessoriesHAP; + } + + /** + * Publishes this Accessory on the local network for iOS clients to communicate with. + * + * @param {Object} info - Required info for publishing. + * @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 + * as a string like "031-45-154". + * @param {string} info.category - One of the values of the Accessory.Category enum, like Accessory.Category.SWITCH. + * This is a hint to iOS clients about what "type" of Accessory this represents, so + * that for instance an appropriate icon can be drawn for the user while adding a + * new Accessory. + */ + publish = (info: PublishInfo, allowInsecureRequest?: boolean) => { + // attempt to load existing AccessoryInfo from disk + this._accessoryInfo = AccessoryInfo.load(info.username); + + // if we don't have one, create a new one. + if (!this._accessoryInfo) { + debug("[%s] Creating new AccessoryInfo for our HAP server", this.displayName); + this._accessoryInfo = AccessoryInfo.create(info.username); + } + + if (info.setupID) { + this._setupID = info.setupID; + } else if (this._accessoryInfo.setupID === undefined || this._accessoryInfo.setupID === "") { + this._setupID = this._generateSetupID(); + } else { + this._setupID = this._accessoryInfo.setupID; + } + + this._accessoryInfo.setupID = this._setupID; + + // make sure we have up-to-date values in AccessoryInfo, then save it in case they changed (or if we just created it) + this._accessoryInfo.displayName = this.displayName; + this._accessoryInfo.category = info.category || Categories.OTHER; + this._accessoryInfo.pincode = info.pincode; + this._accessoryInfo.save(); + + // if (this._isBridge) { + // this.relayServer = new RelayServer(this._accessoryInfo); + // this.addService(this.relayServer.relayService()); + // } + + // create our IdentifierCache so we can provide clients with stable aid/iid's + this._identifierCache = IdentifierCache.load(info.username); + + // if we don't have one, create a new one. + if (!this._identifierCache) { + debug("[%s] Creating new IdentifierCache", this.displayName); + this._identifierCache = new IdentifierCache(info.username); + } + + //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 + //accessories. + if (this._isBridge && this.bridgedAccessories.length == 0) + this.disableUnusedIDPurge(); + + // assign aid/iid + this._assignIDs(this._identifierCache); + + // get our accessory information in HAP format and determine if our configuration (that is, our + // Accessories/Services/Characteristics) has changed since the last time we were published. make + // sure to omit actual values since these are not part of the "configuration". + var config = this.toHAP({omitValues:true}); + + // now convert it into a hash code and check it against the last one we made, if we have one + var shasum = crypto.createHash('sha1'); + shasum.update(JSON.stringify(config)); + var configHash = shasum.digest('hex'); + + if (configHash !== this._accessoryInfo.configHash) { + + // our configuration has changed! we'll need to bump our config version number + this._accessoryInfo.configVersion++; + this._accessoryInfo.configHash = configHash; + this._accessoryInfo.save(); + } + + // create our Advertiser which broadcasts our presence over mdns + this._advertiser = new Advertiser(this._accessoryInfo, info.mdns); + + // create our HAP server which handles all communication between iOS devices and us + this._server = new HAPServer(this._accessoryInfo, this.relayServer); + this._server.allowInsecureRequest = !!allowInsecureRequest + this._server.on(HAPServerEventTypes.LISTENING, this._onListening); + this._server.on(HAPServerEventTypes.IDENTIFY, this._handleIdentify); + this._server.on(HAPServerEventTypes.PAIR, this._handlePair); + this._server.on(HAPServerEventTypes.UNPAIR, this._handleUnpair); + this._server.on(HAPServerEventTypes.ACCESSORIES, this._handleAccessories); + this._server.on(HAPServerEventTypes.GET_CHARACTERISTICS, this._handleGetCharacteristics); + this._server.on(HAPServerEventTypes.SET_CHARACTERISTICS, this._handleSetCharacteristics); + this._server.on(HAPServerEventTypes.SESSION_CLOSE, this._handleSessionClose); + + if (this.cameraSource) { + this._server.on(HAPServerEventTypes.REQUEST_RESOURCE, this._handleResource); + } + + const targetPort = info.port || 0; + this._server.listen(targetPort); + } + + /** + * Removes this Accessory from the local network + * Accessory object will no longer vaild after invoking this method + * Trying to invoke publish() on the object will result undefined behavior + */ + destroy = () => { + this.unpublish(); + if (this._accessoryInfo) { + this._accessoryInfo.remove(); + this._accessoryInfo = undefined; + } + if (this._identifierCache) { + this._identifierCache.remove(); + this._identifierCache = undefined; + } + } + + unpublish = () => { + if (this._server) { + this._server.stop(); + this._server = undefined; + } + if (this._advertiser) { + this._advertiser.stopAdvertising(); + this._advertiser = undefined; + } + } + + _updateConfiguration = () => { + if (this._advertiser && this._advertiser.isAdvertising()) { + // get our accessory information in HAP format and determine if our configuration (that is, our + // Accessories/Services/Characteristics) has changed since the last time we were published. make + // sure to omit actual values since these are not part of the "configuration". + var config = this.toHAP({omitValues:true}); + + // now convert it into a hash code and check it against the last one we made, if we have one + var shasum = crypto.createHash('sha1'); + shasum.update(JSON.stringify(config)); + var configHash = shasum.digest('hex'); + + if (this._accessoryInfo && configHash !== this._accessoryInfo.configHash) { + + // our configuration has changed! we'll need to bump our config version number + this._accessoryInfo.configVersion++; + this._accessoryInfo.configHash = configHash; + this._accessoryInfo.save(); + } + + // update our advertisement so HomeKit on iOS can pickup new accessory + this._advertiser.updateAdvertisement(); + } + } + + _onListening = (port: number) => { + // the HAP server is listening, so we can now start advertising our presence. + this._advertiser && this._advertiser.startAdvertising(port); + this.emit(AccessoryEventTypes.LISTENING, port); + } + +// Called when an unpaired client wishes for us to identify ourself + _handleIdentify = (callback: IdentifyCallback) => { + var paired = false; + this._identificationRequest(paired, callback); + } + +// Called when HAPServer has completed the pairing process with a client + _handlePair = (username: string, publicKey: Buffer, callback: PairCallback) => { + + debug("[%s] Paired with client %s", this.displayName, username); + + this._accessoryInfo && this._accessoryInfo.addPairedClient(username, publicKey); + this._accessoryInfo && this._accessoryInfo.save(); + + // update our advertisement so it can pick up on the paired status of AccessoryInfo + this._advertiser && this._advertiser.updateAdvertisement(); + + callback(); + } + +// Called when HAPServer wishes to remove/unpair the pairing information of a client + _handleUnpair = (username: string, callback: UnpairCallback) => { + + debug("[%s] Unpairing with client %s", this.displayName, username); + + // Unpair + this._accessoryInfo && this._accessoryInfo.removePairedClient(username); + this._accessoryInfo && this._accessoryInfo.save(); + + // update our advertisement so it can pick up on the paired status of AccessoryInfo + if (this._advertiser) { + this._advertiser.updateAdvertisement(); + } + + callback(); + } + +// Called when an iOS client wishes to know all about our accessory via JSON payload + _handleAccessories = (callback: HandleAccessoriesCallback) => { + + // 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 + }); + } + +// 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, connectionID: string) => { + + // build up our array of responses to the characteristics requested asynchronously + var characteristics: Characteristic[] = []; + var statusKey = remote ? 's' : 'status'; + var valueKey = remote ? 'v' : 'value'; + + data.forEach((characteristicData) => { + var aid = characteristicData.aid; + var iid = characteristicData.iid; + + var includeEvent = characteristicData.e; + + var characteristic = this.findCharacteristic(characteristicData.aid, characteristicData.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 = { + 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; + } + + // Found the Characteristic! Get the value! + debug('[%s] Getting value for Characteristic "%s"', this.displayName, characteristic.displayName); + + // we want to remember "who" made this request, so that we don't send them an event notification + // about any changes that occurred as a result of the request. For instance, if after querying + // the current value of a characteristic, the value turns out to be different than the previously + // cached Characteristic value, an internal 'change' event will be emitted which will cause us to + // notify all connected clients about that new value. But this client is about to get the new value + // anyway, so we don't want to notify it twice. + var context = events; + + // set the value and wait for success + characteristic.getValue((err, value) => { + + debug('[%s] Got Characteristic "%s" value: %s', this.displayName, characteristic!.displayName, value); + + if (err) { + debug('[%s] Error getting value for Characteristic "%s": %s', this.displayName, characteristic!.displayName, err.message); + var response: any = { + aid: aid, + iid: iid + }; + response[statusKey] = hapStatus(err); + characteristics.push(response); + } else { + var response: any = { + aid: aid, + iid: iid + }; + response[valueKey] = value; + response[statusKey] = 0; + + if (includeEvent) { + var eventName = aid + '.' + iid; + response['e'] = (events[eventName] === true); + } + + // compose the response and add it to the list + characteristics.push(response); + } + + // have we collected all responses yet? + if (characteristics.length === data.length) + callback(null, characteristics); + + }, context, connectionID); + + }); + } + +// 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 = (data: CharacteristicData[], events: CharacteristicEvents, callback: HandleSetCharacteristicsCallback, remote: boolean, connectionID: string) => { + + // 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)); + + // build up our array of responses to the characteristics requested asynchronously + var characteristics: Characteristic[] = []; + + 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; + + var statusKey = remote ? 's' : 'status'; + + var 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 = { + 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; + } + + // we want to remember "who" initiated this change, so that we don't send them an event notification + // about the change they just made. We do this by leveraging the arbitrary "context" object supported + // by Characteristic and passed on to the corresponding 'change' events bubbled up from Characteristic + // through Service and Accessory. We'll assign it to the events object since it essentially represents + // the connection requesting the change. + var context = events; + + // if "ev" is present, that means we need to register or unregister this client for change events for + // this characteristic. + if (typeof ev !== 'undefined') { + debug('[%s] %s Characteristic "%s" for events', this.displayName, ev ? "Registering" : "Unregistering", characteristic.displayName); + + // store event registrations in the supplied "events" dict which is associated with the connection making + // the request. + var eventName = aid + '.' + iid; + + if (ev === true && events[eventName] != true) { + events[eventName] = true; // value is arbitrary, just needs to be non-falsey + characteristic.subscribe(); + } + + if (ev === false && events[eventName] != undefined) { + characteristic.unsubscribe(); + delete events[eventName]; // unsubscribe by deleting name from dict + } + } + + // Found the characteristic - set the value if there is one + if (typeof value !== 'undefined') { + + debug('[%s] Setting Characteristic "%s" to value %s', this.displayName, characteristic.displayName, value); + + // set the value and wait for success + characteristic.setValue(value, (err) => { + + if (err) { + debug('[%s] Error setting Characteristic "%s" to value %s: ', this.displayName, characteristic!.displayName, value, err.message); + + var response: any = { + aid: aid, + iid: iid + }; + response[statusKey] = hapStatus(err); + characteristics.push(response); + } else { + var response: any = { + aid: aid, + iid: iid + }; + response[statusKey] = 0; + + if (includeValue) + response['value'] = characteristic!.value; + + characteristics.push(response); + } + + // have we collected all responses yet? + if (characteristics.length === data.length) + callback(null, characteristics); + + }, context, connectionID); + + } else { + // no value to set, so we're done (success) + var response: any = { + aid: aid, + iid: iid + }; + response[statusKey] = 0; + characteristics.push(response); + + // have we collected all responses yet? + if (characteristics.length === data.length) + callback(null, characteristics); + } + + }); + } + + _handleResource = (data: Resource, callback: NodeCallback) => { + if (data["resource-type"] == ResourceTypes.IMAGE) { + if (this.cameraSource) { + this.cameraSource.handleSnapshotRequest({ + width: data["image-width"], + height: data["image-height"] + }, callback); + return; + } + } + + callback(new Error('resource not found')); + } + + _handleSessionClose = (sessionID: string, events: CharacteristicEvents) => { + if (this.cameraSource && this.cameraSource.handleCloseConnection) { + this.cameraSource.handleCloseConnection(sessionID); + } + + this._unsubscribeEvents(events); + } + + _unsubscribeEvents = (events: CharacteristicEvents) => { + for (var key in events) { + if (key.indexOf('.') !== -1) { + try { + var id = key.split('.'); + var aid = Number.parseInt(id[0]); + var iid = Number.parseInt(id[1]); + + var characteristic = this.findCharacteristic(aid, iid); + if (characteristic) { + characteristic.unsubscribe(); + } + } catch (e) { + } + } + } + } + +// Called internally above when a change was detected in one of our hosted Characteristics somewhere in our hierarchy. + _handleCharacteristicChange = (change: ServiceCharacteristicChange) => { + if (!this._server) + return; // we're not running a HAPServer, so there's no one to notify about this event + + var data = { + characteristics: [{ + aid: change.accessory.aid, + iid: change.characteristic.iid, + value: change.newValue + }] + }; + + // name for this event that corresponds to what we stored when the client signed up (in handleSetCharacteristics) + var eventName = change.accessory.aid + '.' + change.characteristic.iid; + + // 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; + + // pass it along to notifyClients() so that it can omit the connection where events === excludeEvents. + this._server.notifyClients(eventName, data, excludeEvents); + } + + _setupService = (service: Service) => { + service.on(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, () => { + if (!this.bridged) { + this._updateConfiguration(); + } else { + this.emit(AccessoryEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({accessory:this, service })); + } + }); + + // listen for changes in characteristics and bubble them up + service.on(ServiceEventTypes.CHARACTERISTIC_CHANGE, (change: any) => { + this.emit(AccessoryEventTypes.SERVICE_CHARACTERISTIC_CHANGE, clone(change, {service })); + + // if 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 })); + + }); + } + + _sideloadServices = (targetServices: Service[]) => { + for (var index in targetServices) { + var target = targetServices[index]; + this._setupService(target); + } + + this.services = targetServices.slice(); + + // Fix Identify + this + .getService(Service.AccessoryInformation)! + .getCharacteristic(Characteristic.Identify)! + .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { + if (value) { + var paired = true; + this._identificationRequest(paired, callback); + } + }); + } + + _generateSetupID = () => { + const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + const bytes = crypto.randomBytes(4); + let setupID = ''; + + for (var i = 0; i < 4; i++) { + var index = bytes.readUInt8(i) % 26; + setupID += chars.charAt(index); + } + + return setupID; + } +} + +function hapStatus(err: Error) { + + // Validate that the message is a valid HAPServer.Status + let value: number | string = 0; // default if not found or + + for( const k in Status ) { + if (Status[k] == err.message) + { + value = err.message; + break; + } + } + + if ( value == 0 ) + value = Status.SERVICE_COMMUNICATION_FAILURE; // default if not found or 0 + + return(parseInt(`${value}`)); +} diff --git a/lib/AccessoryLoader.js b/src/lib/AccessoryLoader.ts similarity index 76% rename from lib/AccessoryLoader.js rename to src/lib/AccessoryLoader.ts index 4682130a7..7c72dbf7a 100644 --- a/lib/AccessoryLoader.js +++ b/src/lib/AccessoryLoader.ts @@ -1,31 +1,31 @@ -'use strict'; - -var fs = require('fs'); -var path = require('path'); -var Accessory = require('./Accessory').Accessory; -var Service = require('./Service').Service; -var Characteristic = require('./Characteristic').Characteristic; -var uuid = require('./util/uuid'); -var debug = require('debug')('AccessoryLoader'); - -module.exports = { - loadDirectory: loadDirectory, - parseAccessoryJSON: parseAccessoryJSON, - parseServiceJSON: parseServiceJSON, - parseCharacteristicJSON: parseCharacteristicJSON -}; +import fs from 'fs'; +import path from 'path'; + +import createDebug from 'debug'; + +import { Accessory } from './Accessory'; +import { Service } from './Service'; +import { + Characteristic, + CharacteristicEventTypes, CharacteristicGetCallback, + CharacteristicSetCallback +} from './Characteristic'; +import * as uuid from './util/uuid'; +import { CharacteristicValue, NodeCallback, Nullable } from '../types'; + +const debug = createDebug('AccessoryLoader'); /** * Loads all accessories from the given folder. Handles object-literal-style accessories, "accessory factories", * and new-API style modules. */ -function loadDirectory(dir) { +export function loadDirectory(dir: string): Accessory[] { // exported accessory objects loaded from this dir - var accessories = []; + var accessories: Accessory[] = []; - fs.readdirSync(dir).forEach(function(file) { + fs.readdirSync(dir).forEach((file) => { // "Accessories" are modules that export a single accessory. if (file.split('_').pop() === 'accessory.js') { @@ -45,14 +45,14 @@ function loadDirectory(dir) { // now we need to coerce all accessory objects into instances of Accessory (some or all of them may // be object-literal JSON-style accessories) - return accessories.map(function(accessory) { + return accessories.map((accessory) => { if(accessory === null || accessory === undefined) { //check if accessory is not empty console.log("Invalid accessory!"); return false; } else { return (accessory instanceof Accessory) ? accessory : parseAccessoryJSON(accessory); } - }).filter(function(accessory) { return accessory ? true : false; }); + }).filter((accessory: Accessory | false) => { return accessory ? true : false; }) as Accessory[]; } /** @@ -60,12 +60,12 @@ function loadDirectory(dir) { * newer-style structures of Accessory/Service/Characteristic objects. */ -function parseAccessoryJSON(json) { +export function parseAccessoryJSON(json: any) { // parse services first so we can extract the accessory name - var services = []; + var services: Service[] = []; - json.services.forEach(function(serviceJSON) { + json.services.forEach(function(serviceJSON: any) { var service = parseServiceJSON(serviceJSON); services.push(service); }); @@ -85,7 +85,9 @@ function parseAccessoryJSON(json) { var 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 accessory.username = json.username; + // @ts-ignore accessory.pincode = json.pincode; // clear out the default services @@ -99,18 +101,18 @@ function parseAccessoryJSON(json) { return accessory; } -function parseServiceJSON(json) { +export function parseServiceJSON(json: any) { var serviceUUID = json.sType; // build characteristics first so we can extract the Name (if present) - var characteristics = []; + var characteristics: Characteristic[] = []; - json.characteristics.forEach(function(characteristicJSON) { + json.characteristics.forEach((characteristicJSON: any) => { var characteristic = parseCharacteristicJSON(characteristicJSON); characteristics.push(characteristic); }); - var displayName = null; + var displayName: Nullable = null; // extract the "Name" characteristic to use for 'type' discrimination if necessary characteristics.forEach(function(characteristic) { @@ -119,7 +121,7 @@ function parseServiceJSON(json) { }); // 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); + var 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 @@ -129,7 +131,7 @@ function parseServiceJSON(json) { return service; } -function parseCharacteristicJSON(json) { +export function parseCharacteristicJSON(json: any) { var characteristicUUID = json.cType; var characteristic = new Characteristic(json.manfDescription || characteristicUUID, characteristicUUID); @@ -148,11 +150,13 @@ function parseCharacteristicJSON(json) { // 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; var updateFunc = json.onUpdate; // optional function(value) @@ -160,15 +164,15 @@ function parseCharacteristicJSON(json) { var registerFunc = json.onRegister; // optional function if (updateFunc) { - characteristic.on('set', function(value, callback) { + characteristic.on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { updateFunc(value); - callback(); + callback && callback(); }); } if (readFunc) { - characteristic.on('get', function(callback) { - readFunc(function(value) { + characteristic.on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { + readFunc((value: any) => { callback(null, value); // old onRead callbacks don't use Error as first param }); }); diff --git a/src/lib/Advertiser.spec.ts b/src/lib/Advertiser.spec.ts new file mode 100644 index 000000000..f697fc116 --- /dev/null +++ b/src/lib/Advertiser.spec.ts @@ -0,0 +1,98 @@ +import { Advertiser } from './Advertiser'; +import { AccessoryInfo } from './model/AccessoryInfo'; +import './gen'; + +const createAdvertiser = () => { + return new Advertiser(new AccessoryInfo('displayName'), { + multicast: false, + interface: 'ipv6', + port: 80, + ip: '1.1.1.1', + ttl: 30, + loopback: true, + reuseAddr: false, + }); +}; + +describe('Advertiser', () => { + describe('#constructor()', () => { + it('should create a setup hash', () => { + const advertiser = createAdvertiser(); + + expect(advertiser._setupHash).toBeTruthy(); + }); + }); + + describe('#startAdvertising()', () => { + it('should start advertising if not currently doing so', () => { + const advertiser = createAdvertiser(); + + expect(advertiser._advertisement).toBeNull(); + advertiser.startAdvertising(80); + expect(advertiser._bonjourService.publish).toHaveBeenCalledTimes(1); + expect(advertiser._advertisement).not.toBeNull(); + }); + + it('should stop advertising if already doing so', () => { + const advertiser = createAdvertiser(); + + expect(advertiser._advertisement).toBeNull(); + advertiser.startAdvertising(80); + advertiser.stopAdvertising = jest.fn(); + advertiser.startAdvertising(80); + expect(advertiser.stopAdvertising).toHaveBeenCalledTimes(1); + expect(advertiser.isAdvertising()).toBeTruthy(); + }); + }); + + describe('#isAdvertising()', () => { + it('should be true if currently advertising', () => { + const advertiser = createAdvertiser(); + advertiser.startAdvertising(80); + expect(advertiser.isAdvertising()).toBeTruthy(); + }); + + it('should be false if not currently advertising', () => { + const advertiser = createAdvertiser(); + expect(advertiser.isAdvertising()).toBeFalsy(); + }); + }); + + describe('#updateAdvertisement()', () => { + it('should send an updated TXT record if currently advertising', () => { + const advertiser = createAdvertiser(); + + expect(advertiser.isAdvertising()).toBeFalsy(); + advertiser.startAdvertising(80); + expect(advertiser.isAdvertising()).toBeTruthy(); + + advertiser.updateAdvertisement(); + expect(advertiser._advertisement!.updateTxt).toHaveBeenCalledTimes(1); + }); + }); + + describe('#stopAdvertising()', () => { + it('should stop and destroy all services if currently advertising', () => { + const advertiser = createAdvertiser(); + + expect(advertiser.isAdvertising()).toBeFalsy(); + advertiser.startAdvertising(80); + expect(advertiser.isAdvertising()).toBeTruthy(); + + const advertisement = advertiser._advertisement!; + advertiser.stopAdvertising(); + expect(advertisement.stop).toHaveBeenCalledTimes(1); + expect(advertisement.destroy).toHaveBeenCalledTimes(1); + expect(advertiser.isAdvertising()).toBeFalsy(); + expect(advertiser._bonjourService.destroy).toHaveBeenCalledTimes(1); + }); + + it('should destroy only Bonjour service if not currently advertising', () => { + const advertiser = createAdvertiser(); + + advertiser.stopAdvertising(); + expect(advertiser.isAdvertising()).toBeFalsy(); + expect(advertiser._bonjourService.destroy).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/src/lib/Advertiser.ts b/src/lib/Advertiser.ts new file mode 100644 index 000000000..634c47a00 --- /dev/null +++ b/src/lib/Advertiser.ts @@ -0,0 +1,114 @@ +import crypto from 'crypto'; + +import bonjour, { BonjourHap, MulticastOptions, Service } from 'bonjour-hap'; + +import { Nullable } from '../types'; +import { AccessoryInfo } from './model/AccessoryInfo'; + +/** + * Advertiser uses mdns to broadcast the presence of an Accessory to the local network. + * + * Note that as of iOS 9, an accessory can only pair with a single client. Instead of pairing your + * accessories with multiple iOS devices in your home, Apple intends for you to use Home Sharing. + * To support this requirement, we provide the ability to be "discoverable" or not (via a "service flag" on the + * mdns payload). + */ +export class Advertiser { + + _bonjourService: BonjourHap; + _advertisement: Nullable; + _setupHash: string; + + constructor(public accessoryInfo: AccessoryInfo, mdnsConfig: MulticastOptions) { + this._bonjourService = bonjour(mdnsConfig); + this._advertisement = null; + this._setupHash = this._computeSetupHash(); + } + + startAdvertising = (port: number) => { + + // stop advertising if necessary + if (this._advertisement) { + this.stopAdvertising(); + } + + var txtRecord = { + md: this.accessoryInfo.displayName, + pv: "1.0", + id: this.accessoryInfo.username, + "c#": this.accessoryInfo.configVersion + "", // "accessory conf" - represents the "configuration version" of an Accessory. Increasing this "version number" signals iOS devices to re-fetch /accessories data. + "s#": "1", // "accessory state" + "ff": "0", + "ci": this.accessoryInfo.category as unknown as string, + "sf": this.accessoryInfo.paired() ? "0" : "1", // "sf == 1" means "discoverable by HomeKit iOS clients" + "sh": this._setupHash + }; + + /** + * The host name of the component is probably better to be + * the username of the hosted accessory + '.local'. + * By default 'bonjour' doesnt add '.local' at the end of the os.hostname + * this causes to return 'raspberrypi' on raspberry pi / raspbian + * then when the phone queryies for A/AAAA record it is being queried + * on normal dns, not on mdns. By Adding the username of the accessory + * probably the problem will also fix a possible problem + * of having multiple pi's on same network + */ + var host = this.accessoryInfo.username.replace(/\:/ig, "_") + '.local'; + var advertiseName = this.accessoryInfo.displayName + + "-" + + crypto.createHash('sha512').update(this.accessoryInfo.username, 'utf8').digest('hex').slice(0, 4).toUpperCase(); + + // create/recreate our advertisement + this._advertisement = this._bonjourService.publish({ + name: advertiseName, + type: "hap", + port: port, + txt: txtRecord, + host: host + }); + } + + isAdvertising = () => { + return (this._advertisement != null); + } + + updateAdvertisement = () => { + if (this._advertisement) { + + var txtRecord = { + md: this.accessoryInfo.displayName, + pv: "1.0", + id: this.accessoryInfo.username, + "c#": this.accessoryInfo.configVersion + "", // "accessory conf" - represents the "configuration version" of an Accessory. Increasing this "version number" signals iOS devices to re-fetch /accessories data. + "s#": "1", // "accessory state" + "ff": "0", + "ci": `${this.accessoryInfo.category}`, + "sf": this.accessoryInfo.paired() ? "0" : "1", // "sf == 1" means "discoverable by HomeKit iOS clients" + "sh": this._setupHash + }; + + this._advertisement.updateTxt(txtRecord); + } + } + + stopAdvertising = () => { + if (this._advertisement) { + this._advertisement.stop(); + this._advertisement.destroy(); + this._advertisement = null; + } + + this._bonjourService.destroy(); + } + + _computeSetupHash = () => { + var setupHashMaterial = this.accessoryInfo.setupID + this.accessoryInfo.username; + var hash = crypto.createHash('sha512'); + hash.update(setupHashMaterial); + var setupHash = hash.digest().slice(0, 4).toString('base64'); + + return setupHash; + } +} + diff --git a/lib/Bridge.js b/src/lib/Bridge.ts similarity index 52% rename from lib/Bridge.js rename to src/lib/Bridge.ts index 010eb8588..f7757c678 100644 --- a/lib/Bridge.js +++ b/src/lib/Bridge.ts @@ -1,21 +1,13 @@ -'use strict'; - -var Accessory = require('./Accessory').Accessory; -var inherits = require('util').inherits; - -module.exports = { - Bridge: Bridge -}; +import { Accessory } from './Accessory'; /** * Bridge is a special type of HomeKit Accessory that hosts other Accessories "behind" it. This way you * can simply publish() the Bridge (with a single HAPServer on a single port) and all bridged Accessories * will be hosted automatically, instead of needed to publish() every single Accessory as a separate server. */ - -function Bridge(displayName, serialNumber) { - Accessory.call(this, displayName, serialNumber); - this._isBridge = true; +export class Bridge extends Accessory { + constructor(displayName: string, serialNumber: string) { + super(displayName, serialNumber); + this._isBridge = true; + } } - -inherits(Bridge, Accessory); diff --git a/src/lib/Camera.ts b/src/lib/Camera.ts new file mode 100644 index 000000000..ae2abf617 --- /dev/null +++ b/src/lib/Camera.ts @@ -0,0 +1,244 @@ +import crypto from 'crypto'; +import fs from 'fs'; +import ip from 'ip'; +import { ChildProcessWithoutNullStreams, spawn } from 'child_process'; + +import { Service } from './Service'; +import { + PreparedStreamRequestCallback, + PreparedStreamResponse, + PrepareStreamRequest, StreamController, + StreamControllerOptions, + StreamRequest, + StreamRequestTypes +} from "./StreamController"; +import * as uuid from './util/uuid'; +import { Address, NodeCallback, SessionIdentifier } from '../types'; + +export type SnapshotRequest = { + height: number; + width: number; +} + +export type SessionInfo = { + address: string; + audio_port: number; + audio_srtp: Buffer; + audio_ssrc: number; + video_port: number; + video_srtp: Buffer; + video_ssrc: number; +} + +export class Camera { + services: Service[] = []; + streamControllers: StreamController[] = []; + pendingSessions: Record = {}; + ongoingSessions: Record = {}; + + constructor() { + + const options: StreamControllerOptions = { + proxy: false, // Requires RTP/RTCP MUX Proxy + disable_audio_proxy: false, // If proxy = true, you can opt out audio proxy via this + srtp: true, // Supports SRTP AES_CM_128_HMAC_SHA1_80 encryption + video: { + resolutions: [ + [1920, 1080, 30], // Width, Height, framerate + [320, 240, 15], // Apple Watch requires this configuration + [1280, 960, 30], + [1280, 720, 30], + [1024, 768, 30], + [640, 480, 30], + [640, 360, 30], + [480, 360, 30], + [480, 270, 30], + [320, 240, 30], + [320, 180, 30], + ], + codec: { + profiles: [0, 1, 2], // Enum, please refer StreamController.VideoCodecParamProfileIDTypes + levels: [0, 1, 2] // Enum, please refer StreamController.VideoCodecParamLevelTypes + } + }, + audio: { + comfort_noise: false, + codecs: [ + { + type: "OPUS", // Audio Codec + samplerate: 24 // 8, 16, 24 KHz + }, + { + type: "AAC-eld", + samplerate: 16 + } + ] + } + } + + this.createCameraControlService(); + this._createStreamControllers(2, options); + } + + handleSnapshotRequest = (request: SnapshotRequest, callback: NodeCallback) => { + // Image request: {width: number, height: number} + // Please override this and invoke callback(error, image buffer) when the snapshot is ready + + var snapshot = fs.readFileSync(__dirname + '/res/snapshot.jpg'); + callback(undefined, snapshot); + } + + handleCloseConnection = (connectionID: string) => { + this.streamControllers.forEach(function(controller) { + controller.handleCloseConnection(connectionID); + }); + } + + prepareStream = (request: PrepareStreamRequest, callback: PreparedStreamRequestCallback) => { + // Invoked when iOS device requires stream + + const sessionInfo: Partial = {}; + + const sessionID: SessionIdentifier = request["sessionID"]; + const targetAddress = request["targetAddress"]; + + sessionInfo["address"] = targetAddress; + + var response: Partial = {}; + + let videoInfo = request["video"]; + if (videoInfo) { + let targetPort = videoInfo["port"]; + let srtp_key = videoInfo["srtp_key"]; + let srtp_salt = videoInfo["srtp_salt"]; + + // SSRC is a 32 bit integer that is unique per stream + let ssrcSource = crypto.randomBytes(4); + ssrcSource[0] = 0; + let ssrc = ssrcSource.readInt32BE(0); + + let videoResp = { + port: targetPort, + ssrc: ssrc, + srtp_key: srtp_key, + srtp_salt: srtp_salt + }; + + response["video"] = videoResp; + + sessionInfo["video_port"] = targetPort; + sessionInfo["video_srtp"] = Buffer.concat([srtp_key, srtp_salt]); + sessionInfo["video_ssrc"] = ssrc; + } + + let audioInfo = request["audio"]; + if (audioInfo) { + let targetPort = audioInfo["port"]; + let srtp_key = audioInfo["srtp_key"]; + let srtp_salt = audioInfo["srtp_salt"]; + + // SSRC is a 32 bit integer that is unique per stream + let ssrcSource = crypto.randomBytes(4); + ssrcSource[0] = 0; + let ssrc = ssrcSource.readInt32BE(0); + + let audioResp = { + port: targetPort, + ssrc: ssrc, + srtp_key: srtp_key, + srtp_salt: srtp_salt + }; + + response["audio"] = audioResp; + + sessionInfo["audio_port"] = targetPort; + sessionInfo["audio_srtp"] = Buffer.concat([srtp_key, srtp_salt]); + sessionInfo["audio_ssrc"] = ssrc; + } + + let currentAddress = ip.address(); + var addressResp: Partial
= { + address: currentAddress + }; + + if (ip.isV4Format(currentAddress)) { + addressResp["type"] = "v4"; + } else { + addressResp["type"] = "v6"; + } + + response["address"] = addressResp as Address; + this.pendingSessions[uuid.unparse(sessionID)] = sessionInfo as SessionInfo; + + callback(response as PreparedStreamResponse); + } + + handleStreamRequest = (request: StreamRequest) => { + // Invoked when iOS device asks stream to start/stop/reconfigure + var sessionID = request["sessionID"]; + var requestType = request["type"]; + if (sessionID) { + let sessionIdentifier = uuid.unparse(sessionID); + + if (requestType == StreamRequestTypes.START) { + var sessionInfo = this.pendingSessions[sessionIdentifier]; + if (sessionInfo) { + var width = 1280; + var height = 720; + var fps = 30; + var bitrate = 300; + + let videoInfo = request["video"]; + if (videoInfo) { + width = videoInfo["width"]; + height = videoInfo["height"]; + + let expectedFPS = videoInfo["fps"]; + if (expectedFPS < fps) { + fps = expectedFPS; + } + + bitrate = videoInfo["max_bit_rate"]; + } + + let targetAddress = sessionInfo["address"]; + let targetVideoPort = sessionInfo["video_port"]; + let videoKey = sessionInfo["video_srtp"]; + let videoSsrc = sessionInfo["video_ssrc"]; + + let ffmpegCommand = '-re -f avfoundation -r 29.970000 -i 0:0 -threads 0 -vcodec libx264 -an -pix_fmt yuv420p -r '+ fps +' -f rawvideo -tune zerolatency -vf scale='+ width +':'+ height +' -b:v '+ bitrate +'k -bufsize '+ bitrate +'k -payload_type 99 -ssrc '+ videoSsrc +' -f rtp -srtp_out_suite AES_CM_128_HMAC_SHA1_80 -srtp_out_params '+videoKey.toString('base64')+' srtp://'+targetAddress+':'+targetVideoPort+'?rtcpport='+targetVideoPort+'&localrtcpport='+targetVideoPort+'&pkt_size=1378'; + this.ongoingSessions[sessionIdentifier] = spawn('ffmpeg', ffmpegCommand.split(' '), { env: process.env }); + } + + delete this.pendingSessions[sessionIdentifier]; + } else if (requestType == StreamRequestTypes.STOP) { + var ffmpegProcess = this.ongoingSessions[sessionIdentifier]; + if (ffmpegProcess) { + ffmpegProcess.kill('SIGKILL'); + } + + delete this.ongoingSessions[sessionIdentifier]; + } + } + } + + createCameraControlService = () => { + var controlService = new Service.CameraControl('', ''); + + // Developer can add control characteristics like rotation, night vision at here. + + this.services.push(controlService); + } + +// Private + _createStreamControllers = (maxStreams: number, options: StreamControllerOptions) => { + + for (let i = 0; i < maxStreams; i++) { + const streamController = new StreamController(i, options, this); + + this.services.push(streamController.service!); + this.streamControllers.push(streamController); + } + } +} + diff --git a/src/lib/Characteristic.spec.ts b/src/lib/Characteristic.spec.ts new file mode 100644 index 000000000..94211fa51 --- /dev/null +++ b/src/lib/Characteristic.spec.ts @@ -0,0 +1,340 @@ +import { Characteristic, CharacteristicEventTypes, Formats } from './Characteristic'; +import { generate } from './util/uuid'; +import './gen'; + +const createCharacteristic = (type: Formats) => { + return new Characteristic('Test', generate('Foo'), { format: type, perms: [] }); +} + +describe('Characteristic', () => { + + describe('#setProps()', () => { + it('should overwrite existing properties', () => { + const characteristic = createCharacteristic(Formats.BOOL); + + const NEW_PROPS = {format: Formats.STRING, perms: []}; + characteristic.setProps(NEW_PROPS); + + expect(characteristic.props).toEqual(NEW_PROPS); + }); + }); + + describe('#subscribe()', () => { + it('correctly adds a single subscription', () => { + const characteristic = createCharacteristic(Formats.BOOL); + const subscribeSpy = jest.fn(); + characteristic.on(CharacteristicEventTypes.SUBSCRIBE, subscribeSpy); + characteristic.subscribe(); + + expect(subscribeSpy).toHaveBeenCalledTimes(1); + expect(characteristic.subscriptions).toEqual(1); + }); + + it('correctly adds multiple subscriptions', () => { + const characteristic = createCharacteristic(Formats.BOOL); + const subscribeSpy = jest.fn(); + characteristic.on(CharacteristicEventTypes.SUBSCRIBE, subscribeSpy); + characteristic.subscribe(); + characteristic.subscribe(); + characteristic.subscribe(); + + expect(subscribeSpy).toHaveBeenCalledTimes(1); + expect(characteristic.subscriptions).toEqual(3); + }); + }); + + describe('#unsubscribe()', () => { + it('correctly removes a single subscription', () => { + const characteristic = createCharacteristic(Formats.BOOL); + const subscribeSpy = jest.fn(); + const unsubscribeSpy = jest.fn(); + characteristic.on(CharacteristicEventTypes.SUBSCRIBE, subscribeSpy); + characteristic.on(CharacteristicEventTypes.UNSUBSCRIBE, unsubscribeSpy); + characteristic.subscribe(); + characteristic.unsubscribe(); + + expect(subscribeSpy).toHaveBeenCalledTimes(1); + expect(unsubscribeSpy).toHaveBeenCalledTimes(1); + expect(characteristic.subscriptions).toEqual(0); + }); + + it('correctly removes multiple subscriptions', () => { + const characteristic = createCharacteristic(Formats.BOOL); + const subscribeSpy = jest.fn(); + const unsubscribeSpy = jest.fn(); + characteristic.on(CharacteristicEventTypes.SUBSCRIBE, subscribeSpy); + characteristic.on(CharacteristicEventTypes.UNSUBSCRIBE, unsubscribeSpy); + characteristic.subscribe(); + characteristic.subscribe(); + characteristic.subscribe(); + characteristic.unsubscribe(); + characteristic.unsubscribe(); + characteristic.unsubscribe(); + + expect(subscribeSpy).toHaveBeenCalledTimes(1); + expect(unsubscribeSpy).toHaveBeenCalledTimes(1); + expect(characteristic.subscriptions).toEqual(0); + }); + }); + + describe('#getValue()', () => { + 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); + }); + }); + + 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); + }); + }); + }); + + describe('#validateValue()', () => { + + it('should validate an integer property', () => { + const VALUE = 1024; + const characteristic = createCharacteristic(Formats.INT); + expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + }); + + it('should validate a float property', () => { + const VALUE = 1.024; + const characteristic = createCharacteristic(Formats.FLOAT); + expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + }); + + it('should validate a UINT8 property', () => { + const VALUE = 10; + const characteristic = createCharacteristic(Formats.UINT8); + expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + }); + + it('should validate a UINT16 property', () => { + const VALUE = 10; + const characteristic = createCharacteristic(Formats.UINT16); + expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + }); + + it('should validate a UINT32 property', () => { + const VALUE = 10; + const characteristic = createCharacteristic(Formats.UINT32); + expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + }); + + it('should validate a UINT64 property', () => { + const VALUE = 10; + const characteristic = createCharacteristic(Formats.UINT64); + expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + }); + + it('should validate a boolean property', () => { + const VALUE = true; + const characteristic = createCharacteristic(Formats.BOOL); + expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + }); + + it('should validate a string property', () => { + const VALUE = 'Test'; + const characteristic = createCharacteristic(Formats.STRING); + expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + }); + + it('should validate a data property', () => { + const VALUE = {}; + const characteristic = createCharacteristic(Formats.DATA); + expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + }); + + it('should validate a TLV8 property', () => { + const VALUE = ''; + const characteristic = createCharacteristic(Formats.TLV8); + expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + }); + + it('should validate a dictionary property', () => { + const VALUE = {}; + const characteristic = createCharacteristic(Formats.DICTIONARY); + expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + }); + + it('should validate an array property', () => { + const VALUE = ['asd']; + const characteristic = createCharacteristic(Formats.ARRAY); + expect(characteristic.validateValue(VALUE)).toEqual(VALUE); + }); + }); + + describe('#setValue()', () => { + it(`should set error values as the characteristic's status property`, () => { + const VALUE = new Error(); + const characteristic = createCharacteristic(Formats.DATA); + characteristic.setValue(VALUE); + expect(characteristic.status).toEqual(VALUE); + }); + }); + + describe('#updateValue()', () => { + it(`should set error values as the characteristic's status property`, () => { + const VALUE = new Error(); + const characteristic = createCharacteristic(Formats.DATA); + characteristic.setValue(VALUE); + expect(characteristic.status).toEqual(VALUE); + }); + }); + + describe('#getDefaultValue()', () => { + + it('should get the correct default value for a boolean property', () => { + const characteristic = createCharacteristic(Formats.BOOL); + expect(characteristic.getDefaultValue()).toEqual(false); + }); + + it('should get the correct default value for a string property', () => { + const characteristic = createCharacteristic(Formats.STRING); + expect(characteristic.getDefaultValue()).toEqual(''); + }); + + it('should get the correct default value for a data property', () => { + const characteristic = createCharacteristic(Formats.DATA); + expect(characteristic.getDefaultValue()).toEqual(null); + }); + + it('should get the correct default value for a TLV8 property', () => { + const characteristic = createCharacteristic(Formats.TLV8); + expect(characteristic.getDefaultValue()).toEqual(null); + }); + + it('should get the correct default value for a dictionary property', () => { + const characteristic = createCharacteristic(Formats.DICTIONARY); + expect(characteristic.getDefaultValue()).toEqual({}); + }); + + it('should get the correct default value for an array property', () => { + const characteristic = createCharacteristic(Formats.ARRAY); + expect(characteristic.getDefaultValue()).toEqual([]); + }); + + }); + + describe('#toHAP()', () => { + + }); + + describe(`@${CharacteristicEventTypes.GET}`, () => { + + it('should call any listeners for the event', () => { + const characteristic = createCharacteristic(Formats.STRING); + + const listenerCallback = jest.fn(); + const getValueCallback = jest.fn(); + + characteristic.getValue(getValueCallback); + characteristic.on(CharacteristicEventTypes.GET, listenerCallback); + characteristic.getValue(getValueCallback); + + expect(listenerCallback).toHaveBeenCalledTimes(1); + expect(getValueCallback).toHaveBeenCalledTimes(1); + }); + }); + + describe(`@${CharacteristicEventTypes.SET}`, () => { + + it('should call any listeners for the event', () => { + const characteristic = createCharacteristic(Formats.STRING); + + const VALUE = 'NewValue'; + const listenerCallback = jest.fn(); + const setValueCallback = jest.fn(); + + characteristic.setValue(VALUE, setValueCallback) + characteristic.on(CharacteristicEventTypes.SET, listenerCallback); + characteristic.setValue(VALUE, setValueCallback); + + expect(listenerCallback).toHaveBeenCalledTimes(1); + expect(setValueCallback).toHaveBeenCalledTimes(1); + }); + }); + + describe(`@${CharacteristicEventTypes.CHANGE}`, () => { + + it('should call any listeners for the event when the characteristic is event-only, and the value is set', () => { + const characteristic = createCharacteristic(Formats.STRING); + characteristic.eventOnlyCharacteristic = true; + + const VALUE = 'NewValue'; + const listenerCallback = jest.fn(); + const setValueCallback = jest.fn(); + + characteristic.setValue(VALUE, setValueCallback) + characteristic.on(CharacteristicEventTypes.CHANGE, listenerCallback); + characteristic.setValue(VALUE, setValueCallback) + + expect(listenerCallback).toHaveBeenCalledTimes(1); + expect(setValueCallback).toHaveBeenCalledTimes(2); + }); + + it('should call any listeners for the event when the characteristic is event-only, and the value is updated', () => { + const characteristic = createCharacteristic(Formats.STRING); + // characteristic.eventOnlyCharacteristic = true; + + const VALUE = 'NewValue'; + const listenerCallback = jest.fn(); + const updateValueCallback = jest.fn(); + + characteristic.on(CharacteristicEventTypes.CHANGE, listenerCallback); + characteristic.updateValue(VALUE, updateValueCallback) + + expect(listenerCallback).toHaveBeenCalledTimes(1); + expect(updateValueCallback).toHaveBeenCalledTimes(1); + }); + }); + + describe(`@${CharacteristicEventTypes.SUBSCRIBE}`, () => { + + it('should call any listeners for the event', () => { + const characteristic = createCharacteristic(Formats.STRING); + + const cb = jest.fn(); + + characteristic.on(CharacteristicEventTypes.SUBSCRIBE, cb); + characteristic.subscribe(); + + expect(cb).toHaveBeenCalledTimes(1); + }); + }); + + describe(`@${CharacteristicEventTypes.UNSUBSCRIBE}`, () => { + + it('should call any listeners for the event', () => { + const characteristic = createCharacteristic(Formats.STRING); + + const cb = jest.fn(); + + characteristic.subscribe(); + characteristic.on(CharacteristicEventTypes.UNSUBSCRIBE, cb); + characteristic.unsubscribe(); + + expect(cb).toHaveBeenCalledTimes(1); + }); + + it('should not call any listeners for the event if none are registered', () => { + const characteristic = createCharacteristic(Formats.STRING); + + const cb = jest.fn(); + + characteristic.on(CharacteristicEventTypes.UNSUBSCRIBE, cb); + characteristic.unsubscribe(); + + expect(cb).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/src/lib/Characteristic.ts b/src/lib/Characteristic.ts new file mode 100644 index 000000000..b93081712 --- /dev/null +++ b/src/lib/Characteristic.ts @@ -0,0 +1,716 @@ +import Decimal from 'decimal.js'; +import bufferShim from 'buffer-shims'; + +import { once } from './util/once'; +import { IdentifierCache } from './model/IdentifierCache'; +import { + CharacteristicChange, + CharacteristicValue, + HapCharacteristic, + Nullable, + ToHAPOptions, + VoidCallback, +} from '../types'; +import { EventEmitter } from './EventEmitter'; +import * as HomeKitTypes from './gen'; + +// Known HomeKit formats +export enum Formats { + BOOL = 'bool', + INT = 'int', + FLOAT = 'float', + STRING = 'string', + UINT8 = 'uint8', + UINT16 = 'uint16', + UINT32 = 'uint32', + UINT64 = 'uint64', + DATA = 'data', + TLV8 = 'tlv8', + ARRAY = 'array', //Not in HAP Spec + DICTIONARY = 'dict' //Not in HAP Spec +} + +// Known HomeKit unit types +export enum Units { + // HomeKit only defines Celsius, for Fahrenheit, it requires iOS app to do the conversion. + CELSIUS = 'celsius', + PERCENTAGE = 'percentage', + ARC_DEGREE = 'arcdegrees', + LUX = 'lux', + SECONDS = 'seconds' +} + +// Known HomeKit permission types +export enum Perms { + READ = 'pr', //Kept for backwards compatability + PAIRED_READ = 'pr', //Added to match HAP's terminology + WRITE = 'pw', //Kept for backwards compatability + PAIRED_WRITE = 'pw', //Added to match HAP's terminology + NOTIFY = 'ev', //Kept for backwards compatability + EVENTS = 'ev', //Added to match HAP's terminology + ADDITIONAL_AUTHORIZATION = 'aa', + TIMED_WRITE = 'tw', //Not currently supported by IP + HIDDEN = 'hd', + WRITE_RESPONSE = 'wr' +} + +export interface CharacteristicProps { + format: Formats; + unit?: Units; + perms: Perms[]; + ev?: boolean; + description?: string; + minValue?: number; + maxValue?: number; + minStep?: number; + maxLen?: number; + maxDataLen?: number; + validValues?: number[]; + validValueRanges?: [number, number]; +} + +export enum CharacteristicEventTypes { + GET = "get", + SET = "set", + SUBSCRIBE = "subscribe", + UNSUBSCRIBE = "unsubscribe", + CHANGE = "change", +} + +export type CharacteristicGetCallback> = (error?: Error | null , value?: T) => void +export type CharacteristicSetCallback = (error?: Error | null, value?: CharacteristicValue) => void + +type Events = { + [CharacteristicEventTypes.CHANGE]: (change: CharacteristicChange) => void; + [CharacteristicEventTypes.GET]: (cb: CharacteristicGetCallback, context?: any, connectionID?: string) => void; + [CharacteristicEventTypes.SET]: (value: CharacteristicValue, cb: CharacteristicSetCallback, context?: any, connectionID?: string) => void; + [CharacteristicEventTypes.SUBSCRIBE]: VoidCallback; + [CharacteristicEventTypes.UNSUBSCRIBE]: VoidCallback; +} + +/** + * @deprecated Use CharacteristicEventTypes instead + */ +export type EventCharacteristic = CharacteristicEventTypes.GET | CharacteristicEventTypes.SET | CharacteristicEventTypes.SUBSCRIBE | CharacteristicEventTypes.UNSUBSCRIBE | CharacteristicEventTypes.CHANGE; + +/** + * 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 + * 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 { + + static Formats = Formats; + static Units = Units; + static Perms = Perms; + + 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 CarbonDioxideDetected: typeof HomeKitTypes.Generated.CarbonDioxideDetected; + static CarbonDioxideLevel: typeof HomeKitTypes.Generated.CarbonDioxideLevel; + static CarbonDioxidePeakLevel: typeof HomeKitTypes.Generated.CarbonDioxidePeakLevel; + static CarbonMonoxideDetected: typeof HomeKitTypes.Generated.CarbonMonoxideDetected; + static CarbonMonoxideLevel: typeof HomeKitTypes.Generated.CarbonMonoxideLevel; + static CarbonMonoxidePeakLevel: typeof HomeKitTypes.Generated.CarbonMonoxidePeakLevel; + static Category: typeof HomeKitTypes.Bridged.Category; + static ChargingState: typeof HomeKitTypes.Generated.ChargingState; + static ClosedCaptions: typeof HomeKitTypes.TV.ClosedCaptions; + static ColorTemperature: typeof HomeKitTypes.Generated.ColorTemperature; + static ConfigureBridgedAccessory: typeof HomeKitTypes.Bridged.ConfigureBridgedAccessory; + static ConfigureBridgedAccessoryStatus: typeof HomeKitTypes.Bridged.ConfigureBridgedAccessoryStatus; + static ConfiguredName: typeof HomeKitTypes.TV.ConfiguredName; + static ContactSensorState: typeof HomeKitTypes.Generated.ContactSensorState; + static CoolingThresholdTemperature: typeof HomeKitTypes.Generated.CoolingThresholdTemperature; + static CurrentAirPurifierState: typeof HomeKitTypes.Generated.CurrentAirPurifierState; + static CurrentAmbientLightLevel: typeof HomeKitTypes.Generated.CurrentAmbientLightLevel; + static CurrentDoorState: typeof HomeKitTypes.Generated.CurrentDoorState; + static CurrentFanState: typeof HomeKitTypes.Generated.CurrentFanState; + static CurrentHeaterCoolerState: typeof HomeKitTypes.Generated.CurrentHeaterCoolerState; + static CurrentHeatingCoolingState: typeof HomeKitTypes.Generated.CurrentHeatingCoolingState; + static CurrentHorizontalTiltAngle: typeof HomeKitTypes.Generated.CurrentHorizontalTiltAngle; + static CurrentHumidifierDehumidifierState: typeof HomeKitTypes.Generated.CurrentHumidifierDehumidifierState; + static CurrentMediaState: typeof HomeKitTypes.TV.CurrentMediaState; + static CurrentPosition: typeof HomeKitTypes.Generated.CurrentPosition; + static CurrentRelativeHumidity: typeof HomeKitTypes.Generated.CurrentRelativeHumidity; + static CurrentSlatState: typeof HomeKitTypes.Generated.CurrentSlatState; + static CurrentTemperature: typeof HomeKitTypes.Generated.CurrentTemperature; + static CurrentTiltAngle: typeof HomeKitTypes.Generated.CurrentTiltAngle; + static CurrentTime: typeof HomeKitTypes.Bridged.CurrentTime; + static CurrentVerticalTiltAngle: typeof HomeKitTypes.Generated.CurrentVerticalTiltAngle; + static CurrentVisibilityState: typeof HomeKitTypes.TV.CurrentVisibilityState; + static DayoftheWeek: typeof HomeKitTypes.Bridged.DayoftheWeek; + static DigitalZoom: typeof HomeKitTypes.Generated.DigitalZoom; + static DiscoverBridgedAccessories: typeof HomeKitTypes.Bridged.DiscoverBridgedAccessories; + static DiscoveredBridgedAccessories: typeof HomeKitTypes.Bridged.DiscoveredBridgedAccessories; + static DisplayOrder: typeof HomeKitTypes.TV.DisplayOrder; + static FilterChangeIndication: typeof HomeKitTypes.Generated.FilterChangeIndication; + static FilterLifeLevel: typeof HomeKitTypes.Generated.FilterLifeLevel; + static FirmwareRevision: typeof HomeKitTypes.Generated.FirmwareRevision; + static HardwareRevision: typeof HomeKitTypes.Generated.HardwareRevision; + static HeatingThresholdTemperature: typeof HomeKitTypes.Generated.HeatingThresholdTemperature; + static HoldPosition: typeof HomeKitTypes.Generated.HoldPosition; + static Hue: typeof HomeKitTypes.Generated.Hue; + static Identifier: typeof HomeKitTypes.TV.Identifier; + static Identify: typeof HomeKitTypes.Generated.Identify; + static ImageMirroring: typeof HomeKitTypes.Generated.ImageMirroring; + static ImageRotation: typeof HomeKitTypes.Generated.ImageRotation; + static InUse: typeof HomeKitTypes.Generated.InUse; + static InputDeviceType: typeof HomeKitTypes.TV.InputDeviceType; + static InputSourceType: typeof HomeKitTypes.TV.InputSourceType; + static IsConfigured: typeof HomeKitTypes.Generated.IsConfigured; + /** + * @deprecated Removed in iOS 11. Use ServiceLabelIndex instead. + */ + static LabelIndex: typeof HomeKitTypes.Generated.ServiceLabelIndex; + /** + * @deprecated Removed in iOS 11. Use ServiceLabelNamespace instead. + */ + static LabelNamespace: typeof HomeKitTypes.Generated.ServiceLabelNamespace; + static LeakDetected: typeof HomeKitTypes.Generated.LeakDetected; + static LinkQuality: typeof HomeKitTypes.Bridged.LinkQuality; + static LockControlPoint: typeof HomeKitTypes.Generated.LockControlPoint; + static LockCurrentState: typeof HomeKitTypes.Generated.LockCurrentState; + static LockLastKnownAction: typeof HomeKitTypes.Generated.LockLastKnownAction; + static LockManagementAutoSecurityTimeout: typeof HomeKitTypes.Generated.LockManagementAutoSecurityTimeout; + static LockPhysicalControls: typeof HomeKitTypes.Generated.LockPhysicalControls; + static LockTargetState: typeof HomeKitTypes.Generated.LockTargetState; + static Logs: typeof HomeKitTypes.Generated.Logs; + static Manufacturer: typeof HomeKitTypes.Generated.Manufacturer; + static Model: typeof HomeKitTypes.Generated.Model; + static MotionDetected: typeof HomeKitTypes.Generated.MotionDetected; + static Mute: typeof HomeKitTypes.Generated.Mute; + static Name: typeof HomeKitTypes.Generated.Name; + static NightVision: typeof HomeKitTypes.Generated.NightVision; + static NitrogenDioxideDensity: typeof HomeKitTypes.Generated.NitrogenDioxideDensity; + static ObstructionDetected: typeof HomeKitTypes.Generated.ObstructionDetected; + static OccupancyDetected: typeof HomeKitTypes.Generated.OccupancyDetected; + static On: typeof HomeKitTypes.Generated.On; + static OpticalZoom: typeof HomeKitTypes.Generated.OpticalZoom; + static OutletInUse: typeof HomeKitTypes.Generated.OutletInUse; + static OzoneDensity: typeof HomeKitTypes.Generated.OzoneDensity; + static PM10Density: typeof HomeKitTypes.Generated.PM10Density; + static PM2_5Density: typeof HomeKitTypes.Generated.PM2_5Density; + static PairSetup: typeof HomeKitTypes.Generated.PairSetup; + static PairVerify: typeof HomeKitTypes.Generated.PairVerify; + static PairingFeatures: typeof HomeKitTypes.Generated.PairingFeatures; + static PairingPairings: typeof HomeKitTypes.Generated.PairingPairings; + static 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 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; + /** + * @deprecated Removed in iOS 11. Use SelectedRTPStreamConfiguration instead. + */ + static SelectedStreamConfiguration: typeof HomeKitTypes.Generated.SelectedRTPStreamConfiguration; + static SelectedRTPStreamConfiguration: typeof HomeKitTypes.Generated.SelectedRTPStreamConfiguration; + static SerialNumber: typeof HomeKitTypes.Generated.SerialNumber; + static ServiceLabelIndex: typeof HomeKitTypes.Generated.ServiceLabelIndex; + static ServiceLabelNamespace: typeof HomeKitTypes.Generated.ServiceLabelNamespace; + static SetDuration: typeof HomeKitTypes.Generated.SetDuration; + static SetupEndpoints: typeof HomeKitTypes.Generated.SetupEndpoints; + 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 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 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.Bridged.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; + + iid: Nullable = null; + value: Nullable = null; + status: Nullable = null; + eventOnlyCharacteristic: boolean = false; + props: CharacteristicProps; + subscriptions: number = 0; + + 'valid-values': number[]; + 'valid-values-range': [number, number]; + + constructor(public displayName?: string, public UUID?: string, props?: CharacteristicProps) { + super(); + // @ts-ignore + this.props = props || { + format: null, + unit: null, + minValue: null, + maxValue: null, + minStep: null, + perms: [] + }; + } + + /** + * Copies the given properties to our props member variable, + * and returns 'this' for chaining. + * + * @param 'props' { + * format: , + * unit: , + * perms: array of [Characteristic.Perms] like [Characteristic.Perms.READ, Characteristic.Perms.WRITE] + * ev: , (Optional) + * description: , (Optional) + * minValue: , (Optional) + * maxValue: , (Optional) + * minStep: , (Optional) + * maxLen: , (Optional default: 64) + * maxDataLen: , (Optional default: 2097152) + * valid-values: , (Optional) + * valid-values-range: (Optional) + * } + */ + setProps = (props: CharacteristicProps) => { + for (var key in (props || {})) + if (Object.prototype.hasOwnProperty.call(props, key)) { + // @ts-ignore + this.props[key] = props[key]; + } + return this; + } + + subscribe = () => { + if (this.subscriptions === 0) { + this.emit(CharacteristicEventTypes.SUBSCRIBE); + } + this.subscriptions++; + } + + unsubscribe = () => { + var wasOne = this.subscriptions === 1; + this.subscriptions--; + this.subscriptions = Math.max(this.subscriptions, 0); + if (wasOne) { + this.emit(CharacteristicEventTypes.UNSUBSCRIBE); + } + } + + getValue = (callback?: CharacteristicGetCallback, context?: any, connectionID?: string) => { + // Handle special event only characteristics. + if (this.eventOnlyCharacteristic === true) { + if (callback) { + callback(null, null); + } + return; + } + if (this.listeners(CharacteristicEventTypes.GET).length > 0) { + // allow a listener to handle the fetching of this value, and wait for completion + this.emit(CharacteristicEventTypes.GET, once((err: Error, newValue: Nullable) => { + this.status = err; + if (err) { + // pass the error along to our callback + if (callback) + callback(err); + } else { + newValue = this.validateValue(newValue); //validateValue returns a value that has be cooerced into a valid value. + if (newValue === undefined || newValue === null) + newValue = this.getDefaultValue(); + // getting the value was a success; we can pass it along and also update our cached value + var oldValue = this.value; + this.value = newValue; + if (callback) + callback(null, newValue); + // emit a change event if necessary + if (oldValue !== newValue) + this.emit(CharacteristicEventTypes.CHANGE, {oldValue: oldValue, newValue: newValue, context: context}); + } + }), context, connectionID); + } else { + // no one is listening to the 'get' event, so just return the cached value + if (callback) + callback(this.status, this.value); + } + } + + 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; + switch (this.props.format) { + case Formats.INT: + minStep_resolved = 1; + minValue_resolved = -2147483648; + maxValue_resolved = 2147483647; + isNumericType = true; + break; + case Formats.FLOAT: + minStep_resolved = undefined; + minValue_resolved = undefined; + maxValue_resolved = undefined; + isNumericType = true; + break; + case Formats.UINT8: + minStep_resolved = 1; + minValue_resolved = 0; + maxValue_resolved = 255; + isNumericType = true; + break; + case Formats.UINT16: + minStep_resolved = 1; + minValue_resolved = 0; + maxValue_resolved = 65535; + isNumericType = true; + break; + case Formats.UINT32: + minStep_resolved = 1; + minValue_resolved = 0; + maxValue_resolved = 4294967295; + isNumericType = true; + break; + case Formats.UINT64: + minStep_resolved = 1; + minValue_resolved = 0; + maxValue_resolved = 18446744073709551615; + isNumericType = true; + break; + //All of the following datatypes return from this switch. + case Formats.BOOL: + // @ts-ignore + return (newValue == true); //We don't need to make sure this returns true or false + break; + case Formats.STRING: + let myString = newValue as string || ''; //If null or undefined or anything odd, make it a blank string + myString = String(myString); + var maxLength = this.props.maxLen; + if (maxLength === undefined) + maxLength = 64; //Default Max Length is 64. + if (myString.length > maxLength) + myString = myString.substring(0, maxLength); //Truncate strings that are too long + return myString; //We don't need to do any validation after having truncated the string + break; + case Formats.DATA: + var maxLength = this.props.maxDataLen; + if (maxLength === undefined) + maxLength = 2097152; //Default Max Length is 2097152. + //if (newValue.length>maxLength) //I don't know the best way to handle this since it's unknown binary data. + //I suspect that it will crash HomeKit for this bridge if the length is too long. + return newValue; + 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 (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 (newValue === false) { + return 0; + } + if (newValue === true) { + return 1; + } + if ((this.props.maxValue && !isNaN(this.props.maxValue)) && (this.props.maxValue !== null)) + maxValue_resolved = this.props.maxValue; + if ((this.props.minValue && !isNaN(this.props.minValue)) && (this.props.minValue !== null)) + minValue_resolved = this.props.minValue; + if ((this.props.minStep && !isNaN(this.props.minStep)) && (this.props.minStep !== null)) + minStep_resolved = this.props.minStep; + if (newValue! < minValue_resolved!) + newValue = minValue_resolved!; //Fails Minimum Value Test + if (newValue! > maxValue_resolved!) + newValue = maxValue_resolved!; //Fails Maximum Value Test + if (minStep_resolved !== undefined) { + //Determine how many decimals we need to display + if (Math.floor(minStep_resolved) === minStep_resolved) + stepDecimals = 0; + else + stepDecimals = minStep_resolved.toString().split(".")[1].length || 0; + //Use Decimal to detemine the lowest value within the step. + try { + var decimalVal = new Decimal(Number.parseInt(newValue as string)); + var decimalDiff = decimalVal.mod(minStep_resolved); + decimalVal = decimalVal.minus(decimalDiff); + if (stepDecimals === 0) { + newValue = parseInt(decimalVal.toFixed(0)); + } else { + newValue = parseFloat(decimalVal.toFixed(stepDecimals)); //Convert it to a fixed decimal + } + } catch (e) { + return this.value!; //If we had an error, return the current value. + } + } + if (this['valid-values'] !== undefined) + if (!this['valid-values'].includes(newValue as number)) + return this.value!; //Fails Valid Values Test + if (this['valid-values-range'] !== undefined) { //This is another way Apple has to handle min/max + if (newValue! < this['valid-values-range'][0]) + newValue = this['valid-values-range'][0]; + if (newValue! > this['valid-values-range'][1]) + newValue = this['valid-values-range'][1]; + } + } + return newValue; + } + + setValue = (newValue: Nullable, callback?: CharacteristicSetCallback, context?: any, connectionID?: string): Characteristic => { + if (newValue instanceof Error) { + this.status = newValue; + } else { + this.status = null; + } + newValue = this.validateValue(newValue as Nullable); //validateValue returns a value that has be cooerced into a valid value. + var oldValue = this.value; + if (this.listeners(CharacteristicEventTypes.SET).length > 0) { + // allow a listener to handle the setting of this value, and wait for completion + this.emit(CharacteristicEventTypes.SET, newValue, once((err: Error) => { + this.status = err; + if (err) { + // pass the error along to our callback + if (callback) + callback(err); + } else { + if (newValue === undefined || newValue === null) + newValue = this.getDefaultValue() as CharacteristicValue; + // setting the value was a success; so we can cache it now + this.value = newValue as CharacteristicValue; + if (callback) + callback(); + if (this.eventOnlyCharacteristic === true || oldValue !== newValue) + this.emit(CharacteristicEventTypes.CHANGE, {oldValue: oldValue, newValue: newValue, context: context}); + } + }), context, connectionID); + } else { + if (newValue === undefined || newValue === null) + newValue = this.getDefaultValue() as CharacteristicValue; + // no one is listening to the 'set' event, so just assign the value blindly + this.value = newValue as string | number; + if (callback) + callback(); + if (this.eventOnlyCharacteristic === true || oldValue !== newValue) + this.emit(CharacteristicEventTypes.CHANGE, {oldValue: oldValue, newValue: newValue, context: context}); + } + return this; // for chaining + } + + updateValue = (newValue: Nullable, callback?: () => void, context?: any): Characteristic => { + if (newValue instanceof Error) { + this.status = newValue; + } else { + this.status = null; + } + newValue = this.validateValue(newValue as Nullable); //validateValue returns a value that has be cooerced into a valid value. + if (newValue === undefined || newValue === null) + newValue = this.getDefaultValue() as CharacteristicValue; + // no one is listening to the 'set' event, so just assign the value blindly + var oldValue = this.value; + this.value = newValue; + if (callback) + callback(); + if (this.eventOnlyCharacteristic === true || oldValue !== newValue) + this.emit(CharacteristicEventTypes.CHANGE, {oldValue: oldValue, newValue: newValue, context: context}); + return this; // for chaining + } + + getDefaultValue = (): Nullable => { + 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; + } + } + + _assignID = (identifierCache: IdentifierCache, accessoryName: string, serviceUUID: string, serviceSubtype: string) => { + // 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. + */ + toHAP = (opt?: ToHAPOptions) => { + // ensure our value fits within our constraints if present + var value = this.value; + + if (this.props.minValue != null && value! < this.props.minValue) + value = this.props.minValue; + if (this.props.maxValue != null && value! > this.props.maxValue) + value = this.props.maxValue; + if (this.props.format != null) { + if (this.props.format === Formats.INT) + value = parseInt(value as string); + else if (this.props.format === Formats.UINT8) + value = parseInt(value as string); + else if (this.props.format === Formats.UINT16) + value = parseInt(value as string); + else if (this.props.format === Formats.UINT32) + value = parseInt(value as string); + else if (this.props.format === Formats.UINT64) + value = parseInt(value as string); + else if (this.props.format === Formats.FLOAT) { + value = parseFloat(value as string); + if (this.props.minStep != null) { + var pow = Math.pow(10, decimalPlaces(this.props.minStep)); + value = Math.round(value * pow) / pow; + } + } + } + if (this.eventOnlyCharacteristic === true) { + // @ts-ignore + value = null; + } + + const hap: Partial = { + iid: this.iid!, + type: this.UUID, + perms: this.props.perms, + format: this.props.format, + value: value, + description: this.displayName, + // These properties used to be sent but do not seem to be used: + // + // events: false, + // bonjour: false + }; + if (this.props.validValues != null && this.props.validValues.length > 0) { + hap['valid-values'] = this.props.validValues; + } + if (this.props.validValueRanges != null && this.props.validValueRanges.length > 0 && !(this.props.validValueRanges.length & 1)) { + hap['valid-values-range'] = this.props.validValueRanges; + } + // extra properties + if (this.props.unit != null) + hap.unit = this.props.unit; + if (this.props.maxValue != null) + hap.maxValue = this.props.maxValue; + if (this.props.minValue != null) + hap.minValue = this.props.minValue; + if (this.props.minStep != null) + hap.minStep = this.props.minStep; + // add maxLen if string length is > 64 bytes and trim to max 256 bytes + if (this.props.format === Formats.STRING) { + var str = bufferShim.from(value as string, 'utf8'), len = str.byteLength; + if (len > 256) { // 256 bytes is the max allowed length + hap.value = str.toString('utf8', 0, 256); + hap.maxLen = 256; + } else if (len > 64) { // values below can be ommited + hap.maxLen = len; + } + } + // if we're not readable, omit the "value" property - otherwise iOS will complain about non-compliance + if (this.props.perms.indexOf(Perms.READ) == -1) + delete hap.value; + // delete the "value" property anyway if we were asked to + if (opt && opt.omitValues) + delete hap.value; + return hap as HapCharacteristic; + } +} + + +// Mike Samuel +// http://stackoverflow.com/questions/10454518/javascript-how-to-retrieve-the-number-of-decimals-of-a-string-number +function decimalPlaces(num: number) { + var match = (''+num).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/); + if (!match) { return 0; } + return Math.max( + 0, + // Number of digits right of decimal point. + (match[1] ? match[1].length : 0) + // Adjust for scientific notation. + - (match[2] ? +match[2] : 0)); +} diff --git a/src/lib/EventEmitter.ts b/src/lib/EventEmitter.ts new file mode 100644 index 000000000..c1a550a38 --- /dev/null +++ b/src/lib/EventEmitter.ts @@ -0,0 +1,48 @@ +import { EventEmitter as BaseEventEmitter } from "events"; +import { Callback } from '../types'; + +export type EventKey = string | symbol; +export type Event = T & EventKey; +export type EventMap = { [name: string]: Callback }; + +export class EventEmitter = Event> extends BaseEventEmitter { + addListener(event: K, listener: T[K]): this { + return super.addListener(event, listener); + }; + + on(event: K, listener: T[K]): this { + return super.on(event, listener); + } + + once(event: K, listener: T[K]): this { + return super.once(event, listener); + } + + removeListener(event: K, listener: T[K]): this { + return super.removeListener(event, listener); + } + + removeAllListeners(event?: K): this { + return super.removeAllListeners(event); + } + + setMaxListeners(n: number): this { + return super.setMaxListeners(n); + } + + getMaxListeners(): number { + return super.getMaxListeners(); + } + + listeners(event: K): T[K] [] { + return super.listeners(event) as T[K][]; + } + + emit(event: K, ...args: any[]): boolean { + return super.emit(event, ...args); + } + + listenerCount(type: string): number { + return super.listenerCount(type); + } +} diff --git a/src/lib/HAPServer.ts b/src/lib/HAPServer.ts new file mode 100644 index 000000000..9a6cd23da --- /dev/null +++ b/src/lib/HAPServer.ts @@ -0,0 +1,966 @@ +import crypto from 'crypto'; + +import bufferShim from 'buffer-shims'; +import createDebug from 'debug'; +import srp from 'fast-srp-hap'; +import tweetnacl from 'tweetnacl'; +import url from 'url'; + +import * as encryption from './util/encryption'; +import * as hkdf from './util/hkdf'; +import * as tlv from './util/tlv'; +import { EventedHTTPServer, EventedHTTPServerEvents } from './util/eventedhttp'; +import { once } from './util/once'; +import { IncomingMessage, ServerResponse } from "http"; +import { Characteristic } from './Characteristic'; +import { Accessory, CharacteristicEvents, Resource } from './Accessory'; +import { CharacteristicData, NodeCallback, SessionIdentifier, VoidCallback } from '../types'; +import { EventEmitter } from './EventEmitter'; + +const debug = createDebug('HAPServer'); + +// Various "type" constants for HAP's TLV encoding. +export enum Types { + REQUEST_TYPE = 0x00, + USERNAME = 0x01, + SALT = 0x02, + PUBLIC_KEY = 0x03, + PASSWORD_PROOF = 0x04, + ENCRYPTED_DATA = 0x05, + SEQUENCE_NUM = 0x06, + ERROR_CODE = 0x07, + PROOF = 0x0a +} + +// Error codes and the like, guessed by packet inspection +export enum Codes { + INVALID_REQUEST = 0x02, + INVALID_SIGNATURE = 0x04 +} + +// Status codes for underlying HAP calls +export enum Status { + SUCCESS = 0, + INSUFFICIENT_PRIVILEGES = -70401, + SERVICE_COMMUNICATION_FAILURE = -70402, + RESOURCE_BUSY = -70403, + READ_ONLY_CHARACTERISTIC = -70404, + WRITE_ONLY_CHARACTERISTIC = -70405, + NOTIFICATION_NOT_SUPPORTED = -70406, + OUT_OF_RESOURCE = -70407, + OPERATION_TIMED_OUT = -70408, + RESOURCE_DOES_NOT_EXIST = -70409, + INVALID_VALUE_IN_REQUEST = -70410 +} + +export type Session = { + sessionID: SessionIdentifier; + encryption: HAPEncryption; + srpServer: srp.Server; +}; + +export enum HapRequestMessageTypes { + PAIR_VERIFY = 'pair-verify', + DISCOVERY = 'discovery', + WRITE_CHARACTERISTICS = 'write-characteristics', + READ_CHARACTERISTICS = 'read-characteristics' +} + +export type RemoteSession = { + responseMessage(request: HapRequest, data: Buffer): void; +} + +export type HapRequest = { + messageType: HapRequestMessageTypes + requestBody: any; +} + +export enum HAPServerEventTypes { + IDENTIFY = "identify", + LISTENING = "listening", + PAIR = 'pair', + UNPAIR = 'unpair', + ACCESSORIES = 'accessories', + GET_CHARACTERISTICS = 'get-characteristics', + SET_CHARACTERISTICS = 'set-characteristics', + SESSION_CLOSE = "session-close", + REQUEST_RESOURCE = 'request-resource' +} + +export type Events = { + [HAPServerEventTypes.IDENTIFY]: (cb: VoidCallback) => void; + [HAPServerEventTypes.LISTENING]: (port: number) => void; + [HAPServerEventTypes.PAIR]: (clientUsername: string, clientLTPK: Buffer, cb: VoidCallback) => void; + [HAPServerEventTypes.UNPAIR]: (clientUsername: string, cb: VoidCallback) => void; + [HAPServerEventTypes.ACCESSORIES]: (cb: NodeCallback) => void; + [HAPServerEventTypes.GET_CHARACTERISTICS]: ( + data: CharacteristicData[], + events: CharacteristicEvents, + cb: NodeCallback, + remote: boolean, + connectionID: string, + ) => void; + [HAPServerEventTypes.SET_CHARACTERISTICS]: ( + data: CharacteristicData[], + events: CharacteristicEvents, + cb: NodeCallback, + remote: boolean, + connectionID: string, + ) => void; + [HAPServerEventTypes.SESSION_CLOSE]: (sessionID: string, events: CharacteristicEvents) => void; + [HAPServerEventTypes.REQUEST_RESOURCE]: (data: Resource, cb: NodeCallback) => void; +} + +/** + * The actual HAP server that iOS devices talk to. + * + * Notes + * ----- + * It turns out that the IP-based version of HomeKit's HAP protocol operates over a sort of pseudo-HTTP. + * Accessories are meant to host a TCP socket server that initially behaves exactly as an HTTP/1.1 server. + * So iOS devices will open up a long-lived connection to this server and begin issuing HTTP requests. + * So far, this conforms with HTTP/1.1 Keepalive. However, after the "pairing" process is complete, the + * connection is expected to be "upgraded" to support full-packet encryption of both HTTP headers and data. + * This encryption is NOT SSL. It is a customized ChaCha20+Poly1305 encryption layer. + * + * Additionally, this "HTTP Server" supports sending "event" responses at any time without warning. The iOS + * device simply keeps the connection open after it's finished with HTTP request/response traffic, and while + * the connection is open, the server can elect to issue "EVENT/1.0 200 OK" HTTP-style responses. These are + * 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 Types = Types; + static Codes = Codes; + static Status = Status; + + static handlers: Record = { + '/identify': '_handleIdentify', + '/pair-setup': '_handlePair', + '/pair-verify': '_handlePairVerify', + '/pairings': '_handlePairings', + '/accessories': '_handleAccessories', + '/characteristics': '_handleCharacteristics', + '/resource': '_handleResource' + }; + + _httpServer: EventedHTTPServer; + _relayServer?: any; + + allowInsecureRequest: boolean; + _keepAliveTimerID: NodeJS.Timeout; + + constructor(public accessoryInfo: any, public relayServer?: 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); + if (relayServer) { + this._relayServer = relayServer; + this._relayServer.on('request', this._onRemoteRequest); + this._relayServer.on('encrypt', this._onEncrypt); + this._relayServer.on('decrypt', this._onDecrypt); + } + // so iOS is very reluctant to actually disconnect HAP connections (as in, sending a FIN packet). + // For instance, if you turn off wifi on your phone, it will not close the connection, instead + // it will leave it open and hope that it's still valid when it returns to the network. And Node, + // by itself, does not ever "discover" that the connection has been closed behind it, until a + // potentially very long system-level socket timeout (like, days). To work around this, we have + // invented a manual "keepalive" mechanism where we send "empty" events perodicially, such that + // when Node attempts to write to the socket, it discovers that it's been disconnected after + // an additional one-minute timeout (this timeout appears to be hardcoded). + this._keepAliveTimerID = setInterval(this._onKeepAliveTimerTick, 1000 * 60 * 10); // send keepalive every 10 minutes + } + + listen = (port: number) => { + this._httpServer.listen(port); + } + + 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: []}); + } + + /** + * Notifies connected clients who have subscribed to a particular event. + * + * @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 + */ + 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); + if (this._relayServer) { + if (event !== 'keepalive') { + this._relayServer.sendEvent(event, data, excludeEvents); + } + } + } + + _onListening = (port: number) => { + this.emit(HAPServerEventTypes.LISTENING, port); + } + + // Called when an HTTP request was detected. + _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 = bufferShim.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!; + // all request data received; now process this request + for (var path in HAPServer.handlers) + if (new RegExp('^' + path + '/?$').test(pathname)) { // match exact string and allow trailing slash + const handler = HAPServer.handlers[path] as keyof HAPServer; + this[handler](request, response, session, events, requestData); + return; + } + // 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(); + }); + } + + _onRemoteRequest = (request: HapRequest, remoteSession: RemoteSession, session: Session, events: any) => { + debug('[%s] Remote Request: %s', this.accessoryInfo.username, request.messageType); + if (request.messageType === HapRequestMessageTypes.PAIR_VERIFY) + this._handleRemotePairVerify(request, remoteSession, session); + else if (request.messageType === HapRequestMessageTypes.DISCOVERY) + this._handleRemoteAccessories(request, remoteSession, session); + else if (request.messageType === HapRequestMessageTypes.WRITE_CHARACTERISTICS) + this._handleRemoteCharacteristicsWrite(request, remoteSession, session, events); + else if (request.messageType === HapRequestMessageTypes.READ_CHARACTERISTICS) + this._handleRemoteCharacteristicsRead(request, remoteSession, session, events); + else + debug('[%s] Remote Request Detail: %s', this.accessoryInfo.username, require('util').inspect(request, { + showHidden: false, + depth: null + })); + } + + _onEncrypt = (data: Buffer, encrypted: { data: Buffer; }, session: Session) => { + // instance of HAPEncryption (created in handlePairVerifyStepOne) + var enc = session.encryption; + // if accessoryToControllerKey is not empty, then encryption is enabled for this connection. However, we'll + // need to be careful to ensure that we don't encrypt the last few bytes of the response from handlePairVerifyStepTwo. + // Since all communication calls are asynchronous, we could easily receive this 'encrypt' event for those bytes. + // So we want to make sure that we aren't encrypting data until we have *received* some encrypted data from the + // client first. + if (enc && enc.accessoryToControllerKey.length > 0 && enc.controllerToAccessoryCount.value > 0) { + encrypted.data = encryption.layerEncrypt(data, enc.accessoryToControllerCount, enc.accessoryToControllerKey); + } + } + + _onDecrypt = (data: Buffer, decrypted: { data: number | Buffer; }, session: Session) => { + // possibly an instance of HAPEncryption (created in handlePairVerifyStepOne) + var enc = session.encryption; + // if controllerToAccessoryKey is not empty, then encryption is enabled for this connection. + if (enc && enc.controllerToAccessoryKey.length > 0) { + decrypted.data = encryption.layerDecrypt(data, enc.controllerToAccessoryCount, enc.controllerToAccessoryKey, enc.extraInfo); + } + } + + _onSessionClose = (sessionID: SessionIdentifier, events: any) => { + this.emit(HAPServerEventTypes.SESSION_CLOSE, sessionID, events); + } + + /** + * Unpaired Accessory identification. + */ + _handleIdentify = (request: IncomingMessage, response: ServerResponse, session: Session, events: any, requestData: any) => { + // /identify only works if the accesory 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})); + return; + } + this.emit(HAPServerEventTypes.IDENTIFY, once((err: Error) => { + if (!err) { + debug("[%s] Identification success", this.accessoryInfo.username); + response.writeHead(204); + response.end(); + } else { + debug("[%s] Identification error: %s", this.accessoryInfo.username, err.message); + response.writeHead(500); + response.end(); + } + })); + } + + /** + * iOS <-> Accessory pairing process. + */ + _handlePair = (request: IncomingMessage, response: ServerResponse, session: Session, events: any, requestData: Buffer) => { + // Can only be directly paired with one iOS device + if (!this.allowInsecureRequest && this.accessoryInfo.paired()) { + response.writeHead(403); + response.end(); + return; + } + var objects = tlv.decode(requestData); + var sequence = objects[Types.SEQUENCE_NUM][0]; // value is single byte with sequence number + if (sequence == 0x01) + this._handlePairStepOne(request, response, session); + else if (sequence == 0x03) + this._handlePairStepTwo(request, response, session, objects); + else if (sequence == 0x05) + this._handlePairStepThree(request, response, session, objects); + } + + // M1 + M2 + _handlePairStepOne = (request: IncomingMessage, response: ServerResponse, session: Session) => { + debug("[%s] Pair step 1/5", this.accessoryInfo.username); + var salt = crypto.randomBytes(16); + var srpParams = srp.params["3072"]; + srp.genKey(32, (error: Error, key: Buffer) => { + // create a new SRP server + var srpServer = new srp.Server(srpParams, bufferShim.from(salt), bufferShim.from("Pair-Setup"), bufferShim.from(this.accessoryInfo.pincode), key); + var srpB = srpServer.computeB(); + // attach it to the current TCP session + session.srpServer = srpServer; + response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(Types.SEQUENCE_NUM, 0x02, Types.SALT, salt, Types.PUBLIC_KEY, srpB)); + }); + } + + // M3 + M4 + _handlePairStepTwo = (request: IncomingMessage, response: ServerResponse, session: Session, objects: Record) => { + debug("[%s] Pair step 2/5", this.accessoryInfo.username); + var A = objects[Types.PUBLIC_KEY]; // "A is a public key that exists only for a single login session." + var M1 = objects[Types.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; + srpServer.setA(A); + try { + srpServer.checkM1(M1); + } catch (err) { + // most likely the client supplied an incorrect pincode. + debug("[%s] Error while checking pincode: %s", this.accessoryInfo.username, err.message); + response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(Types.SEQUENCE_NUM, 0x04, Types.ERROR_CODE, Codes.INVALID_REQUEST)); + return; + } + // "M2 is the proof that the server actually knows your password." + var M2 = srpServer.computeM2(); + response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(Types.SEQUENCE_NUM, 0x04, Types.PASSWORD_PROOF, M2)); + } + + // M5-1 + _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[Types.ENCRYPTED_DATA]; + var messageData = bufferShim.alloc(encryptedData.length - 16); + var authTagData = bufferShim.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 = bufferShim.from("Pair-Setup-Encrypt-Salt"); + var encInfo = bufferShim.from("Pair-Setup-Encrypt-Info"); + var outputKey = hkdf.HKDF("sha512", encSalt, S_private, encInfo, 32); + var plaintextBuffer = bufferShim.alloc(messageData.length); + encryption.verifyAndDecrypt(outputKey, bufferShim.from("PS-Msg05"), messageData, authTagData, null, plaintextBuffer); + // decode the client payload and pass it on to the next step + var M5Packet = tlv.decode(plaintextBuffer); + var clientUsername = M5Packet[Types.USERNAME]; + var clientLTPK = M5Packet[Types.PUBLIC_KEY]; + var clientProof = M5Packet[Types.PROOF]; + var hkdfEncKey = outputKey; + this._handlePairStepFour(request, response, session, clientUsername, clientLTPK, clientProof, hkdfEncKey); + } + + // 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 = bufferShim.from("Pair-Setup-Controller-Sign-Salt"); + var controllerInfo = bufferShim.from("Pair-Setup-Controller-Sign-Info"); + var outputKey = hkdf.HKDF("sha512", controllerSalt, S_private, controllerInfo, 32); + var completeData = Buffer.concat([outputKey, clientUsername, clientLTPK]); + if (!tweetnacl.sign.detached.verify(completeData, clientProof, clientLTPK)) { + debug("[%s] Invalid signature", this.accessoryInfo.username); + response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(Types.SEQUENCE_NUM, 0x06, Types.ERROR_CODE, Codes.INVALID_REQUEST)); + return; + } + this._handlePairStepFive(request, response, session, clientUsername, clientLTPK, hkdfEncKey); + } + + // 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 = bufferShim.from("Pair-Setup-Accessory-Sign-Salt"); + var accessoryInfo = bufferShim.from("Pair-Setup-Accessory-Sign-Info"); + var outputKey = hkdf.HKDF("sha512", accessorySalt, S_private, accessoryInfo, 32); + var serverLTPK = this.accessoryInfo.signPk; + var usernameData = bufferShim.from(this.accessoryInfo.username); + var material = Buffer.concat([outputKey, usernameData, serverLTPK]); + var privateKey = bufferShim.from(this.accessoryInfo.signSk); + var serverProof = tweetnacl.sign.detached(material, privateKey); + var message = tlv.encode(Types.USERNAME, usernameData, Types.PUBLIC_KEY, serverLTPK, Types.PROOF, serverProof); + var ciphertextBuffer = bufferShim.alloc(message.length); + var macBuffer = bufferShim.alloc(16); + encryption.encryptAndSeal(hkdfEncKey, bufferShim.from("PS-Msg06"), message, null, ciphertextBuffer, macBuffer); + // finally, notify listeners that we have been paired with a client + 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(); + return; + } + // send final pairing response to client + response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(Types.SEQUENCE_NUM, 0x06, Types.ENCRYPTED_DATA, Buffer.concat([ciphertextBuffer, macBuffer]))); + })); + } + + /** + * iOS <-> Accessory pairing verification. + */ + _handlePairVerify = (request: IncomingMessage, response: ServerResponse, session: Session, events: any, requestData: Buffer) => { + // Don't allow pair-verify without being paired first + if (!this.allowInsecureRequest && !this.accessoryInfo.paired()) { + response.writeHead(403); + response.end(); + return; + } + var objects = tlv.decode(requestData); + var sequence = objects[Types.SEQUENCE_NUM][0]; // value is single byte with sequence number + if (sequence == 0x01) + this._handlePairVerifyStepOne(request, response, session, objects); + else if (sequence == 0x03) + this._handlePairVerifyStepTwo(request, response, session, events, objects); + } + + _handlePairVerifyStepOne = (request: IncomingMessage, response: ServerResponse, session: Session, objects: Record) => { + debug("[%s] Pair verify step 1/2", this.accessoryInfo.username); + var clientPublicKey = objects[Types.PUBLIC_KEY]; // Buffer + // generate new encryption keys for this session + var keyPair = encryption.generateCurve25519KeyPair(); + var secretKey = bufferShim.from(keyPair.secretKey); + var publicKey = bufferShim.from(keyPair.publicKey); + var sharedSec = bufferShim.from(encryption.generateCurve25519SharedSecKey(secretKey, clientPublicKey)); + var usernameData = bufferShim.from(this.accessoryInfo.username); + var material = Buffer.concat([publicKey, usernameData, clientPublicKey]); + var privateKey = bufferShim.from(this.accessoryInfo.signSk); + var serverProof = tweetnacl.sign.detached(material, privateKey); + var encSalt = bufferShim.from("Pair-Verify-Encrypt-Salt"); + var encInfo = bufferShim.from("Pair-Verify-Encrypt-Info"); + var outputKey = hkdf.HKDF("sha512", encSalt, sharedSec, encInfo, 32).slice(0, 32); + // store keys in a new instance of HAPEncryption + var enc = new HAPEncryption(); + enc.clientPublicKey = clientPublicKey; + enc.secretKey = secretKey; + enc.publicKey = publicKey; + enc.sharedSec = sharedSec; + enc.hkdfPairEncKey = outputKey; + // store this in the current TCP session + session.encryption = enc; + // compose the response data in TLV format + var message = tlv.encode(Types.USERNAME, usernameData, Types.PROOF, serverProof); + // encrypt the response + var ciphertextBuffer = bufferShim.alloc(message.length); + var macBuffer = bufferShim.alloc(16); + encryption.encryptAndSeal(outputKey, bufferShim.from("PV-Msg02"), message, null, ciphertextBuffer, macBuffer); + response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(Types.SEQUENCE_NUM, 0x02, Types.ENCRYPTED_DATA, Buffer.concat([ciphertextBuffer, macBuffer]), Types.PUBLIC_KEY, publicKey)); + } + + _handlePairVerifyStepTwo = (request: IncomingMessage, response: ServerResponse, session: Session, events: any, objects: Record) => { + debug("[%s] Pair verify step 2/2", this.accessoryInfo.username); + var encryptedData = objects[Types.ENCRYPTED_DATA]; + var messageData = bufferShim.alloc(encryptedData.length - 16); + var authTagData = bufferShim.alloc(16); + encryptedData.copy(messageData, 0, 0, encryptedData.length - 16); + encryptedData.copy(authTagData, 0, encryptedData.length - 16, encryptedData.length); + var plaintextBuffer = bufferShim.alloc(messageData.length); + // instance of HAPEncryption (created in handlePairVerifyStepOne) + var enc = session.encryption; + if (!encryption.verifyAndDecrypt(enc.hkdfPairEncKey, bufferShim.from("PV-Msg03"), messageData, authTagData, null, plaintextBuffer)) { + debug("[%s] M3: Invalid signature", this.accessoryInfo.username); + response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(Types.ERROR_CODE, Codes.INVALID_REQUEST)); + return; + } + var decoded = tlv.decode(plaintextBuffer); + var clientUsername = decoded[Types.USERNAME]; + var proof = decoded[Types.PROOF]; + var 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()); + // if we're not actually paired, then there's nothing to verify - this client thinks it's paired with us but we + // disagree. Respond with invalid request (seems to match HomeKit Accessory Simulator behavior) + if (!clientPublicKey) { + debug("[%s] Client %s attempting to verify, but we are not paired; rejecting client", this.accessoryInfo.username, clientUsername); + response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(Types.ERROR_CODE, Codes.INVALID_REQUEST)); + return; + } + if (!tweetnacl.sign.detached.verify(material, proof, clientPublicKey)) { + debug("[%s] Client %s provided an invalid signature", this.accessoryInfo.username, clientUsername); + response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(Types.ERROR_CODE, Codes.INVALID_REQUEST)); + return; + } + debug("[%s] Client %s verification complete", this.accessoryInfo.username, clientUsername); + response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(Types.SEQUENCE_NUM, 0x04)); + // now that the client has been verified, we must "upgrade" our pesudo-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 = bufferShim.from("Control-Salt"); + var infoRead = bufferShim.from("Control-Read-Encryption-Key"); + var infoWrite = bufferShim.from("Control-Write-Encryption-Key"); + enc.accessoryToControllerKey = hkdf.HKDF("sha512", encSalt, enc.sharedSec, infoRead, 32); + enc.controllerToAccessoryKey = hkdf.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; + } + + _handleRemotePairVerify = (request: HapRequest, remoteSession: RemoteSession, session: Session) => { + var objects = tlv.decode(request.requestBody); + var sequence = objects[Types.SEQUENCE_NUM][0]; // value is single byte with sequence number + if (sequence == 0x01) + this._handleRemotePairVerifyStepOne(request, remoteSession, session, objects); + else if (sequence == 0x03) + this._handleRemotePairVerifyStepTwo(request, remoteSession, session, objects); + } + + _handleRemotePairVerifyStepOne = (request: HapRequest, remoteSession: RemoteSession, session: Session, objects: Record) => { + debug("[%s] Remote Pair verify step 1/2", this.accessoryInfo.username); + var clientPublicKey = objects[Types.PUBLIC_KEY]; // Buffer + // generate new encryption keys for this session + var keyPair = encryption.generateCurve25519KeyPair(); + var secretKey = bufferShim.from(keyPair.secretKey); + var publicKey = bufferShim.from(keyPair.publicKey); + var sharedSec = bufferShim.from(encryption.generateCurve25519SharedSecKey(secretKey, clientPublicKey)); + var usernameData = bufferShim.from(this.accessoryInfo.username); + var material = Buffer.concat([publicKey, usernameData, clientPublicKey]); + var privateKey = bufferShim.from(this.accessoryInfo.signSk); + var serverProof = tweetnacl.sign.detached(material, privateKey); + var encSalt = bufferShim.from("Pair-Verify-Encrypt-Salt"); + var encInfo = bufferShim.from("Pair-Verify-Encrypt-Info"); + var outputKey = hkdf.HKDF("sha512", encSalt, sharedSec, encInfo, 32).slice(0, 32); + // store keys in a new instance of HAPEncryption + var enc = new HAPEncryption(); + enc.clientPublicKey = clientPublicKey; + enc.secretKey = secretKey; + enc.publicKey = publicKey; + enc.sharedSec = sharedSec; + enc.hkdfPairEncKey = outputKey; + // store this in the current TCP session + session.encryption = enc; + // compose the response data in TLV format + var message = tlv.encode(Types.USERNAME, usernameData, Types.PROOF, serverProof); + // encrypt the response + var ciphertextBuffer = bufferShim.alloc(message.length); + var macBuffer = bufferShim.alloc(16); + encryption.encryptAndSeal(outputKey, bufferShim.from("PV-Msg02"), message, null, ciphertextBuffer, macBuffer); + var response = tlv.encode(Types.SEQUENCE_NUM, 0x02, Types.ENCRYPTED_DATA, Buffer.concat([ciphertextBuffer, macBuffer]), Types.PUBLIC_KEY, publicKey); + remoteSession.responseMessage(request, response); + } + + _handleRemotePairVerifyStepTwo = (request: HapRequest, remoteSession: RemoteSession, session: Session, objects: Record) => { + debug("[%s] Remote Pair verify step 2/2", this.accessoryInfo.username); + var encryptedData = objects[Types.ENCRYPTED_DATA]; + var messageData = bufferShim.alloc(encryptedData.length - 16); + var authTagData = bufferShim.alloc(16); + encryptedData.copy(messageData, 0, 0, encryptedData.length - 16); + encryptedData.copy(authTagData, 0, encryptedData.length - 16, encryptedData.length); + var plaintextBuffer = bufferShim.alloc(messageData.length); + // instance of HAPEncryption (created in handlePairVerifyStepOne) + var enc = session.encryption; + if (!encryption.verifyAndDecrypt(enc.hkdfPairEncKey, bufferShim.from("PV-Msg03"), messageData, authTagData, null, plaintextBuffer)) { + debug("[%s] M3: Invalid signature", this.accessoryInfo.username); + var response = tlv.encode(Types.ERROR_CODE, Codes.INVALID_REQUEST); + remoteSession.responseMessage(request, response); + return; + } + var decoded = tlv.decode(plaintextBuffer); + var clientUsername = decoded[Types.USERNAME]; + var proof = decoded[Types.PROOF]; + var 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()); + // if we're not actually paired, then there's nothing to verify - this client thinks it's paired with us but we + // disagree. Respond with invalid request (seems to match HomeKit Accessory Simulator behavior) + if (!clientPublicKey) { + debug("[%s] Client %s attempting to verify, but we are not paired; rejecting client", this.accessoryInfo.username, clientUsername); + var response = tlv.encode(Types.ERROR_CODE, Codes.INVALID_REQUEST); + remoteSession.responseMessage(request, response); + return; + } + if (!tweetnacl.sign.detached.verify(material, proof, clientPublicKey)) { + debug("[%s] Client %s provided an invalid signature", this.accessoryInfo.username, clientUsername); + var response = tlv.encode(Types.ERROR_CODE, Codes.INVALID_REQUEST); + remoteSession.responseMessage(request, response); + return; + } + debug("[%s] Client %s verification complete", this.accessoryInfo.username, clientUsername); + var encSalt = bufferShim.from("Control-Salt"); + var infoRead = bufferShim.from("Control-Read-Encryption-Key"); + var infoWrite = bufferShim.from("Control-Write-Encryption-Key"); + enc.accessoryToControllerKey = hkdf.HKDF("sha512", encSalt, enc.sharedSec, infoRead, 32); + enc.controllerToAccessoryKey = hkdf.HKDF("sha512", encSalt, enc.sharedSec, infoWrite, 32); + var response = tlv.encode(Types.SEQUENCE_NUM, 0x04); + remoteSession.responseMessage(request, response); + } + + /** + * Pair add/remove + */ + _handlePairings = (request: IncomingMessage, response: ServerResponse, session: Session, events: any, requestData: Buffer) => { + // Only accept /pairing request if there is a secure session + if (!this.allowInsecureRequest && !session.encryption) { + response.writeHead(401, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify({status: Status.INSUFFICIENT_PRIVILEGES})); + return; + } + var objects = tlv.decode(requestData); + var requestType = objects[Types.REQUEST_TYPE][0]; // value is single byte with request type + if (requestType == 3) { + // technically we're already paired and communicating securely if the client is able to call /pairings at all! + // but maybe the client wants to change their public key? so we'll emit 'pair' like we just paired again + debug("[%s] Adding pairing info for client", this.accessoryInfo.username); + var clientUsername = objects[Types.USERNAME]; + var clientLTPK = objects[Types.PUBLIC_KEY]; + 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(); + return; + } + response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(Types.SEQUENCE_NUM, 0x02)); + })); + } else if (requestType == 4) { + debug("[%s] Removing pairing info for client", this.accessoryInfo.username); + var clientUsername = objects[Types.USERNAME]; + this.emit(HAPServerEventTypes.UNPAIR, clientUsername.toString(), once((err?: Error) => { + if (err) { + debug("[%s] Error removing pairing info: %s", this.accessoryInfo.username, err.message); + response.writeHead(500, "Server Error"); + response.end(); + return; + } + response.writeHead(200, {"Content-Type": "application/pairing+tlv8"}); + response.end(tlv.encode(Types.SEQUENCE_NUM, 0x02)); + })); + } + } + + /* + * Handlers for all after-pairing communication, or the bulk of HAP. + */ + + // Called when the client wishes to fetch all data regarding our published Accessories. + _handleAccessories = (request: IncomingMessage, response: ServerResponse, session: Session, events: any, requestData: any) => { + if (!this.allowInsecureRequest && !session.encryption) { + response.writeHead(401, {"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)); + })); + } + + _handleRemoteAccessories = (request: HapRequest, remoteSession: RemoteSession, session: Session) => { + var deserializedRequest = JSON.parse(request.requestBody); + if (!deserializedRequest['attribute-database']) { + var response = { + 'configuration-number': this.accessoryInfo.configVersion + }; + remoteSession.responseMessage(request, bufferShim.from(JSON.stringify(response))); + } else { + var self = this; + // 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); + return; + } + var response = { + 'configuration-number': self.accessoryInfo.configVersion, + 'attribute-database': accessories + }; + remoteSession.responseMessage(request, bufferShim.from(JSON.stringify(response))); + })); + } + } + + // Called when the client wishes to get or set particular characteristics + _handleCharacteristics = (request: IncomingMessage, response: ServerResponse, session: Session, events: any, requestData: { length: number; toString: () => string; }) => { + if (!this.allowInsecureRequest && !session.encryption) { + response.writeHead(401, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify({status: Status.INSUFFICIENT_PRIVILEGES})); + 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' } + 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) + data.push({aid: aid, iid: iid}); + } + this.emit(HAPServerEventTypes.GET_CHARACTERISTICS, data, events, once((err: Error, characteristics: Characteristic[]) => { + if (!characteristics && !err) + err = new Error("characteristics not supplied by the get-characteristics event callback"); + if (err) { + debug("[%s] Error getting characteristics: %s", this.accessoryInfo.username, err.stack); + // rewrite characteristics array to include error status for each characteristic requested + characteristics = []; + for (var i in data) { + characteristics.push({ + aid: data[i].aid, + iid: data[i].iid, + // @ts-ignore + status: Status.SERVICE_COMMUNICATION_FAILURE + }); + } + } + // 207 is "multi-status" since HomeKit may be requesting multiple things and any one can fail independently + response.writeHead(207, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify({characteristics: characteristics})); + }), false, session.sessionID); + } else if (request.method == "PUT") { + if (!session.encryption) { + if (!request.headers || (request.headers && request.headers["authorization"] !== this.accessoryInfo.pincode)) { + response.writeHead(401, {"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})); + return; + } + // requestData is a JSON payload like { characteristics: [ { aid: 1, iid: 8, value: true, ev: true } ] } + var data = JSON.parse(requestData.toString()).characteristics as CharacteristicData[]; // pull out characteristics array + // call out to listeners to retrieve the latest accessories JSON + this.emit(HAPServerEventTypes.SET_CHARACTERISTICS, data, events, once((err: Error, characteristics: Characteristic[]) => { + if (err) { + debug("[%s] Error setting characteristics: %s", this.accessoryInfo.username, err.message); + // rewrite characteristics array to include error status for each characteristic requested + characteristics = []; + for (var i in data) { + characteristics.push({ + aid: data[i].aid, + iid: data[i].iid, + // @ts-ignore + status: Status.SERVICE_COMMUNICATION_FAILURE + }); + } + } + // 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})); + }), false, session.sessionID); + } + } + + // Called when controller request snapshot + _handleResource = (request: IncomingMessage, response: ServerResponse, session: Session, events: any, requestData: { length: number; toString: () => string; }) => { + if (!this.allowInsecureRequest && !session.encryption) { + response.writeHead(401, {"Content-Type": "application/hap+json"}); + response.end(JSON.stringify({status: Status.INSUFFICIENT_PRIVILEGES})); + return; + } + if (this.listeners(HAPServerEventTypes.REQUEST_RESOURCE).length == 0) { + response.writeHead(405); + response.end(); + return; + } + if (request.method == "POST") { + if (!session.encryption) { + if (!request.headers || (request.headers && request.headers["authorization"] !== this.accessoryInfo.pincode)) { + response.writeHead(401, {"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})); + return; + } + // requestData is a JSON payload + var 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) { + debug("[%s] Error getting snapshot: %s", this.accessoryInfo.username, err.message); + response.writeHead(500); + response.end(); + } else { + response.writeHead(200, {"Content-Type": "image/jpeg"}); + response.end(resource); + } + })); + } else { + response.writeHead(405); + response.end(); + } + } + + _handleRemoteCharacteristicsWrite = (request: HapRequest, remoteSession: RemoteSession, session: Session, events: any) => { + var data = JSON.parse(request.requestBody.toString()); + // call out to listeners to retrieve the latest accessories JSON + this.emit(HAPServerEventTypes.SET_CHARACTERISTICS, data, events, once((err: Error, characteristics: Characteristic[]) => { + if (err) { + debug("[%s] Error setting characteristics: %s", this.accessoryInfo.username, err.message); + // rewrite characteristics array to include error status for each characteristic requested + characteristics = []; + for (var i in data) { + characteristics.push({ + aid: data[i].aid, + iid: data[i].iid, + s: Status.SERVICE_COMMUNICATION_FAILURE + } as any); + } + } + remoteSession.responseMessage(request, bufferShim.from(JSON.stringify(characteristics))); + }), true); + } + + _handleRemoteCharacteristicsRead = (request: HapRequest, remoteSession: RemoteSession, session: Session, events: any) => { + var data = JSON.parse(request.requestBody.toString()); + this.emit(HAPServerEventTypes.GET_CHARACTERISTICS, data, events, (err: Error, characteristics: Characteristic[]) => { + if (!characteristics && !err) + err = new Error("characteristics not supplied by the get-characteristics event callback"); + if (err) { + debug("[%s] Error getting characteristics: %s", this.accessoryInfo.username, err.stack); + // rewrite characteristics array to include error status for each characteristic requested + characteristics = []; + for (var i in data) { + characteristics.push({ + aid: data[i].aid, + iid: data[i].iid, + s: Status.SERVICE_COMMUNICATION_FAILURE + } as any); + } + } + remoteSession.responseMessage(request, bufferShim.from(JSON.stringify(characteristics))); + }, true); + } +} + + +/** + * Simple struct to hold vars needed to support HAP encryption. + */ + +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 = bufferShim.alloc(0); + this.secretKey = bufferShim.alloc(0); + this.publicKey = bufferShim.alloc(0); + this.sharedSec = bufferShim.alloc(0); + this.hkdfPairEncKey = bufferShim.alloc(0); + this.accessoryToControllerCount = { value: 0 }; + this.controllerToAccessoryCount = { value: 0 }; + this.accessoryToControllerKey = bufferShim.alloc(0); + this.controllerToAccessoryKey = bufferShim.alloc(0); + this.extraInfo = {}; + } +} diff --git a/src/lib/Service.spec.ts b/src/lib/Service.spec.ts new file mode 100644 index 000000000..90c09a6c2 --- /dev/null +++ b/src/lib/Service.spec.ts @@ -0,0 +1,80 @@ +import { Service, ServiceEventTypes } from './Service'; +import { generate } from './util/uuid'; +import './gen'; +import { Characteristic } from './Characteristic'; + +const createService = () => { + return new Service('Test', generate('Foo'), 'subtype'); +} + +describe('Service', () => { + + describe('#constructor()', () => { + it('should set the name characteristic to the display name', () => { + const service = createService(); + expect(service.getCharacteristic(Characteristic.Name)!.value).toEqual('Test'); + }); + + it('should fail to load with no UUID', () => { + expect(() => { + new Service('Test', '', 'subtype'); + }).toThrow('valid UUID'); + }); + }); + + describe('#addCharacteristic()', () => { + + }); + + describe('#setHiddenService()', () => { + + }); + + describe('#addLinkedService()', () => { + + }); + + describe('#removeLinkedService()', () => { + + }); + + describe('#removeCharacteristic()', () => { + + }); + + describe('#getCharacteristic()', () => { + + }); + + describe('#testCharacteristic()', () => { + + }); + + describe('#setCharacteristic()', () => { + + }); + + describe('#updateCharacteristic()', () => { + + }); + + describe('#addOptionalCharacteristic()', () => { + + }); + + describe('#getCharacteristicByIID()', () => { + + }); + + describe('#toHAP()', () => { + + }); + + describe(`@${ServiceEventTypes.CHARACTERISTIC_CHANGE}`, () => { + + }); + + describe(`@${ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE}`, () => { + + }); +}); diff --git a/src/lib/Service.ts b/src/lib/Service.ts new file mode 100644 index 000000000..ff8d21496 --- /dev/null +++ b/src/lib/Service.ts @@ -0,0 +1,358 @@ +import { + Characteristic, + CharacteristicEventTypes +} from './Characteristic'; +import { clone } from './util/clone'; +import { EventEmitter } from './EventEmitter'; +import { IdentifierCache } from './model/IdentifierCache'; +import { + CharacteristicChange, + CharacteristicValue, + HapCharacteristic, + HapService, + Nullable, + ToHAPOptions, + WithUUID, +} from '../types'; +import * as HomeKitTypes from './gen'; + +export enum ServiceEventTypes { + CHARACTERISTIC_CHANGE = "characteristic-change", + SERVICE_CONFIGURATION_CHANGE = "service-configurationChange", +} + +export type ServiceConfigurationChange = { + service: Service; +}; + +type Events = { + [ServiceEventTypes.CHARACTERISTIC_CHANGE]: (change: CharacteristicChange) => void; + [ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE]: (change: ServiceConfigurationChange) => void; +} + +/** + * @deprecated Use ServiceEventTypes instead + */ +export type EventService = ServiceEventTypes.CHARACTERISTIC_CHANGE | ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE; + +/** + * Service represents a set of grouped values necessary to provide a logical function. For instance, a + * "Door Lock Mechanism" service might contain two values, one for the "desired lock state" and one for the + * "current lock state". A particular Service is distinguished from others by its "type", which is a UUID. + * HomeKit provides a set of known Service UUIDs defined in HomeKit.ts along with a corresponding + * concrete subclass that you can instantiate directly to setup the necessary values. These natively-supported + * Services are expected to contain a particular set of Characteristics. + * + * Unlike Characteristics, where you cannot have two Characteristics with the same UUID in the same Service, + * you can actually have multiple Services with the same UUID in a single Accessory. For instance, imagine + * a Garage Door Opener with both a "security light" and a "backlight" for the display. Each light could be + * a "Lightbulb" Service with the same UUID. To account for this situation, we define an extra "subtype" + * property on Service, that can be a string or other string-convertible object that uniquely identifies the + * Service among its peers in an Accessory. For instance, you might have `service1.subtype = 'security_light'` + * for one and `service2.subtype = 'backlight'` for the other. + * + * You can also define custom Services by providing your own UUID for the type that you generate yourself. + * Custom Services can contain an arbitrary set of Characteristics, but Siri will likely not be able to + * work with these. + * + * @event 'characteristic-change' => function({characteristic, oldValue, newValue, context}) { } + * Emitted after a change in the value of one of our Characteristics has occurred. + */ +export class Service extends EventEmitter { + + static AccessoryInformation: typeof HomeKitTypes.Generated.AccessoryInformation; + static AirPurifier: typeof HomeKitTypes.Generated.AirPurifier; + static AirQualitySensor: typeof HomeKitTypes.Generated.AirQualitySensor; + 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 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; + /** + * @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; + 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 Slat: typeof HomeKitTypes.Generated.Slat; + static SmokeSensor: typeof HomeKitTypes.Generated.SmokeSensor; + 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 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; + + iid: Nullable = null; // assigned later by our containing Accessory + name: Nullable = null; + characteristics: Characteristic[] = []; + optionalCharacteristics: Characteristic[] = []; + isHiddenService?: boolean = false; + isPrimaryService: boolean = false; + linkedServices: Service[] = []; + + constructor(public displayName: string, public UUID: string, public subtype: string) { + super(); + if (!UUID) throw new Error("Services must be created with a valid UUID."); + + // every service has an optional Characteristic.Name property - we'll set it to our displayName + // if one was given + // if you don't provide a display name, some HomeKit apps may choose to hide the device. + if (displayName) { + // create the characteristic if necessary + var nameCharacteristic = + this.getCharacteristic(Characteristic.Name) || + this.addCharacteristic(Characteristic.Name); + + nameCharacteristic.setValue(displayName); + } + } + + addCharacteristic = (characteristic: typeof Characteristic | 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[0], constructorArgs[1], constructorArgs[2]) as Characteristic; + } + // check for UUID conflict + for (var index in this.characteristics) { + var 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. + return existing; + } + throw new Error("Cannot add a Characteristic with the same UUID as another Characteristic in this Service: " + existing.UUID); + } + } + + // 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.characteristics.push(characteristic); + + this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({ service: this })); + + return characteristic; + } + +//Defines this service as hidden + setHiddenService = (isHidden: boolean) => { + this.isHiddenService = isHidden; + this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({ service: this })); + } + +//Allows setting other services that link to this one. + addLinkedService = (newLinkedService: Service) => { + //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 })); + } + + 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); + this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({ service: this })); + } + + removeCharacteristic = (characteristic: Characteristic) => { + var targetCharacteristicIndex; + + for (var index in this.characteristics) { + var existingCharacteristic = this.characteristics[index]; + + if (existingCharacteristic === characteristic) { + targetCharacteristicIndex = index; + break; + } + } + + if (targetCharacteristicIndex) { + this.characteristics.splice(Number.parseInt(targetCharacteristicIndex), 1); + characteristic.removeAllListeners(); + + this.emit(ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE, clone({ service: this })); + } + } + + getCharacteristic = >(name: string | T) => { + + // returns a characteristic object from the service + // If Service.prototype.getCharacteristic(Characteristic.Type) does not find the characteristic, + // but the type is in optionalCharacteristics, it adds the characteristic.type to the service and returns it. + + var index, characteristic: Characteristic; + for (index in this.characteristics) { + characteristic = this.characteristics[index] as Characteristic; + if (typeof name === 'string' && characteristic.displayName === name) { + return characteristic; + } else if (typeof name === 'function' && ((characteristic instanceof name) || (name.UUID === characteristic.UUID))) { + return characteristic; + } + } + if (typeof name === 'function') { + for (index in this.optionalCharacteristics) { + characteristic = this.optionalCharacteristics[index]; + if ((characteristic instanceof name) || (name.UUID === characteristic.UUID)) { + return this.addCharacteristic(name); + } + } + //Not found in optional Characteristics. Adding anyway, but warning about it if it isn't the Name. + if (name !== Characteristic.Name) { + console.warn("HAP Warning: Characteristic %s not in required or optional characteristics for service %s. Adding anyway.", name.UUID, this.UUID); + return this.addCharacteristic(name); + } + } + } + + testCharacteristic = (name: string | Characteristic) => { + // checks for the existence of a characteristic object in the service + var index, characteristic; + for (index in this.characteristics) { + characteristic = this.characteristics[index]; + if (typeof name === 'string' && characteristic.displayName === name) { + return true; + } else if (typeof name === 'function' && ((characteristic instanceof name) || ((name as Characteristic).UUID === characteristic.UUID))) { + return true; + } + } + return false; + } + + setCharacteristic = >(name: string | T, value: CharacteristicValue) => { + 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, value: CharacteristicValue) => { + this.getCharacteristic(name)!.updateValue(value); + return this; + } + + addOptionalCharacteristic = (characteristic: Characteristic | typeof Characteristic) => { + // characteristic might be a constructor like `Characteristic.Brightness` instead of an instance + // of Characteristic. Coerce if necessary. + if (typeof characteristic === 'function') + characteristic = new characteristic() as Characteristic; + + this.optionalCharacteristics.push(characteristic); + } + + getCharacteristicByIID = (iid: number) => { + for (var index in this.characteristics) { + var characteristic = this.characteristics[index]; + if (characteristic.iid === iid) + return characteristic; + } + } + + _assignIDs = (identifierCache: IdentifierCache, accessoryName: string, baseIID: number = 0) => { + // the Accessory Information service must have a (reserved by IdentifierCache) ID of 1 + if (this.UUID === '0000003E-0000-1000-8000-0026BB765291') { + this.iid = 1; + } else { + // assign our own ID based on our UUID + this.iid = baseIID + identifierCache.getIID(accessoryName, this.UUID, this.subtype); + } + + // assign IIDs to our Characteristics + for (var index in this.characteristics) { + var characteristic = this.characteristics[index]; + characteristic._assignID(identifierCache, accessoryName, this.UUID, this.subtype); + } + } + + /** + * Returns a JSON representation of this Accessory suitable for delivering to HAP clients. + */ + toHAP = (opt?: ToHAPOptions) => { + + var characteristicsHAP = []; + + for (var index in this.characteristics) { + var characteristic = this.characteristics[index]; + characteristicsHAP.push(characteristic.toHAP(opt)); + } + + const hap: Partial = { + iid: this.iid!, + type: this.UUID, + characteristics: characteristicsHAP + }; + + if (this.isPrimaryService !== undefined) { + hap['primary'] = this.isPrimaryService; + } + + if (this.isHiddenService !== undefined) { + hap['hidden'] = this.isHiddenService; + } + + if (this.linkedServices.length > 0) { + hap['linked'] = []; + for (var index in this.linkedServices) { + var otherService = this.linkedServices[index]; + hap['linked'].push(otherService.iid!); + } + } + + return hap as HapService; + } + + _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 })); + }); + } + + _sideloadCharacteristics = (targetCharacteristics: Characteristic[]) => { + for (var index in targetCharacteristics) { + var target = targetCharacteristics[index]; + this._setupCharacteristic(target); + } + + this.characteristics = targetCharacteristics.slice(); + } +} diff --git a/src/lib/StreamController.ts b/src/lib/StreamController.ts new file mode 100644 index 000000000..706418650 --- /dev/null +++ b/src/lib/StreamController.ts @@ -0,0 +1,1011 @@ +import crypto from 'crypto'; + +import ip from 'ip'; +import bufferShim from 'buffer-shims'; +import createDebug from 'debug'; + +import * as tlv from './util/tlv'; +import { Service } from './Service'; +import { + Characteristic, + CharacteristicEventTypes, + CharacteristicGetCallback, + CharacteristicSetCallback +} from './Characteristic'; +import RTPProxy from './camera/RTPProxy'; +import { EventEmitter } from './EventEmitter'; +import { Camera } from './Camera'; +import { + Address, + AudioInfo, CharacteristicValue, + NodeCallback, + Nullable, + SessionIdentifier, + Source, + StreamAudioParams, + StreamVideoParams, + VideoInfo, + VoidCallback, +} from '../types'; + +const debug = createDebug('StreamController'); + +export enum SetupTypes { + SESSION_ID = 0x01, + STATUS = 0x02, + ADDRESS = 0x03, + VIDEO_SRTP_PARAM = 0x04, + AUDIO_SRTP_PARAM = 0x05, + VIDEO_SSRC = 0x06, + AUDIO_SSRC = 0x07 +} + +export enum SetupStatus { + SUCCESS = 0x00, + BUSY = 0x01, + ERROR = 0x02 +} + +export enum SetupAddressVer { + IPV4 = 0x00, + IPV6 = 0x01 +} + +export enum SetupAddressInfo { + ADDRESS_VER = 0x01, + ADDRESS = 0x02, + VIDEO_RTP_PORT = 0x03, + AUDIO_RTP_PORT = 0x04 +} + +export enum SetupSRTP_PARAM { + CRYPTO = 0x01, + MASTER_KEY = 0x02, + MASTER_SALT = 0x03 +} + +export enum StreamingStatus { + AVAILABLE = 0x00, + STREAMING = 0x01, + BUSY = 0x02 +} + +export enum RTPConfigTypes { + CRYPTO = 0x02 +} + +export enum SRTPCryptoSuites { + AES_CM_128_HMAC_SHA1_80 = 0x00, + AES_CM_256_HMAC_SHA1_80 = 0x01, + NONE = 0x02 +} + +export enum VideoTypes { + CODEC = 0x01, + CODEC_PARAM = 0x02, + ATTRIBUTES = 0x03, + RTP_PARAM = 0x04 +} + +export enum VideoCodecTypes { + H264 = 0x00 +} + +export enum VideoCodecParamTypes { + PROFILE_ID = 0x01, + LEVEL = 0x02, + PACKETIZATION_MODE = 0x03, + CVO_ENABLED = 0x04, + CVO_ID = 0x05 +} + +export enum VideoCodecParamCVOTypes { + UNSUPPORTED = 0x01, + SUPPORTED = 0x02 +} + +export enum VideoCodecParamProfileIDTypes { + BASELINE = 0x00, + MAIN = 0x01, + HIGH = 0x02 +} + +export enum VideoCodecParamLevelTypes { + TYPE3_1 = 0x00, + TYPE3_2 = 0x01, + TYPE4_0 = 0x02 +} + +export enum VideoCodecParamPacketizationModeTypes { + NON_INTERLEAVED = 0x00 +} + +export enum VideoAttributesTypes { + IMAGE_WIDTH = 0x01, + IMAGE_HEIGHT = 0x02, + FRAME_RATE = 0x03 +} + +export enum SelectedStreamConfigurationTypes { + SESSION = 0x01, + VIDEO = 0x02, + AUDIO = 0x03 +} + +export enum RTPParamTypes { + PAYLOAD_TYPE = 0x01, + SYNCHRONIZATION_SOURCE = 0x02, + MAX_BIT_RATE = 0x03, + RTCP_SEND_INTERVAL = 0x04, + MAX_MTU = 0x05, + COMFORT_NOISE_PAYLOAD_TYPE = 0x06 +} + +export enum AudioTypes { + CODEC = 0x01, + CODEC_PARAM = 0x02, + RTP_PARAM = 0x03, + COMFORT_NOISE = 0x04 +} + +export enum AudioCodecTypes { + PCMU = 0x00, + PCMA = 0x01, + AACELD = 0x02, + OPUS = 0x03 +} + +export enum AudioCodecParamTypes { + CHANNEL = 0x01, + BIT_RATE = 0x02, + SAMPLE_RATE = 0x03, + PACKET_TIME = 0x04 +} + +export enum AudioCodecParamBitRateTypes { + VARIABLE = 0x00, + CONSTANT = 0x01 +} + +export enum AudioCodecParamSampleRateTypes { + KHZ_8 = 0x00, + KHZ_16 = 0x01, + KHZ_24 = 0x02 +} + +export type StreamControllerOptions = { + proxy: boolean; + disable_audio_proxy: boolean; + srtp: boolean; + audio: StreamAudioParams; + video: StreamVideoParams; +} + +export type HandleSetupReadCallback = NodeCallback; +export type HandleSetupWriteCallback = () => any; +export type HandleStartStreamCallback = VoidCallback; + +export enum StreamRequestTypes { + RECONFIGURE = 'reconfigure', + START = 'start', + STOP = 'stop', +} + +export type StreamRequest = { + sessionID: SessionIdentifier; + type: StreamRequestTypes; + video?: VideoInfo; + audio?: AudioInfo; +} + +export type StartStreamRequest = { + sessionID: SessionIdentifier; + type: StreamRequestTypes.START; + audio: AudioInfo; + video: VideoInfo; +} + +export type ReconfigureStreamRequest = { + sessionID: SessionIdentifier; + type: StreamRequestTypes.RECONFIGURE; + audio: AudioInfo; + video: VideoInfo; +} + +export type StopStreamRequest = { + sessionID: SessionIdentifier; + type: StreamRequestTypes.STOP; +} + +export type PrepareStreamRequest = { + sessionID: SessionIdentifier; + targetAddress: string; + audio: Source; + video: Source; +} + +export type PreparedStreamRequestCallback = (response: PreparedStreamResponse) => void; + +export type SourceResponse = { + ssrc: number; + proxy_pt: string; + proxy_server_address: string; + proxy_server_rtp: number; + proxy_server_rtcp: number; +} +export type PreparedStreamResponse = { + address: Address; + audio: Source & Partial; + video: Source & Partial; +} + +export class StreamController { + + static SetupTypes = SetupTypes; + static SetupStatus = SetupStatus; + static SetupAddressVer = SetupAddressVer; + static SetupAddressInfo = SetupAddressInfo; + static SetupSRTP_PARAM = SetupSRTP_PARAM; + static StreamingStatus = StreamingStatus; + static RTPConfigTypes = RTPConfigTypes; + static SRTPCryptoSuites = SRTPCryptoSuites; + static VideoTypes = VideoTypes; + static VideoCodecTypes = VideoCodecTypes; + static VideoCodecParamTypes = VideoCodecParamTypes; + static VideoCodecParamCVOTypes = VideoCodecParamCVOTypes; + static VideoCodecParamProfileIDTypes = VideoCodecParamProfileIDTypes; + static VideoCodecParamLevelTypes = VideoCodecParamLevelTypes; + static VideoCodecParamPacketizationModeTypes = VideoCodecParamPacketizationModeTypes; + static VideoAttributesTypes = VideoAttributesTypes; + static SelectedStreamConfigurationTypes = SelectedStreamConfigurationTypes; + static RTPParamTypes = RTPParamTypes; + static AudioTypes = AudioTypes; + static AudioCodecTypes = AudioCodecTypes; + static AudioCodecParamTypes = AudioCodecParamTypes; + static AudioCodecParamBitRateTypes = AudioCodecParamBitRateTypes; + static AudioCodecParamSampleRateTypes = AudioCodecParamSampleRateTypes; + + requireProxy: boolean; + disableAudioProxy: boolean; + supportSRTP: boolean; + supportedRTPConfiguration: string; + supportedVideoStreamConfiguration: string; + supportedAudioStreamConfiguration: string; + selectedConfiguration: null; + sessionIdentifier: Nullable; + streamStatus: StreamingStatus; + videoOnly: boolean; + connectionID?: string; + + service?: Service; + setupResponse?: string; + + audioProxy: any; + videoProxy: any; + + constructor( + public identifier: any, + public options: StreamControllerOptions, + public cameraSource?: Camera + ) { + if (identifier === undefined) { + throw new Error('Identifier cannot be undefined'); + } + + if (!options) { + throw new Error('Options cannot be undefined'); + } + + if (!cameraSource) { + throw new Error('CameraSource cannot be undefined'); + } + + this.identifier = identifier; + this.cameraSource = cameraSource; + + this.requireProxy = options["proxy"] || false; + this.disableAudioProxy = options["disable_audio_proxy"] || false; + + this.supportSRTP = options["srtp"] || false; + + this.supportedRTPConfiguration = this._supportedRTPConfiguration(this.supportSRTP); + + let videoParams = options["video"]; + if (!videoParams) { + throw new Error('Video parameters cannot be undefined in options'); + } + + this.supportedVideoStreamConfiguration = this._supportedVideoStreamConfiguration(videoParams); + + let audioParams = options["audio"]; + if (!audioParams) { + throw new Error('Audio parameters cannot be undefined in options'); + } + + this.supportedAudioStreamConfiguration = this._supportedAudioStreamConfiguration(audioParams); + + this.selectedConfiguration = null; + this.sessionIdentifier = null; + this.streamStatus = StreamingStatus.AVAILABLE; + this.videoOnly = false; + + this._createService(); + } + + forceStop = () => { + this.connectionID = undefined; + this._handleStopStream(undefined, true); + } + + handleCloseConnection = (connectionID: string) => { + if (this.connectionID && this.connectionID == connectionID) { + this.connectionID = undefined; + this._handleStopStream(); + } + } + +// Private + _createService = () => { + var managementService = new Service.CameraRTPStreamManagement('', this.identifier.toString()); + + managementService + .getCharacteristic(Characteristic.StreamingStatus)! + .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { + var data = tlv.encode( 0x01, this.streamStatus ); + callback(null, data.toString('base64')); + }); + + managementService + .getCharacteristic(Characteristic.SupportedRTPConfiguration)! + .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { + callback(null, this.supportedRTPConfiguration); + }); + + managementService + .getCharacteristic(Characteristic.SupportedVideoStreamConfiguration)! + .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { + callback(null, this.supportedVideoStreamConfiguration); + }); + + managementService + .getCharacteristic(Characteristic.SupportedAudioStreamConfiguration)! + .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { + callback(null, this.supportedAudioStreamConfiguration); + }); + + managementService + .getCharacteristic(Characteristic.SelectedRTPStreamConfiguration)! + .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { + debug('Read SelectedStreamConfiguration'); + callback(null, this.selectedConfiguration); + }) + .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback, context?: any, connectionID?: string) => { + debug('Write SelectedStreamConfiguration'); + this._handleSelectedStreamConfigurationWrite(value, callback, connectionID!); + }); + + managementService + .getCharacteristic(Characteristic.SetupEndpoints)! + .on(CharacteristicEventTypes.GET, (callback: CharacteristicGetCallback) => { + this._handleSetupRead(callback); + }) + .on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => { + this._handleSetupWrite(value, callback); + }); + + this.service = managementService; + } + + _handleSelectedStreamConfigurationWrite = (value: any, callback: any, connectionID: string) => { + this.selectedConfiguration = value; + + var data = bufferShim.from(value, 'base64'); + var objects = tlv.decode(data); + + var session; + + if(objects[SelectedStreamConfigurationTypes.SESSION]) { + session = tlv.decode(objects[SelectedStreamConfigurationTypes.SESSION]); + this.sessionIdentifier = session[0x01]; + + let requestType = session[0x02][0]; + if (requestType == 1) { + if (this.connectionID && this.connectionID != connectionID) { + debug("Received start stream request from a different connection."); + } else { + this.connectionID = connectionID; + } + + this._handleStartStream(objects, session, false, callback); + } else if (requestType == 0) { + if (this.connectionID && this.connectionID != connectionID) { + debug("Received stop stream request from a different connection.") + } else { + this.connectionID = undefined; + } + + this._handleStopStream(callback); + } else if (requestType == 4) { + this._handleStartStream(objects, session, true, callback); + } else { + debug("Unhandled request type: ", requestType); + callback(); + } + } else { + debug("Unexpected request for Selected Stream Configuration"); + callback(); + } + } + + _handleStartStream = (objects: Record, session: any, reconfigure: boolean = false, callback: HandleStartStreamCallback) => { + + var request: Partial = { + sessionID: this.sessionIdentifier!, + type: !reconfigure ? StreamRequestTypes.START : StreamRequestTypes.RECONFIGURE + }; + + let videoPT = null; + let audioPT = null; + + if(objects[SelectedStreamConfigurationTypes.VIDEO]) { + var videoInfo: Partial = {}; + + var video = tlv.decode(objects[SelectedStreamConfigurationTypes.VIDEO]); + var codec = video[VideoTypes.CODEC]; + + if (video[VideoTypes.CODEC_PARAM]) { + var videoCodecParamsTLV = tlv.decode(video[VideoTypes.CODEC_PARAM]); + videoInfo["profile"] = videoCodecParamsTLV[VideoCodecParamTypes.PROFILE_ID].readUInt8(0); + videoInfo["level"] = videoCodecParamsTLV[VideoCodecParamTypes.LEVEL].readUInt8(0); + } + + if (video[VideoTypes.ATTRIBUTES]) { + var videoAttrTLV = tlv.decode(video[VideoTypes.ATTRIBUTES]); + + videoInfo["width"] = videoAttrTLV[VideoAttributesTypes.IMAGE_WIDTH].readUInt16LE(0); + videoInfo["height"] = videoAttrTLV[VideoAttributesTypes.IMAGE_HEIGHT].readUInt16LE(0); + videoInfo["fps"] = videoAttrTLV[VideoAttributesTypes.FRAME_RATE].readUInt8(0); + } + + if (video[VideoTypes.RTP_PARAM]) { + var videoRTPParamsTLV = tlv.decode(video[VideoTypes.RTP_PARAM]); + + if (videoRTPParamsTLV[RTPParamTypes.SYNCHRONIZATION_SOURCE]) { + videoInfo["ssrc"] = videoRTPParamsTLV[RTPParamTypes.SYNCHRONIZATION_SOURCE].readUInt32LE(0); + } + + if (videoRTPParamsTLV[RTPParamTypes.PAYLOAD_TYPE]) { + videoPT = videoRTPParamsTLV[RTPParamTypes.PAYLOAD_TYPE].readUInt8(0); + videoInfo["pt"] = videoPT; + } + + if (videoRTPParamsTLV[RTPParamTypes.MAX_BIT_RATE]) { + videoInfo["max_bit_rate"] = videoRTPParamsTLV[RTPParamTypes.MAX_BIT_RATE].readUInt16LE(0); + } + + if (videoRTPParamsTLV[RTPParamTypes.RTCP_SEND_INTERVAL]) { + videoInfo["rtcp_interval"] = videoRTPParamsTLV[RTPParamTypes.RTCP_SEND_INTERVAL].readUInt32LE(0); + } + + if (videoRTPParamsTLV[RTPParamTypes.MAX_MTU]) { + videoInfo["mtu"] = videoRTPParamsTLV[RTPParamTypes.MAX_MTU].readUInt16LE(0); + } + } + + request["video"] = videoInfo as VideoInfo; + } + + if(objects[SelectedStreamConfigurationTypes.AUDIO]) { + var audioInfo: Partial = {}; + + var audio = tlv.decode(objects[SelectedStreamConfigurationTypes.AUDIO]); + + var codec = audio[AudioTypes.CODEC]; + var audioCodecParamsTLV = tlv.decode(audio[AudioTypes.CODEC_PARAM]); + var audioRTPParamsTLV = tlv.decode(audio[AudioTypes.RTP_PARAM]); + var comfortNoise = tlv.decode(audio[AudioTypes.COMFORT_NOISE]); + + let audioCodec = codec.readUInt8(0); + if (audioCodec !== undefined) { + if (audioCodec == AudioCodecTypes.OPUS) { + audioInfo["codec"] = "OPUS"; + } else if (audioCodec == AudioCodecTypes.AACELD) { + audioInfo["codec"] = "AAC-eld"; + } else { + debug("Unexpected audio codec: %s", audioCodec); + audioInfo["codec"] = audioCodec; + } + } + + audioInfo["channel"] = audioCodecParamsTLV[AudioCodecParamTypes.CHANNEL].readUInt8(0); + audioInfo["bit_rate"] = audioCodecParamsTLV[AudioCodecParamTypes.BIT_RATE].readUInt8(0); + + let sample_rate_enum = audioCodecParamsTLV[AudioCodecParamTypes.SAMPLE_RATE].readUInt8(0); + if (sample_rate_enum !== undefined) { + if (sample_rate_enum == AudioCodecParamSampleRateTypes.KHZ_8) { + audioInfo["sample_rate"] = 8; + } else if (sample_rate_enum == AudioCodecParamSampleRateTypes.KHZ_16) { + audioInfo["sample_rate"] = 16; + } else if (sample_rate_enum == AudioCodecParamSampleRateTypes.KHZ_24) { + audioInfo["sample_rate"] = 24; + } else { + debug("Unexpected audio sample rate: %s", sample_rate_enum); + } + } + + audioInfo["packet_time"] = audioCodecParamsTLV[AudioCodecParamTypes.PACKET_TIME].readUInt8(0); + + var ssrc = audioRTPParamsTLV[RTPParamTypes.SYNCHRONIZATION_SOURCE].readUInt32LE(0); + audioPT = audioRTPParamsTLV[RTPParamTypes.PAYLOAD_TYPE].readUInt8(0); + + audioInfo["pt"] = audioPT; + audioInfo["ssrc"] = ssrc; + audioInfo["max_bit_rate"] = audioRTPParamsTLV[RTPParamTypes.MAX_BIT_RATE].readUInt16LE(0); + audioInfo["rtcp_interval"] = audioRTPParamsTLV[RTPParamTypes.RTCP_SEND_INTERVAL].readUInt32LE(0); + audioInfo["comfort_pt"] = audioRTPParamsTLV[RTPParamTypes.COMFORT_NOISE_PAYLOAD_TYPE].readUInt8(0); + + request["audio"] = audioInfo as AudioInfo; + } + + if (!reconfigure && this.requireProxy) { + this.videoProxy.setOutgoingPayloadType(videoPT); + if (!this.disableAudioProxy) { + this.audioProxy.setOutgoingPayloadType(audioPT); + } + } + + this.cameraSource && this.cameraSource.handleStreamRequest(request as StreamRequest); + + this._updateStreamStatus(StreamingStatus.STREAMING); + callback(); + } + + _handleStopStream = (callback?: VoidCallback, silent: boolean = false) => { + + let request: Partial = { + "sessionID": this.sessionIdentifier!, + "type": StreamRequestTypes.STOP, + }; + + if (!silent) { + this.cameraSource && this.cameraSource.handleStreamRequest(request as StopStreamRequest); + } + + if (this.requireProxy) { + this.videoProxy.destroy(); + if (!this.disableAudioProxy) { + this.audioProxy.destroy(); + } + + this.videoProxy = undefined; + this.audioProxy = undefined; + } + + this._updateStreamStatus(StreamingStatus.AVAILABLE); + + if (callback) { + callback(); + } + } + + _handleSetupWrite = (value: CharacteristicValue, callback: HandleSetupWriteCallback) => { + + var data = bufferShim.from(`${value}`, 'base64'); + var objects = tlv.decode(data) as any; + + this.sessionIdentifier = objects[SetupTypes.SESSION_ID]; + + // Address + var targetAddressPayload = objects[SetupTypes.ADDRESS]; + var processedAddressInfo = tlv.decode(targetAddressPayload) as any; + var isIPv6 = processedAddressInfo[SetupAddressInfo.ADDRESS_VER][0]; + var targetAddress = processedAddressInfo[SetupAddressInfo.ADDRESS].toString('utf8'); + var targetVideoPort = processedAddressInfo[SetupAddressInfo.VIDEO_RTP_PORT].readUInt16LE(0); + var targetAudioPort = processedAddressInfo[SetupAddressInfo.AUDIO_RTP_PORT].readUInt16LE(0); + + // Video SRTP Params + var videoSRTPPayload = objects[SetupTypes.VIDEO_SRTP_PARAM]; + var processedVideoInfo = tlv.decode(videoSRTPPayload) as any; + var videoCryptoSuite = processedVideoInfo[SetupSRTP_PARAM.CRYPTO][0]; + var videoMasterKey = processedVideoInfo[SetupSRTP_PARAM.MASTER_KEY]; + var videoMasterSalt = processedVideoInfo[SetupSRTP_PARAM.MASTER_SALT]; + + // Audio SRTP Params + var audioSRTPPayload = objects[SetupTypes.AUDIO_SRTP_PARAM]; + var processedAudioInfo = tlv.decode(audioSRTPPayload) as any; + var audioCryptoSuite = processedAudioInfo[SetupSRTP_PARAM.CRYPTO][0]; + var audioMasterKey = processedAudioInfo[SetupSRTP_PARAM.MASTER_KEY]; + var audioMasterSalt = processedAudioInfo[SetupSRTP_PARAM.MASTER_SALT]; + + debug( + '\nSession: ', this.sessionIdentifier, + '\nControllerAddress: ', targetAddress, + '\nVideoPort: ', targetVideoPort, + '\nAudioPort: ', targetAudioPort, + '\nVideo Crypto: ', videoCryptoSuite, + '\nVideo Master Key: ', videoMasterKey, + '\nVideo Master Salt: ', videoMasterSalt, + '\nAudio Crypto: ', audioCryptoSuite, + '\nAudio Master Key: ', audioMasterKey, + '\nAudio Master Salt: ', audioMasterSalt + ); + + var request: Partial = { + "sessionID": this.sessionIdentifier!, + }; + + var videoInfo: Partial = {}; + + var audioInfo: Partial = {}; + + if (this.supportSRTP) { + videoInfo["srtp_key"] = videoMasterKey; + videoInfo["srtp_salt"] = videoMasterSalt; + + audioInfo["srtp_key"] = audioMasterKey; + audioInfo["srtp_salt"] = audioMasterSalt; + } + + if (!this.requireProxy) { + request["targetAddress"] = targetAddress; + + videoInfo["port"] = targetVideoPort; + audioInfo["port"] = targetAudioPort; + + request["video"] = videoInfo as Source; + request["audio"] = audioInfo as Source; + + this.cameraSource && this.cameraSource.prepareStream(request as PrepareStreamRequest, (response: PreparedStreamResponse) => { + this._generateSetupResponse(this.sessionIdentifier!, response, callback); + }); + } else { + request["targetAddress"] = ip.address(); + var promises = []; + + var videoSSRCNumber = crypto.randomBytes(4).readUInt32LE(0); + this.videoProxy = new RTPProxy({ + outgoingAddress: targetAddress, + outgoingPort: targetVideoPort, + outgoingSSRC: videoSSRCNumber, + disabled: false + }); + + promises.push(this.videoProxy.setup()); + + if (!this.disableAudioProxy) { + var audioSSRCNumber = crypto.randomBytes(4).readUInt32LE(0); + + this.audioProxy = new RTPProxy({ + outgoingAddress: targetAddress, + outgoingPort: targetAudioPort, + outgoingSSRC: audioSSRCNumber, + disabled: this.videoOnly + }); + + promises.push(this.audioProxy.setup()); + } else { + audioInfo["port"] = targetAudioPort; + audioInfo["targetAddress"] = targetAddress; + } + + Promise.all(promises).then(() => { + videoInfo["proxy_rtp"] = this.videoProxy.incomingRTPPort(); + videoInfo["proxy_rtcp"] = this.videoProxy.incomingRTCPPort(); + + if (!this.disableAudioProxy) { + audioInfo["proxy_rtp"] = this.audioProxy.incomingRTPPort(); + audioInfo["proxy_rtcp"] = this.audioProxy.incomingRTCPPort(); + } + + request["video"] = videoInfo as Source; + request["audio"] = audioInfo as Source; + + this.cameraSource && this.cameraSource.prepareStream(request as PrepareStreamRequest, (response: PreparedStreamResponse) => { + this._generateSetupResponse(this.sessionIdentifier!, response, callback); + }); + }); + } + } + + _generateSetupResponse = (identifier: SessionIdentifier, response: PreparedStreamResponse, callback: VoidCallback) => { + + let ipVer = 0; + let ipAddress = null; + const videoPort = bufferShim.alloc(2); + const audioPort = bufferShim.alloc(2); + + const videoSSRC = bufferShim.alloc(4); + const audioSSRC = bufferShim.alloc(4); + + let videoSRTP = bufferShim.from([0x01, 0x01, 0x02, 0x02, 0x00, 0x03, 0x00]); + let audioSRTP = bufferShim.from([0x01, 0x01, 0x02, 0x02, 0x00, 0x03, 0x00]); + + if (this.requireProxy) { + let currentAddress = ip.address(); + + ipVer = 1; + + if (ip.isV4Format(currentAddress)) { + ipVer = 0; + } + + ipAddress = bufferShim.from(currentAddress); + videoPort.writeUInt16LE(this.videoProxy.outgoingLocalPort(), 0); + + if (!this.disableAudioProxy) { + audioPort.writeUInt16LE(this.audioProxy.outgoingLocalPort(), 0); + } + + let videoInfo = response["video"]; + + let video_pt = videoInfo["proxy_pt"]; + let video_serverAddr = videoInfo["proxy_server_address"]; + let video_serverRTP = videoInfo["proxy_server_rtp"]; + let video_serverRTCP = videoInfo["proxy_server_rtcp"]; + + this.videoProxy.setIncomingPayloadType(video_pt); + this.videoProxy.setServerAddress(video_serverAddr); + this.videoProxy.setServerRTPPort(video_serverRTP); + this.videoProxy.setServerRTCPPort(video_serverRTCP); + + videoSSRC.writeUInt32LE(this.videoProxy.outgoingSSRC, 0); + + let audioInfo = response["audio"]; + + if (!this.disableAudioProxy) { + let audio_pt = audioInfo["proxy_pt"]; + let audio_serverAddr = audioInfo["proxy_server_address"]; + let audio_serverRTP = audioInfo["proxy_server_rtp"]; + let audio_serverRTCP = audioInfo["proxy_server_rtcp"]; + + this.audioProxy.setIncomingPayloadType(audio_pt); + this.audioProxy.setServerAddress(audio_serverAddr); + this.audioProxy.setServerRTPPort(audio_serverRTP); + this.audioProxy.setServerRTCPPort(audio_serverRTCP); + + audioSSRC.writeUInt32LE(this.audioProxy.outgoingSSRC, 0); + } else { + audioPort.writeUInt16LE(audioInfo["port"], 0); + audioSSRC.writeUInt32LE(audioInfo["ssrc"]!, 0); + } + + } else { + let addressInfo = response["address"]; + + if (addressInfo["type"] == "v6") { + ipVer = 1; + } else { + ipVer = 0; + } + + ipAddress = addressInfo["address"]; + + let videoInfo = response["video"]; + videoPort.writeUInt16LE(videoInfo["port"], 0); + videoSSRC.writeUInt32LE(videoInfo["ssrc"]!, 0); + + let audioInfo = response["audio"]; + audioPort.writeUInt16LE(audioInfo["port"], 0); + audioSSRC.writeUInt32LE(audioInfo["ssrc"]!, 0); + + if (this.supportSRTP) { + let videoKey = videoInfo["srtp_key"]; + let videoSalt = videoInfo["srtp_salt"]; + + let audioKey = audioInfo["srtp_key"]; + let audioSalt = audioInfo["srtp_salt"]; + + videoSRTP = tlv.encode( + SetupSRTP_PARAM.CRYPTO, SRTPCryptoSuites.AES_CM_128_HMAC_SHA1_80, + SetupSRTP_PARAM.MASTER_KEY, videoKey, + SetupSRTP_PARAM.MASTER_SALT, videoSalt + ); + + audioSRTP = tlv.encode( + SetupSRTP_PARAM.CRYPTO, SRTPCryptoSuites.AES_CM_128_HMAC_SHA1_80, + SetupSRTP_PARAM.MASTER_KEY, audioKey, + SetupSRTP_PARAM.MASTER_SALT, audioSalt + ); + } + } + + var addressTLV = tlv.encode( + SetupAddressInfo.ADDRESS_VER, ipVer, + SetupAddressInfo.ADDRESS, ipAddress, + SetupAddressInfo.VIDEO_RTP_PORT, videoPort, + SetupAddressInfo.AUDIO_RTP_PORT, audioPort + ); + + var responseTLV = tlv.encode( + SetupTypes.SESSION_ID, identifier, + SetupTypes.STATUS, SetupStatus.SUCCESS, + SetupTypes.ADDRESS, addressTLV, + SetupTypes.VIDEO_SRTP_PARAM, videoSRTP, + SetupTypes.AUDIO_SRTP_PARAM, audioSRTP, + SetupTypes.VIDEO_SSRC, videoSSRC, + SetupTypes.AUDIO_SSRC, audioSSRC + ); + + this.setupResponse = responseTLV.toString('base64'); + callback(); + } + + _updateStreamStatus = (status: number) => { + + this.streamStatus = status; + + this.service && this.service + .getCharacteristic(Characteristic.StreamingStatus)! + .setValue(tlv.encode( 0x01, this.streamStatus ).toString('base64')); + } + + _handleSetupRead = (callback: HandleSetupReadCallback) => { + debug('Setup Read'); + callback(null, this.setupResponse); + } + + _supportedRTPConfiguration = (supportSRTP: boolean) => { + var cryptoSuite = SRTPCryptoSuites.AES_CM_128_HMAC_SHA1_80; + + if (!supportSRTP) { + cryptoSuite = SRTPCryptoSuites.NONE; + debug("Client claims it doesn't support SRTP. The stream may stops working with future iOS releases."); + } + + return tlv.encode( + RTPConfigTypes.CRYPTO, cryptoSuite + ).toString('base64'); + } + + _supportedVideoStreamConfiguration = (videoParams: StreamVideoParams) => { + + let codec = videoParams["codec"]; + if (!codec) { + throw new Error('Video codec cannot be undefined'); + } + + var videoCodecParamsTLV = tlv.encode( + VideoCodecParamTypes.PACKETIZATION_MODE, VideoCodecParamPacketizationModeTypes.NON_INTERLEAVED + ); + + let profiles = codec["profiles"]; + profiles.forEach(function(value) { + let tlvBuffer = tlv.encode(VideoCodecParamTypes.PROFILE_ID, value); + videoCodecParamsTLV = Buffer.concat([videoCodecParamsTLV, tlvBuffer]); + }); + + let levels = codec["levels"]; + levels.forEach(function(value) { + let tlvBuffer = tlv.encode(VideoCodecParamTypes.LEVEL, value); + videoCodecParamsTLV = Buffer.concat([videoCodecParamsTLV, tlvBuffer]); + }); + + let resolutions = videoParams["resolutions"]; + if (!resolutions) { + throw new Error('Video resolutions cannot be undefined'); + } + + var videoAttrsTLV = bufferShim.alloc(0); + resolutions.forEach(function(resolution) { + if (resolution.length != 3) { + throw new Error('Unexpected video resolution'); + } + + var imageWidth = bufferShim.alloc(2); + imageWidth.writeUInt16LE(resolution[0], 0); + var imageHeight = bufferShim.alloc(2); + imageHeight.writeUInt16LE(resolution[1], 0); + var frameRate = bufferShim.alloc(1); + frameRate.writeUInt8(resolution[2], 0); + + var videoAttrTLV = tlv.encode( + VideoAttributesTypes.IMAGE_WIDTH, imageWidth, + VideoAttributesTypes.IMAGE_HEIGHT, imageHeight, + VideoAttributesTypes.FRAME_RATE, frameRate + ); + var videoAttrBuffer = tlv.encode(VideoTypes.ATTRIBUTES, videoAttrTLV); + videoAttrsTLV = Buffer.concat([videoAttrsTLV, videoAttrBuffer]); + }); + + var configurationTLV = tlv.encode( + VideoTypes.CODEC, VideoCodecTypes.H264, + VideoTypes.CODEC_PARAM, videoCodecParamsTLV + ); + + return tlv.encode( + 0x01, Buffer.concat([configurationTLV, videoAttrsTLV]) + ).toString('base64'); + } + + _supportedAudioStreamConfiguration = (audioParams: StreamAudioParams) => { + // Only AACELD and OPUS are accepted by iOS currently, and we need to give it something it will accept + // for it to start the video stream. + + var comfortNoiseValue = 0x00; + + if (audioParams["comfort_noise"] === true) { + comfortNoiseValue = 0x01; + } + + let codecs = audioParams["codecs"]; + if (!codecs) { + throw new Error('Audio codecs cannot be undefined'); + } + + var audioConfigurationsBuffer = bufferShim.alloc(0); + var hasSupportedCodec = false; + + codecs.forEach(function(codecParam){ + var codec = AudioCodecTypes.OPUS; + var bitrate = AudioCodecParamBitRateTypes.CONSTANT; + var samplerate = AudioCodecParamSampleRateTypes.KHZ_24; + + let param_type = codecParam["type"]; + let param_samplerate = codecParam["samplerate"]; + + if (param_type == 'OPUS') { + hasSupportedCodec = true; + bitrate = AudioCodecParamBitRateTypes.VARIABLE; + } else if (param_type == "AAC-eld") { + hasSupportedCodec = true; + codec = AudioCodecTypes.AACELD; + bitrate = AudioCodecParamBitRateTypes.VARIABLE; + } else { + debug("Unsupported codec: ", param_type); + return; + } + + if (param_samplerate == 8) { + samplerate = AudioCodecParamSampleRateTypes.KHZ_8; + } else if (param_samplerate == 16) { + samplerate = AudioCodecParamSampleRateTypes.KHZ_16; + } else if (param_samplerate == 24) { + samplerate = AudioCodecParamSampleRateTypes.KHZ_24; + } else { + debug("Unsupported sample rate: ", param_samplerate); + return; + } + + var audioParamTLV = tlv.encode( + AudioCodecParamTypes.CHANNEL, 1, + AudioCodecParamTypes.BIT_RATE, bitrate, + AudioCodecParamTypes.SAMPLE_RATE, samplerate + ); + + var audioConfiguration = tlv.encode( + AudioTypes.CODEC, codec, + AudioTypes.CODEC_PARAM, audioParamTLV + ); + + audioConfigurationsBuffer = Buffer.concat([audioConfigurationsBuffer, tlv.encode(0x01, audioConfiguration)]); + }); + + // If we're not one of the supported codecs + if(!hasSupportedCodec) { + debug("Client doesn't support any audio codec that HomeKit supports."); + + var codec = AudioCodecTypes.OPUS; + var bitrate = AudioCodecParamBitRateTypes.VARIABLE; + var samplerate = AudioCodecParamSampleRateTypes.KHZ_24; + + var audioParamTLV = tlv.encode( + AudioCodecParamTypes.CHANNEL, 1, + AudioCodecParamTypes.BIT_RATE, bitrate, + AudioCodecParamTypes.SAMPLE_RATE, AudioCodecParamSampleRateTypes.KHZ_24 + ); + + + var audioConfiguration = tlv.encode( + AudioTypes.CODEC, codec, + AudioTypes.CODEC_PARAM, audioParamTLV + ); + + audioConfigurationsBuffer = tlv.encode(0x01, audioConfiguration); + + this.videoOnly = true; + } + + return Buffer.concat([audioConfigurationsBuffer, tlv.encode(0x02, comfortNoiseValue)]).toString('base64'); + } +} + diff --git a/src/lib/camera/RTPProxy.ts b/src/lib/camera/RTPProxy.ts new file mode 100644 index 000000000..cccbcd7e2 --- /dev/null +++ b/src/lib/camera/RTPProxy.ts @@ -0,0 +1,315 @@ +import { Socket, SocketType } from "dgram"; + +const dgram = require('dgram'); +const EventEmitter = require('events').EventEmitter; + +export interface RTPProxyOptions { + disabled: boolean; + isIPV6?: boolean; + outgoingAddress: string; + outgoingPort: number; + outgoingSSRC: number; +} + +export default class RTPProxy extends EventEmitter { + + startingPort: number = 10000; + type: SocketType; + outgoingAddress: string; + outgoingPort: number; + incomingPayloadType: number; + outgoingSSRC: number; + incomingSSRC: number | null; + outgoingPayloadType: number | null; + disabled: boolean; + + incomingRTPSocket!: Socket; + incomingRTCPSocket!: Socket; + outgoingSocket!: Socket; + serverAddress?: string; + serverRTPPort?: number; + serverRTCPPort?: number; + + constructor(public options: RTPProxyOptions) { + super(); + + this.type = options.isIPV6 ? 'udp6' : 'udp4'; + + this.startingPort = 10000; + + this.outgoingAddress = options.outgoingAddress; + this.outgoingPort = options.outgoingPort; + this.incomingPayloadType = 0; + this.outgoingSSRC = options.outgoingSSRC; + this.disabled = options.disabled; + this.incomingSSRC = null; + this.outgoingPayloadType = null; + } + + setup = () => { + return this.createSocketPair(this.type) + .then((sockets) => { + this.incomingRTPSocket = sockets[0]; + this.incomingRTCPSocket = sockets[1]; + + return this.createSocket(this.type); + }).then((socket) => { + this.outgoingSocket = socket; + this.onBound(); + }); + } + + destroy = () => { + if (this.incomingRTPSocket) { + this.incomingRTPSocket.close(); + } + + if (this.incomingRTCPSocket) { + this.incomingRTCPSocket.close(); + } + + if (this.outgoingSocket) { + this.outgoingSocket.close(); + } + } + + incomingRTPPort = () => { + const address = this.incomingRTPSocket.address(); + + if (typeof address !== 'string') { + return address.port; + } + } + + incomingRTCPPort = () => { + const address = this.incomingRTCPSocket.address(); + + if (typeof address !== 'string') { + return address.port; + } + } + + outgoingLocalPort = () => { + const address = this.outgoingSocket.address(); + + if (typeof address !== 'string') { + return address.port; + } + } + + setServerAddress = (address: string) => { + this.serverAddress = address; + } + + setServerRTPPort = (port: number) => { + this.serverRTPPort = port; + } + + setServerRTCPPort = (port: number) => { + this.serverRTCPPort = port; + } + + setIncomingPayloadType = (pt: number) => { + this.incomingPayloadType = pt; + } + + setOutgoingPayloadType = (pt: number) => { + this.outgoingPayloadType = pt; + } + + sendOut = (msg: Buffer) => { + // Just drop it if we're not setup yet, I guess. + if(!this.outgoingAddress || !this.outgoingPort) + return; + + this.outgoingSocket.send(msg, this.outgoingPort, this.outgoingAddress); + } + + sendBack = (msg: Buffer) => { + // Just drop it if we're not setup yet, I guess. + if(!this.serverAddress || !this.serverRTCPPort) + return; + + this.outgoingSocket.send(msg, this.serverRTCPPort, this.serverAddress); + } + + onBound = () => { + if(this.disabled) + return; + + this.incomingRTPSocket.on('message', (msg, rinfo) => { + this.rtpMessage(msg); + }); + + this.incomingRTCPSocket.on('message', (msg, rinfo) => { + this.rtcpMessage(msg); + }); + + this.outgoingSocket.on('message', (msg, rinfo) => { + this.rtcpReply(msg); + }); + } + + rtpMessage = (msg: Buffer) => { + + if(msg.length < 12) { + // Not a proper RTP packet. Just forward it. + this.sendOut(msg); + return; + } + + let mpt = msg.readUInt8(1); + let pt = mpt & 0x7F; + if(pt == this.incomingPayloadType) { + // @ts-ignore + mpt = (mpt & 0x80) | this.outgoingPayloadType; + msg.writeUInt8(mpt, 1); + } + + if(this.incomingSSRC === null) + this.incomingSSRC = msg.readUInt32BE(4); + + msg.writeUInt32BE(this.outgoingSSRC, 8); + this.sendOut(msg); + } + + processRTCPMessage = (msg: Buffer, transform: (pt: number, packet: Buffer) => Buffer) => { + let rtcpPackets = []; + let offset = 0; + while((offset + 4) <= msg.length) { + let pt = msg.readUInt8(offset + 1); + let len = msg.readUInt16BE(offset + 2) * 4; + if((offset + 4 + len) > msg.length) + break; + let packet = msg.slice(offset, offset + 4 + len); + + packet = transform(pt, packet); + + if(packet) + rtcpPackets.push(packet); + + offset += 4 + len; + } + + if(rtcpPackets.length > 0) + return Buffer.concat(rtcpPackets); + + return null; + } + + rtcpMessage = (msg: Buffer) => { + + let processed = this.processRTCPMessage(msg, (pt, packet) => { + if(pt != 200 || packet.length < 8) + return packet; + + if(this.incomingSSRC === null) + this.incomingSSRC = packet.readUInt32BE(4); + packet.writeUInt32BE(this.outgoingSSRC, 4); + return packet; + }); + + if(processed) + this.sendOut(processed); + } + + rtcpReply = (msg: Buffer) => { + + let processed = this.processRTCPMessage(msg, (pt, packet) => { + if(pt != 201 || packet.length < 12) + return packet; + + // Assume source 1 is the one we want to edit. + // @ts-ignore + packet.writeUInt32BE(this.incomingSSRC, 8); + return packet; + }); + + + if(processed) + this.sendOut(processed); + } + + createSocket = (type: SocketType): Promise => { + return new Promise((resolve, reject) => { + const retry = () => { + const socket = dgram.createSocket(type); + + const bindErrorHandler = () => { + if(this.startingPort == 65535) + this.startingPort = 10000; + else + ++this.startingPort; + + socket.close(); + retry(); + }; + + socket.once('error', bindErrorHandler); + + socket.on('listening', () => { + resolve(socket); + }); + + socket.bind(this.startingPort); + }; + + retry(); + }); + } + + createSocketPair = (type: SocketType): Promise => { + return new Promise((resolve, reject) => { + const retry = () => { + let socket1 = dgram.createSocket(type); + let socket2 = dgram.createSocket(type); + let state = {socket1: 0, socket2: 0}; + + const recheck = () => { + if(state.socket1 == 0 || state.socket2 == 0) + return; + + if(state.socket1 == 2 && state.socket2 == 2) { + resolve([socket1, socket2]); + return; + } + + if(this.startingPort == 65534) + this.startingPort = 10000; + else + ++this.startingPort; + + socket1.close(); + socket2.close(); + + retry(); + } + + socket1.once('error', function() { + state.socket1 = 1; + recheck(); + }); + + socket2.once('error', function() { + state.socket2 = 1; + recheck(); + }); + + socket1.once('listening', function() { + state.socket1 = 2; + recheck(); + }); + + socket2.once('listening', function() { + state.socket2 = 2; + recheck(); + }); + + socket1.bind(this.startingPort); + socket2.bind(this.startingPort + 1); + } + + retry(); + }); + } +} diff --git a/src/lib/gen/HomeKit-Bridge.ts b/src/lib/gen/HomeKit-Bridge.ts new file mode 100644 index 000000000..56ce3a1ca --- /dev/null +++ b/src/lib/gen/HomeKit-Bridge.ts @@ -0,0 +1,683 @@ +import { Characteristic, Formats, Perms } from '../Characteristic'; +import { Service } from '../Service'; + +/** + * + * Removed in iOS 11 + * + */ + +/** + * Characteristic "App Matching Identifier" + */ + +export class AppMatchingIdentifier extends Characteristic { + + static readonly UUID: string = '000000A4-0000-1000-8000-0026BB765291'; + + constructor() { + super('App Matching Identifier', AppMatchingIdentifier.UUID); + this.setProps({ + format: Formats.TLV8, + perms: [Perms.READ] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.AppMatchingIdentifier = AppMatchingIdentifier; + +/** + * Characteristic "Programmable Switch Output State" + */ + +export class ProgrammableSwitchOutputState extends Characteristic { + + static readonly UUID: string = '00000074-0000-1000-8000-0026BB765291'; + + constructor() { + super('Programmable Switch Output State', ProgrammableSwitchOutputState.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + minStep: 1, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.ProgrammableSwitchOutputState = ProgrammableSwitchOutputState; + +/** + * Characteristic "Software Revision" + */ + +export class SoftwareRevision extends Characteristic { + + static readonly UUID: string = '00000054-0000-1000-8000-0026BB765291'; + + constructor() { + super('Software Revision', SoftwareRevision.UUID); + this.setProps({ + format: Formats.STRING, + perms: [Perms.READ] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.SoftwareRevision = SoftwareRevision; + +/** + * Service "Camera Control" + */ + +export class CameraControl extends Service { + + static readonly UUID: string = '00000111-0000-1000-8000-0026BB765291' + + constructor(displayName: string, subtype: string) { + super(displayName, CameraControl.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.On); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.CurrentHorizontalTiltAngle); + this.addOptionalCharacteristic(Characteristic.CurrentVerticalTiltAngle); + this.addOptionalCharacteristic(Characteristic.TargetHorizontalTiltAngle); + this.addOptionalCharacteristic(Characteristic.TargetVerticalTiltAngle); + this.addOptionalCharacteristic(Characteristic.NightVision); + this.addOptionalCharacteristic(Characteristic.OpticalZoom); + this.addOptionalCharacteristic(Characteristic.DigitalZoom); + this.addOptionalCharacteristic(Characteristic.ImageRotation); + this.addOptionalCharacteristic(Characteristic.ImageMirroring); + this.addOptionalCharacteristic(Characteristic.Name); + } +} + +Service.CameraControl = CameraControl; + +/** + * Service "Stateful Programmable Switch" + */ + +export class StatefulProgrammableSwitch extends Service { + + static readonly UUID: string = '00000088-0000-1000-8000-0026BB765291' + + constructor(displayName: string, subtype: string) { + super(displayName, StatefulProgrammableSwitch.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.ProgrammableSwitchEvent); + this.addCharacteristic(Characteristic.ProgrammableSwitchOutputState); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Name); + } +} + +Service.StatefulProgrammableSwitch = StatefulProgrammableSwitch; + +/** + * + * Removed in iOS 10 + * + */ + +/** + * Characteristic "Accessory Identifier" + */ + +export class AccessoryIdentifier extends Characteristic { + + static readonly UUID: string = '00000057-0000-1000-8000-0026BB765291'; + + constructor() { + super('Accessory Identifier', AccessoryIdentifier.UUID); + this.setProps({ + format: Formats.STRING, + perms: [Perms.READ] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.AccessoryIdentifier = AccessoryIdentifier; + +/** + * Characteristic "Category" + */ + +export class Category extends Characteristic { + + static readonly UUID: string = '000000A3-0000-1000-8000-0026BB765291'; + + constructor() { + super('Category', Category.UUID); + this.setProps({ + format: Formats.UINT16, + maxValue: 16, + minValue: 1, + minStep: 1, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.Category = Category; + +/** + * Characteristic "Configure Bridged Accessory" + */ + +export class ConfigureBridgedAccessory extends Characteristic { + + static readonly UUID: string = '000000A0-0000-1000-8000-0026BB765291'; + + constructor() { + super('Configure Bridged Accessory', ConfigureBridgedAccessory.UUID); + this.setProps({ + format: Formats.TLV8, + perms: [Perms.WRITE] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.ConfigureBridgedAccessory = ConfigureBridgedAccessory; + +/** + * Characteristic "Configure Bridged Accessory Status" + */ + +export class ConfigureBridgedAccessoryStatus extends Characteristic { + + static readonly UUID: string = '0000009D-0000-1000-8000-0026BB765291'; + + constructor() { + super('Configure Bridged Accessory Status', ConfigureBridgedAccessoryStatus.UUID); + this.setProps({ + format: Formats.TLV8, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.ConfigureBridgedAccessoryStatus = ConfigureBridgedAccessoryStatus; + +/** + * Characteristic "Current Time" + */ + +export class CurrentTime extends Characteristic { + + static readonly UUID: string = '0000009B-0000-1000-8000-0026BB765291'; + + constructor() { + super('Current Time', CurrentTime.UUID); + this.setProps({ + format: Formats.STRING, + perms: [Perms.READ, Perms.WRITE] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.CurrentTime = CurrentTime; + +/** + * Characteristic "Day of the Week" + */ + +export class DayoftheWeek extends Characteristic { + + static readonly UUID: string = '00000098-0000-1000-8000-0026BB765291'; + + constructor() { + super('Day of the Week', DayoftheWeek.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 7, + minValue: 1, + minStep: 1, + perms: [Perms.READ, Perms.WRITE] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.DayoftheWeek = DayoftheWeek; + +/** + * Characteristic "Discover Bridged Accessories" + */ + +export class DiscoverBridgedAccessories extends Characteristic { + + // The value property of DiscoverBridgedAccessories must be one of the following: + static readonly START_DISCOVERY = 0; + static readonly STOP_DISCOVERY = 1; + + static readonly UUID: string = '0000009E-0000-1000-8000-0026BB765291'; + + constructor() { + super('Discover Bridged Accessories', DiscoverBridgedAccessories.UUID); + this.setProps({ + format: Formats.UINT8, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.DiscoverBridgedAccessories = DiscoverBridgedAccessories; + +/** + * Characteristic "Discovered Bridged Accessories" + */ + +export class DiscoveredBridgedAccessories extends Characteristic { + + static readonly UUID: string = '0000009F-0000-1000-8000-0026BB765291'; + + constructor() { + super('Discovered Bridged Accessories', DiscoveredBridgedAccessories.UUID); + this.setProps({ + format: Formats.UINT16, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.DiscoveredBridgedAccessories = DiscoveredBridgedAccessories; + +/** + * Characteristic "Link Quality" + */ + +export class LinkQuality extends Characteristic { + + static readonly UUID: string = '0000009C-0000-1000-8000-0026BB765291'; + + constructor() { + super('Link Quality', LinkQuality.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 4, + minValue: 1, + minStep: 1, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.LinkQuality = LinkQuality; + +/** + * Characteristic "Reachable" + */ + +export class Reachable extends Characteristic { + + static readonly UUID: string = '00000063-0000-1000-8000-0026BB765291'; + + constructor() { + super('Reachable', Reachable.UUID); + this.setProps({ + format: Formats.BOOL, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.Reachable = Reachable; + +/** + * Characteristic "Relay Control Point" + */ + +export class RelayControlPoint extends Characteristic { + + static readonly UUID: string = '0000005E-0000-1000-8000-0026BB765291'; + + constructor() { + super('Relay Control Point', RelayControlPoint.UUID); + this.setProps({ + format: Formats.TLV8, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.RelayControlPoint = RelayControlPoint; + +/** + * Characteristic "Relay Enabled" + */ + +export class RelayEnabled extends Characteristic { + + static readonly UUID: string = '0000005B-0000-1000-8000-0026BB765291'; + + constructor() { + super('Relay Enabled', RelayEnabled.UUID); + this.setProps({ + format: Formats.BOOL, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.RelayEnabled = RelayEnabled; + +/** + * Characteristic "Relay State" + */ + +export class RelayState extends Characteristic { + + static readonly UUID: string = '0000005C-0000-1000-8000-0026BB765291'; + + constructor() { + super('Relay State', RelayState.UUID); + this.setProps({ + format: Formats.UINT8, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.RelayState = RelayState; + +/** + * Characteristic "Time Update" + */ + +export class TimeUpdate extends Characteristic { + + static readonly UUID: string = '0000009A-0000-1000-8000-0026BB765291'; + + constructor() { + super('Time Update', TimeUpdate.UUID); + this.setProps({ + format: Formats.BOOL, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.TimeUpdate = TimeUpdate; + +/** + * Characteristic "Tunnel Connection Timeout " + */ + +export class TunnelConnectionTimeout extends Characteristic { + + static readonly UUID: string = '00000061-0000-1000-8000-0026BB765291'; + + constructor() { + super('Tunnel Connection Timeout ', TunnelConnectionTimeout.UUID); + this.setProps({ + format: Formats.UINT32, + perms: [Perms.WRITE, Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.TunnelConnectionTimeout = TunnelConnectionTimeout; + +/** + * Characteristic "Tunneled Accessory Advertising" + */ + +export class TunneledAccessoryAdvertising extends Characteristic { + + static readonly UUID: string = '00000060-0000-1000-8000-0026BB765291'; + + constructor() { + super('Tunneled Accessory Advertising', TunneledAccessoryAdvertising.UUID); + this.setProps({ + format: Formats.BOOL, + perms: [Perms.WRITE, Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.TunneledAccessoryAdvertising = TunneledAccessoryAdvertising; + +/** + * Characteristic "Tunneled Accessory Connected" + */ + +export class TunneledAccessoryConnected extends Characteristic { + + static readonly UUID: string = '00000059-0000-1000-8000-0026BB765291'; + + constructor() { + super('Tunneled Accessory Connected', TunneledAccessoryConnected.UUID); + this.setProps({ + format: Formats.BOOL, + perms: [Perms.WRITE, Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.TunneledAccessoryConnected = TunneledAccessoryConnected; + +/** + * Characteristic "Tunneled Accessory State Number" + */ + +export class TunneledAccessoryStateNumber extends Characteristic { + + static readonly UUID: string = '00000058-0000-1000-8000-0026BB765291'; + + constructor() { + super('Tunneled Accessory State Number', TunneledAccessoryStateNumber.UUID); + this.setProps({ + format: Formats.FLOAT, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.TunneledAccessoryStateNumber = TunneledAccessoryStateNumber; + +/** + * Characteristic "Version" + */ + +export class Version extends Characteristic { + + static readonly UUID: string = '00000037-0000-1000-8000-0026BB765291'; + + constructor() { + super('Version', Version.UUID); + this.setProps({ + format: Formats.STRING, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.Version = Version; + +/** + * 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 + 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-TV.ts b/src/lib/gen/HomeKit-TV.ts new file mode 100644 index 000000000..29880d9ef --- /dev/null +++ b/src/lib/gen/HomeKit-TV.ts @@ -0,0 +1,550 @@ +// Manually created from metadata in HomeKitDaemon + +import { Characteristic, Perms, Formats } from '../Characteristic'; +import { Service } from '../Service'; + +/** + * Characteristic "Active Identifier" + */ + +export class ActiveIdentifier extends Characteristic { + + static readonly UUID: string = '000000E7-0000-1000-8000-0026BB765291'; + + constructor() { + super('Active Identifier', ActiveIdentifier.UUID); + this.setProps({ + format: Formats.UINT32, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.ActiveIdentifier = ActiveIdentifier; + +/** + * Characteristic "Configured Name" + */ + +export class ConfiguredName extends Characteristic { + + static readonly UUID: string = '000000E3-0000-1000-8000-0026BB765291'; + + constructor() { + super('Configured Name', ConfiguredName.UUID); + this.setProps({ + format: Formats.STRING, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.ConfiguredName = ConfiguredName; + +/** + * Characteristic "Sleep Discovery Mode" + */ + +export class SleepDiscoveryMode extends Characteristic { + +// The value property of SleepDiscoveryMode must be one of the following: + static readonly NOT_DISCOVERABLE = 0; + static readonly ALWAYS_DISCOVERABLE = 1; + + static readonly UUID: string = '000000E8-0000-1000-8000-0026BB765291'; + + constructor() { + super('Sleep Discovery Mode', SleepDiscoveryMode.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0,1], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.SleepDiscoveryMode = SleepDiscoveryMode; + +/** + * Characteristic "Closed Captions" + */ + +export class ClosedCaptions extends Characteristic { + + // The value property of ClosedCaptions must be one of the following: + static readonly DISABLED = 0; + static readonly ENABLED = 1; + + static readonly UUID: string = '000000DD-0000-1000-8000-0026BB765291'; + + constructor() { + super('Closed Captions', ClosedCaptions.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0,1], + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.ClosedCaptions = ClosedCaptions; + +/** + * Characteristic "Display Order" + */ + +export class DisplayOrder extends Characteristic { + + static readonly UUID: string = '00000136-0000-1000-8000-0026BB765291'; + + constructor() { + super('Display Order', DisplayOrder.UUID); + this.setProps({ + format: Formats.TLV8, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.DisplayOrder = DisplayOrder; + +/** + * Characteristic "Current Media State" + */ + +export class CurrentMediaState extends Characteristic { + + static readonly UUID: string = '000000E0-0000-1000-8000-0026BB765291'; + + constructor() { + super('Current Media State', CurrentMediaState.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 3, + minValue: 0, + validValues: [0,1,2,3], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.CurrentMediaState = CurrentMediaState; + +/** + * Characteristic "Target Media State" + */ + +export class TargetMediaState extends Characteristic { + +// The value property of TargetMediaState must be one of the following: + static readonly PLAY = 0; + static readonly PAUSE = 1; + static readonly STOP = 2; + + static readonly UUID: string = '00000137-0000-1000-8000-0026BB765291'; + + constructor() { + super('Target Media State', TargetMediaState.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 2, + minValue: 0, + validValues: [0,1,2,3], + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.TargetMediaState = TargetMediaState; + +/** + * Characteristic "Picture Mode" + */ + +export class PictureMode extends Characteristic { + +// The value property of PictureMode must be one of the following: + static readonly OTHER = 0; + static readonly STANDARD = 1; + static readonly CALIBRATED = 2; + static readonly CALIBRATED_DARK = 3; + static readonly VIVID = 4; + static readonly GAME = 5; + static readonly COMPUTER = 6; + static readonly CUSTOM = 7; + + static readonly UUID: string = '000000E2-0000-1000-8000-0026BB765291'; + + constructor() { + super('Picture Mode', PictureMode.UUID); + this.setProps({ + format: Formats.UINT16, + maxValue: 13, + minValue: 0, + validValues: [0,1,2,3,4,5,6,7,8,9,10,11,12,13], + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.PictureMode = PictureMode; + +/** + * Characteristic "Power Mode Selection" + */ + +export class PowerModeSelection extends Characteristic { + + // The value property of PowerModeSelection must be one of the following: + static readonly SHOW = 0; + static readonly HIDE = 1; + + static readonly UUID: string = '000000DF-0000-1000-8000-0026BB765291'; + + constructor() { + super('Power Mode Selection', PowerModeSelection.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0,1], + perms: [Perms.WRITE] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.PowerModeSelection = PowerModeSelection; + +/** + * Characteristic "Remote Key" + */ + +export class RemoteKey extends Characteristic { + +// The value property of RemoteKey must be one of the following: + static readonly REWIND = 0; + static readonly FAST_FORWARD = 1; + static readonly NEXT_TRACK = 2; + static readonly PREVIOUS_TRACK = 3; + static readonly ARROW_UP = 4; + static readonly ARROW_DOWN = 5; + static readonly ARROW_LEFT = 6; + static readonly ARROW_RIGHT = 7; + static readonly SELECT = 8; + static readonly BACK = 9; + static readonly EXIT = 10; + static readonly PLAY_PAUSE = 11; + static readonly INFORMATION = 15; + + static readonly UUID: string = '000000E1-0000-1000-8000-0026BB765291'; + + constructor() { + super('Remote Key', RemoteKey.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 16, + minValue: 0, + validValues: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16], + perms: [Perms.WRITE] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.RemoteKey = RemoteKey; + +/** + * Characteristic "Input Source Type" + */ + +export class InputSourceType extends Characteristic { + +// The value property of InputSourceType must be one of the following: + static readonly OTHER = 0; + static readonly HOME_SCREEN = 1; + static readonly TUNER = 2; + static readonly HDMI = 3; + static readonly COMPOSITE_VIDEO = 4; + static readonly S_VIDEO = 5; + static readonly COMPONENT_VIDEO = 6; + static readonly DVI = 7; + static readonly AIRPLAY = 8; + static readonly USB = 9; + static readonly APPLICATION = 10; + + static readonly UUID: string = '000000DB-0000-1000-8000-0026BB765291'; + + constructor() { + super('Input Source Type', InputSourceType.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 10, + minValue: 0, + validValues: [0,1,2,3,4,5,6,7,8,9,10], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.InputSourceType = InputSourceType; + +/** + * Characteristic "Input Device Type" + */ + +export class InputDeviceType extends Characteristic { + + // The value property of InputDeviceType must be one of the following: + static readonly OTHER = 0; + static readonly TV = 1; + static readonly RECORDING = 2; + static readonly TUNER = 3; + static readonly PLAYBACK = 4; + static readonly AUDIO_SYSTEM = 5; + + static readonly UUID: string = '000000DC-0000-1000-8000-0026BB765291'; + + constructor() { + super('Input Device Type', InputDeviceType.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 5, + minValue: 0, + validValues: [0,1,2,3,4,5], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.InputDeviceType = InputDeviceType; + +/** + * Characteristic "Identifier" + */ + +export class Identifier extends Characteristic { + + static readonly UUID: string = '000000E6-0000-1000-8000-0026BB765291'; + + constructor() { + super('Identifier', Identifier.UUID); + this.setProps({ + format: Formats.UINT32, + minValue: 0, + minStep: 1, + perms: [Perms.READ] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.Identifier = Identifier; + +/** + * Characteristic "Current Visibility State" + */ + +export class CurrentVisibilityState extends Characteristic { + +// The value property of CurrentVisibilityState must be one of the following: + static readonly SHOWN = 0; + static readonly HIDDEN = 1; + + static readonly UUID: string = '00000135-0000-1000-8000-0026BB765291'; + + constructor() { + super('Current Visibility State', CurrentVisibilityState.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 3, + minValue: 0, + validValues: [0,1,2,3], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.CurrentVisibilityState = CurrentVisibilityState; + +/** + * Characteristic "Target Visibility State" + */ + +export class TargetVisibilityState extends Characteristic { + +// The value property of TargetVisibilityState must be one of the following: + static readonly SHOWN = 0; + static readonly HIDDEN = 1; + + static readonly UUID: string = '00000134-0000-1000-8000-0026BB765291'; + + constructor() { + super('Target Visibility State', TargetVisibilityState.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0,1], + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.TargetVisibilityState = TargetVisibilityState; + +/** + * Characteristic "Volume Control Type" + */ + +export class VolumeControlType extends Characteristic { + +// The value property of VolumeControlType must be one of the following: + static readonly NONE = 0; + static readonly RELATIVE = 1; + static readonly RELATIVE_WITH_CURRENT = 2; + static readonly ABSOLUTE = 3; + + static readonly UUID: string = '000000E9-0000-1000-8000-0026BB765291'; + + constructor() { + super('Volume Control Type', VolumeControlType.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 3, + minValue: 0, + validValues: [0,1,2,3], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.VolumeControlType = VolumeControlType; + +/** + * Characteristic "Volume Selector" + */ + +export class VolumeSelector extends Characteristic { + +// The value property of VolumeSelector must be one of the following: + static readonly INCREMENT = 0; + static readonly DECREMENT = 1; + + static readonly UUID: string = '000000EA-0000-1000-8000-0026BB765291'; + + constructor() { + super('Volume Selector', VolumeSelector.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0,1], + perms: [Perms.WRITE] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.VolumeSelector = VolumeSelector; + + +/** + * Service "Television" + */ + +export class Television extends Service { + + static readonly UUID: string = '000000D8-0000-1000-8000-0026BB765291'; + + constructor(displayName: string, subtype: string) { + super(displayName, Television.UUID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.Active); + this.addCharacteristic(Characteristic.ActiveIdentifier); + this.addCharacteristic(Characteristic.ConfiguredName); + this.addCharacteristic(Characteristic.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.RemoteKey); + } +} + +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); + this.addCharacteristic(Characteristic.CurrentVisibilityState); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.Identifier); + this.addOptionalCharacteristic(Characteristic.InputDeviceType); + this.addOptionalCharacteristic(Characteristic.TargetVisibilityState); + this.addOptionalCharacteristic(Characteristic.Name); + } +} + +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); + this.addOptionalCharacteristic(Characteristic.Name); + } +} + +Service.TelevisionSpeaker = TelevisionSpeaker; diff --git a/src/lib/gen/HomeKit.ts b/src/lib/gen/HomeKit.ts new file mode 100644 index 000000000..1ad487d2f --- /dev/null +++ b/src/lib/gen/HomeKit.ts @@ -0,0 +1,4038 @@ +// THIS FILE IS AUTO-GENERATED - DO NOT MODIFY + +import { Characteristic, Formats, Perms, Units } from '../Characteristic'; +import { Service } from '../Service'; + +/** + * Characteristic "Accessory Flags" + */ + +export class AccessoryFlags extends Characteristic { + + static readonly UUID: string = '000000A6-0000-1000-8000-0026BB765291'; + + constructor() { + super('Accessory Flags', AccessoryFlags.UUID); + this.setProps({ + format: Formats.UINT32, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.AccessoryFlags = AccessoryFlags; + +/** + * Characteristic "Active" + */ + +export class Active extends Characteristic { + + // The value property of Active must be one of the following: + static readonly INACTIVE = 0; + static readonly ACTIVE = 1; + + static readonly UUID: string = '000000B0-0000-1000-8000-0026BB765291'; + + constructor() { + super('Active', Active.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0, 1], + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.Active = Active; + +/** + * Characteristic "Administrator Only Access" + */ + +export class AdministratorOnlyAccess extends Characteristic { + + static readonly UUID: string = '00000001-0000-1000-8000-0026BB765291'; + + constructor() { + super('Administrator Only Access', AdministratorOnlyAccess.UUID); + this.setProps({ + format: Formats.BOOL, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.AdministratorOnlyAccess = AdministratorOnlyAccess; + +/** + * Characteristic "Air Particulate Density" + */ + +export class AirParticulateDensity extends Characteristic { + + static readonly UUID: string = '00000064-0000-1000-8000-0026BB765291'; + + constructor() { + super('Air Particulate Density', AirParticulateDensity.UUID); + this.setProps({ + format: Formats.FLOAT, + maxValue: 1000, + minValue: 0, + minStep: 1, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.AirParticulateDensity = AirParticulateDensity; + +/** + * Characteristic "Air Particulate Size" + */ + +export class AirParticulateSize extends Characteristic { + + // The value property of AirParticulateSize must be one of the following: + static readonly _2_5_M = 0; + static readonly _10_M = 1; + + static readonly UUID: string = '00000065-0000-1000-8000-0026BB765291'; + + constructor() { + super('Air Particulate Size', AirParticulateSize.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0, 1], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.AirParticulateSize = AirParticulateSize; + + +/** + * Characteristic "Air Quality" + */ + +export class AirQuality extends Characteristic { + + // The value property of AirQuality must be one of the following: + static readonly UNKNOWN = 0; + static readonly EXCELLENT = 1; + static readonly GOOD = 2; + static readonly FAIR = 3; + static readonly INFERIOR = 4; + static readonly POOR = 5; + + static readonly UUID: string = '00000095-0000-1000-8000-0026BB765291'; + + constructor() { + super('Air Quality', AirQuality.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 5, + minValue: 0, + validValues: [0, 1, 2, 3, 4, 5], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.AirQuality = AirQuality; + + +/** + * Characteristic "Audio Feedback" + */ + +export class AudioFeedback extends Characteristic { + + static readonly UUID: string = '00000005-0000-1000-8000-0026BB765291'; + + constructor() { + super('Audio Feedback', AudioFeedback.UUID); + this.setProps({ + format: Formats.BOOL, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.AudioFeedback = AudioFeedback; + +/** + * Characteristic "Battery Level" + */ + +export class BatteryLevel extends Characteristic { + + static readonly UUID: string = '00000068-0000-1000-8000-0026BB765291'; + + constructor() { + super('Battery Level', BatteryLevel.UUID); + this.setProps({ + format: Formats.UINT8, + unit: Units.PERCENTAGE, + maxValue: 100, + minValue: 0, + minStep: 1, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.BatteryLevel = BatteryLevel; + +/** + * Characteristic "Brightness" + */ + +export class Brightness extends Characteristic { + + static readonly UUID: string = '00000008-0000-1000-8000-0026BB765291'; + + constructor() { + super('Brightness', Brightness.UUID); + this.setProps({ + format: Formats.INT, + unit: Units.PERCENTAGE, + maxValue: 100, + minValue: 0, + minStep: 1, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.Brightness = Brightness; + +/** + * Characteristic "Carbon Dioxide Detected" + */ + +export class CarbonDioxideDetected extends Characteristic { + + // The value property of CarbonDioxideDetected must be one of the following: + static readonly CO2_LEVELS_NORMAL = 0; + static readonly CO2_LEVELS_ABNORMAL = 1; + + static readonly UUID: string = '00000092-0000-1000-8000-0026BB765291'; + + constructor() { + super('Carbon Dioxide Detected', CarbonDioxideDetected.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0, 1], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.CarbonDioxideDetected = CarbonDioxideDetected; + +/** + * Characteristic "Carbon Dioxide Level" + */ + +export class CarbonDioxideLevel extends Characteristic { + + static readonly UUID: string = '00000093-0000-1000-8000-0026BB765291'; + + constructor() { + super('Carbon Dioxide Level', CarbonDioxideLevel.UUID); + this.setProps({ + format: Formats.FLOAT, + maxValue: 100000, + minValue: 0, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.CarbonDioxideLevel = CarbonDioxideLevel; + +/** + * Characteristic "Carbon Dioxide Peak Level" + */ + +export class CarbonDioxidePeakLevel extends Characteristic { + + static readonly UUID: string = '00000094-0000-1000-8000-0026BB765291'; + + constructor() { + super('Carbon Dioxide Peak Level', CarbonDioxidePeakLevel.UUID); + this.setProps({ + format: Formats.FLOAT, + maxValue: 100000, + minValue: 0, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.CarbonDioxidePeakLevel = CarbonDioxidePeakLevel; + +/** + * Characteristic "Carbon Monoxide Detected" + */ + +export class CarbonMonoxideDetected extends Characteristic { + + // The value property of CarbonMonoxideDetected must be one of the following: + static readonly CO_LEVELS_NORMAL = 0; + static readonly CO_LEVELS_ABNORMAL = 1; + + static readonly UUID: string = '00000069-0000-1000-8000-0026BB765291'; + + constructor() { + super('Carbon Monoxide Detected', CarbonMonoxideDetected.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0, 1], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.CarbonMonoxideDetected = CarbonMonoxideDetected; + +/** + * Characteristic "Carbon Monoxide Level" + */ + +export class CarbonMonoxideLevel extends Characteristic { + + static readonly UUID: string = '00000090-0000-1000-8000-0026BB765291'; + + constructor() { + super('Carbon Monoxide Level', CarbonMonoxideLevel.UUID); + this.setProps({ + format: Formats.FLOAT, + maxValue: 100, + minValue: 0, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.CarbonMonoxideLevel = CarbonMonoxideLevel; + +/** + * Characteristic "Carbon Monoxide Peak Level" + */ + +export class CarbonMonoxidePeakLevel extends Characteristic { + + static readonly UUID: string = '00000091-0000-1000-8000-0026BB765291'; + + constructor() { + super('Carbon Monoxide Peak Level', CarbonMonoxidePeakLevel.UUID); + this.setProps({ + format: Formats.FLOAT, + maxValue: 100, + minValue: 0, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.CarbonMonoxidePeakLevel = CarbonMonoxidePeakLevel; + +/** + * Characteristic "Charging State" + */ + +export class ChargingState extends Characteristic { + + // The value property of ChargingState must be one of the following: + static readonly NOT_CHARGING = 0; + static readonly CHARGING = 1; + static readonly NOT_CHARGEABLE = 2; + + static readonly UUID: string = '0000008F-0000-1000-8000-0026BB765291'; + + constructor() { + super('Charging State', ChargingState.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 2, + minValue: 0, + validValues: [0, 1, 2], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.ChargingState = ChargingState; + +/** + * Characteristic "Color Temperature" + */ + +export class ColorTemperature extends Characteristic { + + static readonly UUID: string = '000000CE-0000-1000-8000-0026BB765291'; + + constructor() { + super('Color Temperature', ColorTemperature.UUID); + this.setProps({ + format: Formats.UINT32, + maxValue: 500, + minValue: 140, + minStep: 1, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.ColorTemperature = ColorTemperature; + +/** + * Characteristic "Contact Sensor State" + */ + +export class ContactSensorState extends Characteristic { + + // The value property of ContactSensorState must be one of the following: + static readonly CONTACT_DETECTED = 0; + static readonly CONTACT_NOT_DETECTED = 1; + + static readonly UUID: string = '0000006A-0000-1000-8000-0026BB765291'; + + constructor() { + super('Contact Sensor State', ContactSensorState.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0, 1], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.ContactSensorState = ContactSensorState; + +/** + * Characteristic "Cooling Threshold Temperature" + */ + +export class CoolingThresholdTemperature extends Characteristic { + + static readonly UUID: string = '0000000D-0000-1000-8000-0026BB765291'; + + constructor() { + super('Cooling Threshold Temperature', CoolingThresholdTemperature.UUID); + this.setProps({ + format: Formats.FLOAT, + unit: Units.CELSIUS, + maxValue: 35, + minValue: 10, + minStep: 0.1, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.CoolingThresholdTemperature = CoolingThresholdTemperature; + +/** + * Characteristic "Current Air Purifier State" + */ + +export class CurrentAirPurifierState extends Characteristic { + + // The value property of CurrentAirPurifierState must be one of the following: + static readonly INACTIVE = 0; + static readonly IDLE = 1; + static readonly PURIFYING_AIR = 2; + + static readonly UUID: string = '000000A9-0000-1000-8000-0026BB765291'; + + constructor() { + super('Current Air Purifier State', CurrentAirPurifierState.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 2, + minValue: 0, + validValues: [0, 1, 2], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.CurrentAirPurifierState = CurrentAirPurifierState; + + +/** + * Characteristic "Current Ambient Light Level" + */ + +export class CurrentAmbientLightLevel extends Characteristic { + + static readonly UUID: string = '0000006B-0000-1000-8000-0026BB765291'; + + constructor() { + super('Current Ambient Light Level', CurrentAmbientLightLevel.UUID); + this.setProps({ + format: Formats.FLOAT, + unit: Units.LUX, + maxValue: 100000, + minValue: 0.0001, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.CurrentAmbientLightLevel = CurrentAmbientLightLevel; + +/** + * Characteristic "Current Door State" + */ + +export class CurrentDoorState extends Characteristic { + + // The value property of CurrentDoorState must be one of the following: + static readonly OPEN = 0; + static readonly CLOSED = 1; + static readonly OPENING = 2; + static readonly CLOSING = 3; + static readonly STOPPED = 4; + + static readonly UUID: string = '0000000E-0000-1000-8000-0026BB765291'; + + constructor() { + super('Current Door State', CurrentDoorState.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 4, + minValue: 0, + validValues: [0, 1, 2, 3, 4], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.CurrentDoorState = CurrentDoorState; + +/** + * Characteristic "Current Fan State" + */ + +export class CurrentFanState extends Characteristic { + + // The value property of CurrentFanState must be one of the following: + static readonly INACTIVE = 0; + static readonly IDLE = 1; + static readonly BLOWING_AIR = 2; + + static readonly UUID: string = '000000AF-0000-1000-8000-0026BB765291'; + + constructor() { + super('Current Fan State', CurrentFanState.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 2, + minValue: 0, + validValues: [0, 1, 2], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.CurrentFanState = CurrentFanState; + +/** + * Characteristic "Current Heater Cooler State" + */ + +export class CurrentHeaterCoolerState extends Characteristic { + + // The value property of CurrentHeaterCoolerState must be one of the following: + static readonly INACTIVE = 0; + static readonly IDLE = 1; + static readonly HEATING = 2; + static readonly COOLING = 3; + + static readonly UUID: string = '000000B1-0000-1000-8000-0026BB765291'; + + constructor() { + super('Current Heater Cooler State', CurrentHeaterCoolerState.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 3, + minValue: 0, + validValues: [0, 1, 2, 3], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.CurrentHeaterCoolerState = CurrentHeaterCoolerState; + +/** + * Characteristic "Current Heating Cooling State" + */ + +export class CurrentHeatingCoolingState extends Characteristic { + + // The value property of CurrentHeatingCoolingState must be one of the following: + static readonly OFF = 0; + static readonly HEAT = 1; + static readonly COOL = 2; + + static readonly UUID: string = '0000000F-0000-1000-8000-0026BB765291'; + + constructor() { + super('Current Heating Cooling State', CurrentHeatingCoolingState.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 2, + minValue: 0, + validValues: [0, 1, 2], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.CurrentHeatingCoolingState = CurrentHeatingCoolingState; + +/** + * Characteristic "Current Horizontal Tilt Angle" + */ + +export class CurrentHorizontalTiltAngle extends Characteristic { + + static readonly UUID: string = '0000006C-0000-1000-8000-0026BB765291'; + + constructor() { + super('Current Horizontal Tilt Angle', CurrentHorizontalTiltAngle.UUID); + this.setProps({ + format: Formats.INT, + unit: Units.ARC_DEGREE, + maxValue: 90, + minValue: -90, + minStep: 1, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.CurrentHorizontalTiltAngle = CurrentHorizontalTiltAngle; + +/** + * Characteristic "Current Humidifier Dehumidifier State" + */ + +export class CurrentHumidifierDehumidifierState extends Characteristic { + + // The value property of CurrentHumidifierDehumidifierState must be one of the following: + static readonly INACTIVE = 0; + static readonly IDLE = 1; + static readonly HUMIDIFYING = 2; + static readonly DEHUMIDIFYING = 3; + + static readonly UUID: string = '000000B3-0000-1000-8000-0026BB765291'; + + constructor() { + super('Current Humidifier Dehumidifier State', CurrentHumidifierDehumidifierState.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 3, + minValue: 0, + validValues: [0, 1, 2, 3], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.CurrentHumidifierDehumidifierState = CurrentHumidifierDehumidifierState; + +/** + * Characteristic "Current Position" + */ + +export class CurrentPosition extends Characteristic { + + static readonly UUID: string = '0000006D-0000-1000-8000-0026BB765291'; + + constructor() { + super('Current Position', CurrentPosition.UUID); + this.setProps({ + format: Formats.UINT8, + unit: Units.PERCENTAGE, + maxValue: 100, + minValue: 0, + minStep: 1, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.CurrentPosition = CurrentPosition; + +/** + * Characteristic "Current Relative Humidity" + */ + +export class CurrentRelativeHumidity extends Characteristic { + + static readonly UUID: string = '00000010-0000-1000-8000-0026BB765291'; + + constructor() { + super('Current Relative Humidity', CurrentRelativeHumidity.UUID); + this.setProps({ + format: Formats.FLOAT, + unit: Units.PERCENTAGE, + maxValue: 100, + minValue: 0, + minStep: 1, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.CurrentRelativeHumidity = CurrentRelativeHumidity; + +/** + * Characteristic "Current Slat State" + */ + +export class CurrentSlatState extends Characteristic { + + // The value property of CurrentSlatState must be one of the following: + static readonly FIXED = 0; + static readonly JAMMED = 1; + static readonly SWINGING = 2; + + static readonly UUID: string = '000000AA-0000-1000-8000-0026BB765291'; + + constructor() { + super('Current Slat State', CurrentSlatState.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 2, + minValue: 0, + validValues: [0, 1, 2], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.CurrentSlatState = CurrentSlatState; + +/** + * Characteristic "Current Temperature" + */ + +export class CurrentTemperature extends Characteristic { + + static readonly UUID: string = '00000011-0000-1000-8000-0026BB765291'; + + constructor() { + super('Current Temperature', CurrentTemperature.UUID); + this.setProps({ + format: Formats.FLOAT, + unit: Units.CELSIUS, + maxValue: 100, + minValue: 0, + minStep: 0.1, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.CurrentTemperature = CurrentTemperature; + +/** + * Characteristic "Current Tilt Angle" + */ + +export class CurrentTiltAngle extends Characteristic { + + static readonly UUID: string = '000000C1-0000-1000-8000-0026BB765291'; + + constructor() { + super('Current Tilt Angle', CurrentTiltAngle.UUID); + this.setProps({ + format: Formats.INT, + unit: Units.ARC_DEGREE, + maxValue: 90, + minValue: -90, + minStep: 1, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.CurrentTiltAngle = CurrentTiltAngle; + +/** + * Characteristic "Current Vertical Tilt Angle" + */ + +export class CurrentVerticalTiltAngle extends Characteristic { + + static readonly UUID: string = '0000006E-0000-1000-8000-0026BB765291'; + + constructor() { + super('Current Vertical Tilt Angle', CurrentVerticalTiltAngle.UUID); + this.setProps({ + format: Formats.INT, + unit: Units.ARC_DEGREE, + maxValue: 90, + minValue: -90, + minStep: 1, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.CurrentVerticalTiltAngle = CurrentVerticalTiltAngle; + +/** + * Characteristic "Digital Zoom" + */ + +export class DigitalZoom extends Characteristic { + + static readonly UUID: string = '0000011D-0000-1000-8000-0026BB765291'; + + constructor() { + super('Digital Zoom', DigitalZoom.UUID); + this.setProps({ + format: Formats.FLOAT, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.DigitalZoom = DigitalZoom; + +/** + * Characteristic "Filter Change Indication" + */ + +export class FilterChangeIndication extends Characteristic { + + // The value property of FilterChangeIndication must be one of the following: + static readonly FILTER_OK = 0; + static readonly CHANGE_FILTER = 1; + + static readonly UUID: string = '000000AC-0000-1000-8000-0026BB765291'; + + constructor() { + super('Filter Change Indication', FilterChangeIndication.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0, 1], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.FilterChangeIndication = FilterChangeIndication; + +/** + * Characteristic "Filter Life Level" + */ + +export class FilterLifeLevel extends Characteristic { + + static readonly UUID: string = '000000AB-0000-1000-8000-0026BB765291'; + + constructor() { + super('Filter Life Level', FilterLifeLevel.UUID); + this.setProps({ + format: Formats.FLOAT, + maxValue: 100, + minValue: 0, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.FilterLifeLevel = FilterLifeLevel; + +/** + * Characteristic "Firmware Revision" + */ + +export class FirmwareRevision extends Characteristic { + + static readonly UUID: string = '00000052-0000-1000-8000-0026BB765291'; + + constructor() { + super('Firmware Revision', FirmwareRevision.UUID); + this.setProps({ + format: Formats.STRING, + perms: [Perms.READ] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.FirmwareRevision = FirmwareRevision; + +/** + * Characteristic "Hardware Revision" + */ + +export class HardwareRevision extends Characteristic { + + static readonly UUID: string = '00000053-0000-1000-8000-0026BB765291'; + + constructor() { + super('Hardware Revision', HardwareRevision.UUID); + this.setProps({ + format: Formats.STRING, + perms: [Perms.READ] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.HardwareRevision = HardwareRevision; + +/** + * Characteristic "Heating Threshold Temperature" + */ + +export class HeatingThresholdTemperature extends Characteristic { + + static readonly UUID: string = '00000012-0000-1000-8000-0026BB765291'; + + constructor() { + super('Heating Threshold Temperature', HeatingThresholdTemperature.UUID); + this.setProps({ + format: Formats.FLOAT, + unit: Units.CELSIUS, + maxValue: 25, + minValue: 0, + minStep: 0.1, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.HeatingThresholdTemperature = HeatingThresholdTemperature; + +/** + * Characteristic "Hold Position" + */ + +export class HoldPosition extends Characteristic { + + static readonly UUID: string = '0000006F-0000-1000-8000-0026BB765291'; + + constructor() { + super('Hold Position', HoldPosition.UUID); + this.setProps({ + format: Formats.BOOL, + perms: [Perms.WRITE] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.HoldPosition = HoldPosition; + +/** + * Characteristic "Hue" + */ + +export class Hue extends Characteristic { + + static readonly UUID: string = '00000013-0000-1000-8000-0026BB765291'; + + constructor() { + super('Hue', Hue.UUID); + this.setProps({ + format: Formats.FLOAT, + unit: Units.ARC_DEGREE, + maxValue: 360, + minValue: 0, + minStep: 1, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.Hue = Hue; + +/** + * Characteristic "Identify" + */ + +export class Identify extends Characteristic { + + static readonly UUID: string = '00000014-0000-1000-8000-0026BB765291'; + + constructor() { + super('Identify', Identify.UUID); + this.setProps({ + format: Formats.BOOL, + perms: [Perms.WRITE] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.Identify = Identify; + +/** + * Characteristic "Image Mirroring" + */ + +export class ImageMirroring extends Characteristic { + + static readonly UUID: string = '0000011F-0000-1000-8000-0026BB765291'; + + constructor() { + super('Image Mirroring', ImageMirroring.UUID); + this.setProps({ + format: Formats.BOOL, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.ImageMirroring = ImageMirroring; + +/** + * Characteristic "Image Rotation" + */ + +export class ImageRotation extends Characteristic { + + static readonly UUID: string = '0000011E-0000-1000-8000-0026BB765291'; + + constructor() { + super('Image Rotation', ImageRotation.UUID); + this.setProps({ + format: Formats.FLOAT, + unit: Units.ARC_DEGREE, + maxValue: 270, + minValue: 0, + minStep: 90, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.ImageRotation = ImageRotation; + +/** + * Characteristic "In Use" + */ + +export class InUse extends Characteristic { + + // The value property of InUse must be one of the following: + static readonly NOT_IN_USE = 0; + static readonly IN_USE = 1; + + static readonly UUID: string = '000000D2-0000-1000-8000-0026BB765291'; + + constructor() { + super('In Use', InUse.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0, 1], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.InUse = InUse; + +/** + * Characteristic "Is Configured" + */ + +export class IsConfigured extends Characteristic { + + // The value property of IsConfigured must be one of the following: + static readonly NOT_CONFIGURED = 0; + static readonly CONFIGURED = 1; + + static readonly UUID: string = '000000D6-0000-1000-8000-0026BB765291'; + + constructor() { + super('Is Configured', IsConfigured.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0, 1], + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.IsConfigured = IsConfigured; + +/** + * Characteristic "Leak Detected" + */ + +export class LeakDetected extends Characteristic { + + // The value property of LeakDetected must be one of the following: + static readonly LEAK_NOT_DETECTED = 0; + static readonly LEAK_DETECTED = 1; + + static readonly UUID: string = '00000070-0000-1000-8000-0026BB765291'; + + constructor() { + super('Leak Detected', LeakDetected.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0, 1], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.LeakDetected = LeakDetected; + +/** + * Characteristic "Lock Control Point" + */ + +export class LockControlPoint extends Characteristic { + + static readonly UUID: string = '00000019-0000-1000-8000-0026BB765291'; + + constructor() { + super('Lock Control Point', LockControlPoint.UUID); + this.setProps({ + format: Formats.TLV8, + perms: [Perms.WRITE] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.LockControlPoint = LockControlPoint; + +/** + * Characteristic "Lock Current State" + */ + +export class LockCurrentState extends Characteristic { + + // The value property of LockCurrentState must be one of the following: + static readonly UNSECURED = 0; + static readonly SECURED = 1; + static readonly JAMMED = 2; + static readonly UNKNOWN = 3; + + static readonly UUID: string = '0000001D-0000-1000-8000-0026BB765291'; + + constructor() { + super('Lock Current State', LockCurrentState.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 3, + minValue: 0, + validValues: [0, 1, 2, 3], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.LockCurrentState = LockCurrentState; + +/** + * Characteristic "Lock Last Known Action" + */ + +export class LockLastKnownAction extends Characteristic { + + // The value property of LockLastKnownAction must be one of the following: + static readonly SECURED_PHYSICALLY_INTERIOR = 0; + static readonly UNSECURED_PHYSICALLY_INTERIOR = 1; + static readonly SECURED_PHYSICALLY_EXTERIOR = 2; + static readonly UNSECURED_PHYSICALLY_EXTERIOR = 3; + static readonly SECURED_BY_KEYPAD = 4; + static readonly UNSECURED_BY_KEYPAD = 5; + static readonly SECURED_REMOTELY = 6; + static readonly UNSECURED_REMOTELY = 7; + static readonly SECURED_BY_AUTO_SECURE_TIMEOUT = 8; + + static readonly UUID: string = '0000001C-0000-1000-8000-0026BB765291'; + + constructor() { + super('Lock Last Known Action', LockLastKnownAction.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 8, + minValue: 0, + validValues: [0, 1, 2, 3, 4, 5, 6, 7, 8], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.LockLastKnownAction = LockLastKnownAction; + +/** + * Characteristic "Lock Management Auto Security Timeout" + */ + +export class LockManagementAutoSecurityTimeout extends Characteristic { + + static readonly UUID: string = '0000001A-0000-1000-8000-0026BB765291'; + + constructor() { + super('Lock Management Auto Security Timeout', LockManagementAutoSecurityTimeout.UUID); + this.setProps({ + format: Formats.UINT32, + unit: Units.SECONDS, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.LockManagementAutoSecurityTimeout = LockManagementAutoSecurityTimeout; + +/** + * Characteristic "Lock Physical Controls" + */ + +export class LockPhysicalControls extends Characteristic { + + // The value property of LockPhysicalControls must be one of the following: + static readonly CONTROL_LOCK_DISABLED = 0; + static readonly CONTROL_LOCK_ENABLED = 1; + + static readonly UUID: string = '000000A7-0000-1000-8000-0026BB765291'; + + constructor() { + super('Lock Physical Controls', LockPhysicalControls.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0, 1], + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.LockPhysicalControls = LockPhysicalControls; + +/** + * Characteristic "Lock Target State" + */ + +export class LockTargetState extends Characteristic { + + // The value property of LockTargetState must be one of the following: + static readonly UNSECURED = 0; + static readonly SECURED = 1; + + static readonly UUID: string = '0000001E-0000-1000-8000-0026BB765291'; + + constructor() { + super('Lock Target State', LockTargetState.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0, 1], + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.LockTargetState = LockTargetState; + +/** + * Characteristic "Logs" + */ + +export class Logs extends Characteristic { + + static readonly UUID: string = '0000001F-0000-1000-8000-0026BB765291'; + + constructor() { + super('Logs', Logs.UUID); + this.setProps({ + format: Formats.TLV8, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.Logs = Logs; + +/** + * Characteristic "Manufacturer" + */ + +export class Manufacturer extends Characteristic { + + static readonly UUID: string = '00000020-0000-1000-8000-0026BB765291'; + + constructor() { + super('Manufacturer', Manufacturer.UUID); + this.setProps({ + format: Formats.STRING, + perms: [Perms.READ] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.Manufacturer = Manufacturer; + +/** + * Characteristic "Model" + */ + +export class Model extends Characteristic { + + static readonly UUID: string = '00000021-0000-1000-8000-0026BB765291'; + + constructor() { + super('Model', Model.UUID); + this.setProps({ + format: Formats.STRING, + perms: [Perms.READ] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.Model = Model; + +/** + * Characteristic "Motion Detected" + */ + +export class MotionDetected extends Characteristic { + + static readonly UUID: string = '00000022-0000-1000-8000-0026BB765291'; + + constructor() { + super('Motion Detected', MotionDetected.UUID); + this.setProps({ + format: Formats.BOOL, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.MotionDetected = MotionDetected; + +/** + * Characteristic "Mute" + */ + +export class Mute extends Characteristic { + + static readonly UUID: string = '0000011A-0000-1000-8000-0026BB765291'; + + constructor() { + super('Mute', Mute.UUID); + this.setProps({ + format: Formats.BOOL, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.Mute = Mute; + +/** + * Characteristic "Name" + */ + +export class Name extends Characteristic { + + static readonly UUID: string = '00000023-0000-1000-8000-0026BB765291'; + + constructor() { + super('Name', Name.UUID); + this.setProps({ + format: Formats.STRING, + perms: [Perms.READ] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.Name = Name; + +/** + * Characteristic "Night Vision" + */ + +export class NightVision extends Characteristic { + + static readonly UUID: string = '0000011B-0000-1000-8000-0026BB765291'; + + constructor() { + super('Night Vision', NightVision.UUID); + this.setProps({ + format: Formats.BOOL, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.NightVision = NightVision; + +/** + * Characteristic "Nitrogen Dioxide Density" + */ + +export class NitrogenDioxideDensity extends Characteristic { + + static readonly UUID: string = '000000C4-0000-1000-8000-0026BB765291'; + + constructor() { + super('Nitrogen Dioxide Density', NitrogenDioxideDensity.UUID); + this.setProps({ + format: Formats.FLOAT, + maxValue: 1000, + minValue: 0, + minStep: 1, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.NitrogenDioxideDensity = NitrogenDioxideDensity; + +/** + * Characteristic "Obstruction Detected" + */ + +export class ObstructionDetected extends Characteristic { + + static readonly UUID: string = '00000024-0000-1000-8000-0026BB765291'; + + constructor() { + super('Obstruction Detected', ObstructionDetected.UUID); + this.setProps({ + format: Formats.BOOL, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.ObstructionDetected = ObstructionDetected; + +/** + * Characteristic "Occupancy Detected" + */ + +export class OccupancyDetected extends Characteristic { + + // The value property of OccupancyDetected must be one of the following: + static readonly OCCUPANCY_NOT_DETECTED = 0; + static readonly OCCUPANCY_DETECTED = 1; + + static readonly UUID: string = '00000071-0000-1000-8000-0026BB765291'; + + constructor() { + super('Occupancy Detected', OccupancyDetected.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0, 1], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.OccupancyDetected = OccupancyDetected; + +/** + * Characteristic "On" + */ + +export class On extends Characteristic { + + static readonly UUID: string = '00000025-0000-1000-8000-0026BB765291'; + + constructor() { + super('On', On.UUID); + this.setProps({ + format: Formats.BOOL, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.On = On; + +/** + * Characteristic "Optical Zoom" + */ + +export class OpticalZoom extends Characteristic { + + static readonly UUID: string = '0000011C-0000-1000-8000-0026BB765291'; + + constructor() { + super('Optical Zoom', OpticalZoom.UUID); + this.setProps({ + format: Formats.FLOAT, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.OpticalZoom = OpticalZoom; + +/** + * Characteristic "Outlet In Use" + */ + +export class OutletInUse extends Characteristic { + + static readonly UUID: string = '00000026-0000-1000-8000-0026BB765291'; + + constructor() { + super('Outlet In Use', OutletInUse.UUID); + this.setProps({ + format: Formats.BOOL, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.OutletInUse = OutletInUse; + +/** + * Characteristic "Ozone Density" + */ + +export class OzoneDensity extends Characteristic { + + static readonly UUID: string = '000000C3-0000-1000-8000-0026BB765291'; + + constructor() { + super('Ozone Density', OzoneDensity.UUID); + this.setProps({ + format: Formats.FLOAT, + maxValue: 1000, + minValue: 0, + minStep: 1, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.OzoneDensity = OzoneDensity; + +/** + * Characteristic "Pair Setup" + */ + +export class PairSetup extends Characteristic { + + static readonly UUID: string = '0000004C-0000-1000-8000-0026BB765291'; + + constructor() { + super('Pair Setup', PairSetup.UUID); + this.setProps({ + format: Formats.TLV8, + perms: [Perms.READ, Perms.WRITE] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.PairSetup = PairSetup; + +/** + * Characteristic "Pair Verify" + */ + +export class PairVerify extends Characteristic { + + static readonly UUID: string = '0000004E-0000-1000-8000-0026BB765291'; + + constructor() { + super('Pair Verify', PairVerify.UUID); + this.setProps({ + format: Formats.TLV8, + perms: [Perms.READ, Perms.WRITE] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.PairVerify = PairVerify; + +/** + * Characteristic "Pairing Features" + */ + +export class PairingFeatures extends Characteristic { + + static readonly UUID: string = '0000004F-0000-1000-8000-0026BB765291'; + + constructor() { + super('Pairing Features', PairingFeatures.UUID); + this.setProps({ + format: Formats.UINT8, + perms: [Perms.READ] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.PairingFeatures = PairingFeatures; + +/** + * Characteristic "Pairing Pairings" + */ + +export class PairingPairings extends Characteristic { + + static readonly UUID: string = '00000050-0000-1000-8000-0026BB765291'; + + constructor() { + super('Pairing Pairings', PairingPairings.UUID); + this.setProps({ + format: Formats.TLV8, + perms: [Perms.READ, Perms.WRITE] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.PairingPairings = PairingPairings; + +/** + * Characteristic "PM10 Density" + */ + +export class PM10Density extends Characteristic { + + static readonly UUID: string = '000000C7-0000-1000-8000-0026BB765291'; + + constructor() { + super('PM10 Density', PM10Density.UUID); + this.setProps({ + format: Formats.FLOAT, + maxValue: 1000, + minValue: 0, + minStep: 1, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.PM10Density = PM10Density; + +/** + * Characteristic "PM2.5 Density" + */ + +export class PM2_5Density extends Characteristic { + + static readonly UUID: string = '000000C6-0000-1000-8000-0026BB765291'; + + constructor() { + super('PM2.5 Density', PM2_5Density.UUID); + this.setProps({ + format: Formats.FLOAT, + maxValue: 1000, + minValue: 0, + minStep: 1, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.PM2_5Density = PM2_5Density; + +/** + * Characteristic "Position State" + */ + +export class PositionState extends Characteristic { + + // The value property of PositionState must be one of the following: + static readonly DECREASING = 0; + static readonly INCREASING = 1; + static readonly STOPPED = 2; + + static readonly UUID: string = '00000072-0000-1000-8000-0026BB765291'; + + constructor() { + super('Position State', PositionState.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 2, + minValue: 0, + validValues: [0, 1, 2], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.PositionState = PositionState; + +/** + * Characteristic "Program Mode" + */ + +export class ProgramMode extends Characteristic { + + // The value property of ProgramMode must be one of the following: + static readonly NO_PROGRAM_SCHEDULED = 0; + static readonly PROGRAM_SCHEDULED = 1; + static readonly PROGRAM_SCHEDULED_MANUAL_MODE_ = 2; + + static readonly UUID: string = '000000D1-0000-1000-8000-0026BB765291'; + + constructor() { + super('Program Mode', ProgramMode.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 2, + minValue: 0, + validValues: [0, 1, 2], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.ProgramMode = ProgramMode; + +/** + * Characteristic "Programmable Switch Event" + */ + +export class ProgrammableSwitchEvent extends Characteristic { + + // The value property of ProgrammableSwitchEvent must be one of the following: + static readonly SINGLE_PRESS = 0; + static readonly DOUBLE_PRESS = 1; + static readonly LONG_PRESS = 2; + + static readonly UUID: string = '00000073-0000-1000-8000-0026BB765291'; + + constructor() { + super('Programmable Switch Event', ProgrammableSwitchEvent.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 2, + minValue: 0, + validValues: [0, 1, 2], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.eventOnlyCharacteristic = true; //Manual addition. + this.value = this.getDefaultValue(); + } +} + +Characteristic.ProgrammableSwitchEvent = ProgrammableSwitchEvent; + +/** + * Characteristic "Relative Humidity Dehumidifier Threshold" + */ + +export class RelativeHumidityDehumidifierThreshold extends Characteristic { + + static readonly UUID: string = '000000C9-0000-1000-8000-0026BB765291'; + + constructor() { + super('Relative Humidity Dehumidifier Threshold', RelativeHumidityDehumidifierThreshold.UUID); + this.setProps({ + format: Formats.FLOAT, + maxValue: 100, + minValue: 0, + minStep: 1, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.RelativeHumidityDehumidifierThreshold = RelativeHumidityDehumidifierThreshold; + +/** + * Characteristic "Relative Humidity Humidifier Threshold" + */ + +export class RelativeHumidityHumidifierThreshold extends Characteristic { + + static readonly UUID: string = '000000CA-0000-1000-8000-0026BB765291'; + + constructor() { + super('Relative Humidity Humidifier Threshold', RelativeHumidityHumidifierThreshold.UUID); + this.setProps({ + format: Formats.FLOAT, + unit: Units.PERCENTAGE, + maxValue: 100, + minValue: 0, + minStep: 1, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.RelativeHumidityHumidifierThreshold = RelativeHumidityHumidifierThreshold; + +/** + * Characteristic "Remaining Duration" + */ + +export class RemainingDuration extends Characteristic { + + static readonly UUID: string = '000000D4-0000-1000-8000-0026BB765291'; + + constructor() { + super('Remaining Duration', RemainingDuration.UUID); + this.setProps({ + format: Formats.UINT32, + maxValue: 3600, + minValue: 0, + minStep: 1, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.RemainingDuration = RemainingDuration; + +/** + * Characteristic "Reset Filter Indication" + */ + +export class ResetFilterIndication extends Characteristic { + + static readonly UUID: string = '000000AD-0000-1000-8000-0026BB765291'; + + constructor() { + super('Reset Filter Indication', ResetFilterIndication.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 1, + minStep: 1, + perms: [Perms.WRITE] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.ResetFilterIndication = ResetFilterIndication; + +/** + * Characteristic "Rotation Direction" + */ + +export class RotationDirection extends Characteristic { + + // The value property of RotationDirection must be one of the following: + static readonly CLOCKWISE = 0; + static readonly COUNTER_CLOCKWISE = 1; + + static readonly UUID: string = '00000028-0000-1000-8000-0026BB765291'; + + constructor() { + super('Rotation Direction', RotationDirection.UUID); + this.setProps({ + format: Formats.INT, + maxValue: 1, + minValue: 0, + validValues: [0, 1], + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.RotationDirection = RotationDirection; + +/** + * Characteristic "Rotation Speed" + */ + +export class RotationSpeed extends Characteristic { + + static readonly UUID: string = '00000029-0000-1000-8000-0026BB765291'; + + constructor() { + super('Rotation Speed', RotationSpeed.UUID); + this.setProps({ + format: Formats.FLOAT, + unit: Units.PERCENTAGE, + maxValue: 100, + minValue: 0, + minStep: 1, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.RotationSpeed = RotationSpeed; + +/** + * Characteristic "Saturation" + */ + +export class Saturation extends Characteristic { + + static readonly UUID: string = '0000002F-0000-1000-8000-0026BB765291'; + + constructor() { + super('Saturation', Saturation.UUID); + this.setProps({ + format: Formats.FLOAT, + unit: Units.PERCENTAGE, + maxValue: 100, + minValue: 0, + minStep: 1, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.Saturation = Saturation; + +/** + * Characteristic "Security System Alarm Type" + */ + +export class SecuritySystemAlarmType extends Characteristic { + + static readonly UUID: string = '0000008E-0000-1000-8000-0026BB765291'; + + constructor() { + super('Security System Alarm Type', SecuritySystemAlarmType.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + minStep: 1, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.SecuritySystemAlarmType = SecuritySystemAlarmType; + +/** + * Characteristic "Security System Current State" + */ + +export class SecuritySystemCurrentState extends Characteristic { + + // The value property of SecuritySystemCurrentState must be one of the following: + static readonly STAY_ARM = 0; + static readonly AWAY_ARM = 1; + static readonly NIGHT_ARM = 2; + static readonly DISARMED = 3; + static readonly ALARM_TRIGGERED = 4; + + static readonly UUID: string = '00000066-0000-1000-8000-0026BB765291'; + + constructor() { + super('Security System Current State', SecuritySystemCurrentState.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 4, + minValue: 0, + validValues: [0, 1, 2, 3, 4], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.SecuritySystemCurrentState = SecuritySystemCurrentState; + +/** + * Characteristic "Security System Target State" + */ + +export class SecuritySystemTargetState extends Characteristic { + + // The value property of SecuritySystemTargetState must be one of the following: + static readonly STAY_ARM = 0; + static readonly AWAY_ARM = 1; + static readonly NIGHT_ARM = 2; + static readonly DISARM = 3; + + static readonly UUID: string = '00000067-0000-1000-8000-0026BB765291'; + + constructor() { + super('Security System Target State', SecuritySystemTargetState.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 3, + minValue: 0, + validValues: [0, 1, 2, 3], + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.SecuritySystemTargetState = SecuritySystemTargetState; + +/** + * Characteristic "Selected RTP Stream Configuration" + */ + +export class SelectedRTPStreamConfiguration extends Characteristic { + + static readonly UUID: string = '00000117-0000-1000-8000-0026BB765291'; + + constructor() { + super('Selected RTP Stream Configuration', SelectedRTPStreamConfiguration.UUID); + this.setProps({ + format: Formats.TLV8, + perms: [Perms.READ, Perms.WRITE] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.SelectedRTPStreamConfiguration = SelectedRTPStreamConfiguration; + +/** + * Characteristic "Serial Number" + */ + +export class SerialNumber extends Characteristic { + + static readonly UUID: string = '00000030-0000-1000-8000-0026BB765291'; + + constructor() { + super('Serial Number', SerialNumber.UUID); + this.setProps({ + format: Formats.STRING, + perms: [Perms.READ] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.SerialNumber = SerialNumber; + +/** + * Characteristic "Service Label Index" + */ + +export class ServiceLabelIndex extends Characteristic { + + static readonly UUID: string = '000000CB-0000-1000-8000-0026BB765291'; + + constructor() { + super('Service Label Index', ServiceLabelIndex.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 255, + minValue: 1, + minStep: 1, + perms: [Perms.READ] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.ServiceLabelIndex = ServiceLabelIndex; + +/** + * Characteristic "Service Label Namespace" + */ + +export class ServiceLabelNamespace extends Characteristic { + + // The value property of ServiceLabelNamespace must be one of the following: + static readonly DOTS = 0; + static readonly ARABIC_NUMERALS = 1; + + static readonly UUID: string = '000000CD-0000-1000-8000-0026BB765291'; + + constructor() { + super('Service Label Namespace', ServiceLabelNamespace.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0, 1], + perms: [Perms.READ] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.ServiceLabelNamespace = ServiceLabelNamespace; + +/** + * Characteristic "Set Duration" + */ + +export class SetDuration extends Characteristic { + + static readonly UUID: string = '000000D3-0000-1000-8000-0026BB765291'; + + constructor() { + super('Set Duration', SetDuration.UUID); + this.setProps({ + format: Formats.UINT32, + maxValue: 3600, + minValue: 0, + minStep: 1, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.SetDuration = SetDuration; + +/** + * Characteristic "Setup Endpoints" + */ + +export class SetupEndpoints extends Characteristic { + + static readonly UUID: string = '00000118-0000-1000-8000-0026BB765291'; + + constructor() { + super('Setup Endpoints', SetupEndpoints.UUID); + this.setProps({ + format: Formats.TLV8, + perms: [Perms.READ, Perms.WRITE] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.SetupEndpoints = SetupEndpoints; + +/** + * Characteristic "Slat Type" + */ + +export class SlatType extends Characteristic { + + // The value property of SlatType must be one of the following: + static readonly HORIZONTAL = 0; + static readonly VERTICAL = 1; + + static readonly UUID: string = '000000C0-0000-1000-8000-0026BB765291'; + + constructor() { + super('Slat Type', SlatType.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0, 1], + perms: [Perms.READ] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.SlatType = SlatType; + +/** + * Characteristic "Smoke Detected" + */ + +export class SmokeDetected extends Characteristic { + + // The value property of SmokeDetected must be one of the following: + static readonly SMOKE_NOT_DETECTED = 0; + static readonly SMOKE_DETECTED = 1; + + static readonly UUID: string = '00000076-0000-1000-8000-0026BB765291'; + + constructor() { + super('Smoke Detected', SmokeDetected.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0, 1], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.SmokeDetected = SmokeDetected; + +/** + * Characteristic "Status Active" + */ + +export class StatusActive extends Characteristic { + + static readonly UUID: string = '00000075-0000-1000-8000-0026BB765291'; + + constructor() { + super('Status Active', StatusActive.UUID); + this.setProps({ + format: Formats.BOOL, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.StatusActive = StatusActive; + +/** + * Characteristic "Status Fault" + */ + +export class StatusFault extends Characteristic { + + // The value property of StatusFault must be one of the following: + static readonly NO_FAULT = 0; + static readonly GENERAL_FAULT = 1; + + static readonly UUID: string = '00000077-0000-1000-8000-0026BB765291'; + + constructor() { + super('Status Fault', StatusFault.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0, 1], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.StatusFault = StatusFault; + +/** + * Characteristic "Status Jammed" + */ + +export class StatusJammed extends Characteristic { + + // The value property of StatusJammed must be one of the following: + static readonly NOT_JAMMED = 0; + static readonly JAMMED = 1; + + static readonly UUID: string = '00000078-0000-1000-8000-0026BB765291'; + + constructor() { + super('Status Jammed', StatusJammed.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0, 1], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.StatusJammed = StatusJammed; + +/** + * Characteristic "Status Low Battery" + */ + +export class StatusLowBattery extends Characteristic { + + // The value property of StatusLowBattery must be one of the following: + static readonly BATTERY_LEVEL_NORMAL = 0; + static readonly BATTERY_LEVEL_LOW = 1; + + static readonly UUID: string = '00000079-0000-1000-8000-0026BB765291'; + + constructor() { + super('Status Low Battery', StatusLowBattery.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0, 1], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.StatusLowBattery = StatusLowBattery; + + +/** + * Characteristic "Status Tampered" + */ + +export class StatusTampered extends Characteristic { + + // The value property of StatusTampered must be one of the following: + static readonly NOT_TAMPERED = 0; + static readonly TAMPERED = 1; + + static readonly UUID: string = '0000007A-0000-1000-8000-0026BB765291'; + + constructor() { + super('Status Tampered', StatusTampered.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0, 1], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.StatusTampered = StatusTampered; + +/** + * Characteristic "Streaming Status" + */ + +export class StreamingStatus extends Characteristic { + + static readonly UUID: string = '00000120-0000-1000-8000-0026BB765291'; + + constructor() { + super('Streaming Status', StreamingStatus.UUID); + this.setProps({ + format: Formats.TLV8, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.StreamingStatus = StreamingStatus; + +/** + * Characteristic "Sulphur Dioxide Density" + */ + +export class SulphurDioxideDensity extends Characteristic { + + static readonly UUID: string = '000000C5-0000-1000-8000-0026BB765291'; + + constructor() { + super('Sulphur Dioxide Density', SulphurDioxideDensity.UUID); + this.setProps({ + format: Formats.FLOAT, + maxValue: 1000, + minValue: 0, + minStep: 1, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.SulphurDioxideDensity = SulphurDioxideDensity; + +/** + * Characteristic "Supported Audio Stream Configuration" + */ + +export class SupportedAudioStreamConfiguration extends Characteristic { + + static readonly UUID: string = '00000115-0000-1000-8000-0026BB765291'; + + constructor() { + super('Supported Audio Stream Configuration', SupportedAudioStreamConfiguration.UUID); + this.setProps({ + format: Formats.TLV8, + perms: [Perms.READ] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.SupportedAudioStreamConfiguration = SupportedAudioStreamConfiguration; + +/** + * Characteristic "Supported RTP Configuration" + */ + +export class SupportedRTPConfiguration extends Characteristic { + + static readonly UUID: string = '00000116-0000-1000-8000-0026BB765291'; + + constructor() { + super('Supported RTP Configuration', SupportedRTPConfiguration.UUID); + this.setProps({ + format: Formats.TLV8, + perms: [Perms.READ] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.SupportedRTPConfiguration = SupportedRTPConfiguration; + +/** + * Characteristic "Supported Video Stream Configuration" + */ + +export class SupportedVideoStreamConfiguration extends Characteristic { + + static readonly UUID: string = '00000114-0000-1000-8000-0026BB765291'; + + constructor() { + super('Supported Video Stream Configuration', SupportedVideoStreamConfiguration.UUID); + this.setProps({ + format: Formats.TLV8, + perms: [Perms.READ] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.SupportedVideoStreamConfiguration = SupportedVideoStreamConfiguration; + +/** + * Characteristic "Swing Mode" + */ + +export class SwingMode extends Characteristic { + + // The value property of SwingMode must be one of the following: + static readonly SWING_DISABLED = 0; + static readonly SWING_ENABLED = 1; + + static readonly UUID: string = '000000B6-0000-1000-8000-0026BB765291'; + + constructor() { + super('Swing Mode', SwingMode.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0, 1], + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.SwingMode = SwingMode; + +/** + * Characteristic "Target Air Purifier State" + */ + +export class TargetAirPurifierState extends Characteristic { + + // The value property of TargetAirPurifierState must be one of the following: + static readonly MANUAL = 0; + static readonly AUTO = 1; + + static readonly UUID: string = '000000A8-0000-1000-8000-0026BB765291'; + + constructor() { + super('Target Air Purifier State', TargetAirPurifierState.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0, 1], + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.TargetAirPurifierState = TargetAirPurifierState; + +/** + * Characteristic "Target Air Quality" + */ + +export class TargetAirQuality extends Characteristic { + + // The value property of TargetAirQuality must be one of the following: + static readonly EXCELLENT = 0; + static readonly GOOD = 1; + static readonly FAIR = 2; + + static readonly UUID: string = '000000AE-0000-1000-8000-0026BB765291'; + + constructor() { + super('Target Air Quality', TargetAirQuality.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 2, + minValue: 0, + validValues: [0, 1, 2], + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.TargetAirQuality = TargetAirQuality; + +/** + * Characteristic "Target Door State" + */ + +export class TargetDoorState extends Characteristic { + + // The value property of TargetDoorState must be one of the following: + static readonly OPEN = 0; + static readonly CLOSED = 1; + + static readonly UUID: string = '00000032-0000-1000-8000-0026BB765291'; + + constructor() { + super('Target Door State', TargetDoorState.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0, 1], + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.TargetDoorState = TargetDoorState; + +/** + * Characteristic "Target Fan State" + */ + +export class TargetFanState extends Characteristic { + + // The value property of TargetFanState must be one of the following: + static readonly MANUAL = 0; + static readonly AUTO = 1; + + static readonly UUID: string = '000000BF-0000-1000-8000-0026BB765291'; + + constructor() { + super('Target Fan State', TargetFanState.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0, 1], + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.TargetFanState = TargetFanState; + +/** + * Characteristic "Target Heater Cooler State" + */ + +export class TargetHeaterCoolerState extends Characteristic { + + // The value property of TargetHeaterCoolerState must be one of the following: + static readonly AUTO = 0; + static readonly HEAT = 1; + static readonly COOL = 2; + + static readonly UUID: string = '000000B2-0000-1000-8000-0026BB765291'; + + constructor() { + super('Target Heater Cooler State', TargetHeaterCoolerState.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 2, + minValue: 0, + validValues: [0, 1, 2], + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.TargetHeaterCoolerState = TargetHeaterCoolerState; + +/** + * Characteristic "Target Heating Cooling State" + */ + +export class TargetHeatingCoolingState extends Characteristic { + + // The value property of TargetHeatingCoolingState must be one of the following: + static readonly OFF = 0; + static readonly HEAT = 1; + static readonly COOL = 2; + static readonly AUTO = 3; + + static readonly UUID: string = '00000033-0000-1000-8000-0026BB765291'; + + constructor() { + super('Target Heating Cooling State', TargetHeatingCoolingState.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 3, + minValue: 0, + validValues: [0, 1, 2, 3], + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.TargetHeatingCoolingState = TargetHeatingCoolingState; + +/** + * Characteristic "Target Horizontal Tilt Angle" + */ + +export class TargetHorizontalTiltAngle extends Characteristic { + + static readonly UUID: string = '0000007B-0000-1000-8000-0026BB765291'; + + constructor() { + super('Target Horizontal Tilt Angle', TargetHorizontalTiltAngle.UUID); + this.setProps({ + format: Formats.INT, + unit: Units.ARC_DEGREE, + maxValue: 90, + minValue: -90, + minStep: 1, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.TargetHorizontalTiltAngle = TargetHorizontalTiltAngle; + +/** + * Characteristic "Target Humidifier Dehumidifier State" + */ + +export class TargetHumidifierDehumidifierState extends Characteristic { + + /** + * @deprecated Removed in iOS 11. Use HUMIDIFIER_OR_DEHUMIDIFIER instead. + */ + static readonly AUTO = 0; + + // The value property of TargetHumidifierDehumidifierState must be one of the following: + static readonly HUMIDIFIER_OR_DEHUMIDIFIER = 0; + static readonly HUMIDIFIER = 1; + static readonly DEHUMIDIFIER = 2; + + static readonly UUID: string = '000000B4-0000-1000-8000-0026BB765291'; + + constructor() { + super('Target Humidifier Dehumidifier State', TargetHumidifierDehumidifierState.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 2, + minValue: 0, + validValues: [0, 1, 2], + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.TargetHumidifierDehumidifierState = TargetHumidifierDehumidifierState; + +/** + * Characteristic "Target Position" + */ + +export class TargetPosition extends Characteristic { + + static readonly UUID: string = '0000007C-0000-1000-8000-0026BB765291'; + + constructor() { + super('Target Position', TargetPosition.UUID); + this.setProps({ + format: Formats.UINT8, + unit: Units.PERCENTAGE, + maxValue: 100, + minValue: 0, + minStep: 1, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.TargetPosition = TargetPosition; + +/** + * Characteristic "Target Relative Humidity" + */ + +export class TargetRelativeHumidity extends Characteristic { + + static readonly UUID: string = '00000034-0000-1000-8000-0026BB765291'; + + constructor() { + super('Target Relative Humidity', TargetRelativeHumidity.UUID); + this.setProps({ + format: Formats.FLOAT, + unit: Units.PERCENTAGE, + maxValue: 100, + minValue: 0, + minStep: 1, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.TargetRelativeHumidity = TargetRelativeHumidity; + +/** + * Characteristic "Target Slat State" + */ + +export class TargetSlatState extends Characteristic { + + // The value property of TargetSlatState must be one of the following: + static readonly MANUAL = 0; + static readonly AUTO = 1; + + static readonly UUID: string = '000000BE-0000-1000-8000-0026BB765291'; + + constructor() { + super('Target Slat State', TargetSlatState.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0, 1], + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.TargetSlatState = TargetSlatState; + +/** + * Characteristic "Target Temperature" + */ + +export class TargetTemperature extends Characteristic { + + static readonly UUID: string = '00000035-0000-1000-8000-0026BB765291'; + + constructor() { + super('Target Temperature', TargetTemperature.UUID); + this.setProps({ + format: Formats.FLOAT, + unit: Units.CELSIUS, + maxValue: 38, + minValue: 10, + minStep: 0.1, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.TargetTemperature = TargetTemperature; + +/** + * Characteristic "Target Tilt Angle" + */ + +export class TargetTiltAngle extends Characteristic { + + static readonly UUID: string = '000000C2-0000-1000-8000-0026BB765291'; + + constructor() { + super('Target Tilt Angle', TargetTiltAngle.UUID); + this.setProps({ + format: Formats.INT, + unit: Units.ARC_DEGREE, + maxValue: 90, + minValue: -90, + minStep: 1, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.TargetTiltAngle = TargetTiltAngle; + +/** + * Characteristic "Target Vertical Tilt Angle" + */ + +export class TargetVerticalTiltAngle extends Characteristic { + + static readonly UUID: string = '0000007D-0000-1000-8000-0026BB765291'; + + constructor() { + super('Target Vertical Tilt Angle', TargetVerticalTiltAngle.UUID); + this.setProps({ + format: Formats.INT, + unit: Units.ARC_DEGREE, + maxValue: 90, + minValue: -90, + minStep: 1, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.TargetVerticalTiltAngle = TargetVerticalTiltAngle; + +/** + * Characteristic "Temperature Display Units" + */ + +export class TemperatureDisplayUnits extends Characteristic { + + // The value property of TemperatureDisplayUnits must be one of the following: + static readonly CELSIUS = 0; + static readonly FAHRENHEIT = 1; + + static readonly UUID: string = '00000036-0000-1000-8000-0026BB765291'; + + constructor() { + super('Temperature Display Units', TemperatureDisplayUnits.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 1, + minValue: 0, + validValues: [0, 1], + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.TemperatureDisplayUnits = TemperatureDisplayUnits; + +/** + * Characteristic "Valve Type" + */ + +export class ValveType extends Characteristic { + + // The value property of ValveType must be one of the following: + static readonly GENERIC_VALVE = 0; + static readonly IRRIGATION = 1; + static readonly SHOWER_HEAD = 2; + static readonly WATER_FAUCET = 3; + + static readonly UUID: string = '000000D5-0000-1000-8000-0026BB765291'; + + constructor() { + super('Valve Type', ValveType.UUID); + this.setProps({ + format: Formats.UINT8, + maxValue: 3, + minValue: 0, + validValues: [0, 1, 2, 3], + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.ValveType = ValveType; + +/** + * Characteristic "Version" + */ + +export class Version extends Characteristic { + + static readonly UUID: string = '00000037-0000-1000-8000-0026BB765291'; + + constructor() { + super('Version', Version.UUID); + this.setProps({ + format: Formats.STRING, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.Version = Version; + +/** + * Characteristic "VOC Density" + */ + +export class VOCDensity extends Characteristic { + + static readonly UUID: string = '000000C8-0000-1000-8000-0026BB765291'; + + constructor() { + super('VOC Density', VOCDensity.UUID); + this.setProps({ + format: Formats.FLOAT, + maxValue: 1000, + minValue: 0, + minStep: 1, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.VOCDensity = VOCDensity; + +/** + * Characteristic "Volume" + */ + +export class Volume extends Characteristic { + + static readonly UUID: string = '00000119-0000-1000-8000-0026BB765291'; + + constructor() { + super('Volume', Volume.UUID); + this.setProps({ + format: Formats.UINT8, + unit: Units.PERCENTAGE, + maxValue: 100, + minValue: 0, + minStep: 1, + perms: [Perms.READ, Perms.WRITE, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.Volume = Volume; + +/** + * Characteristic "Water Level" + */ + +export class WaterLevel extends Characteristic { + + static readonly UUID: string = '000000B5-0000-1000-8000-0026BB765291'; + + constructor() { + super('Water Level', WaterLevel.UUID); + this.setProps({ + format: Formats.FLOAT, + maxValue: 100, + minValue: 0, + perms: [Perms.READ, Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + } +} + +Characteristic.WaterLevel = WaterLevel; + +/** + * 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); + this.addCharacteristic(Characteristic.FirmwareRevision); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.HardwareRevision); + this.addOptionalCharacteristic(Characteristic.AccessoryFlags); + } +} + +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); + this.addOptionalCharacteristic(Characteristic.CarbonMonoxideLevel); + this.addOptionalCharacteristic(Characteristic.CarbonDioxideLevel); + } +} + +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.addCharacteristic(Characteristic.ChargingState); + 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.Name); + } +} + +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.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); //Manual fix to add temperature + } +} + +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); + this.addOptionalCharacteristic(Characteristic.Name); + } +} + +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 "Speaker" + */ + +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); + this.addOptionalCharacteristic(Characteristic.Name); + } +} + +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; diff --git a/lib/gen/import.js b/src/lib/gen/import.js similarity index 100% rename from lib/gen/import.js rename to src/lib/gen/import.js diff --git a/src/lib/gen/importAsClasses.ts b/src/lib/gen/importAsClasses.ts new file mode 100644 index 000000000..ec785285c --- /dev/null +++ b/src/lib/gen/importAsClasses.ts @@ -0,0 +1,205 @@ +import fs from 'fs'; +import path from 'path'; + +import plist from 'simple-plist'; + +import { Characteristic, Formats, Units } from "../Characteristic"; + +/** + * This module is intended to be run from the command line. It is a script that extracts Apple's Service + * and Characteristic UUIDs and structures from Apple's own HomeKit Accessory Simulator app. + */ + +// assumed location of the plist we need (might want to make this a command-line argument at some point) +var plistPath = '/Applications/HomeKit Accessory Simulator.app/Contents/Frameworks/HAPAccessoryKit.framework/Versions/A/Resources/default.metadata.plist'; +var metadata = plist.readFileSync(plistPath); + +// begin writing the output file +var outputPath = path.join(__dirname, '..', '..', '..', 'src', 'lib', 'gen', 'HomeKitTypes.generated.ts'); +var output = fs.createWriteStream(outputPath); + +output.write("// THIS FILE IS AUTO-GENERATED - DO NOT MODIFY\n"); +output.write("\n"); +// output.write("var inherits = require('util').inherits;\n"); +output.write("import {\n"); +output.write(" Characteristic,\n"); +output.write(" CharacteristicProps,\n"); +output.write(" Formats,\n"); +output.write(" Perms,\n"); +output.write(" Units,\n"); +output.write("} from '../Characteristic';\n"); +output.write("import { Service } from '../Service';\n"); +output.write("\n"); + +/** + * Characteristics + */ + +// index Characteristics for quick access while building Services +const characteristics: Record = {}; // characteristics[UUID] = classyName + +for (var index in metadata.Characteristics) { + var characteristic = metadata.Characteristics[index]; + var classyName = characteristic.Name.replace(/[\s\-]/g, ""); // "Target Door State" -> "TargetDoorState" + classyName = classyName.replace(/[.]/g, "_"); // "PM2.5" -> "PM2_5" + + // index classyName for when we want to declare these in Services below + characteristics[characteristic.UUID] = classyName; + + output.write(`/**\n * Characteristic "${characteristic.Name}"\n */\n\n`); + output.write(`export class ${classyName} extends Characteristic {\n\n`); + output.write(` static readonly UUID: string = "${characteristic.UUID}";\n\n`); + + if (characteristic.Constraints && characteristic.Constraints.ValidValues) { + // this characteristic can only have one of a defined set of values (like an enum). Define the values + // as static members of our subclass. + output.write(" // The value property of " + classyName + " must be one of the following:\n"); + + for (var value in characteristic.Constraints.ValidValues) { + var name = characteristic.Constraints.ValidValues[value]; + + var constName = name.toUpperCase().replace(/[^\w]+/g, '_'); + if ((/^[1-9]/).test(constName)) constName = "_" + constName; // variables can't start with a number + output.write(` static readonly ${constName} = ${value};\n`); + } + output.write('\n'); + } + + // constructor + output.write(" constructor(\n"); + output.write(" displayName = \"\",\n"); + output.write(" props?: CharacteristicProps,\n"); + output.write(" ) {\n"); + output.write(" props = props || {\n"); + + // apply Characteristic properties + // output.write(" this.setProps({\n"); + output.write(" format: Formats." + getCharacteristicFormatsKey(characteristic.Format)); + + // special unit type? + if (characteristic.Unit) + output.write(",\n unit: Units." + getCharacteristicUnitsKey(characteristic.Unit)); + + // apply any basic constraints if present + if (characteristic.Constraints && typeof characteristic.Constraints.MaximumValue !== 'undefined') + output.write(",\n maxValue: " + characteristic.Constraints.MaximumValue); + + if (characteristic.Constraints && typeof characteristic.Constraints.MinimumValue !== 'undefined') + output.write(",\n minValue: " + characteristic.Constraints.MinimumValue); + + if (characteristic.Constraints && typeof characteristic.Constraints.StepValue !== 'undefined') + output.write(",\n minStep: " + characteristic.Constraints.StepValue); + + output.write(",\n perms: ["); + var sep = "" + for (var i in characteristic.Properties) { + var perms = getCharacteristicPermsKey(characteristic.Properties[i]); + if (perms) { + output.write(sep + "Perms." + getCharacteristicPermsKey(characteristic.Properties[i])); + sep = ", " + } + } + output.write("]"); + + output.write("\n };\n"); + output.write(` super(displayName, ${classyName}.UUID, props);\n\n`); + output.write(" this.value = this.getDefaultValue();\n"); + output.write(` }\n`); + + // set default value + + output.write("};\n\n"); + // output.write("inherits(Characteristic." + classyName + ", Characteristic);\n\n"); + // output.write("Characteristic." + classyName + ".UUID = '" + characteristic.UUID + "';\n\n"); +} + +/** + * Services + */ + +for (var index in metadata.Services) { + var service = metadata.Services[index]; + var classyName = service.Name.replace(/[\s\-]/g, ""); // "Smoke Sensor" -> "SmokeSensor" + + output.write(`/**\n * Service "${service.Name}"\n */\n\n`); + output.write(`export class ${classyName} extends Service {\n\n`); + output.write(` static readonly UUID = '${service.UUID}';\n\n`); + + // constructor + output.write(" constructor(displayName: string, subtype: string) {\n"); + output.write(" super(displayName, ${classyName}.UUID, subtype);\n\n"); + + // add Characteristics for this Service + if (service.RequiredCharacteristics) { + output.write("\n // Required Characteristics\n"); + + for (var index in service.RequiredCharacteristics) { + var characteristicUUID = service.RequiredCharacteristics[index]; + + // look up the classyName from the hash we built above + var characteristicClassyName = characteristics[characteristicUUID]; + + output.write(" this.addCharacteristic(Characteristic." + characteristicClassyName + ");\n"); + } + } + + // add "Optional" Characteristics for this Service + if (service.OptionalCharacteristics) { + output.write("\n // Optional Characteristics\n"); + + for (var index in service.OptionalCharacteristics) { + var characteristicUUID = service.OptionalCharacteristics[index]; + + // look up the classyName from the hash we built above + var characteristicClassyName = characteristics[characteristicUUID]; + + output.write(" this.addOptionalCharacteristic(Characteristic." + characteristicClassyName + ");\n"); + } + } + + output.write(" }\n"); + output.write("}\n\n"); +} + +output.write("var HomeKitTypesBridge = require('./HomeKitTypes-Bridge');\n\n"); + +/** + * Done! + */ + +output.end(); + +/** + * Useful functions + */ + +function getCharacteristicFormatsKey(format: string) { + // coerce 'int32' to 'int' + if (format == 'int32') format = 'int'; + + // look up the key in our known-formats dict + for (var key in Formats) + if (Formats[key] == format) + return key; + + throw new Error("Unknown characteristic format '" + format + "'"); +} + +function getCharacteristicUnitsKey(units: string) { + // look up the key in our known-units dict + for (var key in Units) + if (Units[key] == 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 new file mode 100644 index 000000000..9ccbfe49c --- /dev/null +++ b/src/lib/gen/index.ts @@ -0,0 +1,8 @@ +import * as gen from './HomeKit'; +import * as bridged from './HomeKit-Bridge'; +import * as tv from './HomeKit-TV'; + +export const Generated = gen; +export const Bridged = bridged; +export const TV = tv; + diff --git a/src/lib/model/AccessoryInfo.ts b/src/lib/model/AccessoryInfo.ts new file mode 100644 index 000000000..ec3760e6a --- /dev/null +++ b/src/lib/model/AccessoryInfo.ts @@ -0,0 +1,195 @@ +import storage from 'node-persist'; +import util from 'util'; +import tweetnacl from 'tweetnacl'; +import bufferShim from 'buffer-shims'; + +import { Categories } from '../Accessory'; + +/** + * AccessoryInfo is a model class containing a subset of Accessory data relevant to the internal HAP server, + * such as encryption keys and username. It is persisted to disk. + */ +export class AccessoryInfo { + username: string; + displayName: string; + category: Categories; + pincode: string; + signSk: any; + signPk: any; + pairedClients: Record; + configVersion: number; + configHash: string; + setupID: string; + relayEnabled: boolean; + relayState: number; + relayAccessoryID: string; + relayAdminID: string; + relayPairedControllers: Record; + accessoryBagURL: string; + + constructor(username: string) { + this.username = username; + this.displayName = ""; + // @ts-ignore + this.category = ""; + this.pincode = ""; + this.signSk = bufferShim.alloc(0); + this.signPk = bufferShim.alloc(0); + this.pairedClients = {}; // pairedClients[clientUsername:string] = clientPublicKey:Buffer + this.configVersion = 1; + this.configHash = ""; + + this.setupID = ""; + + this.relayEnabled = false; + this.relayState = 2; + this.relayAccessoryID = ""; + this.relayAdminID = ""; + this.relayPairedControllers = {}; + this.accessoryBagURL = ""; + } + + /** + * Add a paired client to memory. + * @param {string} username + * @param {Buffer} publicKey + */ + addPairedClient = (username: string, publicKey: Buffer) => { + this.pairedClients[username] = publicKey; + } + + /** + * Remove a paired client from memory. + * @param {string} username + */ + removePairedClient = (username: string) => { + delete this.pairedClients[username]; + + if (Object.keys(this.pairedClients).length == 0) { + this.relayEnabled = false; + this.relayState = 2; + this.relayAccessoryID = ""; + this.relayAdminID = ""; + this.relayPairedControllers = {}; + this.accessoryBagURL = ""; + } + } + +// Gets the public key for a paired client as a Buffer, or falsey value if not paired. + getClientPublicKey = (username: string) => { + return this.pairedClients[username]; + } + +// Returns a boolean indicating whether this accessory has been paired with a client. + paired = (): boolean => { + return Object.keys(this.pairedClients).length > 0; // if we have any paired clients, we're paired. + } + + updateRelayEnableState = (state: boolean) => { + this.relayEnabled = state; + } + + updateRelayState = (newState: number) => { + this.relayState = newState; + } + + addPairedRelayClient = (username: string, accessToken: string) => { + this.relayPairedControllers[username] = accessToken; + } + + removePairedRelayClient = (username: string) => { + delete this.relayPairedControllers[username]; + } + + save = () => { + var saved = { + displayName: this.displayName, + category: this.category, + pincode: this.pincode, + signSk: this.signSk.toString('hex'), + signPk: this.signPk.toString('hex'), + pairedClients: {}, + configVersion: this.configVersion, + configHash: this.configHash, + setupID: this.setupID, + relayEnabled: this.relayEnabled, + relayState: this.relayState, + relayAccessoryID: this.relayAccessoryID, + relayAdminID: this.relayAdminID, + relayPairedControllers: this.relayPairedControllers, + accessoryBagURL: this.accessoryBagURL + }; + + for (var username in this.pairedClients) { + var publicKey = this.pairedClients[username]; + //@ts-ignore + saved.pairedClients[username] = publicKey.toString('hex'); + } + + var key = AccessoryInfo.persistKey(this.username); + + storage.setItemSync(key, saved); + storage.persistSync(); + } + + remove = () => { + var key = AccessoryInfo.persistKey(this.username); + + storage.removeItemSync(key); + } + +// Gets a key for storing this AccessoryInfo in the filesystem, like "AccessoryInfo.CC223DE3CEF3.json" + static persistKey = (username: string) => { + return util.format("AccessoryInfo.%s.json", username.replace(/:/g, "").toUpperCase()); + } + + static create = (username: string) => { + var accessoryInfo = new AccessoryInfo(username); + + // Create a new unique key pair for this accessory. + var keyPair = tweetnacl.sign.keyPair(); + + accessoryInfo.signSk = bufferShim.from(keyPair.secretKey); + accessoryInfo.signPk = bufferShim.from(keyPair.publicKey); + + return accessoryInfo; + } + + static load = (username: string) => { + var key = AccessoryInfo.persistKey(username); + var saved = storage.getItem(key); + + if (saved) { + var info = new AccessoryInfo(username); + info.displayName = saved.displayName || ""; + info.category = saved.category || ""; + info.pincode = saved.pincode || ""; + info.signSk = bufferShim.from(saved.signSk || '', 'hex'); + info.signPk = bufferShim.from(saved.signPk || '', 'hex'); + + info.pairedClients = {}; + for (var username in saved.pairedClients || {}) { + var publicKey = saved.pairedClients[username]; + info.pairedClients[username] = bufferShim.from(publicKey, 'hex'); + } + + info.configVersion = saved.configVersion || 1; + info.configHash = saved.configHash || ""; + + info.setupID = saved.setupID || ""; + + info.relayEnabled = saved.relayEnabled || false; + info.relayState = saved.relayState || 2; + info.relayAccessoryID = saved.relayAccessoryID || ""; + info.relayAdminID = saved.relayAdminID || ""; + info.relayPairedControllers = saved.relayPairedControllers || {}; + info.accessoryBagURL = saved.accessoryBagURL || ""; + + return info; + } + else { + return null; + } + } +} + diff --git a/src/lib/model/IdentifierCache.spec.ts b/src/lib/model/IdentifierCache.spec.ts new file mode 100644 index 000000000..e07d0d60e --- /dev/null +++ b/src/lib/model/IdentifierCache.spec.ts @@ -0,0 +1,133 @@ +// @ts-ignore +import storage from 'node-persist'; + +import { IdentifierCache } from './IdentifierCache'; + +const createIdentifierCache = (username = 'username') => { + return new IdentifierCache(username); +} + +describe('IdentifierCache', () => { + + describe('#startTrackingUsage()', () => { + + it ('creates a cache to track usage and expiring keys', () => { + const identifierCache = createIdentifierCache(); + + expect(identifierCache._usedCache).toBeNull(); + identifierCache.startTrackingUsage(); + expect(identifierCache._usedCache).toEqual({}); + }); + }); + + describe('#stopTrackingUsageAndExpireUnused()', () => { + it ('creates a cache to track usage and expiring keys', () => { + const identifierCache = createIdentifierCache(); + + expect(identifierCache._usedCache).toBeNull(); + identifierCache.startTrackingUsage(); + expect(identifierCache._usedCache).toEqual({}); + identifierCache.stopTrackingUsageAndExpireUnused(); + expect(identifierCache._usedCache).toBeNull(); + }); + }); + + describe('#getCache()', () => { + it ('retrieves an item from the cache', () => { + const identifierCache = createIdentifierCache(); + + const VALUE = 1; + identifierCache.setCache('foo', VALUE); + + expect(identifierCache.getCache('foo')).toEqual(VALUE); + }); + + it ('returns undefined if an item is not found in the cache', () => { + const identifierCache = createIdentifierCache(); + + expect(identifierCache.getCache('foo')).toBeUndefined(); + }); + }); + + describe('#setCache()', () => { + it ('overwrites an existing item in the cache', () => { + const identifierCache = createIdentifierCache(); + + const VALUE = 2; + identifierCache.setCache('foo', 1); + identifierCache.setCache('foo', VALUE); + + expect(identifierCache.getCache('foo')).toEqual(VALUE); + }); + }); + + describe('#getAID()', () => { + it('creates an entry in the cache if the key is not found', () => { + const identifierCache = createIdentifierCache(); + + const result = identifierCache.getAID('00'); + expect(result).toEqual(2); + }); + }); + + describe('#getIID()', () => { + it('creates an entry in the cache if the key is not found', () => { + const identifierCache = createIdentifierCache(); + + const result = identifierCache.getIID('00', '11', 'subtype', '99'); + expect(result).toEqual(2); + }); + + it('creates an entry in the cache if the key is not found, without a characteristic UUID', () => { + const identifierCache = createIdentifierCache(); + + const result = identifierCache.getIID('00', '11', 'subtype'); + expect(result).toEqual(2); + }); + + it('creates an entry in the cache if the key is not found, without a service subtype or characteristic UUID', () => { + const identifierCache = createIdentifierCache(); + + const result = identifierCache.getIID('00', '11'); + expect(result).toEqual(2); + }); + }); + + describe('#getNextAID()', () => { + + }); + + describe('#getNextIID()', () => { + + }); + + describe('#save()', () => { + it('persists the cache to file storage', () => { + const identifierCache = createIdentifierCache(); + identifierCache.save(); + + expect(storage.setItemSync).toHaveBeenCalledTimes(1); + expect(storage.persistSync).toHaveBeenCalledTimes(1); + }); + }); + + describe('#remove()', () => { + it('removes the cache from file storage', () => { + const identifierCache = createIdentifierCache(); + identifierCache.remove(); + + expect(storage.removeItemSync).toHaveBeenCalledTimes(1); + }); + }); + + describe('persistKey()', () => { + it('returns a correctly formatted key for persistence', () => { + const key = IdentifierCache.persistKey('username'); + expect(key).toEqual('IdentifierCache.USERNAME.json'); + }); + }); + + describe('load()', () => { + + }); +}); diff --git a/src/lib/model/IdentifierCache.ts b/src/lib/model/IdentifierCache.ts new file mode 100644 index 000000000..61f41e113 --- /dev/null +++ b/src/lib/model/IdentifierCache.ts @@ -0,0 +1,132 @@ +import crypto from 'crypto'; +import util from 'util'; + +import storage from 'node-persist'; + +/** + * IdentifierCache is a model class that manages a system of associating HAP "Accessory IDs" and "Instance IDs" + * with other values that don't usually change. HomeKit Clients use Accessory/Instance IDs as a primary key of + * sorts, so the IDs need to remain "stable". For instance, if you create a HomeKit "Scene" called "Leaving Home" + * that sets your Alarm System's "Target Alarm State" Characteristic to "Arm Away", that Scene will store whatever + * "Instance ID" it was given for the "Target Alarm State" Characteristic. If the ID changes later on this server, + * the scene will stop working. + */ +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 + + constructor(public username: string) { + } + + startTrackingUsage = () => { + this._usedCache = {}; + } + + stopTrackingUsageAndExpireUnused = () => { + // simply rotate in the new cache that was built during our normal getXYZ() calls. + this._cache = this._usedCache || this._cache; + this._usedCache = null; + } + + getCache = (key: string) => { + var value = this._cache[key]; + // track this cache item if needed + if (this._usedCache && typeof value !== 'undefined') + this._usedCache[key] = value; + return value; + } + + setCache = (key: string, value: number) => { + this._cache[key] = value; + // track this cache item if needed + if (this._usedCache) + this._usedCache[key] = value; + return value; + } + + getAID = (accessoryUUID: string) => { + var 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 + + '|' + serviceUUID + + (serviceSubtype ? '|' + serviceSubtype : '') + + (characteristicUUID ? '|' + characteristicUUID : ''); + // ensure that our "next IID" field for this accessory is not expired + this.getCache(accessoryUUID + '|nextIID'); + return this.getCache(key) || this.setCache(key, this.getNextIID(accessoryUUID)); + } + + getNextAID = () => { + var key = '|nextAID'; + var 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 + 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 + if (newCacheHash != this._savedCacheHash) { //check if cache need to be saved and proceed accordingly + var saved = { + cache: this._cache + }; + var key = IdentifierCache.persistKey(this.username); + storage.setItemSync(key, saved); + storage.persistSync(); + this._savedCacheHash = newCacheHash; //update hash of saved cache for future use + } + } + + remove = () => { + var key = IdentifierCache.persistKey(this.username); + storage.removeItemSync(key); + } + + /** + * Persisting to File System + */ + // Gets a key for storing this IdentifierCache in the filesystem, like "IdentifierCache.CC223DE3CEF3.json" + static persistKey = (username: string) => { + return util.format("IdentifierCache.%s.json", username.replace(/:/g, "").toUpperCase()); + } + + static load = (username: string) => { + var key = IdentifierCache.persistKey(username); + var saved = storage.getItem(key); + if (saved) { + var 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 + return info; + } else { + return null; + } + } +} + + + + + + + + + + + + + diff --git a/lib/res/snapshot.jpg b/src/lib/res/snapshot.jpg similarity index 100% rename from lib/res/snapshot.jpg rename to src/lib/res/snapshot.jpg diff --git a/lib/util/chacha20poly1305.js b/src/lib/util/chacha20poly1305.ts similarity index 82% rename from lib/util/chacha20poly1305.js rename to src/lib/util/chacha20poly1305.ts index 6a1bf627f..dbce44784 100644 --- a/lib/util/chacha20poly1305.js +++ b/src/lib/util/chacha20poly1305.ts @@ -1,4 +1,3 @@ -'use strict'; /* chacha20 - 256 bits */ // Written in 2014 by Devi Mandiri. Public domain. @@ -6,60 +5,50 @@ // Implementation derived from chacha-ref.c version 20080118 // See for details: http://cr.yp.to/chacha/chacha-20080128.pdf -var bufferShim = require('buffer-shims'); - -module.exports = { - Chacha20Ctx: Chacha20Ctx, - chacha20_keysetup: chacha20_keysetup, - chacha20_ivsetup: chacha20_ivsetup, - chacha20_keystream: chacha20_keystream, - chacha20_update: chacha20_update, - chacha20_final: chacha20_final, - - Poly1305Ctx: Poly1305Ctx, - poly1305_init: poly1305_init, - poly1305_update: poly1305_update, - poly1305_finish: poly1305_finish, - poly1305_auth: poly1305_auth, - poly1305_verify: poly1305_verify -}; +import bufferShim from 'buffer-shims'; var Chacha20KeySize = 32; var Chacha20NonceSize = 8; -function Chacha20Ctx() { +export class Chacha20Ctx { + input: any[]; + leftover: number; + buffer: Uint8Array; + + constructor() { this.input = new Array(16); this.leftover = 0; this.buffer = bufferShim.alloc(64); -}; + } +} -function load32(x, i) { +export function load32(x: Uint8Array, i: number) { return x[i] | (x[i+1]<<8) | (x[i+2]<<16) | (x[i+3]<<24); } -function store32(x, i, u) { +export function store32(x: any, i: number, u: number) { x[i] = u & 0xff; u >>>= 8; x[i+1] = u & 0xff; u >>>= 8; x[i+2] = u & 0xff; u >>>= 8; x[i+3] = u & 0xff; } -function plus(v, w) { +export function plus(v: number, w: number) { return (v + w) >>> 0; } -function rotl32(v, c) { +export function rotl32(v: number, c: number) { return ((v << c) >>> 0) | (v >>> (32 - c)); } -function quarterRound(x, a, b, c, d) { +export function quarterRound(x: any, a: number, b: number, c: number, d: number) { x[a] = plus(x[a], x[b]); x[d] = rotl32(x[d] ^ x[a], 16); x[c] = plus(x[c], x[d]); x[b] = rotl32(x[b] ^ x[c], 12); x[a] = plus(x[a], x[b]); x[d] = rotl32(x[d] ^ x[a], 8); x[c] = plus(x[c], x[d]); x[b] = rotl32(x[b] ^ x[c], 7); } -function chacha20_keysetup(ctx, key) { +export function chacha20_keysetup(ctx: Chacha20Ctx, key: Uint8Array) { ctx.input[0] = 1634760805; ctx.input[1] = 857760878; ctx.input[2] = 2036477234; @@ -69,14 +58,14 @@ function chacha20_keysetup(ctx, key) { } } -function chacha20_ivsetup(ctx, iv) { +export function chacha20_ivsetup(ctx: Chacha20Ctx, iv: Uint8Array) { ctx.input[12] = 0; ctx.input[13] = 0; ctx.input[14] = load32(iv, 0); ctx.input[15] = load32(iv, 4); } -function chacha20_encrypt(ctx, dst, src, len) { +export function chacha20_encrypt(ctx: Chacha20Ctx, dst: Uint8Array, src: Uint8Array, len: number) { var x = new Array(16); var buf = new Array(64); var i = 0, dpos = 0, spos = 0; @@ -115,11 +104,11 @@ function chacha20_encrypt(ctx, dst, src, len) { } } -function chacha20_decrypt(ctx, dst, src, len) { +export function chacha20_decrypt(ctx: Chacha20Ctx, dst: Buffer, src: Buffer, len: number) { chacha20_encrypt(ctx, dst, src, len); } -function chacha20_update(ctx, dst, src, inlen) { +export function chacha20_update(ctx: Chacha20Ctx, dst: Buffer, src: Buffer, inlen: number) { var bytes = 0; var out_start = 0; var out_inc = 0; @@ -165,7 +154,7 @@ function chacha20_update(ctx, dst, src, inlen) { return out_inc - out_start; } -function chacha20_final(ctx, dst) { +export function chacha20_final(ctx: Chacha20Ctx, dst: Buffer) { if (ctx.leftover != 0) { chacha20_encrypt(ctx, dst, ctx.buffer, 64); } @@ -173,7 +162,7 @@ function chacha20_final(ctx, dst) { return ctx.leftover; } -function chacha20_keystream(ctx, dst, len) { +export function chacha20_keystream(ctx: Chacha20Ctx, dst: Buffer, len: number) { for (var i = 0; i < len; ++i) dst[i] = 0; chacha20_encrypt(ctx, dst, dst, len); } @@ -188,25 +177,35 @@ function chacha20_keystream(ctx, dst, len) { var Poly1305KeySize = 32; var Poly1305TagSize = 16; -function Poly1305Ctx() { - this.buffer = new Array(Poly1305TagSize); +export class Poly1305Ctx { + + buffer: Buffer; + leftover: number; + r: any[]; + h: any[]; + pad: any[]; + finished: number; + + constructor() { + this.buffer = new Array(Poly1305TagSize) as unknown as Buffer; this.leftover = 0; this.r = new Array(10); this.h = new Array(10); this.pad = new Array(8); this.finished = 0; -}; + } +} -function U8TO16(p, pos) { +export function U8TO16(p: Uint8Array, pos: number) { return ((p[pos] & 0xff) & 0xffff) | (((p[pos+1] & 0xff) & 0xffff) << 8); } -function U16TO8(p, pos, v) { +export function U16TO8(p: Uint8Array, pos: number, v: number) { p[pos] = (v ) & 0xff; p[pos+1] = (v >>> 8) & 0xff; } -function poly1305_init(ctx, key) { +export function poly1305_init(ctx: Poly1305Ctx, key: Buffer) { var t = [], i = 0; for (i = 8; i--;) t[i] = U8TO16(key, i*2); @@ -232,7 +231,7 @@ function poly1305_init(ctx, key) { ctx.finished = 0; } -function poly1305_blocks(ctx, m, mpos, bytes) { +export function poly1305_blocks(ctx: Poly1305Ctx, m: Uint8Array, mpos: number, bytes: number) { var hibit = ctx.finished ? 0 : (1 << 11); var t = [], d = [], c = 0, i = 0, j = 0; @@ -275,7 +274,7 @@ function poly1305_blocks(ctx, m, mpos, bytes) { } } -function poly1305_update(ctx, m, bytes) { +export function poly1305_update(ctx: Poly1305Ctx, m: Uint8Array, bytes: number) { var want = 0, i = 0, mpos = 0; if (ctx.leftover) { @@ -309,7 +308,7 @@ function poly1305_update(ctx, m, bytes) { } } -function poly1305_finish(ctx, mac) { +export function poly1305_finish(ctx: Poly1305Ctx, mac: Uint8Array) { var g = [], c = 0, mask = 0, f = 0, i = 0; if (ctx.leftover) { @@ -381,14 +380,14 @@ function poly1305_finish(ctx, mac) { } } -function poly1305_auth(mac, m, bytes, key) { +export function poly1305_auth(mac: Uint8Array, m: Uint8Array, bytes: number, key: Buffer) { var ctx = new Poly1305Ctx(); poly1305_init(ctx, key); poly1305_update(ctx, m, bytes); poly1305_finish(ctx, mac); } -function poly1305_verify(mac1, mac2) { +export function poly1305_verify(mac1: any, mac2: any) { var dif = 0; for (var i = 0; i < 16; i++) { dif |= (mac1[i] ^ mac2[i]); @@ -408,21 +407,24 @@ function poly1305_verify(mac1, mac2) { // I think max input length = 2^32-1 = 4294967295 = ~3.9Gb due to the ToUint32 abstract operation. // Whatever ;) -function AeadCtx(key) { +export class AeadCtx { + key: Buffer; + constructor(key: Buffer) { this.key = key; -}; + } +} -function aead_init(c20ctx, key, nonce) { +export function aead_init(c20ctx: Chacha20Ctx, key: Buffer, nonce: Buffer) { chacha20_keysetup(c20ctx, key); chacha20_ivsetup(c20ctx, nonce); - var subkey = []; + var subkey: any = []; chacha20_keystream(c20ctx, subkey, 64); return subkey.slice(0, 32); } -function store64(dst, pos, num) { +export function store64(dst: Uint8Array, pos: number, num: number) { var hi = 0, lo = num >>> 0; if ((+(Math.abs(num))) >= 1) { if (num > 0) { @@ -441,10 +443,10 @@ function store64(dst, pos, num) { dst[pos+7] = hi & 0xff; } -function aead_mac(key, ciphertext, data) { +export function aead_mac(key: Buffer, ciphertext: string, data: string) { var clen = ciphertext.length; var dlen = data.length; - var m = new Array(clen + dlen + 16); + var m: any = new Array(clen + dlen + 16); var i = dlen; for (; i--;) m[i] = data[i]; @@ -453,28 +455,28 @@ function aead_mac(key, ciphertext, data) { for (i = clen; i--;) m[dlen+8+i] = ciphertext[i]; store64(m, clen+dlen+8, clen); - var mac = []; + var mac: any = []; poly1305_auth(mac, m, m.length, key); return mac; } -function aead_encrypt(ctx, nonce, input, ad) { +export function aead_encrypt(ctx: AeadCtx, nonce: Buffer, input: Buffer, ad: string) { var c = new Chacha20Ctx(); var key = aead_init(c, ctx.key, nonce); - var ciphertext = []; + var ciphertext: any = []; chacha20_encrypt(c, ciphertext, input, input.length); var mac = aead_mac(key, ciphertext, ad); - var out = []; + var out: any = []; out = out.concat(ciphertext, mac); return out; } -function aead_decrypt(ctx, nonce, ciphertext, ad) { +export function aead_decrypt(ctx: AeadCtx, nonce: Buffer, ciphertext: any, ad: string) { var c = new Chacha20Ctx(); var key = aead_init(c, ctx.key, nonce); var clen = ciphertext.length - Poly1305TagSize; @@ -483,7 +485,7 @@ function aead_decrypt(ctx, nonce, ciphertext, ad) { if (poly1305_verify(digest, mac) !== 1) return false; - var out = []; + var out: any = []; chacha20_decrypt(c, out, ciphertext, clen); return out; } diff --git a/lib/util/clone.js b/src/lib/util/clone.ts similarity index 60% rename from lib/util/clone.js rename to src/lib/util/clone.ts index 5238e2af2..297a122af 100644 --- a/lib/util/clone.js +++ b/src/lib/util/clone.ts @@ -1,24 +1,17 @@ -'use strict'; - -module.exports = { - clone: clone -}; - - /** * A simple clone function that also allows you to pass an "extend" object whose properties will be * added to the cloned copy of the original object passed. */ -function clone(object, extend) { +export function clone(object: T, extend?: U): T & U { - var cloned = {}; + var cloned = {} as Record; for (var key in object) { cloned[key] = object[key]; } - for (var key in extend) { - cloned[key] = extend[key]; + for (var key2 in extend) { + cloned[key2] = extend[key2]; } return cloned; diff --git a/lib/util/encryption.js b/src/lib/util/encryption.ts similarity index 83% rename from lib/util/encryption.js rename to src/lib/util/encryption.ts index 6b328f62c..a18aee7fb 100644 --- a/lib/util/encryption.js +++ b/src/lib/util/encryption.ts @@ -1,22 +1,15 @@ -'use strict'; - -var crypto = require('crypto'); -var chacha20poly1305 = require('./chacha20poly1305'); -var tweetnacl = require('tweetnacl'); -var assert = require('assert'); -var bufferShim = require('buffer-shims'); -var debug = require('debug')('encryption'); - -module.exports = { - generateCurve25519KeyPair: generateCurve25519KeyPair, - generateCurve25519SharedSecKey: generateCurve25519SharedSecKey, - layerEncrypt: layerEncrypt, - layerDecrypt: layerDecrypt, - verifyAndDecrypt: verifyAndDecrypt, - encryptAndSeal: encryptAndSeal -}; - -function fromHex(h) { +import assert from 'assert'; +import crypto from 'crypto'; + +import bufferShim from 'buffer-shims'; +import createDebug from 'debug'; +import tweetnacl from 'tweetnacl'; + +import * as chacha20poly1305 from './chacha20poly1305'; + +const debug = createDebug('encryption'); + +function fromHex(h: string) { h.replace(/([^0-9a-f])/g, ''); var out = [], len = h.length, w = ''; for (var i = 0; i < len; i += 2) { @@ -31,19 +24,21 @@ function fromHex(h) { return out; } -function generateCurve25519KeyPair() { - var keyPair = tweetnacl.box.keyPair(); - return keyPair; +export function generateCurve25519KeyPair() { + return tweetnacl.box.keyPair(); } -function generateCurve25519SharedSecKey(priKey, pubKey) { - var sharedSec = tweetnacl.scalarMult(priKey, pubKey); - return sharedSec; +export function generateCurve25519SharedSecKey(priKey: Uint8Array, pubKey: Uint8Array) { + return tweetnacl.scalarMult(priKey, pubKey); } //Security Layer Enc/Dec -function layerEncrypt(data, count, key) { +type Count = { + value: any; +} + +export function layerEncrypt(data: Buffer, count: Count, key: Buffer) { var result = bufferShim.alloc(0); var total = data.length; for (var offset = 0; offset < total; ) { @@ -66,7 +61,7 @@ function layerEncrypt(data, count, key) { return result; } -function layerDecrypt(packet, count, key, extraInfo) { +export function layerDecrypt(packet: Buffer, count: Count, key: Buffer, extraInfo: Record) { // Handle Extra Info if (extraInfo.leftoverData != undefined) { packet = Buffer.concat([extraInfo.leftoverData, packet]); @@ -108,7 +103,7 @@ function layerDecrypt(packet, count, key, extraInfo) { } //General Enc/Dec -function verifyAndDecrypt(key,nonce,ciphertext,mac,addData,plaintext) { +export function verifyAndDecrypt(key: Buffer, nonce: Buffer, ciphertext: Buffer, mac: Buffer, addData: Buffer | null | undefined, plaintext: Buffer) { var ctx = new chacha20poly1305.Chacha20Ctx(); chacha20poly1305.chacha20_keysetup(ctx, key); chacha20poly1305.chacha20_ivsetup(ctx, nonce); @@ -141,7 +136,7 @@ function verifyAndDecrypt(key,nonce,ciphertext,mac,addData,plaintext) { writeUInt64LE(ciphertext.length, leTextDataLen, 0); chacha20poly1305.poly1305_update(poly1305_contxt, leTextDataLen, 8); - var poly_out = []; + var poly_out = [] as unknown as Uint8Array; chacha20poly1305.poly1305_finish(poly1305_contxt, poly_out); if (chacha20poly1305.poly1305_verify(mac, poly_out) != 1) { @@ -154,7 +149,7 @@ function verifyAndDecrypt(key,nonce,ciphertext,mac,addData,plaintext) { } } -function encryptAndSeal(key,nonce,plaintext,addData,ciphertext,mac) { +export function encryptAndSeal(key: Buffer, nonce: Buffer, plaintext: Buffer, addData: Buffer | null | undefined, ciphertext: Buffer, mac: Buffer) { var ctx = new chacha20poly1305.Chacha20Ctx(); chacha20poly1305.chacha20_keysetup(ctx, key); chacha20poly1305.chacha20_ivsetup(ctx, nonce); @@ -196,7 +191,7 @@ function encryptAndSeal(key,nonce,plaintext,addData,ciphertext,mac) { var MAX_UINT32 = 0x00000000FFFFFFFF var MAX_INT53 = 0x001FFFFFFFFFFFFF -function onesComplement(number) { +function onesComplement(number: number) { number = ~number if (number < 0) { number = (number & 0x7FFFFFFF) + 0x80000000 @@ -204,7 +199,7 @@ function onesComplement(number) { return number } -function uintHighLow(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 @@ -216,7 +211,7 @@ function uintHighLow(number) { return [high, low] } -function intHighLow(number) { +function intHighLow(number: number) { if (number > -1) { return uintHighLow(number) } @@ -233,15 +228,13 @@ function intHighLow(number) { return [high, low] } -function writeUInt64BE(number, buffer, offset) { - offset = offset || 0 +function writeUInt64BE(number: number, buffer: Buffer, offset: number = 0) { var hl = uintHighLow(number) buffer.writeUInt32BE(hl[0], offset) buffer.writeUInt32BE(hl[1], offset + 4) } -function writeUInt64LE (number, buffer, offset) { - offset = offset || 0 +function writeUInt64LE (number: number, buffer: Buffer, offset: number = 0) { var hl = uintHighLow(number) buffer.writeUInt32LE(hl[1], offset) buffer.writeUInt32LE(hl[0], offset + 4) diff --git a/src/lib/util/eventedhttp.ts b/src/lib/util/eventedhttp.ts new file mode 100644 index 000000000..d8af5bbbe --- /dev/null +++ b/src/lib/util/eventedhttp.ts @@ -0,0 +1,363 @@ +import net, { AddressInfo, Socket } from 'net'; +import http, { IncomingMessage, OutgoingMessage, ServerResponse } from 'http'; + +import createDebug from 'debug'; +import bufferShim from 'buffer-shims'; + +import * as uuid from './uuid'; +import { Nullable } from '../../types'; +import { EventEmitter } from '../EventEmitter'; +import { Session } from '../HAPServer'; + +const debug = createDebug('EventedHTTPServer'); + +export enum EventedHTTPServerEvents { + LISTENING = 'listening', + REQUEST = 'request', + DECRYPT = 'decrypt', + ENCRYPT = 'encrypt', + CLOSE = 'close', + SESSION_CLOSE = 'session-close', +} + +export type Events = { + [EventedHTTPServerEvents.LISTENING]: (port: number) => void; + [EventedHTTPServerEvents.REQUEST]: (request: IncomingMessage, response: ServerResponse, session: Session, events: any) => void; + [EventedHTTPServerEvents.DECRYPT]: (data: Buffer, decrypted: { data: Buffer; }, session: Session) => void; + [EventedHTTPServerEvents.ENCRYPT]: (data: Buffer, encrypted: { data: number | Buffer; }, session: Session) => void; + [EventedHTTPServerEvents.CLOSE]: (events: any) => void; + [EventedHTTPServerEvents.SESSION_CLOSE]: (sessionID: string, events: any) => void; +}; + +/** + * EventedHTTPServer provides an HTTP-like server that supports HAP "extensions" for security and events. + * + * Implementation + * -------------- + * In order to implement the "custom HTTP" server required by the HAP protocol (see HAPServer.js) without completely + * reinventing the wheel, we create both a generic TCP socket server as well as a standard Node HTTP server. + * The TCP socket server acts as a proxy, allowing users of this class to transform data (for encryption) as necessary + * and passing through bytes directly to the HTTP server for processing. This way we get Node to do all + * the "heavy lifting" of HTTP like parsing headers and formatting responses. + * + * Events are sent by simply waiting for current HTTP traffic to subside and then sending a custom response packet + * directly down the wire via the socket. + * + * Each connection to the main TCP server gets its own internal HTTP server, so we can track ongoing requests/responses + * for safe event insertion. + * + * @event 'listening' => function() { } + * Emitted when the server is fully set up and ready to receive connections. + * + * @event 'request' => function(request, response, session, events) { } + * Just like the 'http' module, request is http.IncomingMessage and response is http.ServerResponse. + * The 'session' param is an arbitrary object that you can use to store data associated with this connection; + * it will not be used by this class. The 'events' param is an object where the keys are the names of + * events that this connection has signed up for. It is initially empty and listeners are expected to manage it. + * + * @event 'decrypt' => function(data, {decrypted.data}, session) { } + * Fired when we receive data from the client device. You may detemine whether the data is encrypted, and if + * so, you can decrypt the data and store it into a new 'data' property of the 'decrypted' argument. If data is not + * encrypted, you can simply leave 'data' as null and the original data will be passed through as-is. + * + * @event 'encrypt' => function(data, {encrypted.data}, session) { } + * Fired when we wish to send data to the client device. If necessary, set the 'data' property of the + * 'encrypted' argument to be the encrypted data and it will be sent instead. + */ +export class EventedHTTPServer extends EventEmitter { + + _tcpServer: net.Server; + _connections: EventedHTTPServerConnection[]; + + constructor() { + super(); + this._tcpServer = net.createServer(); + this._connections = []; // track all open connections (for sending events) + } + + listen = (targetPort: number) => { + this._tcpServer.listen(targetPort); + + this._tcpServer.on('listening', () => { + const address = this._tcpServer.address(); + + if (address && typeof address !== 'string') { + var port = address.port; + debug("Server listening on port %s", port); + this.emit(EventedHTTPServerEvents.LISTENING, port); + } + + }); + + this._tcpServer.on('connection', this._onConnection); + } + + stop = () => { + this._tcpServer.close(); + this._connections = []; + } + + sendEvent = ( + event: string, + data: Buffer | string, + contentType: string, + exclude?: Record + ) => { + for (var index in this._connections) { + var connection = this._connections[index]; + 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) => { + var connection = new EventedHTTPServerConnection(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.CLOSE, (events: any) => { this._handleConnectionClose(connection, events); }); + this._connections.push(connection); + } + + _handleConnectionClose = ( + connection: EventedHTTPServerConnection, + events: Record + ) => { + this.emit(EventedHTTPServerEvents.SESSION_CLOSE, connection.sessionID, events); + + // remove it from our array of connections for events + this._connections.splice(this._connections.indexOf(connection), 1); + } +} + + +/** + * 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() { } + */ +class EventedHTTPServerConnection extends EventEmitter { + + sessionID: string; + _remoteAddress: string; + _pendingClientSocketData: Nullable; + _fullySetup: boolean; + _writingResponse: boolean; + _pendingEventData: Buffer; + _clientSocket: Socket; + _httpServer: http.Server; + _serverSocket: Nullable; + _session: { sessionID: string; }; + _events: Record; + _httpPort?: number; + + constructor(clientSocket: Socket) { + super(); + + this.sessionID = uuid.generate(clientSocket.remoteAddress + ':' + clientSocket.remotePort); + this._remoteAddress = clientSocket.remoteAddress!; // cache because it becomes undefined in 'onClientSocketClose' + this._pendingClientSocketData = bufferShim.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._pendingEventData = bufferShim.alloc(0); // event data waiting to be sent until after an in-progress HTTP response is being written + // 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. + // serverSocket is our connection to our own internal httpServer + this._serverSocket = null; // created after httpServer 'listening' event + // create our internal HTTP server for this connection that we will proxy data to and from + this._httpServer = http.createServer(); + this._httpServer.timeout = 0; // clients expect to hold connections open as long as they want + this._httpServer.keepAliveTimeout = 0; // workaround for https://github.com/nodejs/node/issues/13391 + this._httpServer.on('listening', this._onHttpServerListening); + this._httpServer.on('request', this._onHttpServerRequest); + this._httpServer.on('error', this._onHttpServerError); + this._httpServer.listen(0); + // an arbitrary dict that users of this class can store values in to associate with this particular connection + this._session = { + sessionID: this.sessionID + }; + // 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", this._remoteAddress); + } + + 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); + 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 = bufferShim.from(data); + } + // format this payload as an HTTP response + var linebreak = bufferShim.from("0D0A", "hex"); + data = Buffer.concat([ + bufferShim.from('EVENT/1.0 200 OK'), linebreak, + bufferShim.from('Content-Type: ' + contentType), linebreak, + bufferShim.from('Content-Length: ' + data.length), linebreak, + linebreak, + data + ]); + // give listeners an opportunity to encrypt this data before sending it to the client + var encrypted = {data: null}; + this.emit(EventedHTTPServerEvents.ENCRYPT, data, encrypted, this._session); + if (encrypted.data) { + // @ts-ignore + data = encrypted.data as Buffer; + } + // 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 = Buffer.concat([this._pendingEventData, data]); + else + this._clientSocket.write(data); + } + + _sendPendingEvents = () => { + // an existing HTTP response was finished, so let's flush our pending event buffer if necessary! + if (this._pendingEventData.length > 0) { + debug("[%s] Writing pending HTTP event data", this._remoteAddress); + this._clientSocket.write(this._pendingEventData); + } + // clear the buffer + this._pendingEventData = bufferShim.alloc(0); + } + + // Called only once right after constructor finishes + _onHttpServerListening = () => { + this._httpPort = (this._httpServer.address() as AddressInfo).port; + debug("[%s] HTTP server listening on port %s", this._remoteAddress, this._httpPort); + // closes before this are due to retrying listening, which don't need to be handled + this._httpServer.on('close', this._onHttpServerClose); + // now we can establish a connection to this running HTTP server for proxying data + this._serverSocket = net.createConnection(this._httpPort); + this._serverSocket.on('connect', this._onServerSocketConnect); + this._serverSocket.on('data', this._onServerSocketData); + this._serverSocket.on('close', this._onServerSocketClose); + this._serverSocket.on('error', this._onServerSocketError); // we MUST register for this event, otherwise the error will bubble up to the top and crash the node process entirely. + } + + // 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; + } + } + + // Received data from client (iOS) + _onClientSocketData = (data: Buffer) => { + // give listeners an opportunity to decrypt this data before processing it as HTTP + var decrypted = {data: null}; + this.emit(EventedHTTPServerEvents.DECRYPT, data, decrypted, this._session); + if (decrypted.data) + data = decrypted.data as unknown as Buffer; + 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]); + } + } + + // Received data from HTTP Server + _onServerSocketData = (data: Buffer | string) => { + // give listeners an opportunity to encrypt this data before sending it to the client + var 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); + } + + // 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(); + } + + // 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 + } + + _onHttpServerRequest = (request: IncomingMessage, response: OutgoingMessage) => { + debug("[%s] HTTP request: %s", this._remoteAddress, request.url); + this._writingResponse = true; + // 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); + } + + _onHttpServerClose = () => { + debug("[%s] HTTP server was closed", this._remoteAddress); + // notify listeners that we are completely closed + this.emit(EventedHTTPServerEvents.CLOSE, this._events); + } + + _onHttpServerError = (err: Error & { code?: string }) => { + debug("[%s] HTTP server error: %s", this._remoteAddress, err.message); + if (err.code === 'EADDRINUSE') { + this._httpServer.close(); + this._httpServer.listen(0); + } + } + + _onClientSocketClose = () => { + debug("[%s] Client connection closed", this._remoteAddress); + // shutdown the other side + this._serverSocket && this._serverSocket.destroy(); + } + + _onClientSocketError = (err: Error) => { + debug("[%s] Client connection error: %s", this._remoteAddress, err.message); + // _onClientSocketClose will be called next + } +} + + + + + + + + + + + + + diff --git a/src/lib/util/hkdf.ts b/src/lib/util/hkdf.ts new file mode 100644 index 000000000..ff99be98d --- /dev/null +++ b/src/lib/util/hkdf.ts @@ -0,0 +1,35 @@ +import crypto, { BinaryLike } from 'crypto'; +import bufferShim from 'buffer-shims'; + +export function HKDF(hashAlg: string, salt: BinaryLike, ikm: BinaryLike, info: Buffer, size: number) { + // create the hash alg to see if it exists and get its length + const hash = crypto.createHash(hashAlg); + const hashLength = hash.digest().length; + + // now we compute the PRK + const hmac = crypto.createHmac(hashAlg, salt); + hmac.update(ikm); + const prk = hmac.digest(); + + const buffers = []; + const num_blocks = Math.ceil(size / hashLength); + let prev = bufferShim.alloc(0); + let output; + + info = bufferShim.from(info); + + for (var i=0; i { + it('should call a function once', () => { + const spy = jest.fn(); + const callback = once(spy); + callback(); + + expect(spy).toHaveBeenCalledTimes(1); + }); + + it('should throw an error if a function is called more than once', () => { + const spy = jest.fn(); + const callback = once(spy); + callback(); + + expect(() => { + callback(); + }).toThrow(' already been called'); + expect(spy).toHaveBeenCalledTimes(1); + }); +}); diff --git a/lib/util/once.js b/src/lib/util/once.ts similarity index 60% rename from lib/util/once.js rename to src/lib/util/once.ts index 9126d5e2f..8d42c2fad 100644 --- a/lib/util/once.js +++ b/src/lib/util/once.ts @@ -1,19 +1,13 @@ -'use strict'; - -module.exports = { - once: once -}; - -function once(func) { +export function once(func: Function) { var called = false; - return function() { + return (...args: any[]) => { if (called) { throw new Error("This callback function has already been called by someone else; it can only be called one time."); } else { called = true; - return func.apply(this, arguments); + return func(...args); } }; } diff --git a/src/lib/util/tlv.spec.ts b/src/lib/util/tlv.spec.ts new file mode 100644 index 000000000..5b18ca699 --- /dev/null +++ b/src/lib/util/tlv.spec.ts @@ -0,0 +1,22 @@ +import { decode, encode } from './tlv'; + +describe('#encode()', () => { + + it('encodes a single value correctly in the TLV format', () => { + const encoded = encode(0, Buffer.from('ASD')) + + const result = decode(encoded); + expect(result[0].toString()).toEqual('ASD'); + }); + + it('encodes multiple values correctly in the TLV format', () => { + const encoded = encode(0, Buffer.from('ASD'), 1, Buffer.from('QWE')) + + const result = decode(encoded); + expect(result[0].toString()).toEqual('ASD'); + expect(result[1].toString()).toEqual('QWE'); + }); +}); + +describe('#decode()', () => { +}); diff --git a/lib/util/tlv.js b/src/lib/util/tlv.ts similarity index 80% rename from lib/util/tlv.js rename to src/lib/util/tlv.ts index 1a828a667..f68a2a7cd 100644 --- a/lib/util/tlv.js +++ b/src/lib/util/tlv.ts @@ -1,26 +1,19 @@ -'use strict'; - -var bufferShim = require('buffer-shims'); +import bufferShim from 'buffer-shims'; /** * Type Length Value encoding/decoding, used by HAP as a wire format. * https://en.wikipedia.org/wiki/Type-length-value */ -module.exports = { - encode: encode, - decode: decode -}; - -function encode(type, data /*, type, data, type, data... */) { +export function encode(type: number, data: Buffer | number | string, ...args: any[]) { var encodedTLVBuffer = bufferShim.alloc(0); // coerce data to Buffer if needed if (typeof data === 'number') - data = bufferShim.from([data]); + data = bufferShim.from([data]) as Buffer; else if (typeof data === 'string') - data = bufferShim.from(data); + data = bufferShim.from(data) as Buffer; if (data.length <= 255) { encodedTLVBuffer = Buffer.concat([bufferShim.from([type,data.length]),data]); @@ -44,11 +37,11 @@ function encode(type, data /*, type, data, type, data... */) { } // do we have more to encode? - if (arguments.length > 2) { + if (args.length >= 2) { // chop off the first two arguments which we already processed, and process the rest recursively - var remainingArguments = Array.prototype.slice.call(arguments, 2); - var remainingTLVBuffer = encode.apply(this, remainingArguments); + const [ nextType, nextData, ...nextArgs ] = args; + const remainingTLVBuffer = encode(nextType, nextData, ...nextArgs); // append the remaining encoded arguments directly to the buffer encodedTLVBuffer = Buffer.concat([encodedTLVBuffer, remainingTLVBuffer]); @@ -57,9 +50,9 @@ function encode(type, data /*, type, data, type, data... */) { return encodedTLVBuffer; } -function decode(data) { +export function decode(data: Buffer) { - var objects = {}; + var objects: Record = {}; var leftLength = data.length; var currentIndex = 0; diff --git a/lib/util/uuid.js b/src/lib/util/uuid.ts similarity index 63% rename from lib/util/uuid.js rename to src/lib/util/uuid.ts index d9085d60e..a3797c968 100644 --- a/lib/util/uuid.js +++ b/src/lib/util/uuid.ts @@ -1,40 +1,32 @@ -'use strict'; - -var crypto = require('crypto'); - -module.exports = { - generate: generate, - isValid: isValid, - unparse: unparse -}; +import crypto, { BinaryLike } from 'crypto'; // http://stackoverflow.com/a/25951500/66673 -function generate(data) { - var sha1sum = crypto.createHash('sha1'); +export function generate(data: BinaryLike) { + const sha1sum = crypto.createHash('sha1'); sha1sum.update(data); - var s = sha1sum.digest('hex'); - var i = -1; - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + const s = sha1sum.digest('hex'); + let i = -1; + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c: string) => { i += 1; switch (c) { - case 'x': - return s[i]; case 'y': return ((parseInt('0x' + s[i], 16) & 0x3) | 0x8).toString(16); + case 'x': + default: + return s[i]; } }); } -var validUUIDRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i +const VALID_UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; -function isValid(UUID) { - return validUUIDRegex.test(UUID); +export function isValid(UUID: string) { + return VALID_UUID_REGEX.test(UUID); } -var _byteToHex = []; // https://github.com/defunctzombie/node-uuid/blob/master/uuid.js -function unparse(buf, offset) { - var i = offset || 0; +export function unparse(buf: Buffer | string, offset: number = 0) { + let i = offset; return buf[i++].toString(16) + buf[i++].toString(16) + buf[i++].toString(16) + buf[i++].toString(16) + '-' + buf[i++].toString(16) + buf[i++].toString(16) + '-' + diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 000000000..8d6e9a449 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,106 @@ +import { Status } from './lib/HAPServer'; +import { Characteristic, CharacteristicProps } from './lib/Characteristic'; + +export type Nullable = T | null; + +export type WithUUID = T & { UUID: string }; + +export interface ToHAPOptions { + omitValues: boolean; +} + +export type 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 + & Pick +export type HapCharacteristic = HAPProps & { + iid: number; + type: string; + value: string | number | {} | null; +} +export type CharacteristicValue = PrimitiveTypes | PrimitiveTypes[] | { [key: string]: PrimitiveTypes }; +export type CharacteristicChange = { + newValue: CharacteristicValue; + oldValue: CharacteristicValue; + context?: any; + characteristic: Characteristic; +}; +export type HapService = { + iid: number; + type: string; + + characteristics: HapCharacteristic[]; + primary: boolean; + hidden: boolean; + linked: number[]; +} + +export type CharacteristicData = { + aid: number; + iid: number; + v?: string; + value?: string; + s?: Status; + status?: Status; + e?: string; + ev?: boolean; + r?: boolean; +} +export type AudioCodec = { + samplerate: number; + type: string; +} +export type VideoCodec = { + levels: number[]; + profiles: number[]; +} +export type Source = { + port: number; + srtp_key: Buffer; + srtp_salt: Buffer; + targetAddress?: string; + proxy_rtp?: number; + proxy_rtcp?: number; +}; +export type Address = { + address: string; + type: 'v4' | 'v6'; +} +export type AudioInfo = { + codec: string | number; + channel: number; + bit_rate: number; + sample_rate: number; + packet_time: number; + pt: number; + ssrc: number; + max_bit_rate: number; + rtcp_interval: number; + comfort_pt: number; +}; +export type VideoInfo = { + profile: number; + level: number; + width: number; + height: number; + fps: number; + ssrc: number; + pt: number; + max_bit_rate: number; + rtcp_interval: number; + mtu: number; +}; +export type SessionIdentifier = Buffer | string; +export type StreamAudioParams = { + comfort_noise: boolean; + codecs: AudioCodec[]; +}; +export type Resolution = [number, number, number]; +export type StreamVideoParams = { + codec?: VideoCodec; + resolutions: Resolution[]; +}; diff --git a/src/types/bonjour-hap.d.ts b/src/types/bonjour-hap.d.ts new file mode 100644 index 000000000..76084649e --- /dev/null +++ b/src/types/bonjour-hap.d.ts @@ -0,0 +1,60 @@ +declare module 'bonjour-hap' { + + export enum Protocols { + TCP = 'tcp', + UDP = 'udp', + } + + export type Nullable = T | null; + export type TxtRecord = Record; + + export class Service { + name: string; + type: string; + subtypes: Nullable; + protocol: Protocols; + host: string; + port: number; + fqdn: string; + txt: Nullable>; + published: boolean; + + start(): void; + stop(callback?: () => void): void; + destroy(): void; + updateTxt(txt: TxtRecord): void; + } + + export type PublishOptions = { + category?: any, + host?: string; + name?: string; + pincode?: string; + port: number; + protocol?: Protocols; + subtypes?: string[]; + txt?: Record; + type?: string; + username?: string; + }; + + export class BonjourHap { + publish(options: PublishOptions): Service; + unpublishAll(callback: () => void): void; + destroy(): void; + } + + + export type MulticastOptions = { + multicast: boolean; + interface: string; + port: number; + ip: string; + ttl: number; + loopback: boolean; + reuseAddr: boolean; + }; + function createWithOptions(options: MulticastOptions): BonjourHap; + + export default createWithOptions; +} diff --git a/src/types/buffer-shims.d.ts b/src/types/buffer-shims.d.ts new file mode 100644 index 000000000..50c587cc4 --- /dev/null +++ b/src/types/buffer-shims.d.ts @@ -0,0 +1,5 @@ +declare module 'buffer-shims' { + + const B: typeof Buffer; + export = B; +} diff --git a/src/types/fast-srp-hap.d.ts b/src/types/fast-srp-hap.d.ts new file mode 100644 index 000000000..b1442c8ea --- /dev/null +++ b/src/types/fast-srp-hap.d.ts @@ -0,0 +1,15 @@ +declare module 'fast-srp-hap' { + + export const params: Record; + export function genKey(num: number, callback: (err: Error, key: Buffer) => void): void; + + export class Server { + constructor(srpParams: any, salt: Buffer, pair: Buffer, pin: Buffer, key: Buffer); + + setA(a: Buffer): void; + checkM1(m1: Buffer): void; + computeB(): Buffer; + computeK(): Buffer; + computeM2(): Buffer; + } +} diff --git a/src/types/node-persist.d.ts b/src/types/node-persist.d.ts new file mode 100644 index 000000000..2e3047ee4 --- /dev/null +++ b/src/types/node-persist.d.ts @@ -0,0 +1,9 @@ +declare module 'node-persist' { + export function create(): any; + + export function initSync(opts?: { dir: string }): void; + export function getItem(key: string): any; + export function setItemSync(key: string, data: any): void; + export function persistSync(): void; + export function removeItemSync(key: string): void; +} diff --git a/src/types/simple-plist.d.ts b/src/types/simple-plist.d.ts new file mode 100644 index 000000000..8bc749692 --- /dev/null +++ b/src/types/simple-plist.d.ts @@ -0,0 +1,3 @@ +declare module 'simple-plist' { + export function readFileSync(path: string): Record; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..4ab7ffce9 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "lib": [ + "es2015", + "es2016", + "es2017", + "es2018" + ], + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true + }, + "include": [ + "src/" + ], + "exclude": [ + "**/*.spec.ts" + ] +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 000000000..470290a80 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,3814 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.5.5": + version "7.5.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d" + integrity sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw== + dependencies: + "@babel/highlight" "^7.0.0" + +"@babel/core@^7.1.0": + version "7.5.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.5.5.tgz#17b2686ef0d6bc58f963dddd68ab669755582c30" + integrity sha512-i4qoSr2KTtce0DmkuuQBV4AuQgGPUcPXMr9L5MyYAtk06z068lQ10a4O009fe5OB/DfNV+h+qqT7ddNV8UnRjg== + dependencies: + "@babel/code-frame" "^7.5.5" + "@babel/generator" "^7.5.5" + "@babel/helpers" "^7.5.5" + "@babel/parser" "^7.5.5" + "@babel/template" "^7.4.4" + "@babel/traverse" "^7.5.5" + "@babel/types" "^7.5.5" + convert-source-map "^1.1.0" + debug "^4.1.0" + json5 "^2.1.0" + lodash "^4.17.13" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/generator@^7.4.0", "@babel/generator@^7.5.5": + version "7.5.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.5.5.tgz#873a7f936a3c89491b43536d12245b626664e3cf" + integrity sha512-ETI/4vyTSxTzGnU2c49XHv2zhExkv9JHLTwDAFz85kmcwuShvYG2H08FwgIguQf4JC75CBnXAUM5PqeF4fj0nQ== + dependencies: + "@babel/types" "^7.5.5" + jsesc "^2.5.1" + lodash "^4.17.13" + source-map "^0.5.0" + trim-right "^1.0.1" + +"@babel/helper-function-name@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz#a0ceb01685f73355d4360c1247f582bfafc8ff53" + integrity sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw== + dependencies: + "@babel/helper-get-function-arity" "^7.0.0" + "@babel/template" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-get-function-arity@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz#83572d4320e2a4657263734113c42868b64e49c3" + integrity sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-plugin-utils@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250" + integrity sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA== + +"@babel/helper-split-export-declaration@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz#ff94894a340be78f53f06af038b205c49d993677" + integrity sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q== + dependencies: + "@babel/types" "^7.4.4" + +"@babel/helpers@^7.5.5": + version "7.5.5" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.5.5.tgz#63908d2a73942229d1e6685bc2a0e730dde3b75e" + integrity sha512-nRq2BUhxZFnfEn/ciJuhklHvFOqjJUD5wpx+1bxUF2axL9C+v4DE/dmp5sT2dKnpOs4orZWzpAZqlCy8QqE/7g== + dependencies: + "@babel/template" "^7.4.4" + "@babel/traverse" "^7.5.5" + "@babel/types" "^7.5.5" + +"@babel/highlight@^7.0.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.5.0.tgz#56d11312bd9248fa619591d02472be6e8cb32540" + integrity sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ== + dependencies: + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^4.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.4.4", "@babel/parser@^7.5.5": + version "7.5.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.5.5.tgz#02f077ac8817d3df4a832ef59de67565e71cca4b" + integrity sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g== + +"@babel/plugin-syntax-object-rest-spread@^7.0.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz#3b7a3e733510c57e820b9142a6579ac8b0dfad2e" + integrity sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/template@^7.1.0", "@babel/template@^7.4.0", "@babel/template@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.4.tgz#f4b88d1225689a08f5bc3a17483545be9e4ed237" + integrity sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.4.4" + "@babel/types" "^7.4.4" + +"@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.5.5": + version "7.5.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.5.5.tgz#f664f8f368ed32988cd648da9f72d5ca70f165bb" + integrity sha512-MqB0782whsfffYfSjH4TM+LMjrJnhCNEDMDIjeTpl+ASaUvxcjoiVCo/sM1GhS1pHOXYfWVCYneLjMckuUxDaQ== + dependencies: + "@babel/code-frame" "^7.5.5" + "@babel/generator" "^7.5.5" + "@babel/helper-function-name" "^7.1.0" + "@babel/helper-split-export-declaration" "^7.4.4" + "@babel/parser" "^7.5.5" + "@babel/types" "^7.5.5" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.13" + +"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.5.5": + version "7.5.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.5.5.tgz#97b9f728e182785909aa4ab56264f090a028d18a" + integrity sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw== + dependencies: + esutils "^2.0.2" + lodash "^4.17.13" + to-fast-properties "^2.0.0" + +"@cnakazawa/watch@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef" + integrity sha512-r5160ogAvGyHsal38Kux7YYtodEKOj89RGb28ht1jh3SJb08VwRwAKKJL0bGb04Zd/3r9FL3BFIc3bBidYffCA== + dependencies: + exec-sh "^0.3.2" + minimist "^1.2.0" + +"@jest/console@^24.7.1": + version "24.7.1" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-24.7.1.tgz#32a9e42535a97aedfe037e725bd67e954b459545" + integrity sha512-iNhtIy2M8bXlAOULWVTUxmnelTLFneTNEkHCgPmgd+zNwy9zVddJ6oS5rZ9iwoscNdT5mMwUd0C51v/fSlzItg== + dependencies: + "@jest/source-map" "^24.3.0" + chalk "^2.0.1" + slash "^2.0.0" + +"@jest/core@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-24.8.0.tgz#fbbdcd42a41d0d39cddbc9f520c8bab0c33eed5b" + integrity sha512-R9rhAJwCBQzaRnrRgAdVfnglUuATXdwTRsYqs6NMdVcAl5euG8LtWDe+fVkN27YfKVBW61IojVsXKaOmSnqd/A== + dependencies: + "@jest/console" "^24.7.1" + "@jest/reporters" "^24.8.0" + "@jest/test-result" "^24.8.0" + "@jest/transform" "^24.8.0" + "@jest/types" "^24.8.0" + ansi-escapes "^3.0.0" + chalk "^2.0.1" + exit "^0.1.2" + graceful-fs "^4.1.15" + jest-changed-files "^24.8.0" + jest-config "^24.8.0" + jest-haste-map "^24.8.0" + jest-message-util "^24.8.0" + jest-regex-util "^24.3.0" + jest-resolve-dependencies "^24.8.0" + jest-runner "^24.8.0" + jest-runtime "^24.8.0" + jest-snapshot "^24.8.0" + jest-util "^24.8.0" + jest-validate "^24.8.0" + jest-watcher "^24.8.0" + micromatch "^3.1.10" + p-each-series "^1.0.0" + pirates "^4.0.1" + realpath-native "^1.1.0" + rimraf "^2.5.4" + strip-ansi "^5.0.0" + +"@jest/environment@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-24.8.0.tgz#0342261383c776bdd652168f68065ef144af0eac" + integrity sha512-vlGt2HLg7qM+vtBrSkjDxk9K0YtRBi7HfRFaDxoRtyi+DyVChzhF20duvpdAnKVBV6W5tym8jm0U9EfXbDk1tw== + dependencies: + "@jest/fake-timers" "^24.8.0" + "@jest/transform" "^24.8.0" + "@jest/types" "^24.8.0" + jest-mock "^24.8.0" + +"@jest/fake-timers@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-24.8.0.tgz#2e5b80a4f78f284bcb4bd5714b8e10dd36a8d3d1" + integrity sha512-2M4d5MufVXwi6VzZhJ9f5S/wU4ud2ck0kxPof1Iz3zWx6Y+V2eJrES9jEktB6O3o/oEyk+il/uNu9PvASjWXQw== + dependencies: + "@jest/types" "^24.8.0" + jest-message-util "^24.8.0" + jest-mock "^24.8.0" + +"@jest/reporters@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.8.0.tgz#075169cd029bddec54b8f2c0fc489fd0b9e05729" + integrity sha512-eZ9TyUYpyIIXfYCrw0UHUWUvE35vx5I92HGMgS93Pv7du+GHIzl+/vh8Qj9MCWFK/4TqyttVBPakWMOfZRIfxw== + dependencies: + "@jest/environment" "^24.8.0" + "@jest/test-result" "^24.8.0" + "@jest/transform" "^24.8.0" + "@jest/types" "^24.8.0" + chalk "^2.0.1" + exit "^0.1.2" + glob "^7.1.2" + istanbul-lib-coverage "^2.0.2" + istanbul-lib-instrument "^3.0.1" + istanbul-lib-report "^2.0.4" + istanbul-lib-source-maps "^3.0.1" + istanbul-reports "^2.1.1" + jest-haste-map "^24.8.0" + jest-resolve "^24.8.0" + jest-runtime "^24.8.0" + jest-util "^24.8.0" + jest-worker "^24.6.0" + node-notifier "^5.2.1" + slash "^2.0.0" + source-map "^0.6.0" + string-length "^2.0.0" + +"@jest/source-map@^24.3.0": + version "24.3.0" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-24.3.0.tgz#563be3aa4d224caf65ff77edc95cd1ca4da67f28" + integrity sha512-zALZt1t2ou8le/crCeeiRYzvdnTzaIlpOWaet45lNSqNJUnXbppUUFR4ZUAlzgDmKee4Q5P/tKXypI1RiHwgag== + dependencies: + callsites "^3.0.0" + graceful-fs "^4.1.15" + source-map "^0.6.0" + +"@jest/test-result@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-24.8.0.tgz#7675d0aaf9d2484caa65e048d9b467d160f8e9d3" + integrity sha512-+YdLlxwizlfqkFDh7Mc7ONPQAhA4YylU1s529vVM1rsf67vGZH/2GGm5uO8QzPeVyaVMobCQ7FTxl38QrKRlng== + dependencies: + "@jest/console" "^24.7.1" + "@jest/types" "^24.8.0" + "@types/istanbul-lib-coverage" "^2.0.0" + +"@jest/test-sequencer@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-24.8.0.tgz#2f993bcf6ef5eb4e65e8233a95a3320248cf994b" + integrity sha512-OzL/2yHyPdCHXEzhoBuq37CE99nkme15eHkAzXRVqthreWZamEMA0WoetwstsQBCXABhczpK03JNbc4L01vvLg== + dependencies: + "@jest/test-result" "^24.8.0" + jest-haste-map "^24.8.0" + jest-runner "^24.8.0" + jest-runtime "^24.8.0" + +"@jest/transform@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-24.8.0.tgz#628fb99dce4f9d254c6fd9341e3eea262e06fef5" + integrity sha512-xBMfFUP7TortCs0O+Xtez2W7Zu1PLH9bvJgtraN1CDST6LBM/eTOZ9SfwS/lvV8yOfcDpFmwf9bq5cYbXvqsvA== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^24.8.0" + babel-plugin-istanbul "^5.1.0" + chalk "^2.0.1" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.1.15" + jest-haste-map "^24.8.0" + jest-regex-util "^24.3.0" + jest-util "^24.8.0" + micromatch "^3.1.10" + realpath-native "^1.1.0" + slash "^2.0.0" + source-map "^0.6.1" + write-file-atomic "2.4.1" + +"@jest/types@^24.8.0": + version "24.8.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.8.0.tgz#f31e25948c58f0abd8c845ae26fcea1491dea7ad" + integrity sha512-g17UxVr2YfBtaMUxn9u/4+siG1ptg9IGYAYwvpwn61nBg779RXnjE/m7CxYcIzEt0AbHZZAHSEZNhkE2WxURVg== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^1.1.1" + "@types/yargs" "^12.0.9" + +"@types/babel__core@^7.1.0": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.2.tgz#608c74f55928033fce18b99b213c16be4b3d114f" + integrity sha512-cfCCrFmiGY/yq0NuKNxIQvZFy9kY/1immpSpTngOnyIbD4+eJOG5mxphhHDv3CHL9GltO4GcKr54kGBg3RNdbg== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.0.2" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.0.2.tgz#d2112a6b21fad600d7674274293c85dce0cb47fc" + integrity sha512-NHcOfab3Zw4q5sEE2COkpfXjoE7o+PmqD9DQW4koUT3roNxwziUdXGnRndMat/LJNUtePwn1TlP4do3uoe3KZQ== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.0.2" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.0.2.tgz#4ff63d6b52eddac1de7b975a5223ed32ecea9307" + integrity sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.0.7" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.7.tgz#2496e9ff56196cc1429c72034e07eab6121b6f3f" + integrity sha512-CeBpmX1J8kWLcDEnI3Cl2Eo6RfbGvzUctA+CjZUhOKDFbLfcr7fc4usEqLNWetrlJd7RhAkyYe2czXop4fICpw== + dependencies: + "@babel/types" "^7.3.0" + +"@types/debug@^4.1.4": + version "4.1.4" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.4.tgz#56eec47706f0fd0b7c694eae2f3172e6b0b769da" + integrity sha512-D9MyoQFI7iP5VdpEyPZyjjqIJ8Y8EDNQFIFVLOmeg1rI1xiHOChyUPMPRUVfqFCerxfE+yS3vMyj37F6IdtOoQ== + +"@types/ip@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@types/ip/-/ip-1.1.0.tgz#aec4f5bfd49e4a4c53b590d88c36eb078827a7c0" + integrity sha512-dwNe8gOoF70VdL6WJBwVHtQmAX4RMd62M+mAB9HQFjG1/qiCLM/meRy95Pd14FYBbEDwCq7jgJs89cHpLBu4HQ== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" + integrity sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg== + +"@types/istanbul-lib-report@*": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz#e5471e7fa33c61358dd38426189c037a58433b8c" + integrity sha512-3BUTyMzbZa2DtDI2BkERNC6jJw2Mr2Y0oGI7mRxYNBPxppbtEK1F66u3bKwU2g+wxwWI7PAoRpJnOY1grJqzHg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz#7a8cbf6a406f36c8add871625b278eaf0b0d255a" + integrity sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA== + dependencies: + "@types/istanbul-lib-coverage" "*" + "@types/istanbul-lib-report" "*" + +"@types/jest-diff@*": + version "20.0.1" + resolved "https://registry.yarnpkg.com/@types/jest-diff/-/jest-diff-20.0.1.tgz#35cc15b9c4f30a18ef21852e255fdb02f6d59b89" + integrity sha512-yALhelO3i0hqZwhjtcr6dYyaLoCHbAMshwtj6cGxTvHZAKXHsYGdff6E8EPw3xLKY0ELUTQ69Q1rQiJENnccMA== + +"@types/jest@^24.0.15": + version "24.0.15" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-24.0.15.tgz#6c42d5af7fe3b44ffff7cc65de7bf741e8fa427f" + integrity sha512-MU1HIvWUme74stAoc3mgAi+aMlgKOudgEvQDIm1v4RkrDudBh1T+NFp5sftpBAdXdx1J0PbdpJ+M2EsSOi1djA== + dependencies: + "@types/jest-diff" "*" + +"@types/node@*", "@types/node@^12.6.8": + version "12.6.8" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.6.8.tgz#e469b4bf9d1c9832aee4907ba8a051494357c12c" + integrity sha512-aX+gFgA5GHcDi89KG5keey2zf0WfZk/HAQotEamsK2kbey+8yGKcson0hbK8E+v0NArlCJQCqMP161YhV6ZXLg== + +"@types/stack-utils@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" + integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== + +"@types/yargs@^12.0.2", "@types/yargs@^12.0.9": + version "12.0.12" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.12.tgz#45dd1d0638e8c8f153e87d296907659296873916" + integrity sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw== + +abab@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f" + integrity sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w== + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +acorn-globals@^4.1.0: + version "4.3.2" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.2.tgz#4e2c2313a597fd589720395f6354b41cd5ec8006" + integrity sha512-BbzvZhVtZP+Bs1J1HcwrQe8ycfO0wStkSGxuul3He3GkHOIZ6eTqOkPuw9IP1X3+IkOo4wiJmwkobzXYz4wewQ== + dependencies: + acorn "^6.0.1" + acorn-walk "^6.0.1" + +acorn-walk@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c" + integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA== + +acorn@^5.5.3: + version "5.7.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" + integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== + +acorn@^6.0.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.2.1.tgz#3ed8422d6dec09e6121cc7a843ca86a330a86b51" + integrity sha512-JD0xT5FCRDNyjDda3Lrg/IxFscp9q4tiYtxE1/nOzlKCk7hIRuYjhq1kCNkbPjMRMZuFq20HNQn1I9k8Oj0E+Q== + +ajv@^6.5.5: + version "6.10.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52" + integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw== + dependencies: + fast-deep-equal "^2.0.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" + integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +ansi-regex@^4.0.0, ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +are-we-there-yet@~1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" + integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +arg@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.1.tgz#485f8e7c390ce4c5f78257dbea80d4be11feda4c" + integrity sha512-SlmP3fEA88MBv0PypnXZ8ZfJhwmDeIE3SP71j37AiXQBXYosPV0x6uISAaHYSlSVhmHOVkomen0tbGk6Anlebw== + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= + +arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + +array-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" + integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= + +array-flatten@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" + integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= + +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= + +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + +async-limiter@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" + integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +atob@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" + integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== + +babel-jest@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.8.0.tgz#5c15ff2b28e20b0f45df43fe6b7f2aae93dba589" + integrity sha512-+5/kaZt4I9efoXzPlZASyK/lN9qdRKmmUav9smVc0ruPQD7IsfucQ87gpOE8mn2jbDuS6M/YOW6n3v9ZoIfgnw== + dependencies: + "@jest/transform" "^24.8.0" + "@jest/types" "^24.8.0" + "@types/babel__core" "^7.1.0" + babel-plugin-istanbul "^5.1.0" + babel-preset-jest "^24.6.0" + chalk "^2.4.2" + slash "^2.0.0" + +babel-plugin-istanbul@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz#df4ade83d897a92df069c4d9a25cf2671293c854" + integrity sha512-5LphC0USA8t4i1zCtjbbNb6jJj/9+X6P37Qfirc/70EQ34xKlMW+a1RHGwxGI+SwWpNwZ27HqvzAobeqaXwiZw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + find-up "^3.0.0" + istanbul-lib-instrument "^3.3.0" + test-exclude "^5.2.3" + +babel-plugin-jest-hoist@^24.6.0: + version "24.6.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.6.0.tgz#f7f7f7ad150ee96d7a5e8e2c5da8319579e78019" + integrity sha512-3pKNH6hMt9SbOv0F3WVmy5CWQ4uogS3k0GY5XLyQHJ9EGpAT9XWkFd2ZiXXtkwFHdAHa5j7w7kfxSP5lAIwu7w== + dependencies: + "@types/babel__traverse" "^7.0.6" + +babel-preset-jest@^24.6.0: + version "24.6.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-24.6.0.tgz#66f06136eefce87797539c0d63f1769cc3915984" + integrity sha512-pdZqLEdmy1ZK5kyRUfvBb2IfTPb2BUvIJczlPspS8fWmBQslNNDBqVfh7BW5leOVJMDZKzjD8XEyABTk6gQ5yw== + dependencies: + "@babel/plugin-syntax-object-rest-spread" "^7.0.0" + babel-plugin-jest-hoist "^24.6.0" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +base64-js@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.6.tgz#7b859f79f0bbbd55867ba67a7fab397e24a20947" + integrity sha1-e4WfefC7vVWGe6Z6f6s5fiSiCUc= + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + +bonjour-hap@^3.5.1: + version "3.5.4" + resolved "https://registry.yarnpkg.com/bonjour-hap/-/bonjour-hap-3.5.4.tgz#ba7d2e6220000cdb50bb80078a72ccdbcefa1c6f" + integrity sha512-MgU27SEZYQ09Skm71Xa7SZoAg259V4IlAJNWaloFVMlYDn1OjJy3nkwSixRHnDzrcLWocVI84f9exI1ObZChBw== + dependencies: + array-flatten "^2.1.0" + deep-equal "^1.0.1" + dns-equal "^1.0.0" + dns-txt "^2.0.2" + multicast-dns "^7.2.0" + multicast-dns-service-types "^1.1.0" + +bplist-creator@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/bplist-creator/-/bplist-creator-0.0.4.tgz#4ac0496782e127a85c1d2026a4f5eb22a7aff991" + integrity sha1-SsBJZ4LhJ6hcHSAmpPXrIqev+ZE= + dependencies: + stream-buffers "~0.2.3" + +bplist-parser@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.0.6.tgz#38da3471817df9d44ab3892e27707bbbd75a11b9" + integrity sha1-ONo0cYF9+dRKs4kuJ3B7u9daEbk= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + +browser-process-hrtime@^0.1.2: + version "0.1.3" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz#616f00faef1df7ec1b5bf9cfe2bdc3170f26c7b4" + integrity sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw== + +browser-resolve@^1.11.3: + version "1.11.3" + resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6" + integrity sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ== + dependencies: + resolve "1.1.7" + +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.0.tgz#65fc784bf7f87c009b973c12db6546902fa9c7b5" + integrity sha512-8zsjWrQkkBoLK6uxASk1nJ2SKv97ltiGDo6A3wA0/yRPz+CwmEyDo0hUrhIuukG2JHpAl3bvFIixw2/3Hi0DOg== + dependencies: + node-int64 "^0.4.0" + +buffer-from@1.x, buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +buffer-indexof@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" + integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g== + +buffer-shims@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" + integrity sha1-mXjOMXOIxkmth5MCjDR37wRKi1E= + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" + integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= + +camelcase@^5.0.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +capture-exit@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" + integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g== + dependencies: + rsvp "^4.8.4" + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chownr@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.2.tgz#a18f1e0b269c8a6a5d3c86eb298beb14c3dd7bf6" + integrity sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A== + +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +cliui@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" + integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ== + dependencies: + string-width "^2.1.1" + strip-ansi "^4.0.0" + wrap-ansi "^2.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@~2.20.0: + version "2.20.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" + integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== + +component-emitter@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + +convert-source-map@^1.1.0, convert-source-map@^1.4.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20" + integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A== + dependencies: + safe-buffer "~5.1.1" + +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= + +core-util-is@1.0.2, core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +cross-spawn@^6.0.0: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.4.0.tgz#9d31328229d3c565c61e586b02041a28fccdccf1" + integrity sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA== + dependencies: + cssom "0.3.x" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + +data-urls@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.1.0.tgz#15ee0582baa5e22bb59c77140da8f9c76963bbfe" + integrity sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ== + dependencies: + abab "^2.0.0" + whatwg-mimetype "^2.2.0" + whatwg-url "^7.0.0" + +debug@^2.2.0, debug@^2.3.3: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^3.2.6: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + +debug@^4.1.0, debug@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +decimal.js@^7.2.3: + version "7.5.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-7.5.1.tgz#cf4cf5eeb9faa24fc4ee6af361faebb7bfcca2ce" + integrity sha512-1K5Y6MykxQYfHBcFfAj2uBaLmwreq4MsjsvrlgcEOvg+X82IeeXlIVIVkBMiypksu+yo9vcYP6lfU3qTedofSQ== + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + +deep-equal@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" + integrity sha1-9dJgKStmDghO/0zbyfCK0yR0SLU= + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +define-properties@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + +detect-libc@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + +detect-newline@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" + integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I= + +diff-sequences@^24.3.0: + version "24.3.0" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.3.0.tgz#0f20e8a1df1abddaf4d9c226680952e64118b975" + integrity sha512-xLqpez+Zj9GKSnPWS0WZw1igGocZ+uua8+y+5dDNTT934N3QuY1sp2LkHzwiaYQGz60hMq0pjAshdeXm5VUOEw== + +diff@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff" + integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q== + +dns-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" + integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0= + +dns-packet@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-4.2.0.tgz#3fd6f5ff5a4ec3194ed0b15312693ffe8776b343" + integrity sha512-bn1AKpfkFbm0MIioOMHZ5qJzl2uypdBwI4nYNsqvhjsegBhcKJUlCrMPWLx6JEezRjxZmxhtIz/FkBEur2l8Cw== + dependencies: + ip "^1.1.5" + safe-buffer "^5.1.1" + +dns-txt@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" + integrity sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY= + dependencies: + buffer-indexof "^1.0.0" + +domexception@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" + integrity sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug== + dependencies: + webidl-conversions "^4.0.2" + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +end-of-stream@^1.1.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" + integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q== + dependencies: + once "^1.4.0" + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.5.1: + version "1.13.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" + integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg== + dependencies: + es-to-primitive "^1.2.0" + function-bind "^1.1.1" + has "^1.0.3" + is-callable "^1.1.4" + is-regex "^1.0.4" + object-keys "^1.0.12" + +es-to-primitive@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377" + integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +escodegen@^1.9.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.1.tgz#c485ff8d6b4cdb89e27f4a856e91f118401ca510" + integrity sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw== + dependencies: + esprima "^3.1.3" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +esprima@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" + integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= + +estraverse@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" + integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM= + +esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= + +exec-sh@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.2.tgz#6738de2eb7c8e671d0366aea0b0db8c6f7d7391b" + integrity sha512-9sLAvzhI5nc8TpuQUh4ahMdCrWT00wPWz7j47/emR5+2qEfoZP5zzUXvx+vdx+H6ohhnsYC31iX04QLYJK8zTg== + +execa@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +expect@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-24.8.0.tgz#471f8ec256b7b6129ca2524b2a62f030df38718d" + integrity sha512-/zYvP8iMDrzaaxHVa724eJBCKqSHmO0FA7EDkBiRHxg6OipmMn1fN+C8T9L9K8yr7UONkOifu6+LLH+z76CnaA== + dependencies: + "@jest/types" "^24.8.0" + ansi-styles "^3.2.0" + jest-get-type "^24.8.0" + jest-matcher-utils "^24.8.0" + jest-message-util "^24.8.0" + jest-regex-util "^24.3.0" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + +fast-deep-equal@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" + integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" + integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= + +fast-levenshtein@~2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +fast-srp-hap@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fast-srp-hap/-/fast-srp-hap-1.0.1.tgz#377124d196bc6a5157aae5b37bf5fa35bb4ad2d9" + integrity sha1-N3Ek0Za8alFXquWze/X6NbtK0tk= + +fb-watchman@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58" + integrity sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg= + dependencies: + bser "^2.0.0" + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= + dependencies: + map-cache "^0.2.2" + +fs-minipass@^1.2.5: + version "1.2.6" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07" + integrity sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ== + dependencies: + minipass "^2.2.1" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@^1.2.7: + version "1.2.9" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.9.tgz#3f5ed66583ccd6f400b5a00db6f7e861363e388f" + integrity sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw== + dependencies: + nan "^2.12.1" + node-pre-gyp "^0.12.0" + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +get-caller-file@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" + integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== + +get-stream@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.0.0" + +glob@^7.1.1, glob@^7.1.2, glob@^7.1.3: + version "7.1.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" + integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2: + version "4.2.0" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.0.tgz#8d8fdc73977cb04104721cb53666c1ca64cd328b" + integrity sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg== + +growly@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" + integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= + +handlebars@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67" + integrity sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw== + dependencies: + neo-async "^2.6.0" + optimist "^0.6.1" + source-map "^0.6.1" + optionalDependencies: + uglify-js "^3.1.4" + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.0: + version "5.1.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" + integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== + dependencies: + ajv "^6.5.5" + har-schema "^2.0.0" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" + integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +has@^1.0.1, has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hosted-git-info@^2.1.4: + version "2.7.1" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" + integrity sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w== + +html-encoding-sniffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" + integrity sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw== + dependencies: + whatwg-encoding "^1.0.1" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +iconv-lite@0.4.24, iconv-lite@^0.4.4: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ignore-walk@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" + integrity sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ== + dependencies: + minimatch "^3.0.4" + +import-local@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" + integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== + dependencies: + pkg-dir "^3.0.0" + resolve-cwd "^2.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@~1.3.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +invert-kv@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" + integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== + +ip@^1.1.3, ip@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" + integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= + +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== + dependencies: + kind-of "^6.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-callable@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" + integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== + +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== + dependencies: + kind-of "^6.0.0" + +is-date-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" + integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY= + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + dependencies: + is-plain-object "^2.0.4" + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= + dependencies: + kind-of "^3.0.2" + +is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-regex@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" + integrity sha1-VRdIm1RwkbCTDglWVM7SXul+lJE= + dependencies: + has "^1.0.1" + +is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= + +is-symbol@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" + integrity sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw== + dependencies: + has-symbols "^1.0.0" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= + +isarray@1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +istanbul-lib-coverage@^2.0.2, istanbul-lib-coverage@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49" + integrity sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA== + +istanbul-lib-instrument@^3.0.1, istanbul-lib-instrument@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz#a5f63d91f0bbc0c3e479ef4c5de027335ec6d630" + integrity sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA== + dependencies: + "@babel/generator" "^7.4.0" + "@babel/parser" "^7.4.3" + "@babel/template" "^7.4.0" + "@babel/traverse" "^7.4.3" + "@babel/types" "^7.4.0" + istanbul-lib-coverage "^2.0.5" + semver "^6.0.0" + +istanbul-lib-report@^2.0.4: + version "2.0.8" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz#5a8113cd746d43c4889eba36ab10e7d50c9b4f33" + integrity sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ== + dependencies: + istanbul-lib-coverage "^2.0.5" + make-dir "^2.1.0" + supports-color "^6.1.0" + +istanbul-lib-source-maps@^3.0.1: + version "3.0.6" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz#284997c48211752ec486253da97e3879defba8c8" + integrity sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^2.0.5" + make-dir "^2.1.0" + rimraf "^2.6.3" + source-map "^0.6.1" + +istanbul-reports@^2.1.1: + version "2.2.6" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.2.6.tgz#7b4f2660d82b29303a8fe6091f8ca4bf058da1af" + integrity sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA== + dependencies: + handlebars "^4.1.2" + +jest-changed-files@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.8.0.tgz#7e7eb21cf687587a85e50f3d249d1327e15b157b" + integrity sha512-qgANC1Yrivsq+UrLXsvJefBKVoCsKB0Hv+mBb6NMjjZ90wwxCDmU3hsCXBya30cH+LnPYjwgcU65i6yJ5Nfuug== + dependencies: + "@jest/types" "^24.8.0" + execa "^1.0.0" + throat "^4.0.0" + +jest-cli@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-24.8.0.tgz#b075ac914492ed114fa338ade7362a301693e989" + integrity sha512-+p6J00jSMPQ116ZLlHJJvdf8wbjNbZdeSX9ptfHX06/MSNaXmKihQzx5vQcw0q2G6JsdVkUIdWbOWtSnaYs3yA== + dependencies: + "@jest/core" "^24.8.0" + "@jest/test-result" "^24.8.0" + "@jest/types" "^24.8.0" + chalk "^2.0.1" + exit "^0.1.2" + import-local "^2.0.0" + is-ci "^2.0.0" + jest-config "^24.8.0" + jest-util "^24.8.0" + jest-validate "^24.8.0" + prompts "^2.0.1" + realpath-native "^1.1.0" + yargs "^12.0.2" + +jest-config@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-24.8.0.tgz#77db3d265a6f726294687cbbccc36f8a76ee0f4f" + integrity sha512-Czl3Nn2uEzVGsOeaewGWoDPD8GStxCpAe0zOYs2x2l0fZAgPbCr3uwUkgNKV3LwE13VXythM946cd5rdGkkBZw== + dependencies: + "@babel/core" "^7.1.0" + "@jest/test-sequencer" "^24.8.0" + "@jest/types" "^24.8.0" + babel-jest "^24.8.0" + chalk "^2.0.1" + glob "^7.1.1" + jest-environment-jsdom "^24.8.0" + jest-environment-node "^24.8.0" + jest-get-type "^24.8.0" + jest-jasmine2 "^24.8.0" + jest-regex-util "^24.3.0" + jest-resolve "^24.8.0" + jest-util "^24.8.0" + jest-validate "^24.8.0" + micromatch "^3.1.10" + pretty-format "^24.8.0" + realpath-native "^1.1.0" + +jest-diff@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.8.0.tgz#146435e7d1e3ffdf293d53ff97e193f1d1546172" + integrity sha512-wxetCEl49zUpJ/bvUmIFjd/o52J+yWcoc5ZyPq4/W1LUKGEhRYDIbP1KcF6t+PvqNrGAFk4/JhtxDq/Nnzs66g== + dependencies: + chalk "^2.0.1" + diff-sequences "^24.3.0" + jest-get-type "^24.8.0" + pretty-format "^24.8.0" + +jest-docblock@^24.3.0: + version "24.3.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.3.0.tgz#b9c32dac70f72e4464520d2ba4aec02ab14db5dd" + integrity sha512-nlANmF9Yq1dufhFlKG9rasfQlrY7wINJbo3q01tu56Jv5eBU5jirylhF2O5ZBnLxzOVBGRDz/9NAwNyBtG4Nyg== + dependencies: + detect-newline "^2.1.0" + +jest-each@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.8.0.tgz#a05fd2bf94ddc0b1da66c6d13ec2457f35e52775" + integrity sha512-NrwK9gaL5+XgrgoCsd9svsoWdVkK4gnvyhcpzd6m487tXHqIdYeykgq3MKI1u4I+5Zf0tofr70at9dWJDeb+BA== + dependencies: + "@jest/types" "^24.8.0" + chalk "^2.0.1" + jest-get-type "^24.8.0" + jest-util "^24.8.0" + pretty-format "^24.8.0" + +jest-environment-jsdom@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.8.0.tgz#300f6949a146cabe1c9357ad9e9ecf9f43f38857" + integrity sha512-qbvgLmR7PpwjoFjM/sbuqHJt/NCkviuq9vus9NBn/76hhSidO+Z6Bn9tU8friecegbJL8gzZQEMZBQlFWDCwAQ== + dependencies: + "@jest/environment" "^24.8.0" + "@jest/fake-timers" "^24.8.0" + "@jest/types" "^24.8.0" + jest-mock "^24.8.0" + jest-util "^24.8.0" + jsdom "^11.5.1" + +jest-environment-node@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.8.0.tgz#d3f726ba8bc53087a60e7a84ca08883a4c892231" + integrity sha512-vIGUEScd1cdDgR6sqn2M08sJTRLQp6Dk/eIkCeO4PFHxZMOgy+uYLPMC4ix3PEfM5Au/x3uQ/5Tl0DpXXZsJ/Q== + dependencies: + "@jest/environment" "^24.8.0" + "@jest/fake-timers" "^24.8.0" + "@jest/types" "^24.8.0" + jest-mock "^24.8.0" + jest-util "^24.8.0" + +jest-get-type@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.8.0.tgz#a7440de30b651f5a70ea3ed7ff073a32dfe646fc" + integrity sha512-RR4fo8jEmMD9zSz2nLbs2j0zvPpk/KCEz3a62jJWbd2ayNo0cb+KFRxPHVhE4ZmgGJEQp0fosmNz84IfqM8cMQ== + +jest-haste-map@^24.8.0: + version "24.8.1" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.8.1.tgz#f39cc1d2b1d907e014165b4bd5a957afcb992982" + integrity sha512-SwaxMGVdAZk3ernAx2Uv2sorA7jm3Kx+lR0grp6rMmnY06Kn/urtKx1LPN2mGTea4fCT38impYT28FfcLUhX0g== + dependencies: + "@jest/types" "^24.8.0" + anymatch "^2.0.0" + fb-watchman "^2.0.0" + graceful-fs "^4.1.15" + invariant "^2.2.4" + jest-serializer "^24.4.0" + jest-util "^24.8.0" + jest-worker "^24.6.0" + micromatch "^3.1.10" + sane "^4.0.3" + walker "^1.0.7" + optionalDependencies: + fsevents "^1.2.7" + +jest-jasmine2@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.8.0.tgz#a9c7e14c83dd77d8b15e820549ce8987cc8cd898" + integrity sha512-cEky88npEE5LKd5jPpTdDCLvKkdyklnaRycBXL6GNmpxe41F0WN44+i7lpQKa/hcbXaQ+rc9RMaM4dsebrYong== + dependencies: + "@babel/traverse" "^7.1.0" + "@jest/environment" "^24.8.0" + "@jest/test-result" "^24.8.0" + "@jest/types" "^24.8.0" + chalk "^2.0.1" + co "^4.6.0" + expect "^24.8.0" + is-generator-fn "^2.0.0" + jest-each "^24.8.0" + jest-matcher-utils "^24.8.0" + jest-message-util "^24.8.0" + jest-runtime "^24.8.0" + jest-snapshot "^24.8.0" + jest-util "^24.8.0" + pretty-format "^24.8.0" + throat "^4.0.0" + +jest-leak-detector@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-24.8.0.tgz#c0086384e1f650c2d8348095df769f29b48e6980" + integrity sha512-cG0yRSK8A831LN8lIHxI3AblB40uhv0z+SsQdW3GoMMVcK+sJwrIIyax5tu3eHHNJ8Fu6IMDpnLda2jhn2pD/g== + dependencies: + pretty-format "^24.8.0" + +jest-matcher-utils@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.8.0.tgz#2bce42204c9af12bde46f83dc839efe8be832495" + integrity sha512-lex1yASY51FvUuHgm0GOVj7DCYEouWSlIYmCW7APSqB9v8mXmKSn5+sWVF0MhuASG0bnYY106/49JU1FZNl5hw== + dependencies: + chalk "^2.0.1" + jest-diff "^24.8.0" + jest-get-type "^24.8.0" + pretty-format "^24.8.0" + +jest-message-util@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.8.0.tgz#0d6891e72a4beacc0292b638685df42e28d6218b" + integrity sha512-p2k71rf/b6ns8btdB0uVdljWo9h0ovpnEe05ZKWceQGfXYr4KkzgKo3PBi8wdnd9OtNh46VpNIJynUn/3MKm1g== + dependencies: + "@babel/code-frame" "^7.0.0" + "@jest/test-result" "^24.8.0" + "@jest/types" "^24.8.0" + "@types/stack-utils" "^1.0.1" + chalk "^2.0.1" + micromatch "^3.1.10" + slash "^2.0.0" + stack-utils "^1.0.1" + +jest-mock@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.8.0.tgz#2f9d14d37699e863f1febf4e4d5a33b7fdbbde56" + integrity sha512-6kWugwjGjJw+ZkK4mDa0Df3sDlUTsV47MSrT0nGQ0RBWJbpODDQ8MHDVtGtUYBne3IwZUhtB7elxHspU79WH3A== + dependencies: + "@jest/types" "^24.8.0" + +jest-pnp-resolver@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz#ecdae604c077a7fbc70defb6d517c3c1c898923a" + integrity sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ== + +jest-regex-util@^24.3.0: + version "24.3.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-24.3.0.tgz#d5a65f60be1ae3e310d5214a0307581995227b36" + integrity sha512-tXQR1NEOyGlfylyEjg1ImtScwMq8Oh3iJbGTjN7p0J23EuVX1MA8rwU69K4sLbCmwzgCUbVkm0FkSF9TdzOhtg== + +jest-resolve-dependencies@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-24.8.0.tgz#19eec3241f2045d3f990dba331d0d7526acff8e0" + integrity sha512-hyK1qfIf/krV+fSNyhyJeq3elVMhK9Eijlwy+j5jqmZ9QsxwKBiP6qukQxaHtK8k6zql/KYWwCTQ+fDGTIJauw== + dependencies: + "@jest/types" "^24.8.0" + jest-regex-util "^24.3.0" + jest-snapshot "^24.8.0" + +jest-resolve@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-24.8.0.tgz#84b8e5408c1f6a11539793e2b5feb1b6e722439f" + integrity sha512-+hjSzi1PoRvnuOICoYd5V/KpIQmkAsfjFO71458hQ2Whi/yf1GDeBOFj8Gxw4LrApHsVJvn5fmjcPdmoUHaVKw== + dependencies: + "@jest/types" "^24.8.0" + browser-resolve "^1.11.3" + chalk "^2.0.1" + jest-pnp-resolver "^1.2.1" + realpath-native "^1.1.0" + +jest-runner@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-24.8.0.tgz#4f9ae07b767db27b740d7deffad0cf67ccb4c5bb" + integrity sha512-utFqC5BaA3JmznbissSs95X1ZF+d+4WuOWwpM9+Ak356YtMhHE/GXUondZdcyAAOTBEsRGAgH/0TwLzfI9h7ow== + dependencies: + "@jest/console" "^24.7.1" + "@jest/environment" "^24.8.0" + "@jest/test-result" "^24.8.0" + "@jest/types" "^24.8.0" + chalk "^2.4.2" + exit "^0.1.2" + graceful-fs "^4.1.15" + jest-config "^24.8.0" + jest-docblock "^24.3.0" + jest-haste-map "^24.8.0" + jest-jasmine2 "^24.8.0" + jest-leak-detector "^24.8.0" + jest-message-util "^24.8.0" + jest-resolve "^24.8.0" + jest-runtime "^24.8.0" + jest-util "^24.8.0" + jest-worker "^24.6.0" + source-map-support "^0.5.6" + throat "^4.0.0" + +jest-runtime@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-24.8.0.tgz#05f94d5b05c21f6dc54e427cd2e4980923350620" + integrity sha512-Mq0aIXhvO/3bX44ccT+czU1/57IgOMyy80oM0XR/nyD5zgBcesF84BPabZi39pJVA6UXw+fY2Q1N+4BiVUBWOA== + dependencies: + "@jest/console" "^24.7.1" + "@jest/environment" "^24.8.0" + "@jest/source-map" "^24.3.0" + "@jest/transform" "^24.8.0" + "@jest/types" "^24.8.0" + "@types/yargs" "^12.0.2" + chalk "^2.0.1" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.1.15" + jest-config "^24.8.0" + jest-haste-map "^24.8.0" + jest-message-util "^24.8.0" + jest-mock "^24.8.0" + jest-regex-util "^24.3.0" + jest-resolve "^24.8.0" + jest-snapshot "^24.8.0" + jest-util "^24.8.0" + jest-validate "^24.8.0" + realpath-native "^1.1.0" + slash "^2.0.0" + strip-bom "^3.0.0" + yargs "^12.0.2" + +jest-serializer@^24.4.0: + version "24.4.0" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-24.4.0.tgz#f70c5918c8ea9235ccb1276d232e459080588db3" + integrity sha512-k//0DtglVstc1fv+GY/VHDIjrtNjdYvYjMlbLUed4kxrE92sIUewOi5Hj3vrpB8CXfkJntRPDRjCrCvUhBdL8Q== + +jest-snapshot@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-24.8.0.tgz#3bec6a59da2ff7bc7d097a853fb67f9d415cb7c6" + integrity sha512-5ehtWoc8oU9/cAPe6fez6QofVJLBKyqkY2+TlKTOf0VllBB/mqUNdARdcjlZrs9F1Cv+/HKoCS/BknT0+tmfPg== + dependencies: + "@babel/types" "^7.0.0" + "@jest/types" "^24.8.0" + chalk "^2.0.1" + expect "^24.8.0" + jest-diff "^24.8.0" + jest-matcher-utils "^24.8.0" + jest-message-util "^24.8.0" + jest-resolve "^24.8.0" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + pretty-format "^24.8.0" + semver "^5.5.0" + +jest-util@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.8.0.tgz#41f0e945da11df44cc76d64ffb915d0716f46cd1" + integrity sha512-DYZeE+XyAnbNt0BG1OQqKy/4GVLPtzwGx5tsnDrFcax36rVE3lTA5fbvgmbVPUZf9w77AJ8otqR4VBbfFJkUZA== + dependencies: + "@jest/console" "^24.7.1" + "@jest/fake-timers" "^24.8.0" + "@jest/source-map" "^24.3.0" + "@jest/test-result" "^24.8.0" + "@jest/types" "^24.8.0" + callsites "^3.0.0" + chalk "^2.0.1" + graceful-fs "^4.1.15" + is-ci "^2.0.0" + mkdirp "^0.5.1" + slash "^2.0.0" + source-map "^0.6.0" + +jest-validate@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.8.0.tgz#624c41533e6dfe356ffadc6e2423a35c2d3b4849" + integrity sha512-+/N7VOEMW1Vzsrk3UWBDYTExTPwf68tavEPKDnJzrC6UlHtUDU/fuEdXqFoHzv9XnQ+zW6X3qMZhJ3YexfeLDA== + dependencies: + "@jest/types" "^24.8.0" + camelcase "^5.0.0" + chalk "^2.0.1" + jest-get-type "^24.8.0" + leven "^2.1.0" + pretty-format "^24.8.0" + +jest-watcher@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-24.8.0.tgz#58d49915ceddd2de85e238f6213cef1c93715de4" + integrity sha512-SBjwHt5NedQoVu54M5GEx7cl7IGEFFznvd/HNT8ier7cCAx/Qgu9ZMlaTQkvK22G1YOpcWBLQPFSImmxdn3DAw== + dependencies: + "@jest/test-result" "^24.8.0" + "@jest/types" "^24.8.0" + "@types/yargs" "^12.0.9" + ansi-escapes "^3.0.0" + chalk "^2.0.1" + jest-util "^24.8.0" + string-length "^2.0.0" + +jest-worker@^24.6.0: + version "24.6.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.6.0.tgz#7f81ceae34b7cde0c9827a6980c35b7cdc0161b3" + integrity sha512-jDwgW5W9qGNvpI1tNnvajh0a5IE/PuGLFmHk6aR/BZFz8tSgGw17GsDPXAJ6p91IvYDjOw8GpFbvvZGAK+DPQQ== + dependencies: + merge-stream "^1.0.1" + supports-color "^6.1.0" + +jest@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-24.8.0.tgz#d5dff1984d0d1002196e9b7f12f75af1b2809081" + integrity sha512-o0HM90RKFRNWmAWvlyV8i5jGZ97pFwkeVoGvPW1EtLTgJc2+jcuqcbbqcSZLE/3f2S5pt0y2ZBETuhpWNl1Reg== + dependencies: + import-local "^2.0.0" + jest-cli "^24.8.0" + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + +jsdom@^11.5.1: + version "11.12.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.12.0.tgz#1a80d40ddd378a1de59656e9e6dc5a3ba8657bc8" + integrity sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw== + dependencies: + abab "^2.0.0" + acorn "^5.5.3" + acorn-globals "^4.1.0" + array-equal "^1.0.0" + cssom ">= 0.3.2 < 0.4.0" + cssstyle "^1.0.0" + data-urls "^1.0.0" + domexception "^1.0.1" + escodegen "^1.9.1" + html-encoding-sniffer "^1.0.2" + left-pad "^1.3.0" + nwsapi "^2.0.7" + parse5 "4.0.0" + pn "^1.1.0" + request "^2.87.0" + request-promise-native "^1.0.5" + sax "^1.2.4" + symbol-tree "^3.2.2" + tough-cookie "^2.3.4" + w3c-hr-time "^1.0.1" + webidl-conversions "^4.0.2" + whatwg-encoding "^1.0.3" + whatwg-mimetype "^2.1.0" + whatwg-url "^6.4.1" + ws "^5.2.0" + xml-name-validator "^3.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +json5@2.x, json5@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.0.tgz#e7a0c62c48285c628d20a10b85c89bb807c32850" + integrity sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ== + dependencies: + minimist "^1.2.0" + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" + integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== + +kleur@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +lcid@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf" + integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA== + dependencies: + invert-kv "^2.0.0" + +left-pad@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e" + integrity sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA== + +leven@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580" + integrity sha1-wuep93IJTe6dNCAq6KzORoeHVYA= + +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +load-json-file@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" + integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= + dependencies: + graceful-fs "^4.1.2" + parse-json "^4.0.0" + pify "^3.0.0" + strip-bom "^3.0.0" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +lodash-node@~2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/lodash-node/-/lodash-node-2.4.1.tgz#ea82f7b100c733d1a42af76801e506105e2a80ec" + integrity sha1-6oL3sQDHM9GkKvdoAeUGEF4qgOw= + +lodash.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" + integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= + +lodash@^4.17.11, lodash@^4.17.13: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + +loose-envify@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +make-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + +make-error@1.x, make-error@^1.1.1: + version "1.3.5" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8" + integrity sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g== + +makeerror@1.0.x: + version "1.0.11" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" + integrity sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw= + dependencies: + tmpl "1.0.x" + +map-age-cleaner@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" + integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== + dependencies: + p-defer "^1.0.0" + +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= + dependencies: + object-visit "^1.0.0" + +mem@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178" + integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w== + dependencies: + map-age-cleaner "^0.1.1" + mimic-fn "^2.0.0" + p-is-promise "^2.0.0" + +merge-stream@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-1.0.1.tgz#4041202d508a342ba00174008df0c251b8c135e1" + integrity sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE= + dependencies: + readable-stream "^2.0.1" + +micromatch@^3.1.10, micromatch@^3.1.4: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + +mime-db@1.40.0: + version "1.40.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" + integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.24" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" + integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ== + dependencies: + mime-db "1.40.0" + +mimic-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +minimist@^1.1.1, minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= + +minimist@~0.0.1: + version "0.0.10" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" + integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8= + +minipass@^2.2.1, minipass@^2.3.5: + version "2.3.5" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" + integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA== + dependencies: + safe-buffer "^5.1.2" + yallist "^3.0.0" + +minizlib@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" + integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA== + dependencies: + minipass "^2.2.1" + +mixin-deep@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" + integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +mkdirp@0.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +multicast-dns-service-types@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" + integrity sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE= + +multicast-dns@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.0.tgz#7aa49a7efba931a346011aa02e7d1c314a65ac77" + integrity sha512-Tu2QORGOFANB124NWQ/JTRhMf/ODouVLEuvu5Dz8YWEU55zQgRgFGnBHfIh5PbfNDAuaRl7yLB+pgWhSqVxi2Q== + dependencies: + dns-packet "^4.0.0" + thunky "^1.0.2" + +nan@^2.12.1: + version "2.14.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" + integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== + +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +needle@^2.2.1: + version "2.4.0" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c" + integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg== + dependencies: + debug "^3.2.6" + iconv-lite "^0.4.4" + sax "^1.2.4" + +neo-async@^2.6.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" + integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= + +node-modules-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" + integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= + +node-notifier@^5.2.1: + version "5.4.0" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.4.0.tgz#7b455fdce9f7de0c63538297354f3db468426e6a" + integrity sha512-SUDEb+o71XR5lXSTyivXd9J7fCloE3SyP4lSgt3lU2oSANiox+SxlNRGPjDKrwU1YN3ix2KN/VGGCg0t01rttQ== + dependencies: + growly "^1.3.0" + is-wsl "^1.1.0" + semver "^5.5.0" + shellwords "^0.1.1" + which "^1.3.0" + +node-persist@^0.0.11: + version "0.0.11" + resolved "https://registry.yarnpkg.com/node-persist/-/node-persist-0.0.11.tgz#d66eba3ebef620f079530fa7b13076a906665874" + integrity sha1-1m66Pr72IPB5Uw+nsTB2qQZmWHQ= + dependencies: + mkdirp "~0.5.1" + q "~1.1.1" + +node-pre-gyp@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149" + integrity sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A== + dependencies: + detect-libc "^1.0.2" + mkdirp "^0.5.1" + needle "^2.2.1" + nopt "^4.0.1" + npm-packlist "^1.1.6" + npmlog "^4.0.2" + rc "^1.2.7" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^4" + +nopt@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" + integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= + dependencies: + abbrev "1" + osenv "^0.1.4" + +normalize-package-data@^2.3.2: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + dependencies: + remove-trailing-separator "^1.0.1" + +npm-bundled@^1.0.1: + version "1.0.6" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" + integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g== + +npm-packlist@^1.1.6: + version "1.4.4" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.4.tgz#866224233850ac534b63d1a6e76050092b5d2f44" + integrity sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw== + dependencies: + ignore-walk "^3.0.1" + npm-bundled "^1.0.1" + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= + dependencies: + path-key "^2.0.0" + +npmlog@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + +nwsapi@^2.0.7: + version "2.1.4" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.1.4.tgz#e006a878db23636f8e8a67d33ca0e4edf61a842f" + integrity sha512-iGfd9Y6SFdTNldEy2L0GUhcarIutFmk+MPWIn9dmj8NMIup03G08uUF2KGbbmv/Ux4RT0VZJoP/sVbWA6d/VIw== + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-keys@^1.0.12: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= + dependencies: + isobject "^3.0.0" + +object.getownpropertydescriptors@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" + integrity sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY= + dependencies: + define-properties "^1.1.2" + es-abstract "^1.5.1" + +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= + dependencies: + isobject "^3.0.1" + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +optimist@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" + integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY= + dependencies: + minimist "~0.0.1" + wordwrap "~0.0.2" + +optionator@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" + integrity sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q= + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.4" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + wordwrap "~1.0.0" + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= + +os-locale@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" + integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q== + dependencies: + execa "^1.0.0" + lcid "^2.0.0" + mem "^4.0.0" + +os-tmpdir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +osenv@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +p-defer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" + integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= + +p-each-series@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-1.0.0.tgz#930f3d12dd1f50e7434457a22cd6f04ac6ad7f71" + integrity sha1-kw89Et0fUOdDRFeiLNbwSsatf3E= + dependencies: + p-reduce "^1.0.0" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= + +p-is-promise@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e" + integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg== + +p-limit@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.0.tgz#417c9941e6027a9abcba5092dd2904e255b5fbc2" + integrity sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ== + dependencies: + p-try "^2.0.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-reduce@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa" + integrity sha1-GMKw3ZNqRpClKfgjH1ig/bakffo= + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +parse5@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" + integrity sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA== + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^2.0.0, path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +path-type@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" + integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== + dependencies: + pify "^3.0.0" + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= + +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +pirates@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" + integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA== + dependencies: + node-modules-regexp "^1.0.0" + +pkg-dir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" + integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== + dependencies: + find-up "^3.0.0" + +plist@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/plist/-/plist-1.1.0.tgz#ff6708590c97cc438e7bc45de5251bd725f3f89d" + integrity sha1-/2cIWQyXzEOOe8Rd5SUb1yXz+J0= + dependencies: + base64-js "0.0.6" + util-deprecate "1.0.0" + xmlbuilder "2.2.1" + xmldom "0.1.x" + +pn@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" + integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA== + +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +pretty-format@^24.8.0: + version "24.8.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.8.0.tgz#8dae7044f58db7cb8be245383b565a963e3c27f2" + integrity sha512-P952T7dkrDEplsR+TuY7q3VXDae5Sr7zmQb12JU/NDQa/3CH7/QW0yvqLcGN6jL+zQFKaoJcPc+yJxMTGmosqw== + dependencies: + "@jest/types" "^24.8.0" + ansi-regex "^4.0.0" + ansi-styles "^3.2.0" + react-is "^16.8.4" + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +prompts@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.1.0.tgz#bf90bc71f6065d255ea2bdc0fe6520485c1b45db" + integrity sha512-+x5TozgqYdOwWsQFZizE/Tra3fKvAoy037kOyU6cgz84n8f6zxngLOV4O32kTwt9FcLCxAqw0P/c8rOr9y+Gfg== + dependencies: + kleur "^3.0.2" + sisteransi "^1.0.0" + +psl@^1.1.24, psl@^1.1.28: + version "1.2.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.2.0.tgz#df12b5b1b3a30f51c329eacbdef98f3a6e136dc6" + integrity sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA== + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +q@~1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/q/-/q-1.1.2.tgz#6357e291206701d99f197ab84e57e8ad196f2a89" + integrity sha1-Y1fikSBnAdmfGXq4TlforRlvKok= + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +react-is@^16.8.4: + version "16.8.6" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" + integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA== + +read-pkg-up@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-4.0.0.tgz#1b221c6088ba7799601c808f91161c66e58f8978" + integrity sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA== + dependencies: + find-up "^3.0.0" + read-pkg "^3.0.0" + +read-pkg@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" + integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= + dependencies: + load-json-file "^4.0.0" + normalize-package-data "^2.3.2" + path-type "^3.0.0" + +readable-stream@^2.0.1, readable-stream@^2.0.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +realpath-native@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c" + integrity sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA== + dependencies: + util.promisify "^1.0.0" + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + +repeat-element@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== + +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + +request-promise-core@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.2.tgz#339f6aababcafdb31c799ff158700336301d3346" + integrity sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag== + dependencies: + lodash "^4.17.11" + +request-promise-native@^1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.7.tgz#a49868a624bdea5069f1251d0a836e0d89aa2c59" + integrity sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w== + dependencies: + request-promise-core "1.1.2" + stealthy-require "^1.1.1" + tough-cookie "^2.3.3" + +request@^2.87.0: + version "2.88.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" + integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.0" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.4.3" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-main-filename@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" + integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= + +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +resolve-cwd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" + integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo= + dependencies: + resolve-from "^3.0.0" + +resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + integrity sha1-six699nWiBvItuZTM17rywoYh0g= + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= + +resolve@1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" + integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= + +resolve@1.x, resolve@^1.10.0, resolve@^1.3.2: + version "1.11.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.1.tgz#ea10d8110376982fef578df8fc30b9ac30a07a3e" + integrity sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw== + dependencies: + path-parse "^1.0.6" + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + +rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +rsvp@^4.8.4: + version "4.8.5" + resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" + integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== + +safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2: + version "5.2.0" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= + dependencies: + ret "~0.1.10" + +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sane@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded" + integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA== + dependencies: + "@cnakazawa/watch" "^1.0.3" + anymatch "^2.0.0" + capture-exit "^2.0.0" + exec-sh "^0.3.2" + execa "^1.0.0" + fb-watchman "^2.0.0" + micromatch "^3.1.4" + minimist "^1.1.1" + walker "~1.0.5" + +sax@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5, semver@^5.5.0, semver@^5.6.0: + version "5.7.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" + integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA== + +semver@^6.0.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +set-blocking@^2.0.0, set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +set-value@^2.0.0, set-value@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" + integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +shellwords@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" + integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== + +signal-exit@^3.0.0, signal-exit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + +simple-plist@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/simple-plist/-/simple-plist-0.0.4.tgz#7f863438b63cb75df99dd81b8336d7c5075cfc0b" + integrity sha1-f4Y0OLY8t135ndgbgzbXxQdc/As= + dependencies: + bplist-creator "0.0.4" + bplist-parser "0.0.6" + plist "1.1.0" + +sisteransi@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.2.tgz#ec57d64b6f25c4f26c0e2c7dd23f2d7f12f7e418" + integrity sha512-ZcYcZcT69nSLAR2oLN2JwNmLkJEKGooFMCdvOkFrToUt/WfcRWqhIg4P4KwY4dmLbuyXIx4o4YmPsvMRJYJd/w== + +slash@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" + integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + +source-map-resolve@^0.5.0: + version "0.5.2" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" + integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA== + dependencies: + atob "^2.1.1" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-support@^0.5.6: + version "0.5.12" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599" + integrity sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-url@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" + integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= + +source-map@^0.5.0, source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +spdx-correct@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" + integrity sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" + integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== + +spdx-expression-parse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" + integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.5" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" + integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== + +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== + dependencies: + extend-shallow "^3.0.0" + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +stack-utils@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8" + integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA== + +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +stealthy-require@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" + integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= + +stream-buffers@~0.2.3: + version "0.2.6" + resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-0.2.6.tgz#181c08d5bb3690045f69401b9ae6a7a0cf3313fc" + integrity sha1-GBwI1bs2kARfaUAbmuanoM8zE/w= + +string-length@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed" + integrity sha1-1A27aGo6zpYMHP/KVivyxF+DY+0= + dependencies: + astral-regex "^1.0.0" + strip-ansi "^4.0.0" + +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + +symbol-tree@^3.2.2: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + +tar@^4: + version "4.4.10" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.10.tgz#946b2810b9a5e0b26140cf78bea6b0b0d689eba1" + integrity sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA== + dependencies: + chownr "^1.1.1" + fs-minipass "^1.2.5" + minipass "^2.3.5" + minizlib "^1.2.1" + mkdirp "^0.5.0" + safe-buffer "^5.1.2" + yallist "^3.0.3" + +test-exclude@^5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.2.3.tgz#c3d3e1e311eb7ee405e092dac10aefd09091eac0" + integrity sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g== + dependencies: + glob "^7.1.3" + minimatch "^3.0.4" + read-pkg-up "^4.0.0" + require-main-filename "^2.0.0" + +throat@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" + integrity sha1-iQN8vJLFarGJJua6TLsgDhVnKmo= + +thunky@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.0.3.tgz#f5df732453407b09191dae73e2a8cc73f381a826" + integrity sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow== + +tmpl@1.0.x: + version "1.0.4" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" + integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE= + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +tough-cookie@^2.3.3, tough-cookie@^2.3.4: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + +tough-cookie@~2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" + integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== + dependencies: + psl "^1.1.24" + punycode "^1.4.1" + +tr46@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" + integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk= + dependencies: + punycode "^2.1.0" + +trim-right@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" + integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= + +ts-jest@^24.0.2: + version "24.0.2" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-24.0.2.tgz#8dde6cece97c31c03e80e474c749753ffd27194d" + integrity sha512-h6ZCZiA1EQgjczxq+uGLXQlNgeg02WWJBbeT8j6nyIBRQdglqbvzDoHahTEIiS6Eor6x8mK6PfZ7brQ9Q6tzHw== + dependencies: + bs-logger "0.x" + buffer-from "1.x" + fast-json-stable-stringify "2.x" + json5 "2.x" + make-error "1.x" + mkdirp "0.x" + resolve "1.x" + semver "^5.5" + yargs-parser "10.x" + +ts-node@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.3.0.tgz#e4059618411371924a1fb5f3b125915f324efb57" + integrity sha512-dyNS/RqyVTDcmNM4NIBAeDMpsAdaQ+ojdf0GOLqE6nwJOgzEkdRNzJywhDfwnuvB10oa6NLVG1rUJQCpRN7qoQ== + dependencies: + arg "^4.1.0" + diff "^4.0.1" + make-error "^1.1.1" + source-map-support "^0.5.6" + yn "^3.0.0" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +tweetnacl@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.1.tgz#2594d42da73cd036bd0d2a54683dd35a6b55ca17" + integrity sha512-kcoMoKTPYnoeS50tzoqjPY3Uv9axeuuFAZY9M/9zFnhoVvRfxz9K29IMPD7jGmt2c8SW7i3gT9WqDl2+nV7p4A== + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +typescript@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" + integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g== + +uglify-js@^3.1.4: + version "3.6.0" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.0.tgz#704681345c53a8b2079fb6cec294b05ead242ff5" + integrity sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg== + dependencies: + commander "~2.20.0" + source-map "~0.6.1" + +union-value@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" + integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^2.0.1" + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= + +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + +util-deprecate@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.0.tgz#3007af012c140eae26de05576ec22785cac3abf2" + integrity sha1-MAevASwUDq4m3gVXbsInhcrDq/I= + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +util.promisify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" + integrity sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA== + dependencies: + define-properties "^1.1.2" + object.getownpropertydescriptors "^2.0.3" + +uuid@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +w3c-hr-time@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz#82ac2bff63d950ea9e3189a58a65625fedf19045" + integrity sha1-gqwr/2PZUOqeMYmlimViX+3xkEU= + dependencies: + browser-process-hrtime "^0.1.2" + +walker@^1.0.7, walker@~1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" + integrity sha1-L3+bj9ENZ3JisYqITijRlhjgKPs= + dependencies: + makeerror "1.0.x" + +webidl-conversions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" + integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== + +whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3: + version "1.0.5" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== + dependencies: + iconv-lite "0.4.24" + +whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" + integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== + +whatwg-url@^6.4.1: + version "6.5.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8" + integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ== + dependencies: + lodash.sortby "^4.7.0" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" + +whatwg-url@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.0.0.tgz#fde926fa54a599f3adf82dff25a9f7be02dc6edd" + integrity sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ== + dependencies: + lodash.sortby "^4.7.0" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + +which@^1.2.9, which@^1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" + +wordwrap@~0.0.2: + version "0.0.3" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" + integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= + +wordwrap@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= + +wrap-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write-file-atomic@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.1.tgz#d0b05463c188ae804396fd5ab2a370062af87529" + integrity sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg== + dependencies: + graceful-fs "^4.1.11" + imurmurhash "^0.1.4" + signal-exit "^3.0.2" + +ws@^5.2.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f" + integrity sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA== + dependencies: + async-limiter "~1.0.0" + +xml-name-validator@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" + integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== + +xmlbuilder@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-2.2.1.tgz#9326430f130d87435d4c4086643aa2926e105a32" + integrity sha1-kyZDDxMNh0NdTECGZDqikm4QWjI= + dependencies: + lodash-node "~2.4.1" + +xmldom@0.1.x: + version "0.1.27" + resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9" + integrity sha1-1QH5ezvbQDr4757MIFcxh6rawOk= + +"y18n@^3.2.1 || ^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" + integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== + +yallist@^3.0.0, yallist@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" + integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== + +yargs-parser@10.x: + version "10.1.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8" + integrity sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ== + dependencies: + camelcase "^4.1.0" + +yargs-parser@^11.1.1: + version "11.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4" + integrity sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs@^12.0.2: + version "12.0.5" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13" + integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw== + dependencies: + cliui "^4.0.0" + decamelize "^1.2.0" + find-up "^3.0.0" + get-caller-file "^1.0.1" + os-locale "^3.0.0" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^2.0.0" + which-module "^2.0.0" + y18n "^3.2.1 || ^4.0.0" + yargs-parser "^11.1.1" + +yn@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.0.tgz#fcbe2db63610361afcc5eb9e0ac91e976d046114" + integrity sha512-kKfnnYkbTfrAdd0xICNFw7Atm8nKpLcLv9AZGEt+kczL/WQVai4e2V6ZN8U/O+iI6WrNuJjNNOyu4zfhl9D3Hg==