Code cleanup with stronger type checking
This commit is contained in:
parent
498b59bfef
commit
25ae519d3d
@ -8,10 +8,24 @@ import * as url from 'url';
|
||||
import httpMessageParser from './/httpParser';
|
||||
import { Dictionary } from '../../../Types/types';
|
||||
|
||||
interface IRequest {
|
||||
method: 'PUT' | 'POST' | 'GET' | 'DELETE' | 'PATCH';
|
||||
url: string;
|
||||
maxAttempts: number;
|
||||
headers: Dictionary<string>;
|
||||
body: string;
|
||||
}
|
||||
|
||||
export const parseMessage = httpMessageParser;
|
||||
|
||||
export function createConnection(instance: { ipAddress: string, port: number }, pin: string, body: any) {
|
||||
const client = net.createConnection({
|
||||
/**
|
||||
* Create a socket connection.
|
||||
* @param instance The host connection params
|
||||
* @param pin The authorization token
|
||||
* @param body The connection body
|
||||
*/
|
||||
export function createConnection(instance: { ipAddress: string, port: number }, pin: string, body: any): net.Socket {
|
||||
const client: net.Socket = net.createConnection({
|
||||
host: instance.ipAddress,
|
||||
port: instance.port,
|
||||
});
|
||||
@ -40,7 +54,7 @@ function _headersToString(headers: Dictionary<string>) {
|
||||
return (response);
|
||||
}
|
||||
|
||||
function _buildMessage(request: any) {
|
||||
function _buildMessage(request: IRequest) {
|
||||
const context = url.parse(request.url);
|
||||
let message;
|
||||
|
||||
|
@ -8,7 +8,7 @@ import { get, put } from 'request-promise-native';
|
||||
|
||||
import { Services, Characteristics } from './hap-types';
|
||||
import { HapMonitor } from './monitor';
|
||||
import { IHapAccessoriesRespType, IServiceType, ICharacteristicType, IHapInstance, createDefaultCharacteristicType } from './interfaces';
|
||||
import { IHapAccessoriesRespType, IServiceType, ICharacteristicType, IHapInstance, createDefaultCharacteristicType, IAccessoryResp } from './interfaces';
|
||||
|
||||
export type HapAccessoriesRespType = IHapAccessoriesRespType;
|
||||
export type ServiceType = IServiceType;
|
||||
@ -33,7 +33,12 @@ export interface IDevice {
|
||||
type: string
|
||||
}
|
||||
|
||||
export class HapClient extends EventEmitter {
|
||||
interface IConfig {
|
||||
debug?: boolean;
|
||||
instanceBlacklist?: string[];
|
||||
}
|
||||
|
||||
export class HapClient {
|
||||
private bonjour = Bonjour();
|
||||
private browser?: Bonjour.Browser;
|
||||
private discoveryInProgress = false;
|
||||
@ -41,10 +46,10 @@ export class HapClient extends EventEmitter {
|
||||
private log: (msg: string) => void;
|
||||
private pin: string;
|
||||
private debugEnabled: boolean;
|
||||
private config: {
|
||||
debug?: boolean;
|
||||
instanceBlacklist?: string[];
|
||||
};
|
||||
private config: IConfig = {
|
||||
debug: true,
|
||||
instanceBlacklist: [],
|
||||
}
|
||||
|
||||
private instances: IHapInstance[] = [];
|
||||
|
||||
@ -61,13 +66,10 @@ export class HapClient extends EventEmitter {
|
||||
logger?: any;
|
||||
config: any;
|
||||
}) {
|
||||
super();
|
||||
|
||||
this.pin = opts.pin;
|
||||
this.log = opts.logger;
|
||||
this.debugEnabled = true;
|
||||
this.config = opts.config;
|
||||
this.startDiscovery();
|
||||
}
|
||||
|
||||
private debug(msg: any) {
|
||||
@ -84,7 +86,7 @@ export class HapClient extends EventEmitter {
|
||||
|
||||
public refreshInstances() {
|
||||
if (!this.discoveryInProgress) {
|
||||
this.startDiscovery();
|
||||
this.discover();
|
||||
} else {
|
||||
try {
|
||||
this.debug(`[HapClient] Discovery :: Re-broadcasting discovery query`);
|
||||
@ -92,118 +94,123 @@ export class HapClient extends EventEmitter {
|
||||
this.browser.update();
|
||||
}
|
||||
|
||||
} catch (e) { }
|
||||
} catch{ }
|
||||
}
|
||||
}
|
||||
|
||||
private async startDiscovery() {
|
||||
this.discoveryInProgress = true;
|
||||
public async discover(): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
this.discoveryInProgress = true;
|
||||
|
||||
this.browser = this.bonjour.find({
|
||||
type: 'hap'
|
||||
});
|
||||
this.browser = this.bonjour.find({
|
||||
type: 'hap'
|
||||
});
|
||||
|
||||
// start matching services
|
||||
this.browser.start();
|
||||
this.debug(`[HapClient] Discovery :: Started`);
|
||||
// start matching services
|
||||
this.browser.start();
|
||||
this.debug(`[HapClient] Discovery :: Started`);
|
||||
|
||||
// stop discovery after 20 seconds
|
||||
setTimeout(() => {
|
||||
if (this.browser) {
|
||||
this.browser.stop();
|
||||
}
|
||||
this.debug(`[HapClient] Discovery :: Ended`);
|
||||
this.discoveryInProgress = false;
|
||||
}, 60000);
|
||||
|
||||
// service found
|
||||
this.browser.on('up', async (service: any) => {
|
||||
let device = service as IDevice;
|
||||
if (!device || !device.txt) {
|
||||
this.debug(`[HapClient] Discovery :: Ignoring device that contains no txt records. ${JSON.stringify(device)}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const instance: IHapInstance = {
|
||||
displayName: device.name,
|
||||
ipAddress: device.addresses[0],
|
||||
name: device.txt.md,
|
||||
username: device.txt.id,
|
||||
port: device.port,
|
||||
}
|
||||
|
||||
this.debug(`[HapClient] Discovery :: Found HAP device ${instance.displayName} with username ${instance.username}`);
|
||||
|
||||
// update an existing instance
|
||||
const existingInstanceIndex = this.instances.findIndex(x => x.username === instance.username);
|
||||
if (existingInstanceIndex > -1) {
|
||||
|
||||
if (
|
||||
this.instances[existingInstanceIndex].port !== instance.port ||
|
||||
this.instances[existingInstanceIndex].name !== instance.name
|
||||
) {
|
||||
this.instances[existingInstanceIndex].port = instance.port;
|
||||
this.instances[existingInstanceIndex].name = instance.name;
|
||||
this.debug(`[HapClient] Discovery :: [${this.instances[existingInstanceIndex].ipAddress}:${instance.port} ` +
|
||||
`(${instance.username})] Instance Updated`);
|
||||
this.emit('instance-discovered', instance);
|
||||
// service found
|
||||
this.browser.on('up', async (service: any) => {
|
||||
let device = service as IDevice;
|
||||
if (!device || !device.txt) {
|
||||
this.debug(`[HapClient] Discovery :: Ignoring device that contains no txt records. ${JSON.stringify(device)}`);
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
const instance: IHapInstance = {
|
||||
displayName: device.name,
|
||||
ipAddress: device.addresses[0],
|
||||
name: device.txt.md,
|
||||
username: device.txt.id,
|
||||
port: device.port,
|
||||
}
|
||||
|
||||
//Comenting out because of lack of config
|
||||
this.debug(`[HapClient] Discovery :: Found HAP device ${instance.displayName} with username ${instance.username}`);
|
||||
|
||||
// check instance is not on the blacklist
|
||||
// if (this.config.instanceBlacklist && this.config.instanceBlacklist.find(x => instance.username.toLowerCase() === x.toLowerCase())) {
|
||||
// this.debug(`[HapClient] Discovery :: Instance with username ${instance.username} found in blacklist. Disregarding.`);
|
||||
// return;
|
||||
// }
|
||||
// update an existing instance
|
||||
const existingInstanceIndex = this.instances.findIndex(x => x.username === instance.username);
|
||||
if (existingInstanceIndex > -1) {
|
||||
|
||||
for (const ip of device.addresses) {
|
||||
if (ip.match(/^(?:(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\.(?!$)|$)){4}$/)) {
|
||||
try {
|
||||
this.debug(`[HapClient] Discovery :: Testing ${instance.displayName} ${instance.username} via http://${ip}:${device.port}/accessories`);
|
||||
const test = await get(`http://${ip}:${device.port}/accessories`, {
|
||||
json: true,
|
||||
timeout: 1000,
|
||||
});
|
||||
if (test.accessories) {
|
||||
this.debug(`[HapClient] Discovery :: Success ${instance.displayName} ${instance.username} via http://${ip}:${device.port}/accessories`);
|
||||
instance.ipAddress = ip;
|
||||
if (
|
||||
this.instances[existingInstanceIndex].port !== instance.port ||
|
||||
this.instances[existingInstanceIndex].name !== instance.name
|
||||
) {
|
||||
this.instances[existingInstanceIndex].port = instance.port;
|
||||
this.instances[existingInstanceIndex].name = instance.name;
|
||||
this.debug(`[HapClient] Discovery :: [${this.instances[existingInstanceIndex].ipAddress}:${instance.port} ` +
|
||||
`(${instance.username})] Instance Updated`);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//Comenting out because of lack of config
|
||||
|
||||
//check instance is not on the blacklist
|
||||
if (this.config.instanceBlacklist && this.config.instanceBlacklist.find(x => instance.username.toLowerCase() === x.toLowerCase())) {
|
||||
this.debug(`[HapClient] Discovery :: Instance with username ${instance.username} found in blacklist. Disregarding.`);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const ip of device.addresses) {
|
||||
if (ip.match(/^(?:(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\.(?!$)|$)){4}$/)) {
|
||||
try {
|
||||
this.debug(`[HapClient] Discovery :: Testing ${instance.displayName} ${instance.username} via http://${ip}:${device.port}/accessories`);
|
||||
const test = await get(`http://${ip}:${device.port}/accessories`, {
|
||||
json: true,
|
||||
timeout: 1000,
|
||||
headers: { Authorization: this.pin }
|
||||
});
|
||||
if (test.accessories) {
|
||||
this.debug(`[HapClient] Discovery :: Success ${instance.displayName} ${instance.username} via http://${ip}:${device.port}/accessories`);
|
||||
instance.ipAddress = ip;
|
||||
}
|
||||
break;
|
||||
} catch (e) {
|
||||
this.debug(`[HapClient] Discovery :: ***Failed*** ${instance.displayName} ${instance.username} via http://${ip}:${device.port}/accessories: ${e.message}`);
|
||||
}
|
||||
break;
|
||||
} catch (e) {
|
||||
this.debug(`[HapClient] Discovery :: ***Failed*** ${instance.displayName} ${instance.username} via http://${ip}:${device.port}/accessories`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// store instance record
|
||||
if (instance.ipAddress) {
|
||||
this.instances.push(instance);
|
||||
this.debug(`[HapClient] Discovery :: [${instance.displayName} - ${instance.ipAddress}:${instance.port} (${instance.username})] Instance Registered`);
|
||||
this.emit('instance-discovered', instance);
|
||||
} else {
|
||||
this.debug(`[HapClient] Discovery :: Could not register to device ${instance.displayName} with username ${instance.username}`);
|
||||
}
|
||||
// store instance record
|
||||
if (instance.ipAddress) {
|
||||
this.instances.push(instance);
|
||||
this.debug(`[HapClient] Discovery :: [${instance.displayName} - ${instance.ipAddress}:${instance.port} (${instance.username})] Instance Registered`);
|
||||
} else {
|
||||
this.debug(`[HapClient] Discovery :: Could not register to device ${instance.displayName} with username ${instance.username}`);
|
||||
}
|
||||
});
|
||||
|
||||
// stop discovery after 20 seconds
|
||||
setTimeout(() => {
|
||||
if (this.browser) {
|
||||
this.browser.stop();
|
||||
}
|
||||
this.debug(`[HapClient] Discovery :: Ended`);
|
||||
this.discoveryInProgress = false;
|
||||
return resolve();
|
||||
}, 20000);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private async getAccessories() {
|
||||
private humanizeString(string: string) {
|
||||
return inflection.titleize(decamelize(string));
|
||||
}
|
||||
|
||||
public async getAccessories(): Promise<Array<IAccessoryResp>> {
|
||||
if (!this.instances.length) {
|
||||
this.debug('[HapClient] Cannot load accessories. No Homebridge instances have been discovered.');
|
||||
}
|
||||
|
||||
const accessories = [];
|
||||
const accessories: Array<IAccessoryResp> = [];
|
||||
for (const instance of this.instances) {
|
||||
try {
|
||||
const resp: IHapAccessoriesRespType = await get(`http://${instance.ipAddress}:${instance.port}/accessories`, { json: true });
|
||||
for (const accessory of resp.accessories) {
|
||||
resp.accessories && resp.accessories.forEach((accessory: IAccessoryResp) => {
|
||||
accessory.instance = instance;
|
||||
accessories.push(accessory);
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
if (this.log) {
|
||||
this.debugErr(`[HapClient] [${instance.displayName} - ${instance.ipAddress}:${instance.port} (${instance.username})] Failed to connect`, e);
|
||||
@ -337,17 +344,30 @@ export class HapClient extends EventEmitter {
|
||||
return services;
|
||||
}
|
||||
|
||||
async getService(iid: number) {
|
||||
const services = await this.getAllServices();
|
||||
return services.find(x => x.iid === iid);
|
||||
public getService(iid: number): Promise<IServiceType | undefined> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const services = await this.getAllServices();
|
||||
return resolve(services.find(x => x.iid === iid));
|
||||
} catch (err) {
|
||||
return reject(err);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
async getServiceByName(serviceName: string) {
|
||||
const services = await this.getAllServices();
|
||||
return services.find(x => x.serviceName === serviceName);
|
||||
public async getServiceByName(serviceName: string): Promise<IServiceType | undefined> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const services = await this.getAllServices();
|
||||
return resolve(services.find(x => x.serviceName === serviceName));
|
||||
} catch (err) {
|
||||
return reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async refreshServiceCharacteristics(service: IServiceType): Promise<IServiceType> {
|
||||
public async refreshServiceCharacteristics(service: IServiceType): Promise<IServiceType> {
|
||||
const iids: number[] = service.serviceCharacteristics.map(c => c.iid);
|
||||
|
||||
const resp = await get(`http://${service.instance.ipAddress}:${service.instance.port}/characteristics`, {
|
||||
@ -357,15 +377,15 @@ export class HapClient extends EventEmitter {
|
||||
json: true
|
||||
});
|
||||
|
||||
resp.characteristics.forEach((c: ICharacteristicType) => {
|
||||
const characteristic = service.serviceCharacteristics.find(x => x.iid === c.iid && x.aid === service.aid);
|
||||
characteristic!.value = c.value;
|
||||
resp.characteristics.forEach((charType: ICharacteristicType) => {
|
||||
const characteristic = service.serviceCharacteristics.find(x => x.iid === charType.iid && x.aid === service.aid);
|
||||
characteristic!.value = charType.value;
|
||||
});
|
||||
|
||||
return service;
|
||||
}
|
||||
|
||||
async getCharacteristic(service: IServiceType, iid: number): Promise<ICharacteristicType> {
|
||||
public async getCharacteristic(service: IServiceType, iid: number): Promise<ICharacteristicType> {
|
||||
const resp = await get(`http://${service.instance.ipAddress}:${service.instance.port}/characteristics`, {
|
||||
qs: {
|
||||
id: `${service.aid}.${iid}`
|
||||
@ -381,7 +401,7 @@ export class HapClient extends EventEmitter {
|
||||
return characteristic ? characteristic : createDefaultCharacteristicType();
|
||||
}
|
||||
|
||||
async setCharacteristic(service: IServiceType, iid: number, value: number | string | boolean): Promise<ICharacteristicType> {
|
||||
public async setCharacteristic(service: IServiceType, iid: number, value: number | string | boolean): Promise<ICharacteristicType> {
|
||||
let characteristic = createDefaultCharacteristicType();
|
||||
try {
|
||||
await put(`http://${service.instance.ipAddress}:${service.instance.port}/characteristics`, {
|
||||
@ -417,9 +437,4 @@ export class HapClient extends EventEmitter {
|
||||
|
||||
return characteristic
|
||||
}
|
||||
|
||||
private humanizeString(string: string) {
|
||||
return inflection.titleize(decamelize(string));
|
||||
}
|
||||
|
||||
}
|
64
src/3rdParty/HapClient/interfaces.ts
vendored
64
src/3rdParty/HapClient/interfaces.ts
vendored
@ -17,35 +17,43 @@ export interface IHapEvInstance {
|
||||
socket?: Socket;
|
||||
}
|
||||
|
||||
export interface IInstanceResp {
|
||||
ipAddress: string;
|
||||
port: number;
|
||||
username: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface ICharacteristicResp {
|
||||
iid: number;
|
||||
type: string;
|
||||
description: string;
|
||||
value: number | string | boolean;
|
||||
format: 'bool' | 'int' | 'float' | 'string' | 'uint8' | 'uint16' | 'uint32' | 'uint64' | 'data' | 'tlv8' | 'array' | 'dictionary';
|
||||
perms: Array<'pr' | 'pw' | 'ev' | 'aa' | 'tw' | 'hd'>;
|
||||
unit?: 'unit' | 'percentage' | 'celsius' | 'arcdegrees' | 'lux' | 'seconds';
|
||||
maxValue?: number;
|
||||
minValue?: number;
|
||||
minStep?: number;
|
||||
}
|
||||
|
||||
export interface IServiceResp {
|
||||
iid: number;
|
||||
type: string;
|
||||
primary: boolean;
|
||||
hidden: boolean;
|
||||
linked?: Array<number>;
|
||||
characteristics: Array<ICharacteristicResp>;
|
||||
}
|
||||
|
||||
export interface IAccessoryResp {
|
||||
instance: IInstanceResp
|
||||
aid: number;
|
||||
services: Array<IServiceResp>;
|
||||
}
|
||||
|
||||
export interface IHapAccessoriesRespType {
|
||||
accessories: Array<{
|
||||
instance: {
|
||||
ipAddress: string;
|
||||
port: number;
|
||||
username: string;
|
||||
name: string;
|
||||
};
|
||||
aid: number;
|
||||
services: Array<{
|
||||
iid: number;
|
||||
type: string;
|
||||
primary: boolean;
|
||||
hidden: boolean;
|
||||
linked?: Array<number>;
|
||||
characteristics: Array<{
|
||||
iid: number;
|
||||
type: string;
|
||||
description: string;
|
||||
value: number | string | boolean;
|
||||
format: 'bool' | 'int' | 'float' | 'string' | 'uint8' | 'uint16' | 'uint32' | 'uint64' | 'data' | 'tlv8' | 'array' | 'dictionary';
|
||||
perms: Array<'pr' | 'pw' | 'ev' | 'aa' | 'tw' | 'hd'>;
|
||||
unit?: 'unit' | 'percentage' | 'celsius' | 'arcdegrees' | 'lux' | 'seconds';
|
||||
maxValue?: number;
|
||||
minValue?: number;
|
||||
minStep?: number;
|
||||
}>;
|
||||
}>;
|
||||
}>;
|
||||
accessories: Array<IAccessoryResp>;
|
||||
}
|
||||
|
||||
export interface IServiceType {
|
||||
|
14
src/3rdParty/HapClient/monitor.ts
vendored
14
src/3rdParty/HapClient/monitor.ts
vendored
@ -1,7 +1,7 @@
|
||||
import { EventEmitter } from 'events';
|
||||
import { IServiceType, IHapEvInstance } from './interfaces';
|
||||
import { createConnection, parseMessage } from './eventedHttpClient';
|
||||
import { CharacteristicType } from '.';
|
||||
import { CharacteristicType } from './hapClient';
|
||||
|
||||
export class HapMonitor extends EventEmitter {
|
||||
private pin: string;
|
||||
@ -25,7 +25,10 @@ export class HapMonitor extends EventEmitter {
|
||||
this.start();
|
||||
}
|
||||
|
||||
start() {
|
||||
/**
|
||||
* Start monitoring
|
||||
*/
|
||||
public start() {
|
||||
for (const instance of this.evInstances) {
|
||||
instance.socket = createConnection(instance, this.pin, { characteristics: instance.evCharacteristics });
|
||||
|
||||
@ -78,7 +81,10 @@ export class HapMonitor extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
finish() {
|
||||
/**
|
||||
* Stop monitoring.
|
||||
*/
|
||||
public stop() {
|
||||
for (const instance of this.evInstances) {
|
||||
if (instance.socket) {
|
||||
try {
|
||||
@ -91,7 +97,7 @@ export class HapMonitor extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
parseServices() {
|
||||
private parseServices() {
|
||||
// get a list of characteristics we can watch for each instance
|
||||
for (const service of this.services) {
|
||||
const evCharacteristics = service.serviceCharacteristics.filter(x => x.perms.includes('ev'));
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { HapClient } from "./3rdParty/HapClient";
|
||||
import { HapClient } from "./3rdParty/HapClient/hapClient";
|
||||
import { HapMonitor } from "./3rdParty/HapClient/monitor";
|
||||
|
||||
let Accessory: any;
|
||||
@ -38,9 +38,9 @@ class AutomationPlatform {
|
||||
config: config
|
||||
});
|
||||
|
||||
this.client.on('instance-discovered', async (instance: any) => {
|
||||
let asdf = instance;
|
||||
|
||||
this.client.discover().then(async () => {
|
||||
let asdf = await this.client.getAccessories();
|
||||
let asdff = "asdf";
|
||||
})
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user