UNPKG

homebridge-hca

Version:

HCA plugin for Homebridge

251 lines (185 loc) 7.3 kB
const http = require('http'); const HcaClient = require('node-hca'); const itemType = require('node-hca/lib/Design/itemType'); const AccessoryFactory = require('./lib/AccessoryFactory'); let Homebridge, self, uuid; module.exports = function (homebridge) { // const Accessory = homebridge.platformAccessory; // const Service = homebridge.hap.Service; // const Characteristic = homebridge.hap.Characteristic; uuid = homebridge.hap.uuid; Homebridge = homebridge; // AccessoryFactory = require('./lib/AccessoryFactory')(Accessory, Service, Characteristic, uuid); // !!! Homebridge.registerPlatform('homebridge-hca', 'HCA', HcaPlatform, true); } const HcaPlatform = function (log, config, api) { self = this; this.log = log; this.config = config; this.accessories = {}; if (!config) { this.log.warn('WARNING: No configuration found... plugin will not be initialized.'); return; } this.host = config.host || 'localhost'; this.port = config.port || 2000; this.password = config.password; this.clientName = config.clientName || 'Homebridge'; this.verbose = config.verbose || config.debug || false; this.log.info(`********************************`); this.log.info(`* Homebridge HCA *`); this.log.info(`********************************`); this.log.info(`Homebridge API version: ${api.version}`); if (api) { this.api = api; this.api.on('didFinishLaunching', function () { const cachedAccessoryCount = Object.keys(this.accessories).length; this.log.info('Found %s cached accessories.', cachedAccessoryCount); init(); }.bind(this)); } } HcaPlatform.prototype.configureAccessory = function (accessory) { accessory.context.isExposed = false; this.accessories[accessory.UUID] = accessory; } HcaPlatform.prototype.discoverAccessories = function () { self.log.info('Discovering accessories...'); const items = self.client.designManager.getItems(); for (let index in items) { const item = items[index]; if (item.voiceAssistantEnabled) { self.addAccessory(item); } } removeUnexposedAccessories(); self.log.info('Discovered %d accessories.', Object.keys(self.accessories).length); } HcaPlatform.prototype.addAccessory = function (item) { const cachedAccessoryUUID = uuid.generate('hca:accessories:' + item.uid); const cachedAccessory = this.accessories[cachedAccessoryUUID]; if (cachedAccessory) { this.updateAccessoryName(cachedAccessory); cachedAccessory.log = this.log; cachedAccessory.client = this.client; cachedAccessory.context.item = item; cachedAccessory.context.isExposed = true; this.accessoryFactory.restoreAccessory(cachedAccessory); this.api.updatePlatformAccessories('homebridge-hca', 'HCA', [cachedAccessory]); this.log.info('Restored %s (%s).', cachedAccessory.displayName, item.id); return; } const accessory = this.accessoryFactory.createAccessory(item); if (!accessory) { this.log.warn('Unsupported accessory: %s (%s).', item.name, item.id); this.log.debug('%s (%s):', item.name, item.id, JSON.stringify(item)); return; } accessory.context.isExposed = true; this.accessories[accessory.UUID] = accessory; this.api.registerPlatformAccessories('homebridge-hca', 'HCA', [accessory]); this.log.warn('[+] Added %s (%s).', accessory.name, item.id); } HcaPlatform.prototype.removeAccessory = function (accessory) { let accessoryId = accessory.context.item ? accessory.context.item.id : '??'; if (this.accessories[accessory.UUID]) { this.log.warn('[-] Removed %s (%s).', accessory.displayName, accessoryId); delete this.accessories[accessory.UUID]; } this.api.unregisterPlatformAccessories("homebridge-hca", "HCA", [accessory]); } HcaPlatform.prototype.updateAccessoryName = function (accessory) { const currentName = accessory.displayName; const newName = accessory.context.item.voiceAssistantName || accessory.context.item.name; if (currentName === newName) return; accessory.name = newName; accessory.displayName = newName; accessory._associatedHAPAccessory.displayName = newName; accessory.services.forEach(function (service) { if (service.displayName === currentName) { service.displayName = newName; } const nameCharacteristic = service.getCharacteristic(Characteristic.Name); nameCharacteristic.setValue(newName); }); this.log.warn(`Accessory name changed: ${currentName} >> ${newName}.`); } const removeUnexposedAccessories = function () { for (let index in self.accessories) { const accessory = self.accessories[index]; if (!accessory.context.isExposed) self.removeAccessory(accessory); } } const setUnexposedAccessories = function () { for (let index in self.accessories) { const accessory = self.accessories[index]; accessory.context.isExposed = false; } } const init = function () { self.client = new HcaClient(self.host, self.port, self.clientName); self.accessoryFactory = new AccessoryFactory(Homebridge, self.log, self.client); self.client.on('Notify', onNotify); self.client.on('Connection:Opened', onConnectionOpened); self.client.on('Connection:Closed', onConnectionClosed); if (self.verbose) { self.client.on('Connection:MessageSent', onMessageSent); self.client.on('Connection:MessageReceived', onMessageReceived); } self.client.once(self.client.topics.clientReady, function () { const serverVersionParts = self.client.serverVersion.split('.'); self.log.info(`HCA Server version: ${self.client.serverVersion} (Protocol ${self.client.serverProtocol}).`) // Ensure we're running a supported version of HCA. if (serverVersionParts[0] <= 14 && serverVersionParts[1] < 1) { self.log.error('This plugin requires HCA Plus version 14.1 (or higher).'); return; } self.discoverAccessories(); }); self.client.connect(self.password); } const onConnectionOpened = function (e) { self.log.info('Connection opened.'); } const onConnectionClosed = function (e) { const msg = `Connection closed. (Code=${e.code}, Reason=${e.reason}, WasClean=${e.wasClean}).`; if (!e.wasClean) { self.log.warn(msg); self.client.removeListener(self.client.topics.clientReady, self.discoverAccessories); self.client.once(self.client.topics.clientReady, self.discoverAccessories); } else { self.log.info(msg); } // setUnreachableAccessories(); } const onMessageSent = function (params) { self.log.debug('[SNT]', params); } const onMessageReceived = function (response) { self.log.debug('[RCV]', JSON.stringify(response)); } const onNotify = function (response) { const designChangeType = parseInt(response.data[0]); if (designChangeType !== 2) { // 2 = major, 3 = minor return; } self.log.warn('Major design change made. Rediscovering accessories.') self.client.designManager.once('Design:Received', function () { self.discoverAccessories(); }); setUnexposedAccessories(); self.client.designManager.init(); } process.on('uncaughtException', function (err) { if (self) self.log.error(err); else console.error(err); process.exit(); }); process.on('exit', function (code) { if (self && self.client) self.client.disconnect(); });