hap-nodejs
Version:
HAP-NodeJS is a Node.js implementation of HomeKit Accessory Server.
492 lines • 20.3 kB
TypeScript
import { EventEmitter } from "events";
import { ConstructorArgs, HAPPincode, InterfaceName, IPAddress, MacAddress, Nullable, VoidCallback, WithUUID } from "../types";
import { Advertiser } from "./Advertiser";
import { Characteristic } from "./Characteristic";
import { Controller, ControllerConstructor, ControllerIdentifier, ControllerServiceMap } from "./controller";
import { HAPServer } from "./HAPServer";
import { AccessoryInfo } from "./model/AccessoryInfo";
import { ControllerStorage } from "./model/ControllerStorage";
import { IdentifierCache } from "./model/IdentifierCache";
import { SerializedService, Service, ServiceCharacteristicChange, ServiceId } from "./Service";
/**
* Known category values. Category is a hint to iOS clients about what "type" of Accessory this represents, for UI only.
*
* @group Accessory
*/
export declare const 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,
AIR_CONDITIONER = 21,
AIR_HUMIDIFIER = 22,
AIR_DEHUMIDIFIER = 23,
APPLE_TV = 24,
HOMEPOD = 25,
SPEAKER = 26,
AIRPORT = 27,
SPRINKLER = 28,
FAUCET = 29,
SHOWER_HEAD = 30,
TELEVISION = 31,
TARGET_CONTROLLER = 32,// Remote Control
ROUTER = 33,
AUDIO_RECEIVER = 34,
TV_SET_TOP_BOX = 35,
TV_STREAMING_STICK = 36
}
/**
* @group Accessory
*/
export interface SerializedAccessory {
displayName: string;
UUID: string;
lastKnownUsername?: MacAddress;
category: Categories;
services: SerializedService[];
linkedServices?: Record<ServiceId, ServiceId[]>;
controllers?: SerializedControllerContext[];
}
/**
* @group Controller API
*/
export interface SerializedControllerContext {
type: ControllerIdentifier;
services: SerializedServiceMap;
}
/**
* @group Controller API
*/
export type SerializedServiceMap = Record<string, ServiceId>;
/**
* @group Controller API
*/
export interface ControllerContext {
controller: Controller;
serviceMap: ControllerServiceMap;
}
/**
* @group Accessory
*/
export declare const enum CharacteristicWarningType {
SLOW_WRITE = "slow-write",
TIMEOUT_WRITE = "timeout-write",
SLOW_READ = "slow-read",
TIMEOUT_READ = "timeout-read",
WARN_MESSAGE = "warn-message",
ERROR_MESSAGE = "error-message",
DEBUG_MESSAGE = "debug-message"
}
/**
* @group Accessory
*/
export interface CharacteristicWarning {
characteristic: Characteristic;
type: CharacteristicWarningType;
message: string;
originatorChain: string[];
stack?: string;
}
/**
* @group Accessory
*/
export interface PublishInfo {
username: MacAddress;
pincode: HAPPincode;
/**
* Specify the category for the HomeKit accessory.
* The category is used only in the mdns advertisement and specifies the devices type
* for the HomeKit controller.
* Currently, this only affects the icon shown in the pairing screen.
* For the Television and Smart Speaker service it also affects the icon shown in
* the Home app when paired.
*/
category?: Categories;
setupID?: string;
/**
* Defines the host where the HAP server will be bound to.
* When undefined the HAP server will bind to all available interfaces
* (see https://nodejs.org/api/net.html#net_server_listen_port_host_backlog_callback).
*
* This property accepts a mixture of IPAddresses and network interface names.
* Depending on the mixture of supplied addresses/names hap-nodejs will bind differently.
*
* It is advised to not just bind to a specific address, but specifying the interface name
* in oder to bind on all address records (and ip version) available.
*
* HAP-NodeJS (or the underlying ciao library) will not report about misspelled interface names,
* as it could be that the interface is currently just down and will come up later.
*
* Here are a few examples:
* - bind: "::"
* Pretty much identical to not specifying anything, as most systems (with ipv6 support)
* will default to the unspecified ipv6 address (with dual stack support).
*
* - bind: "0.0.0.0"
* Binding TCP socket to the unspecified ipv4 address.
* The mdns advertisement will exclude any ipv6 address records.
*
* - bind: ["en0", "lo0"]
* The mdns advertising will advertise all records of the en0 and loopback interface (if available) and
* will also react to address changes on those interfaces.
* In order for the HAP server to accept all those address records (which may contain ipv6 records)
* it will bind on the unspecified ipv6 address "::" (assuming dual stack is supported).
*
* - bind: ["en0", "lo0", "0.0.0.0"]
* Same as above, only that the HAP server will bind on the unspecified ipv4 address "0.0.0.0".
* The mdns advertisement will not advertise any ipv6 records.
*
* - bind: "169.254.104.90"
* This will bind the HAP server to the address 0.0.0.0.
* The mdns advertisement will only advertise the A record 169.254.104.90.
* If the given network interface of that address encounters an ip address change (to a different address),
* the mdns advertisement will result in not advertising an address at all.
* So it is advised to specify an interface name instead of a specific address.
* This is identical with ipv6 addresses.
*
* - bind: ["169.254.104.90", "192.168.1.4"]
* As the HAP TCP socket can only bind to a single address, when specifying multiple ip addresses
* the HAP server will bind to the unspecified ip address (0.0.0.0 if only ipv4 addresses are supplied,
* :: if a mixture or only ipv6 addresses are supplied).
* The mdns advertisement will only advertise the specified ip addresses.
* If the given network interface of that address encounters an ip address change (to different addresses),
* the mdns advertisement will result in not advertising an address at all.
* So it is advised to specify an interface name instead of a specific address.
*
*/
bind?: (InterfaceName | IPAddress) | (InterfaceName | IPAddress)[];
/**
* Defines the port where the HAP server will be bound to.
* When undefined port 0 will be used resulting in a random port.
*/
port?: number;
/**
* If this option is set to true, HAP-NodeJS will add identifying material (based on {@link username})
* to the end of the accessory display name (and bonjour instance name).
* Default: true
*/
addIdentifyingMaterial?: boolean;
/**
* Defines the advertiser used with the published Accessory.
*/
advertiser?: MDNSAdvertiser;
}
/**
* @group Accessory
*/
export declare const enum MDNSAdvertiser {
/**
* Use the `@homebridge/ciao` module as advertiser.
*/
CIAO = "ciao",
/**
* Use the `bonjour-hap` module as advertiser.
*/
BONJOUR = "bonjour-hap",
/**
* Use Avahi/D-Bus as advertiser.
*/
AVAHI = "avahi",
/**
* Use systemd-resolved/D-Bus as advertiser.
*
* Note: The systemd-resolved D-Bus interface doesn't provide means to detect restarts of the service.
* Therefore, we can't detect if our advertisement might be lost due to a restart of the systemd-resolved daemon restart.
* Consequentially, treat this feature as an experimental feature.
*/
RESOLVED = "resolved"
}
/**
* @group Accessory
*/
export type AccessoryCharacteristicChange = ServiceCharacteristicChange & {
service: Service;
};
/**
* @group Service
*/
export interface ServiceConfigurationChange {
service: Service;
}
/**
* @group Accessory
*/
export declare const enum AccessoryEventTypes {
/**
* 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.
*/
IDENTIFY = "identify",
/**
* This event is emitted once the HAP TCP socket is bound.
* At this point the mdns advertisement isn't yet available. Use the {@link ADVERTISED} if you require the accessory to be discoverable.
*/
LISTENING = "listening",
/**
* This event is emitted once the mDNS suite has fully advertised the presence of the accessory.
* This event is guaranteed to be called after {@link LISTENING}.
*/
ADVERTISED = "advertised",
SERVICE_CONFIGURATION_CHANGE = "service-configurationChange",
/**
* Emitted after a change in the value of one of the provided Service's Characteristics.
*/
SERVICE_CHARACTERISTIC_CHANGE = "service-characteristic-change",
PAIRED = "paired",
UNPAIRED = "unpaired",
CHARACTERISTIC_WARNING = "characteristic-warning"
}
/**
* @group Accessory
*/
export declare interface Accessory {
on(event: "identify", listener: (paired: boolean, callback: VoidCallback) => void): this;
on(event: "listening", listener: (port: number, address: string) => void): this;
on(event: "advertised", listener: () => void): this;
on(event: "service-configurationChange", listener: (change: ServiceConfigurationChange) => void): this;
on(event: "service-characteristic-change", listener: (change: AccessoryCharacteristicChange) => void): this;
on(event: "paired", listener: () => void): this;
on(event: "unpaired", listener: () => void): this;
on(event: "characteristic-warning", listener: (warning: CharacteristicWarning) => void): this;
emit(event: "identify", paired: boolean, callback: VoidCallback): boolean;
emit(event: "listening", port: number, address: string): boolean;
emit(event: "advertised"): boolean;
emit(event: "service-configurationChange", change: ServiceConfigurationChange): boolean;
emit(event: "service-characteristic-change", change: AccessoryCharacteristicChange): boolean;
emit(event: "paired"): boolean;
emit(event: "unpaired"): boolean;
emit(event: "characteristic-warning", warning: CharacteristicWarning): boolean;
}
/**
* Accessory is a virtual HomeKit device. It can publish an associated HAP server for iOS devices to communicate
* with - or it can run behind another "Bridge" Accessory server.
*
* 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.
*
* @group Accessory
*/
export declare class Accessory extends EventEmitter {
displayName: string;
UUID: string;
private static readonly TIMEOUT_WARNING;
private static readonly TIMEOUT_AFTER_WARNING;
aid: Nullable<number>;
_isBridge: boolean;
bridged: boolean;
bridge?: Accessory;
bridgedAccessories: Accessory[];
reachable: boolean;
lastKnownUsername?: MacAddress;
category: Categories;
services: Service[];
private primaryService?;
shouldPurgeUnusedIDs: boolean;
/**
* Captures if initialization steps inside {@link publish} have been called.
* This is important when calling {@link publish} multiple times (e.g. after calling {@link unpublish}).
* @private Private API
*/
private initialized;
private controllers;
private serializedControllers?;
private activeCameraController?;
/**
* @private Private API.
*/
_accessoryInfo?: Nullable<AccessoryInfo>;
/**
* @private Private API.
*/
_setupID: Nullable<string>;
/**
* @private Private API.
*/
_identifierCache?: Nullable<IdentifierCache>;
/**
* @private Private API.
*/
controllerStorage: ControllerStorage;
/**
* @private Private API.
*/
_advertiser?: Advertiser;
/**
* @private Private API.
*/
_server?: HAPServer;
/**
* @private Private API.
*/
_setupURI?: string;
private configurationChangeDebounceTimeout?;
/**
* This property captures the time when we last served a /accessories request.
* For multiple bursts of /accessories request we don't want to always contact GET handlers
*/
private lastAccessoriesRequest;
constructor(displayName: string, UUID: string);
private identificationRequest;
/**
* Add the given service instance to the Accessory.
*
* @param service - A {@link Service} instance.
* @returns Returns the service instance passed to the method call.
*/
addService(service: Service): Service;
/**
* Adds a given service by calling the provided {@link Service} constructor with the provided constructor arguments.
* @param serviceConstructor - A {@link Service} service constructor (e.g. {@link Service.Switch}).
* @param constructorArgs - The arguments passed to the given constructor.
* @returns Returns the constructed service instance.
*/
addService<S extends typeof Service>(serviceConstructor: S, ...constructorArgs: ConstructorArgs<S>): Service;
removeService(service: Service): void;
private removeLinkedService;
getService<T extends WithUUID<typeof Service>>(name: string | T): Service | undefined;
getServiceById<T extends WithUUID<typeof Service>>(uuid: string | T, subType: string): Service | undefined;
/**
* Returns the bridging accessory if this accessory is bridged.
* Otherwise, returns itself.
*
* @returns the primary accessory
*/
getPrimaryAccessory: () => Accessory;
addBridgedAccessory(accessory: Accessory, deferUpdate?: boolean): Accessory;
addBridgedAccessories(accessories: Accessory[]): void;
removeBridgedAccessory(accessory: Accessory, deferUpdate?: boolean): void;
removeBridgedAccessories(accessories: Accessory[]): void;
removeAllBridgedAccessories(): void;
private getCharacteristicByIID;
protected getAccessoryByAID(aid: number): Accessory | undefined;
protected findCharacteristic(aid: number, iid: number): Characteristic | undefined;
/**
* This method is used to set up a new Controller for this accessory. See {@link Controller} for a more detailed
* explanation what a Controller is and what it is capable of.
*
* The controller can be passed as an instance of the class or as a constructor (without any necessary parameters)
* for a new Controller.
* Only one Controller of a given {@link ControllerIdentifier} can be configured for a given Accessory.
*
* When called, it will be checked if there are any services and persistent data the Controller (for the given
* {@link ControllerIdentifier}) can be restored from. Otherwise, the Controller will be created with new services.
*
*
* @param controllerConstructor - The Controller instance or constructor to the Controller with no required arguments.
*/
configureController(controllerConstructor: Controller | ControllerConstructor): void;
/**
* This method will remove a given Controller from this accessory.
* The controller object will be restored to its initial state.
* This also means that any event handlers setup for the controller will be removed.
*
* @param controller - The controller which should be removed from the accessory.
*/
removeController(controller: Controller): void;
private handleAccessoryUnpairedForControllers;
private handleUpdatedControllerServiceMap;
setupURI(): string;
/**
* This method is called right before the accessory is published. It should be used to check for common
* mistakes in Accessory structured, which may lead to HomeKit rejecting the accessory when pairing.
* If it is called on a bridge it will call this method for all bridged accessories.
*/
private validateAccessory;
/**
* Assigns aid/iid to ourselves, any Accessories we are bridging, and all associated Services+Characteristics. Uses
* the provided identifierCache to keep IDs stable.
* @private Private API
*/
_assignIDs(identifierCache: IdentifierCache): void;
disableUnusedIDPurge(): void;
enableUnusedIDPurge(): void;
/**
* Manually purge the unused ids if you like, comes handy
* when you have disabled auto purge, so you can do it manually
*/
purgeUnusedIDs(): void;
/**
* Returns a JSON representation of this accessory suitable for delivering to HAP clients.
*/
private toHAP;
/**
* Returns a JSON representation of this accessory without characteristic values.
*/
private internalHAPRepresentation;
/**
* Publishes this accessory on the local network for iOS clients to communicate with.
* - `info.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.
* - `info.pincode` - the 8-digit pin code for clients to use when pairing this Accessory.
* Must be formatted as a string like `031-45-154`.
* - `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.
* @param {{
* username: string;
* pincode: string;
* category: Accessory.Categories;
* }} info - Required info for publishing.
* @param {boolean} allowInsecureRequest - Will allow unencrypted and unauthenticated access to the http server
*/
publish(info: PublishInfo, allowInsecureRequest?: boolean): Promise<void>;
/**
* Removes this Accessory from the local network
* Accessory object will no longer valid after invoking this method
* Trying to invoke publish() on the object will result undefined behavior
*/
destroy(): Promise<void>;
unpublish(): Promise<void>;
private enqueueConfigurationUpdate;
private onListening;
private handleInitialPairSetupFinished;
private handleAddPairing;
private handleRemovePairing;
private handleListPairings;
private handleAccessories;
private handleGetCharacteristics;
private handleCharacteristicRead;
private handleSetCharacteristics;
private handleCharacteristicWrite;
private handleResource;
private handleHAPConnectionClosed;
private handleServiceConfigurationChangeEvent;
private handleCharacteristicChangeEvent;
private sendCharacteristicWarning;
private handleCharacteristicWarning;
private setupServiceEventHandlers;
private _sideloadServices;
private static _generateSetupID;
static serialize(accessory: Accessory): SerializedAccessory;
static deserialize(json: SerializedAccessory): Accessory;
static cleanupAccessoryData(username: MacAddress): void;
private static serializeServiceMap;
private static deserializeServiceMap;
private static parseBindOption;
}
//# sourceMappingURL=Accessory.d.ts.map