homebridge-philips-hue-sync-box
Version:
Homebridge plugin for the Philips Hue Sync Box.
232 lines • 11.3 kB
JavaScript
import { SyncBoxClient } from './lib/client.js';
import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js';
import { ENTERTAINMENT_TV_ACCESSORY, INTENSITY_TV_ACCESSORY, LIGHTBULB, LIGHTBULB_ACCESSORY, MODE_TV_ACCESSORY, SWITCH, SWITCH_ACCESSORY, TV_ACCESSORY, } from './lib/constants.js';
import { SwitchDevice } from './accessories/switch.js';
import { LightbulbDevice } from './accessories/lightbulb.js';
import { TvDevice } from './accessories/tv.js';
import { ModeTvDevice } from './accessories/modeTv.js';
import { IntensityTvDevice } from './accessories/intensityTv.js';
import { EntertainmentTvDevice } from './accessories/entertainmentTv.js';
import { ApiServer } from './api-server.js';
export class HueSyncBoxPlatform {
logger;
platformConfig;
apiInput;
config;
log;
client;
api;
devices = [];
accessories = new Map();
existingAccessories = new Map();
externalAccessories = new Map();
mainAccessory;
TV_ACCESSORY_TYPES_TO_CATEGORY;
constructor(logger, platformConfig, apiInput) {
this.logger = logger;
this.platformConfig = platformConfig;
this.apiInput = apiInput;
if (!apiInput) {
throw new Error('API is not defined');
}
this.config = platformConfig;
this.handleConfigDefaults();
this.api = apiInput;
this.log = logger ?? console;
this.log.info('Initializing platform:', this.config.name);
this.TV_ACCESSORY_TYPES_TO_CATEGORY = {
settopbox: 35 /* this.api.hap.Categories.TV_SET_TOP_BOX */,
tvstick: 36 /* this.api.hap.Categories.TV_STREAMING_STICK */,
audioreceiver: 34 /* this.api.hap.Categories.AUDIO_RECEIVER */,
tv: 31 /* this.api.hap.Categories.TELEVISION */,
};
if (!this.config.syncBoxIpAddress || !this.config.syncBoxApiAccessToken) {
this.log.error('Missing required configuration parameters');
throw new Error('Missing required configuration parameters');
}
this.client = new SyncBoxClient(this.log, this.config);
this.log.info('Finished initializing platform:', this.config.name);
this.api.on('didFinishLaunching', async () => {
this.log.debug('Executed didFinishLaunching callback');
await this.discoverDevices();
});
if (this.config.apiServerEnabled) {
const apiServer = new ApiServer(this);
apiServer.start();
}
}
handleConfigDefaults() {
this.config.updateIntervalInSeconds =
this.config.updateIntervalInSeconds ?? 5;
this.config.apiServerEnabled = this.config.apiServerEnabled ?? false;
this.config.apiServerPort = this.config.apiServerPort ?? 40220;
this.config.defaultOnMode = this.config.defaultOnMode ?? 'video';
this.config.defaultOffMode = this.config.defaultOffMode ?? 'passthrough';
this.config.baseAccessory = this.config.baseAccessory ?? 'lightbulb';
this.config.tvAccessory = this.config.tvAccessory ?? false;
this.config.tvAccessoryType = this.config.tvAccessoryType ?? 'tv';
this.config.tvAccessoryLightbulb =
this.config.tvAccessoryLightbulb ?? false;
this.config.modeTvAccessory = this.config.modeTvAccessory ?? false;
this.config.modeTvAccessoryType = this.config.modeTvAccessoryType ?? 'tv';
this.config.modeTvAccessoryLightbulb =
this.config.modeTvAccessoryLightbulb ?? false;
this.config.intensityTvAccessory =
this.config.intensityTvAccessory ?? false;
this.config.intensityTvAccessoryType =
this.config.intensityTvAccessoryType ?? 'tv';
this.config.intensityTvAccessoryLightbulb =
this.config.intensityTvAccessoryLightbulb ?? false;
this.config.entertainmentTvAccessory =
this.config.entertainmentTvAccessory ?? false;
this.config.entertainmentTvAccessoryType =
this.config.entertainmentTvAccessoryType ?? 'tv';
this.config.entertainmentTvAccessoryLightbulb =
this.config.entertainmentTvAccessoryLightbulb ?? false;
}
configureAccessory(accessory) {
this.log.info('Loading accessory from cache:', accessory.context.kind);
this.existingAccessories.set(accessory.UUID, accessory);
}
async discoverDevices() {
const state = await this.client.getState();
if (!state) {
throw new Error('Could not get state from sync box on initialization. This error is not recoverable, ' +
'ensure the sync box is online and the API token is correct and restart the plugin.');
}
this.log.debug('Discovered state:', state);
const accessories = this.discoverAccessories(state);
const uuids = accessories.map(accessory => {
const uuid = accessory.UUID; // see if an accessory with the same uuid has already been registered and restored from
const existingAccessory = this.existingAccessories.get(uuid);
const isMainAccessory = accessory.UUID === this.mainAccessory?.UUID;
if (existingAccessory) {
this.log.debug('Restoring existing accessory from cache: ', existingAccessory.context.kind);
const device = this.createDevice(existingAccessory, state);
this.accessories.set(accessory.UUID, existingAccessory);
this.devices.push(device);
if (isMainAccessory) {
this.api.updatePlatformAccessories([existingAccessory]);
}
}
else {
this.log.info('Registering new accessory:', accessory.context.kind);
const device = this.createDevice(accessory, state);
this.devices.push(device);
this.accessories.set(accessory.UUID, accessory);
if (isMainAccessory) {
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [
accessory,
]);
}
}
return uuid;
});
this.existingAccessories.forEach(existingAccessory => {
if (!uuids.includes(existingAccessory.UUID)) {
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [
existingAccessory,
]);
this.log.info('Removing existing accessory from cache:', existingAccessory.context.kind);
}
});
this.log.debug(`Publishing external ${Array.from(this.externalAccessories.values()).length} accessories`);
this.api.publishExternalAccessories(PLUGIN_NAME, Array.from(this.externalAccessories.values()));
this.log.debug(`Discovered ${this.devices.length} devices`);
await this.update(state);
setInterval(async () => {
const state = await this.client.getState();
await this.update(state);
}, this.config.updateIntervalInSeconds * 1000);
}
async update(state) {
this.log.debug('Updating state called');
if (!state) {
this.log.warn('Could not get state from sync box. Skipping update.');
return;
}
for (const device of this.devices) {
this.log.debug('Updating device:', device.accessory.displayName);
device.update(state);
}
}
discoverAccessories(state) {
this.log.debug('Loaded existing accessories:', this.existingAccessories.size);
this.log.debug('Discovering accessories');
const accessories = [];
if (this.config.baseAccessory === LIGHTBULB ||
this.config.baseAccessory === SWITCH) {
this.mainAccessory = this.createPlatformAccessory(state, this.config.baseAccessory === LIGHTBULB
? LIGHTBULB_ACCESSORY
: SWITCH_ACCESSORY);
accessories.push(this.mainAccessory);
}
if (this.config.tvAccessory) {
const tvAccessory = this.createTvAccessory(state, TV_ACCESSORY, this.config.tvAccessoryType);
accessories.push(tvAccessory);
}
if (this.config.modeTvAccessory) {
const accessory = this.createTvAccessory(state, MODE_TV_ACCESSORY, this.config.modeTvAccessoryType);
accessories.push(accessory);
}
if (this.config.intensityTvAccessory) {
const accessory = this.createTvAccessory(state, INTENSITY_TV_ACCESSORY, this.config.intensityTvAccessoryType);
accessories.push(accessory);
}
if (this.config.entertainmentTvAccessory) {
const accessory = this.createTvAccessory(state, ENTERTAINMENT_TV_ACCESSORY, this.config.entertainmentTvAccessoryType);
accessories.push(accessory);
}
this.log.debug(`Discovered ${accessories.length} accessories`);
return accessories;
}
createPlatformAccessory(state, kind) {
this.log.debug('Creating new accessory with kind ' + kind + '.');
const uuidSeed = this.config.uuidSeed ?? '';
const accessory = new this.api.platformAccessory(state.device.name, this.api.hap.uuid.generate(kind + uuidSeed));
if (kind === LIGHTBULB_ACCESSORY) {
this.mainAccessory = accessory;
accessory.category = 5 /* this.api.hap.Categories.LIGHTBULB */;
}
else if (kind === SWITCH_ACCESSORY) {
this.mainAccessory = accessory;
accessory.category = 8 /* this.api.hap.Categories.SWITCH */;
}
accessory.context.kind = kind;
return accessory;
}
createTvAccessory(state, accessoryName, accessoryType) {
const accessory = this.createPlatformAccessory(state, accessoryName);
accessory.category =
this.TV_ACCESSORY_TYPES_TO_CATEGORY[accessoryType.toLowerCase()] ??
31 /* this.api.hap.Categories.TELEVISION */;
this.externalAccessories.set(accessory.UUID, accessory);
this.log.debug('Created TV named ' +
accessoryName +
' with type ' +
accessoryType +
' and category ' +
accessory.category);
return accessory;
}
createDevice(accessory, state) {
const type = accessory.context.kind;
switch (type) {
case SWITCH_ACCESSORY:
return new SwitchDevice(this, accessory, state);
case LIGHTBULB_ACCESSORY:
return new LightbulbDevice(this, accessory, state);
case TV_ACCESSORY:
return new TvDevice(this, accessory, state, this.mainAccessory);
case MODE_TV_ACCESSORY:
return new ModeTvDevice(this, accessory, state, this.mainAccessory);
case INTENSITY_TV_ACCESSORY:
return new IntensityTvDevice(this, accessory, state, this.mainAccessory);
case ENTERTAINMENT_TV_ACCESSORY:
return new EntertainmentTvDevice(this, accessory, state, this.mainAccessory);
default:
throw new Error('Unknown accessory type: ' + type);
}
}
}
//# sourceMappingURL=platform.js.map