UNPKG

homebridge-airport-express-connected

Version:

Homebridge plugin that uses mDNS request data to show a occupancy sensor that is activated when connecting to an airport express devices via AirPlay 2.

220 lines 11.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const mdns_js_1 = __importDefault(require("mdns-js")); const settings_1 = require("./settings"); const airportExpressAccessory_1 = __importDefault(require("./airportExpressAccessory")); /** * 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. */ class AirportExpressConnectedPlatform { log; config; api; // this is used to track restored cached accessories accessories = []; characteristic; service; constructor(log, config, api) { this.log = log; this.config = config; this.api = api; this.characteristic = this.api.hap.Characteristic; this.service = this.api.hap.Service; this.log.debug('Finished initializing platform: ', this.config.name); // 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', () => { this.log.debug('Executed didFinishLaunching callback'); this.log.debug('Processing configuration:'); this.log.debug(this.config); this.config = this.processConfiguration(this.config); this.log.debug(this.config); this.loadCachedDevices(); // discover devices periodically if (this.config.discovery.enabled) { // discover devices periodically this.log.info('Searching for AirPort Express devices ...'); this.discoverDevices(); if (this.config.discovery.always) { setInterval(this.discoverDevices.bind(this), this.config.discovery.intervals * 1000); } } else { this.log.info('Not searching for AirPort Express devices, due to the discovery being disabled.'); } }); } /** * 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.log.info(`Cache: Loading accessory ${accessory.displayName}`); // add the restored accessory to the accessories cache so we can track if it has already been registered this.accessories.push(accessory); this.log.debug(`Cache: Finished loading accessory ${accessory.displayName}`); } /** * This is an example method showing how to register discovered accessories. * Accessories must only be registered once, previously created accessories * must not be registered again to prevent "duplicate UUID" errors. */ discoverDevices() { this.log.debug('Discovery: Creating browser'); // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access const mdnsBrowser = mdns_js_1.default.createBrowser(mdns_js_1.default.tcp('airplay')); // discover devices // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access mdnsBrowser.on('ready', () => { this.log.debug('Discovery: Starting discovery with browser'); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access mdnsBrowser.discover(); }); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access mdnsBrowser.on('update', (data) => { // make sure we are looking at an AirPort Express 2nd Gen. if (data.txt === undefined || data.txt.includes('model=AirPort10,115') === false) { return; } // extract serial number const serialNumber = data.txt.find((str) => str.includes('serialNumber'))?.replace('serialNumber=', '') ?? ''; // generate distinct ID const uuid = this.api.hap.uuid.generate(serialNumber); this.log.debug(`Discovery: AirPort Express with serial number ${serialNumber} found. Generated uuid ${uuid}.`); // 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); if (existingAccessory) { this.log.debug(`Discovery: AirPort Express with uuid ${uuid} already exists.`); return; } if (!this.evalBlackWhiteList(serialNumber)) { const l = this.config.discovery.whitelist.enabled ? 'not on the whitelist' : 'on the blacklist'; this.log.debug(`Discovery: Won't add AirPort Express with serial number ${serialNumber} since it is ${l}.`); return; } // check if fullname is legit if (!data.fullname?.includes('._airplay._tcp.local')) { this.log.debug(`Dicovery: Fullname "${data.fullname}" is invalid. Not adding as an accessory.`); return; } // extract additional meta information const displayName = data.fullname.replace('._airplay._tcp.local', ''); // the accessory does not yet exist, so we need to create it this.log.info('Discovery: Adding new accessory ', displayName); // create a new accessory const accessory = new this.api.platformAccessory(displayName, uuid); // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need accessory.context.device = { serialNumber: serialNumber, displayName: displayName, data: data, }; // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` new airportExpressAccessory_1.default(this, accessory); // link the accessory to your platform this.api.registerPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]); this.configureAccessory(accessory); this.log.debug(`Discovery: Finished creating and configuring accessory ${displayName}`); }); setTimeout(() => { try { this.log.debug('Discovery: Stopping browser'); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access mdnsBrowser.stop(); } catch (err) { this.log.debug(`Discovery: Error during stopping the browser: ${err}`); } }, this.config.discovery.intervals * 1000); } evalBlackWhiteList(serialNumber) { if (this.config.discovery.whitelist.enabled && this.config.discovery.blacklist.enabled) { this.log.error('Whitelist and Blacklist cannot be enabled at the same time'); return true; } if (this.config.discovery.whitelist.enabled) { return this.config.discovery.whitelist.list.includes(serialNumber) ? true : false; } if (this.config.discovery.blacklist.enabled) { return !this.config.discovery.blacklist.list.includes(serialNumber); } return true; } loadCachedDevices() { for (const accessory of this.accessories) { const device = accessory.context.device; if (!this.evalBlackWhiteList(device.serialNumber)) { const l = this.config.discovery.whitelist.enabled ? 'not whitelisted' : 'blacklisted'; if (this.config.discovery.discardKnownDevices) { this.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]); this.log.warn(`Cache: Unregistered "${device.displayName}" (serial number: \ ${device.serialNumber}) since it is ${l}.`); return; } else { this.log.warn(`Cache: The cached AirPort Express device "${device.displayName}" (serial number: \ ${device.serialNumber}) would not be discovered with the current configuration as it is ${l}. You can remove the cached \ device in the Homebridge UI or activate to discard known devices that do not match the white- or blacklist rules in the configuration.`); } } this.log.info('Cache: Restoring existing accessory from cache: ', device.displayName); // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` new airportExpressAccessory_1.default(this, accessory); this.log.debug('Finished restoring accessory from cache: ', device.displayName); } } processConfiguration(config) { const c = { name: config.name, platform: config.platform, update: { refreshRate: config.update?.refreshRate ?? 3, ignoreGroupWithLeadingDevice: config.update?.ignoreGroupWithLeadingDevice === false ? false : true, unreachable: { ignore: config.update?.unreachable?.ignore ?? false, threshold: config.update?.unreachable?.threshold ?? 30, reportDisconnect: config.update?.unreachable?.reportDisconnect ?? false, }, }, discovery: { enabled: config.discovery?.enabled ?? true, always: config.discovery?.always ?? true, intervals: config.discovery?.intervals ?? 60, whitelist: { enabled: config.discovery?.whitelist?.enabled ?? false, list: config.discovery?.whitelist?.list ?? [], }, blacklist: { enabled: config.discovery?.blacklist?.enabled ?? false, list: config.discovery?.blacklist?.list ?? [], }, discardKnownDevices: config.discovery?.discardKnownDevices ?? false, }, }; if (c.discovery.whitelist.enabled && c.discovery.blacklist.enabled) { this.log.warn('Both whitelist and blacklist are enabled. Disabling both. Only enable one or the other in the configuration.'); c.discovery.whitelist.enabled = false; c.discovery.blacklist.enabled = false; } return c; } } exports.default = AirportExpressConnectedPlatform; //# sourceMappingURL=airportExpressConnectedPlatform.js.map