homebridge-blaq
Version:
Control and view your garage door(s) remotely with real-time updates using Konnected's BlaQ hardware
159 lines • 7.28 kB
JavaScript
import * as Bonjour from 'bonjour-service';
import { BlaQHub } from './hub.js';
import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js';
import { formatMAC } from './utils/formatters.js';
const maskPassword = (d) => {
if (d.password) {
return {
...d,
password: '***',
};
}
return d;
};
/**
* HomebridgePlatform
* This class is the main constructor for your plugin, this is where you should
* parse the user config and discover/register accessories with Homebridge.
*/
export class BlaQHomebridgePluginPlatform {
logger;
config;
api;
service;
characteristic;
// this is used to track restored cached accessories
accessories = [];
hubs = {};
hubAccessories = {};
bonjourInstance;
constructor(logger, config, api) {
this.logger = logger;
this.config = config;
this.api = api;
this.api = api;
this.service = this.api.hap.Service;
this.characteristic = this.api.hap.Characteristic;
this.bonjourInstance = new Bonjour.Bonjour();
this.logger.debug('Finished initializing platform:', this.config.name || this.config.platform);
// When this event is fired it means Homebridge has restored all cached accessories from disk.
// Dynamic Platform plugins should only register new accessories after this event was fired,
// in order to ensure they weren't added to homebridge already. This event can also be used
// to start discovery of new accessories.
this.api.on('didFinishLaunching', () => {
logger.debug('Executed didFinishLaunching callback');
// run the method to discover / register your devices as accessories
this.discoverDevices();
});
}
/**
* This function is invoked when homebridge restores cached accessories from disk at startup.
* It should be used to setup event handlers for characteristics and update respective values.
*/
configureAccessory(accessory) {
this.logger.info('Loading accessory from cache:', accessory.displayName);
// add the restored accessory to the accessories cache so we can track if it has already been registered
this.accessories.push(accessory);
}
getDeviceKey(device) {
const correctedMAC = formatMAC(device.mac);
if (correctedMAC && this.hubs[correctedMAC]) {
return correctedMAC;
}
if (this.hubs[device.host]) {
return device.host;
}
return correctedMAC || device.host;
}
possiblyRegisterNewDevice(device) {
const deviceKey = this.getDeviceKey(device);
if (!this.hubs[deviceKey]) {
this.hubs[deviceKey] = new BlaQHub(this.config, device, this.registerDiscoveredDevice.bind(this), this.logger);
this.hubAccessories[deviceKey] = [];
}
else {
const existingDiscoverer = this.hubs[deviceKey];
existingDiscoverer.updateHostPort(device.host, device.port);
}
}
possiblyMergeWithManualConfigDevice(deviceToMerge) {
let manualConfigDevice = {};
if (Array.isArray(this.config.devices)) {
for (const configDevice of this.config.devices) {
const matchingMAC = configDevice.mac && formatMAC(configDevice.mac) === formatMAC(deviceToMerge.mac);
const matchingHost = configDevice.host && configDevice.host.toLowerCase() === deviceToMerge.host.toLowerCase();
if (matchingMAC || matchingHost) {
manualConfigDevice = configDevice;
}
}
}
return {
...manualConfigDevice,
...deviceToMerge,
};
}
searchBonjour() {
this.bonjourInstance.find({
type: 'konnected',
protocol: 'tcp',
txt: {
web_api: 'true',
},
}, (service) => {
const isGarageProject = service.txt?.project_name?.toLowerCase()?.includes('garage') ||
service.txt?.project_name?.toLowerCase()?.includes('gdo');
if (service.txt?.web_api === 'true' && isGarageProject) {
const configEntry = this.possiblyMergeWithManualConfigDevice({
host: service.addresses?.[0] || service.host,
port: service.port,
displayName: service.txt?.friendly_name,
mac: formatMAC(service.txt?.mac),
});
this.logger.debug(`Discovered device via mDNS: ${JSON.stringify(maskPassword(configEntry))}`);
this.possiblyRegisterNewDevice(configEntry);
}
});
}
/**
* For each configured device, launch a Discovery probe to fetch its metadata.
* Once that probe completes, we'll register the relevant accessory.
*/
discoverDevices() {
this.searchBonjour();
const FIVE_MINUTES_IN_MS = 5 * 60 * 1000;
setInterval(() => this.searchBonjour(), FIVE_MINUTES_IN_MS);
if (Array.isArray(this.config.devices)) {
for (const configDevice of this.config.devices) {
this.logger.debug(`Discovered device via manual config: ${JSON.stringify(maskPassword(configDevice))}`);
this.possiblyRegisterNewDevice(configDevice);
}
}
}
registerDiscoveredDevice(configDevice, model, serialnumber) {
this.logger.info(`Running registerDiscovered callback for ${model} #${serialnumber}...`);
const correctedMAC = formatMAC(configDevice.mac);
// TODO: This would be the spot to add a UUID override to enable the user to transparently replace a device
const uuid = this.api.hap.uuid.generate(correctedMAC || serialnumber);
// see if an accessory with the same uuid has already been registered and restored from
// the cached devices we stored in the `configureAccessory` method above
const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid);
const configDeviceKey = this.getDeviceKey(configDevice);
if (existingAccessory) {
// refresh services
this.logger.info('Restoring existing accessory from cache:', existingAccessory.displayName);
existingAccessory.context.device = configDevice;
this.api.updatePlatformAccessories(this.hubAccessories[configDeviceKey] || [existingAccessory]);
existingAccessory.services.forEach(service => existingAccessory.removeService(service));
return { platform: this, accessory: existingAccessory };
}
else {
this.logger.info(`Adding new accessory: ${model} #${serialnumber}`);
const accessory = new this.api.platformAccessory(configDevice.displayName, uuid, 4 /* Categories.GARAGE_DOOR_OPENER */);
accessory.context.device = configDevice;
this.hubAccessories[this.getDeviceKey(configDevice)].push(accessory);
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, this.hubAccessories[configDeviceKey]);
return { platform: this, accessory };
}
}
}
//# sourceMappingURL=platform.js.map