homebridge-connector-hub
Version:
A Homebridge plugin to expose Connector+ compatible blinds, curtains, awnings etc to Homekit
184 lines • 9.85 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConnectorHubPlatform = void 0;
const net_1 = require("net");
const connectorAccessory_1 = require("./connectorAccessory");
const connector_device_discovery_1 = require("./connectorhub/connector-device-discovery");
const connector_hub_constants_1 = require("./connectorhub/connector-hub-constants");
const connector_hub_helpers_1 = require("./connectorhub/connector-hub-helpers");
const settings_1 = require("./settings");
const log_1 = require("./util/log");
/**
* This class is the entry point for the plugin. It is responsible for parsing
* the user config, discovering accessories, and registering them.
*/
class ConnectorHubPlatform {
constructor(logger, config, api) {
this.logger = logger;
this.config = config;
this.api = api;
this.Service = this.api.hap.Service;
this.Characteristic = this.api.hap.Characteristic;
// This array is used to track restored cached accessories.
this.cachedAccessories = [];
// This array records the handlers which wrap each accessory.
this.accessoryHandlers = [];
// This array records which hubs have been scanned for devices.
this.scannedHubs = [];
// The list of hubs that have been successfully scanned during discovery.
this.hubIpsScanned = [];
// Configure the custom log with the Homebridge logger and debug config.
log_1.Log.configure(logger, config.enableDebugLog);
// If the config is not valid, bail out immediately. We will not discover
// any new accessories or register any handlers for cached accessories.
const validationErrors = this.validateConfig(config);
if (validationErrors.length > 0) {
log_1.Log.error('Plugin suspended. Invalid configuration:', validationErrors);
return;
}
// Update the retry settings to reflect the config values.
connector_hub_constants_1.kNetworkSettings.maxRetries = config.maxRetries;
connector_hub_constants_1.kNetworkSettings.retryDelayMs = config.retryDelayMs;
connector_hub_constants_1.kNetworkSettings.refreshIntervalMs = config.refreshIntervalMs;
// Notify the user that we have completed platform initialization.
log_1.Log.debug('Finished initializing platform');
// This event is fired when Homebridge has restored all cached accessories.
// We must add handlers for these, and check for any new accessories.
this.api.on('didFinishLaunching', () => {
log_1.Log.debug('Finished restoring all cached accessories from disk');
this.discoverDevices();
});
}
// Validate that the plugin configuration conforms to the expected format.
validateConfig(config) {
const validationErrors = [];
if (!config.connectorKey) {
validationErrors.push('App Key has not been configured');
}
// Enforce default values for all applicable fields.
config.refreshIntervalMs =
(config.refreshIntervalMs || connector_hub_constants_1.kNetworkSettings.refreshIntervalMs);
config.retryDelayMs =
(config.retryDelayMs || connector_hub_constants_1.kNetworkSettings.retryDelayMs);
config.maxRetries = (config.maxRetries || connector_hub_constants_1.kNetworkSettings.maxRetries);
config.reverseDirection = (config.reverseDirection || []);
config.hubIps = (config.hubIps || []);
// Check for invalid entries and compile a list of all validation errors.
const invalidIps = config.hubIps.filter((ip) => !(0, net_1.isIPv4)(ip));
for (const invalidIp of invalidIps) {
validationErrors.push(`Hub IP is not valid IPv4: ${invalidIp}`);
}
if (config.refreshIntervalMs <= 0) {
validationErrors.push('Refresh interval must be > 0');
}
if (config.maxRetries <= 0) {
validationErrors.push('Max request retries must be > 0');
}
if (config.retryDelayMs <= 0) {
validationErrors.push('Request retry delay must be > 0');
}
return validationErrors;
}
/**
* This function is invoked for each cached accessory that homebridge restores
* from disk at startup. Here we add the cached accessories to a list which
* will be examined later during the 'discoverDevices' phase.
*/
configureAccessory(accessory) {
log_1.Log.info('Loading accessory from cache:', accessory.displayName);
this.cachedAccessories.push(accessory);
}
/**
* Iterate over the given hub IPs and begin the discovery process for each.
* Note that we use the term "hub" here to distinguish them from individual
* devices, but in practice a device may be its own hub if, for instance, it
* is a WiFi motor device.
*/
async discoverDevices() {
if (this.config.hubIps.length === 0) {
log_1.Log.info('No device IPs configured, defaulting to multicast discovery');
this.config.hubIps.push(connector_hub_constants_1.kMulticastIp);
}
// Perform device discovery, then repeat at regular intervals.
for (const hubIp of this.config.hubIps) {
(0, connector_device_discovery_1.doDiscovery)(hubIp, this);
}
}
async onDiscoveryCompleteForHub(hubIp) {
// Add this hub to the list of hubs we've scanned.
this.hubIpsScanned.push(hubIp);
// Don't try to remove stale devices until we have heard from evey hub.
if (!this.config.hubIps.every(ip => this.hubIpsScanned.includes(ip))) {
return;
}
log_1.Log.debug('Checking for stale cached accessories...');
(0, connector_device_discovery_1.removeStaleAccessories)(this.cachedAccessories, this);
// Clear the list of scanned hubs for the next round of discovery.
this.hubIpsScanned = [];
}
/**
* Register discovered accessories. Accessories must only be registered once;
* previously created accessories must not be registered again, to avoid
* "duplicate UUID" errors.
*/
async registerDevice(hubIp, deviceState, hubToken) {
// Output the discovered device if we're in debug mode.
log_1.Log.debug('Discovered device:', deviceState);
// If this is a TDBU blind, we may have to create two separate accessories.
const tdbuTypes = (0, connector_device_discovery_1.identifyTdbuDevices)(deviceState);
// Iterate over all TDBU types, if such types exist. Otherwise this will
// just register the plain single-motor device directly.
for (const tdbuType of tdbuTypes) {
// Augment the basic device information with additional details.
const deviceInfo = {
mac: deviceState.mac,
deviceType: deviceState.deviceType,
subType: deviceState.data.type,
tdbuType: tdbuType,
hubIp: hubIp,
hubToken: hubToken,
};
// Generate a unique id for the accessory from its MAC address. Append the
// TDBU type to differentiate the top down from the bottom up accessory.
const uuid = this.api.hap.uuid.generate(deviceInfo.mac + tdbuType);
// Generate a display name for the device from the extended device info.
const displayName = (0, connector_hub_helpers_1.makeDeviceName)(deviceInfo);
// Check whether we have already registered this device in this session.
if (this.accessoryHandlers.some(elem => elem.accessory.UUID === uuid)) {
continue;
}
// See if a cached accessory with the same uuid already exists.
let accessory = this.cachedAccessories.find(accessory => accessory.UUID === uuid);
// If the accessory does not yet exist, we need to create it.
if (!accessory) {
log_1.Log.info('Adding new accessory:', displayName);
accessory = new this.api.platformAccessory(displayName, uuid);
this.api.registerPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
}
else {
// Remove the cached accessory from the list before adding a handler.
this.cachedAccessories.splice(this.cachedAccessories.indexOf(accessory), 1);
}
// Make sure the accessory stays in sync with any device config changes.
accessory.context.device = deviceInfo;
this.api.updatePlatformAccessories([accessory]);
// Create the accessory handler for this accessory.
log_1.Log.debug('Creating handler for accessory:', displayName);
this.accessoryHandlers.push(new connectorAccessory_1.ConnectorAccessory(this, accessory));
}
}
/**
* Unregister a stale accessory. This will remove the accessory from both
* Homebridge and from Homekit.
*/
unregisterDevice(accessory) {
// Unregister the specified accessory from the plugin.
log_1.Log.info('Removing stale accessory:', accessory.displayName);
this.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
// Remove this cached or active accessory from the appropriate list.
this.cachedAccessories.splice((0, connector_hub_helpers_1.spliceIndexOf)(this.cachedAccessories, accessory), 1);
this.accessoryHandlers.splice((0, connector_hub_helpers_1.spliceIndexOf)(this.accessoryHandlers.map(ah => ah.accessory), accessory), 1);
}
}
exports.ConnectorHubPlatform = ConnectorHubPlatform;
//# sourceMappingURL=platform.js.map