UNPKG

homebridge-bondbridge

Version:

Plugin to integrate BondBridge units by Bond to Homekit

747 lines (587 loc) 34.2 kB
'use strict'; // Bb2 includes seperated out for Unit testing const { getAccessoryName, getAccessoryDisplayName } = require( "./utils/getAccessoryNameFunctions" ); const { parseAddQueueTypes } = require( "./Bb2PriorityPollingQueue" ); const { removeTempDir } = require( "./utils/removeTempDir" ); const { updateConfig } = require( "./utils/updateConfig" ); const { writeConfig } = require( "./utils/writeConfig" ); let Logger = require( "./utils/Logger" ); let getAccessoryUUID = require( "./utils/getAccessoryUUID" ); let lcFirst = require( "./utils/lcFirst" ); let isNumeric = require( "./utils/isNumeric" ); let trueTypeOf = require( "./utils/trueTypeOf" ); // Hierarchy variables let HV = require( "./utils/HV" ); // Essential variables let createAccessorysInformationService = require( "./utils/createAccessorysInformationService" ); // Pretty Colors var chalk = require( "chalk" ); // These would already be initialized by index.js let BB2_CHAR_TYPE_ENUMS = require( "./lib/BB2_CHAR_TYPE_ENUMS" ).BB2_CHAR_TYPE_ENUMS; let BB2_DEVICE_TYPE_ENUM = require( "./lib/BB2_DEVICE_TYPE_ENUM" ).BB2_DEVICE_TYPE_ENUM; let BB2_ACC_TYPE_ENUM = require( "./lib/BB2_ACC_TYPE_ENUM" ).BB2_ACC_TYPE_ENUM; let BB2_FORMAT_TYPE_ENUM = BB2_CHAR_TYPE_ENUMS.BB2_FORMAT_TYPE_ENUM; let BB2_UNITS_TYPE_ENUM = BB2_CHAR_TYPE_ENUMS.BB2_UNIT_TYPE_ENUM; let BB2_PERMS_TYPE_ENUM = BB2_CHAR_TYPE_ENUMS.BB2_PERMS_TYPE_ENUM; // The Bb2 Classes const Bb2Accessory = require( "./Bb2Accessory" ).Bb2Accessory; // Settings, Globals and Constants let settings = require( "./bb2Settings" ); const constants = require( "./bb2Constants" ); // Platform definition class Bb2Platform { constructor( log, config, api ) { // Unit testing passes their own logger with debug enabled/disabled // replace with ours if ( typeof log.setOutputEnabled === "function" ) { this.log = log; // Carry the debug flag from the platform settings.bb2Dbg = log.debugEnabled; } else { // By using our own Logger, we don't trigger others this.log = new Logger( ); if ( config[ constants.DEBUG ] == true || config[ "Debug" ] == true || process.env.DEBUG == settings.PLATFORM_NAME ) { settings.bb2Dbg = true; } } this.log.setDebugEnabled( settings.bb2Dbg ); if ( settings.bb2Dbg ) this.log.debug( chalk.blue( `Class Bb2Platform` ) ); if ( config === undefined ) return; this.config = config; this.api = api; this.Service = this.api.hap.Service; this.storagePath = this.api.user.storagePath(); // Pass along the trigger when creating the Bb2Accessory. // Note: The LEVEL starts at -1 as the first one gets incremented to Zero. // // LEVEL 0 Accessories are Platform or Standalone Accessories. // LEVEL 1 Accessories are linked accessories. // LEVEL 2 Accessories are added Platform accessories coerced to // level 2 as a distinction. i.e. TelevisionSpeaker. this.BB2 = constants.PLATFORM; this.LEVEL = -1; this.toBeRestoredPlatforms = [ ]; this.createdBb2Accessories = [ ]; this.createdBb2Platforms = [ ]; this.globalConstants = null; this.services = [ ]; // These would be queues of Characteristics to be polled or get/set via IOS. settings.listOfCreatedPriorityQueues = { }; // Track the polling timers only so that unit testing can cancel them. this.pollingTimers = [ ]; // Create the hierarhy variables this.hV = new HV(); // didFinishLaunching is only called after the // registerPlatform completes. api.on( "didFinishLaunching", async () => { this.log.info( chalk.green( "BondBridge Platform didFinishLaunching" ) ); // Update the config // The original config has no config for accessories and that needs to be generated by createBondbridgeConfig.js this.config = await updateConfig( this.config, this.log, this.storagePath, __dirname ); // write the updated config to a file just for use in some situations writeConfig( this.config, this.storagePath, log ); // Now process these using the updated config this.parseConfigForBb2Directives( this.config ); this.hV.update( this ); this.processNewCharacteristicDefinitions( ); this.log.info( chalk.yellow( "*** Scanning the config and the cache for devices to be removed/restored from cache" ) ); // scan the platform devices to identify which ones to be restored from cache this.scanToBeRestoredDevices( this.log ); // Any accessory NOT to be restored should be removed, find them this.toBeRestoredPlatforms.forEach( ( accessory ) => { if ( ! accessory.toBeRestored ) this.removeAccessory( accessory ); }); // Loop through config.json and restore existing accessory from cache or register new accessory // if it is not already in cache this.discoverDevices( this.log ); // Remove BondBridge shell script temporary working directories on every Homebridge RESTART removeTempDir( this.log ); // Let the Polling Begin this.startPolling(); }); } // As Per HomeBridge: // 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. // // We do not handle restoring cached accessories ( Yet? ). Remove them // as we regenerate everything. configureAccessory( platformAccessory ) { if ( platformAccessory ) { if ( settings.bb2Dbg ) this.log.debug( `Found cached accessory: ${ platformAccessory.displayName }` ); this.toBeRestoredPlatforms.push( platformAccessory ); } } removeAccessory( platformAccessory ) { if ( ! platformAccessory ) return; this.log.info( `Removing existing accessory from cache: ${ platformAccessory.displayName }` ); this.api.unregisterPlatformAccessories( settings.PLUGIN_NAME, settings.PLATFORM_NAME, [ platformAccessory ] ); } // Only parse those BB2 directives we care about parseConfigForBb2Directives( config ) { for ( let key in config ) { let lcKey = lcFirst( key ); if ( key == "UUID" ) { lcKey = "uuid"; } else { // warn now if ( key.charAt( 0 ) === key.charAt( 0 ).toUpperCase( ) ) { this.log.warn( `The config.json Platform key: ${ key } is Capitalized. All keys in the near future will ALWAYS start with a lower case character for homebridge-ui integration.\nTo remove this Warning, Please fix your config.json.` ); } } let value = config[ key ]; switch ( lcKey ) { case constants.TIMEOUT: // Timers are in milliseconds. A low value can result in // failure to get/set values this.timeout = parseInt( value, 10 ); if ( this.timeout < 500 ) this.log.warn( `Default Timeout is in milliseconds. A value of "${ this.timeout }" seems pretty low.` ); break; case constants.INTERVAL: // Intervals are in seconds this.interval = parseInt( value, 10 ) * 1000; break; case constants.STATECHANGERESPONSETIME: // respnse time is in seconds this.stateChangeResponseTime = value * 1000; break; case constants.STATE_CMD_PREFIX: // Not 100% sure why this would be needed, but // added anyway since we have a suffix this.state_cmd_prefix = value; break; case constants.STATE_CMD_SUFFIX: // This gets added after any Get/Set <value> this.state_cmd_suffix = value; break; case constants.STATE_CMD: // What this plugin is all about this.state_cmd = value; break; case constants.OUTPUTCONSTANTS: this.outputConstants = value; break; case constants.STATUSMSG: if ( value === false ) this.statusMsg = "FALSE"; break; case constants.QUEUETYPES: parseAddQueueTypes( this.log, value ); break; case constants.DEFINITIONS: this.definitions = value; break; case constants.CONSTANTS: // Save the constants defined globally so the accessory // can parse it. this.globalConstants = value; break case constants.PLATFORM: // Noop break; case constants.ACCESSORIES: // Noop break; default: // This cannot be because all the Bb2Accessory directives // for Standalone would have to be added. //this.log.error( chalk.red( `Error: Unknown Bb2 Platform config option: "${ key }"` ) ); //process.exit( 448 ) ; } } } // The purpose here is not to duplicate what is in homebridge. Just to // do a little checking to make sure that what is defined won't cause // Bb2 to balk. It is okay if homebridge does though ;-) processNewCharacteristicDefinitions( ) { if ( this.definitions == undefined ) return; if ( trueTypeOf( this.definitions ) != Array ) throw new Error( `${ constants.DEFINITIONS } is not a array.` ); this.definitions.forEach( ( definition, definitionIndex ) => { if ( settings.bb2Dbg ) this.log.debug( `Processing definition index: ${ definitionIndex }` ); if ( trueTypeOf( definition.type ) != String ) throw new Error( `definition.type at index: ${ definitionIndex } is not a String.` ); if ( trueTypeOf( definition.description ) != String ) throw new Error( `definition.description at index: ${ definitionIndex } is not a String.` ); if ( trueTypeOf( definition.props ) != Object ) throw new Error( `definition.props at index: ${ definitionIndex } is not an Object.` ); if ( trueTypeOf( definition.props.format ) != String ) throw new Error( `definition.props.format at index: ${ definitionIndex } is not a String.` ); // Need to check if format is correct let formatIndex = BB2_FORMAT_TYPE_ENUM.properties.Bb2indexOfEnum( i => i.type === definition.props.format ); if ( formatIndex < 0 ) throw new Error( `definition.props.format at index: ${ definitionIndex } is not a valid format.` ); if ( definition.props.units ) { if ( trueTypeOf( definition.props.units ) != String ) throw new Error( `definition.props.units at index: ${ definitionIndex } is not a String.` ); // Need to check if units is correct let unitsIndex = BB2_UNITS_TYPE_ENUM.properties.Bb2indexOfEnum( i => i.type === definition.props.units ); if ( unitsIndex < 0 ) throw new Error( `definition.props.units at index: ${ definitionIndex } is not a valid unit.` ); } if ( definition.props.maxValue && isNumeric( definition.props.maxValue ) != true ) throw new Error( `definition.props.maxValue at index: ${ definitionIndex } is not numeric.` ); if ( definition.props.minValue && ! Number.isFinite( definition.props.minValue ) ) throw new Error( `definition.props.minValue at index: ${ definitionIndex } is not finite.` ); if ( definition.props.minStep && isNumeric( definition.props.minStep ) != true ) throw new Error( `definition.props.minStep at index: ${ definitionIndex } is not numeric.` ); if ( trueTypeOf( definition.props.perms ) != Array ) throw new Error( `definition.props.perms at index: ${ definitionIndex } is not an Array.` ); if ( definition.props.perms.length == 0 ) throw new Error( `definition.props.perms at index: ${ definitionIndex } cannot be an empty Array.` ); definition.props.perms.forEach( ( perm ) => { let permIndex = BB2_PERMS_TYPE_ENUM.properties.Bb2indexOfEnum( i => i.type === perm ); if ( permIndex < 0 ) throw new Error( `definition.props.perms at index: ${ definitionIndex } ${ perm } is not a valid perm.` ); }); if ( definition.validValues ) { if ( trueTypeOf( definition.validValues ) != Object ) throw new Error( `definition.validValues at index: ${ definitionIndex } is not an Object.` ); } else { definition.validValues = { }; } BB2_ACC_TYPE_ENUM.add( this.api, definition.type, definition.description, definition.props, definition.validValues ); if ( settings.bb2Dbg ) this.log.debug( `Created definition type: "${ definition.type }".` ); }); } // Scan the platform accessories and identify devices to be restored from cache scanToBeRestoredDevices( ) { // Homebridge can only handle a max of 150 accessories per bridge, as such, it is prudent to identify and remove those accessories // which are not to be restored before creating new ones // Loop over the config.json devices and identify those need to be restored from cache this.config.accessories && this.config.accessories.forEach( ( device ) => { this.Service=this.api.hap.Service; device.name = getAccessoryName( device ); if ( settings.bb2Dbg ) this.log.debug( `Scanning config.json Platform accessories: ${ device.name }` ); // generate a unique id for the accessory let uuid = getAccessoryUUID( device, this.api.hap.uuid ); // See if an accessory with the same UUID has already been registered const existingAccessory = this.toBeRestoredPlatforms.find(accessory => accessory.UUID === uuid); if (existingAccessory) { if ( settings.bb2Dbg ) this.log.debug( chalk.magenta( `This existing accessory will be restored from cache: ` ), existingAccessory.displayName ); // Define the key 'toBeRestored' and set it to 'true' explicitly to flag that this accessory will be restored from cache existingAccessory.toBeRestored = true; } }); } // These would be platform accessories with/without linked accessories discoverDevices( ) { let platform; let accessory; // loop over the config.json devices and register each one if it has not // already been registered. this.config.accessories && this.config.accessories.forEach( ( device ) => { if ( settings.bb2Dbg ) this.log.debug( `Fetching config.json Platform accessories.` ); this.Service=this.api.hap.Service; device.name = getAccessoryName( device ); let displayName = device.displayName = getAccessoryDisplayName( device ); // generate a unique id for the accessory this should be generated from // something globally unique, but constant, for example, the device serial // number or MAC address. let uuid = getAccessoryUUID( device, this.api.hap.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 // NOTE: HOMEBRIDGE EXAMPLES HAVE THIS AS UPPERCASE UUID. // lower case uuid will not be found. const existingAccessory = this.toBeRestoredPlatforms.find(accessory => accessory.UUID === uuid); if (existingAccessory) { // NOTE: HOMEBRIDGE EXAMPLES HAVE THIS AS UPPERCASE UUID. // lower case uuid will not be found. let duplicatePlatformAccessory = this.createdBb2Platforms.find(accessory => accessory.UUID === existingAccessory.UUID); if ( duplicatePlatformAccessory ) { this.log.error( chalk.red( `Error duplicate platform accessory: ${ duplicatePlatformAccessory.name } uuid:${ duplicatePlatformAccessory.UUID }` ) ); // Next in for.Each object iteration return; } this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName); // if you need to update the accessory.context then you should run // `api.updatePlatformAccessories`. eg.: // existingAccessory.context.device = device; // this.api.updatePlatformAccessories( [ existingAccessory ] ); //existingAccessory.context.device = device; //this.api.updatePlatformAccessories( [ existingAccessory ] ); // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` // new ExamplePlatformAccessory( this, existingAccessory ); platform = existingAccessory; platform.Service = this.Service; // This is how we keep the device status information over restart. // Version 0 // Within STORED_DATA_ARRAY is a list of UUID identified objects // of storedValuesPerCharacteristic. // Version 1 // Within STORED_DATA_ARRAY is a list of UUID identified objects // of Bb2Storage. // If the accessory has linked accessories or standalone accessories, // there infomation gets put in this as well. I'm pretty sure // that previosly these states were lost. // Init the STORED_DATA_ARRAY to empty [ ]. If there was never any // type to use, this is okay to. let STORED_DATA_ARRAY = [ ]; // If the saved context has our STORED_DATA_ARRAY, then use it. if ( existingAccessory.context.STORED_DATA_ARRAY ) { if ( settings.bb2Dbg ) this.log.debug(`Bb2Platform: Using context.STORED_DATA_ARRAY` ); STORED_DATA_ARRAY = existingAccessory.context.STORED_DATA_ARRAY; } let that = this; accessory = new Bb2Accessory( that.log, device, this.api, STORED_DATA_ARRAY, this ); accessory.platform = platform; // Put the accessory into its correct collection array. this.createdBb2Accessories.push( accessory ); // 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.platform.context.STORED_DATA_ARRAY = accessory.STORED_DATA_ARRAY; // Get the properties for this accessories device type let devProperties = BB2_DEVICE_TYPE_ENUM.properties[ accessory.typeIndex ]; if ( settings.bb2Dbg ) this.log.debug( `Step 2. ${ accessory.displayName }.service = platform.getService( Service.${ devProperties.deviceName }, ${ accessory.subType })` ); accessory.service = platform.getService( devProperties.service, accessory.name, accessory.subType ); // Determine which characteristics, if any, will be polled. This // information is also used to define which service.getValue is // used, either immediate, cached or polled. // Already done by new Bb2Acc // accessory.determineCharacteristicsToPollOfAccessoryAndItsChildren( accessory ); // set up all services for those characteristics in the // config.json file accessory.addAllServiceCharacteristicsForAccessory( accessory ); // Create all the services for the accessory, including fakegato // true = from existing. this.createServicesForAccessoriesChildren( accessory, true ) } else { // // the accessory does not yet exist, so we need to create it // this.log.info('Adding new platformAccessory:', displayName); // Create the new PlatformAccessory if ( device.category == undefined ) { if ( settings.bb2Dbg ) this.log.debug( `Step 1. platformAccessory = new platformAccessory( ${ displayName }, ${ uuid } )` ); platform = new this.api.platformAccessory( displayName, uuid ); } else { // Uppercase the category to be nice. Why do I know // this will come back to bite me. let category = this.api.hap.Categories[ String( device.category ).toUpperCase( ) ]; if ( ! category ) throw new Error( `Category specified: ${ device.category } is not a valid homebridge category.` ); if ( settings.bb2Dbg ) this.log.debug( `Step 1. platformAccessory = new platformAccessory( ${ displayName }, ${ uuid }, ${ category } )` ); platform = new this.api.platformAccessory( displayName, uuid, category ); } platform.Service = this.Service; this.log.info( chalk.magenta( `Configuring platformAccessory: ` ) + `${ device.displayName }` ); let that = this; accessory = new Bb2Accessory( that.log, device, this.api, [ ], this ); accessory.platform = platform // Put the accessory into its correct collection array. this.createdBb2Accessories.push( accessory ); if ( settings.bb2Dbg ) this.log.debug( `Created platformAccessory: ${ accessory.displayName }` ); // 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.platform.context.STORED_DATA_ARRAY = accessory.STORED_DATA_ARRAY; // Get the properties for this accessories device type let devProperties = BB2_DEVICE_TYPE_ENUM.properties[ accessory.typeIndex ]; // MOVE OUSTSIDE // Platform Step 2. const tvService = this.tvAccessory.addService( this.Service.Television ); if ( settings.bb2Dbg ) this.log.debug( `Step 2. ${ accessory.displayName }.service = platform.addService( this.Service.${ devProperties.deviceName }, ${ accessory.name }, ${ accessory.subType })` ); accessory.service = platform.addService( new devProperties.service( accessory.name, accessory.subType ) ); // Create all the services for the accessory, including fakegato // false = not from existing. this.createServicesForAccessoriesChildren( accessory, false ) // Step 6. this.api.publishExternalAccessories( PLUGIN_NAME, [ this.tvAccessory ] ); if ( accessory.publishExternally ) { if ( settings.bb2Dbg ) this.log.debug( `Step 6. publishExternalAccessories( ${ settings.PLUGIN_NAME }, [ ${accessory.displayName } ] )` ); this.api.publishExternalAccessories( settings.PLUGIN_NAME, [ platform ] ); } else { if ( settings.bb2Dbg ) this.log.debug( `Step 6. registerPlatformAccessories( ${ settings.PLUGIN_NAME }, ${ settings.PLATFORM_NAME }, [ ${ accessory.displayName } ] ) `); this.api.registerPlatformAccessories( settings.PLUGIN_NAME, settings.PLATFORM_NAME, [ platform ] ); } } // Just a flag to say we have processed this existing platform accessory. // @deprecated homebridge v2 //platform.updateReachability( true ); // For Unit testing only this.createdBb2Platforms.push( platform ); }); } createServicesForAccessoriesChildren( bb2PlatformAccessory, fromExisting ) { // Create the information Service for the platform itself // Unlike Standalone Accessories; The Platform information service is created // for us and the getService hangs off the platform, not the accessory. if ( bb2PlatformAccessory.model ) { if ( settings.bb2Dbg ) bb2PlatformAccessory.log.debug( `Adding model( ${ bb2PlatformAccessory.model } ) to information service of ${ bb2PlatformAccessory.displayName }` ); bb2PlatformAccessory.platform.getService( bb2PlatformAccessory.platform.Service.AccessoryInformation ) .setCharacteristic( this.api.hap.Characteristic.Model, bb2PlatformAccessory.model ); } if ( bb2PlatformAccessory.manufacturer ) { if ( settings.bb2Dbg ) bb2PlatformAccessory.log.debug( `Adding manufacturer( ${ bb2PlatformAccessory.manufacturer } ) to information service of ${ bb2PlatformAccessory.displayName }` ); bb2PlatformAccessory.platform.getService( bb2PlatformAccessory.platform.Service.AccessoryInformation ) .setCharacteristic( this.api.hap.Characteristic.Manufacturer, bb2PlatformAccessory.manufacturer ); } if ( bb2PlatformAccessory.serialNumber ) { if ( settings.bb2Dbg ) bb2PlatformAccessory.log.debug( `Adding serial Number( ${ bb2PlatformAccessory.serialNumber } ) to information service of ${ bb2PlatformAccessory.displayName }` ); bb2PlatformAccessory.platform.getService( bb2PlatformAccessory.platform.Service.AccessoryInformation ) .setCharacteristic( this.api.hap.Characteristic.SerialNumber, bb2PlatformAccessory.serialNumber ); } if ( bb2PlatformAccessory.firmwareRevision ) { if ( settings.bb2Dbg ) bb2PlatformAccessory.log.debug( `Adding Firmware Revision( ${ bb2PlatformAccessory.firmwareRevision } ) to information service of ${ bb2PlatformAccessory.displayName }` ); bb2PlatformAccessory.platform.getService( bb2PlatformAccessory.platform.Service.AccessoryInformation ) .setCharacteristic( this.api.hap.Characteristic.FirmwareRevision, bb2PlatformAccessory.firmwareRevision ); } // Create the service for all the accessories. i.e. Speaker Service // Step 3. // const speakerService = this.tvAccessory.addService( this.Service.TelevisionSpeaker ); bb2PlatformAccessory.accessories && bb2PlatformAccessory.accessories.forEach( ( addedAccessory ) => { // Set the platform of the added accessories so that the accessories can call methods // like this one. addedAccessory.platform = bb2PlatformAccessory.platform; // Get the properties for this accessory's device type let devProperties = BB2_DEVICE_TYPE_ENUM.properties[ addedAccessory.typeIndex ]; // Existing Accessories would have existing services if ( fromExisting == true ) { if ( settings.bb2Dbg ) this.log.debug( `Platform (AddedAccessory-existing) Step 3, ${ addedAccessory.displayName }.service = accessory.platform.getService( Service.${ devProperties.deviceName }, ${ addedAccessory.name }, ${ addedAccessory.subType }` ); // If you have added more than one service of the same type to an accessory, you will need to get the service using the name you defined when adding it. //addedAccessory.service = addedAccessory.platform.getService( devProperties.service, addedAccessory.name, addedAccessory.subType ); addedAccessory.service = addedAccessory.platform.getService( addedAccessory.name, addedAccessory.subType ); } else { if ( settings.bb2Dbg ) this.log.debug( `Platform (AddedAccessory-new) Step 3, ${ addedAccessory.displayName }.service = PlatformAccessory: ${ bb2PlatformAccessory.displayName } addService( Service:${ devProperties.deviceName }, ${ addedAccessory.name }, ${ addedAccessory.subType } )` ); addedAccessory.service = bb2PlatformAccessory.platform.addService( new devProperties.service( addedAccessory.name, addedAccessory.subType ) ); } addedAccessory.addAllServiceCharacteristicsForAccessory( addedAccessory ); // Create Information Service for the addedAccessory if ( settings.bb2Dbg ) addedAccessory.log.debug( `Creating information service for AddedAccessory: ${ addedAccessory.displayName }` ); createAccessorysInformationService( addedAccessory ); // Setup the fakegato service if defined in the config.json file addedAccessory.setupAccessoryFakeGatoService( addedAccessory.fakegatoConfig ); // Move the information service to the top of the list addedAccessory.services.unshift( addedAccessory.informationService ); }); // Create the service for all the linked accessories. i.e. HDMI Service bb2PlatformAccessory.linkedAccessories && bb2PlatformAccessory.linkedAccessories.forEach( ( linkedAccessory ) => { // Set the platform of the linked accessories so that the accessories can call methods // like this one. linkedAccessory.platform = bb2PlatformAccessory.platform; // Get the properties for this linked Accessory device type let devProperties = BB2_DEVICE_TYPE_ENUM.properties[ linkedAccessory.typeIndex ]; // Child accessories can have linked accessories. i.e. HDMI accessory // Step 4. // const hdmi1InputService = this.tvAccessory.addService( this.Service.InputSource, `hdmi1', 'HDMI 1' ); // Existing Accessories would have existing services if ( fromExisting == true ) { if ( settings.bb2Dbg ) this.log.debug( `Platform (LinkedAccessory-existing) Step 4. ${ linkedAccessory.displayName }.service = ${ bb2PlatformAccessory.displayName }.getService:( ${ devProperties.deviceName }.service, ${linkedAccessory.name }, ${linkedAccessory.subType } )` ); // If you have added more than one service of the same type to an accessory, you will need to get the service using the name you defined when adding it. //linkedAccessory.service = linkedAccessory.platform.getService( devProperties.service, linkedAccessory.name, linkedAccessory.subType ); linkedAccessory.service = linkedAccessory.platform.getService( linkedAccessory.name, linkedAccessory.subType ); } else { if ( settings.bb2Dbg ) this.log.debug( `Platform (LinkedAccessory-new) Step 4. ${ linkedAccessory.displayName }.service = ${ bb2PlatformAccessory.displayName }.addService:( ${ devProperties.deviceName }.service, ${linkedAccessory.name }, ${linkedAccessory.subType } )` ); linkedAccessory.service = bb2PlatformAccessory.platform.addService( new devProperties.service( linkedAccessory.name, linkedAccessory.subType ) ); } linkedAccessory.addAllServiceCharacteristicsForAccessory( linkedAccessory ); if ( fromExisting == false ) { if ( settings.bb2Dbg ) this.log.debug( `Platform Step 5. ${ bb2PlatformAccessory.displayName }.service.addLinkedService( ${ linkedAccessory.displayName }.service )` ); bb2PlatformAccessory.service.addLinkedService( linkedAccessory.service ); } // Create Information Service for the linkedAccessory if ( settings.bb2Dbg ) linkedAccessory.log.debug( `Creating information service for Linked Platform Accessory: ${ linkedAccessory.displayName }` ); createAccessorysInformationService( linkedAccessory ); // Setup the fakegato service if defined in the config.json file linkedAccessory.setupAccessoryFakeGatoService( linkedAccessory.fakegatoConfig ); // Move the information service to the top of the list linkedAccessory.services.unshift( linkedAccessory.informationService ); }); // Setup all the characteristics for the platform accessory itself bb2PlatformAccessory.addAllServiceCharacteristicsForAccessory( bb2PlatformAccessory ); // Setup the fakegato service for the platform accessory istelf. bb2PlatformAccessory.setupAccessoryFakeGatoService( bb2PlatformAccessory.fakegatoConfig ); } // The delay definitions are not meant to be changed, except for unit testing // ========================================================================== // staggeredStartDelay - These would be for just polling and to be nice to the system. // queuedStartDelay - As this is both IOS and polling, the delay only happens to // the low priority polling. startPolling( queuedStartDelay = 40000 ) { // Check for any queued characteristics if ( Object.keys( settings.listOfCreatedPriorityQueues ).length == 0 ) { if ( settings.bb2Dbg ) this.log.debug( `No queued polling characteristics` ); return; } // Start polling of each queue of characteristics let lastIndex = Object.keys( settings.listOfCreatedPriorityQueues ).length; Object.keys( settings.listOfCreatedPriorityQueues ).forEach( ( queueName, index ) => { let queue = settings.listOfCreatedPriorityQueues[ queueName ]; let queuedPollingTimer = setTimeout( ( ) => { if ( index == lastIndex -1 ) this.log.info( chalk.magenta( `*** Starting Polling` ) ); queue.startQueue( queue, ( ) => { if ( index == 0 ) this.log.info( chalk.magenta( `*** All characteristics are now being polled` ) ); }); }, queuedStartDelay ); this.pollingTimers.push( queuedPollingTimer ); }); } } exports.Bb2Platform = Bb2Platform;