Skip to content

Commit

Permalink
Added real time monitoring of HB changes
Browse files Browse the repository at this point in the history
  • Loading branch information
NorthernMan54 committed Dec 2, 2024
1 parent 609ea24 commit 8f6b9d9
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 58 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ this.monitor = await this.hapClient.monitorCharacteristics(services?: ServiceTyp
```
this.hapClient.on('instance-discovered', this.instanceDiscovered(instance: HapInstance)); // Emitted during discovery for each HB instance discovered
this.hapClient.on('instance-configuration-changed', this.instanceChanged(instance: HapInstance)); // Emitted during discovery for each HB instance change
this.hapClient.on('discovery-terminated', this.discoveryTerminated()); // Instance discovery was terminated
this.hapClient.on('discovery-ended', this.discoveryEnded()); // Emitted when discovery has ended ( 60 Seconds )
Expand All @@ -42,6 +44,8 @@ this.monitor.on('service-update', this.serviceUpdate(services)); // Emitted when
this.monitor.on('monitor-close', this.monitorClose(instance, hadError)); // Emitted when the connection to a homebridge service is closed ( likely a restart )
this.monitor.on('monitor-error', this.monitorError(instance, error)); // Emitted when the connection to a homebridge service has an error ( likely a restart )
this.monitor.on('monitor-refresh', this.monitorRefresh(instance, error)); // Emitted when the connection to a homebridge instance has been refreshed ( Triggered when an instance is discovered and its port, configuration number or name has changed)
```


Expand Down
27 changes: 18 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,12 @@ export class HapClient extends EventEmitter {
this.debug(`[HapClient] Discovery :: Started`);

// stop discovery after 20 seconds
this.startDiscoveryTimeout = setTimeout(() => {
this.browser.stop();
this.debug(`[HapClient] Discovery :: Ended`);
this.discoveryInProgress = false;
this.emit('discovery-ended');
}, 60000);
// this.startDiscoveryTimeout = setTimeout(() => {
// this.browser.stop();
// this.debug(`[HapClient] Discovery :: Ended`);
// this.discoveryInProgress = false;
// this.emit('discovery-ended');
// }, 60000);

// service found
this.browser.on('up', async (device: Service) => {
Expand All @@ -129,23 +129,31 @@ export class HapClient extends EventEmitter {
port: device.port,
services: [],
connectionFailedCount: 0,
configurationNumber: device.txt['c#'],
};

this.debug(`[HapClient] Discovery :: Found HAP device with username ${instance.username}`);

// update an existing instance
const existingInstanceIndex = this.instances.findIndex(x => x.username === instance.username);
if (existingInstanceIndex > -1) {

// ipAddresses change use case is not handled
const configurationChanged = this.instances[existingInstanceIndex].configurationNumber !== instance.configurationNumber;
if (
this.instances[existingInstanceIndex].port !== instance.port ||
this.instances[existingInstanceIndex].name !== instance.name
this.instances[existingInstanceIndex].name !== instance.name ||
configurationChanged
) {
this.instances[existingInstanceIndex].port = instance.port;
this.instances[existingInstanceIndex].name = instance.name;
this.instances[existingInstanceIndex].configurationNumber = instance.configurationNumber;
this.debug(`[HapClient] Discovery :: [${this.instances[existingInstanceIndex].ipAddress}:${instance.port} ` +
`(${instance.username})] Instance Updated`);
this.emit('instance-discovered', instance);
this.emit('instance-discovered', this.instances[existingInstanceIndex]);
if (configurationChanged) {
this.emit('instance-configuration-changed', this.instances[existingInstanceIndex]);
}
this.hapMonitor?.refreshMonitorConnection(this.instances[existingInstanceIndex]);
}

return;
Expand Down Expand Up @@ -181,6 +189,7 @@ export class HapClient extends EventEmitter {
this.instances.push(instance);
this.debug(`[HapClient] Discovery :: [${instance.ipAddress}:${instance.port} (${instance.username})] Instance Registered`);
this.emit('instance-discovered', instance);
this.hapMonitor?.refreshMonitorConnection(instance);
} else {
this.debug(`[HapClient] Discovery :: Could not register to device with username ${instance.username}`);
}
Expand Down
1 change: 1 addition & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface HapInstance {
username: string;
connectionFailedCount: number;
services: ServiceType[];
configurationNumber: number;
}

export interface HapEvInstance {
Expand Down
118 changes: 69 additions & 49 deletions src/monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,65 +30,70 @@ export class HapMonitor extends EventEmitter {

start() {
for (const instance of this.evInstances) {
try {
instance.socket = createConnection(instance, this.pin, { characteristics: instance.evCharacteristics });
this.connectInstance(instance);
}
}

this.debug(`[HapClient] [${instance.ipAddress}:${instance.port} (${instance.username})] Connected`);
connectInstance(instance: HapEvInstance) {
try {
this.debug(`[HapClient] [${instance.ipAddress}:${instance.port} (${instance.username})] Connecting`);
instance.socket = createConnection(instance, this.pin, { characteristics: instance.evCharacteristics });

instance.socket.on('data', (data) => {
const message = parseMessage(data);
this.debug(`[HapClient] [${instance.ipAddress}:${instance.port} (${instance.username})] Connected`);

if (message.statusCode === 401) {
if (this.logger) {
this.debug(`[HapClient] [${instance.ipAddress}:${instance.port} (${instance.username})] ` +
`${message.statusCode} ${message.statusMessage} - make sure Homebridge pin for this instance is set to ${this.pin}.`);
}
instance.socket.on('data', (data) => {
const message = parseMessage(data);

if (message.statusCode === 401) {
if (this.logger) {
this.debug(`[HapClient] [${instance.ipAddress}:${instance.port} (${instance.username})] ` +
`${message.statusCode} ${message.statusMessage} - make sure Homebridge pin for this instance is set to ${this.pin}.`);
}
}

if (message.protocol === 'EVENT') {
try {
const body = JSON.parse(message.body);
if (body.characteristics && body.characteristics.length) {
this.debug(`[HapClient] [${instance.ipAddress}:${instance.port} (${instance.username})] ` +
`Got Event: ${JSON.stringify(body.characteristics)}`);

const response = body.characteristics.map((c) => {
// find the matching service for each characteristics
const services = this.services.filter(x => x.aid === c.aid && x.instance.username === instance.username);
const service = services.find(x => x.serviceCharacteristics.find(y => y.iid === c.iid));

if (service) {
// find the correct characteristic and update it
const characteristic = service.serviceCharacteristics.find(x => x.iid === c.iid);
if (characteristic) {
characteristic.value = c.value;
service.values[characteristic.type] = c.value;
return service;
}
if (message.protocol === 'EVENT') {
try {
const body = JSON.parse(message.body);
if (body.characteristics && body.characteristics.length) {
this.debug(`[HapClient] [${instance.ipAddress}:${instance.port} (${instance.username})] ` +
`Got Event: ${JSON.stringify(body.characteristics)}`);

const response = body.characteristics.map((c) => {
// find the matching service for each characteristics
const services = this.services.filter(x => x.aid === c.aid && x.instance.username === instance.username);
const service = services.find(x => x.serviceCharacteristics.find(y => y.iid === c.iid));

if (service) {
// find the correct characteristic and update it
const characteristic = service.serviceCharacteristics.find(x => x.iid === c.iid);
if (characteristic) {
characteristic.value = c.value;
service.values[characteristic.type] = c.value;
return service;
}
}

});
});

// push update to listeners
this.emit('service-update', response.filter(x => x));
}
} catch (e) {
// do nothing
// push update to listeners
this.emit('service-update', response.filter(x => x));
}
} catch (e) {
// do nothing
}
});
instance.socket.on('close', (hadError) => {
this.emit('monitor-close', instance, hadError);
this.debug(`[HapClient] [${instance.ipAddress}:${instance.port} (${instance.username})] closed: ${hadError}`);
});
instance.socket.on('error', (error) => { // Even though this is redundant with the close event, it's necessary to catch the error event here
this.emit('monitor-error', instance, error);
this.debug(`[HapClient] [${instance.ipAddress}:${instance.port} (${instance.username})] error: ${error}`);
});
} catch (e) {
this.debug(e);
this.logger.log(`Monitor Start Error [${instance.ipAddress}:${instance.port} (${instance.username})]: ${e.message}`);
}
}
});
instance.socket.on('close', (hadError) => {
this.emit('monitor-close', instance, hadError);
this.debug(`[HapClient] [${instance.ipAddress}:${instance.port} (${instance.username})] closed: ${hadError}`);
});
instance.socket.on('error', (error) => { // Even though this is redundant with the close event, it's necessary to catch the error event here
this.emit('monitor-error', instance, error);
this.debug(`[HapClient] [${instance.ipAddress}:${instance.port} (${instance.username})] error: ${error}`);
});
} catch (e) {
this.debug(e);
this.logger.log(`Monitor Start Error [${instance.ipAddress}:${instance.port} (${instance.username})]: ${e.message}`);
}
}

Expand All @@ -106,6 +111,21 @@ export class HapMonitor extends EventEmitter {
}
}

refreshMonitorConnection(refreshInstance: HapEvInstance) {
this.debug(`[HapClient] [${refreshInstance.ipAddress}:${refreshInstance.port} (${refreshInstance.username})] Refreshing Monitor`);
// console.log('this.evInstances', this.evInstances);
const instance = this.evInstances.find(x => x.username === refreshInstance.username);
if (instance) {
instance.socket.destroy();
instance.socket.removeAllListeners();
instance.port = refreshInstance.port;
instance.ipAddress = refreshInstance.ipAddress;

this.connectInstance(instance);
this.emit('monitor-refresh', instance);
}
}

parseServices() {
// get a list of characteristics we can watch for each instance
for (const service of this.services) {
Expand Down

0 comments on commit 8f6b9d9

Please sign in to comment.