homebridge-bravia-tvos-hv2
Version:
Custom version of Bravia plugin for Homebridge v2
302 lines (251 loc) • 11.8 kB
JavaScript
'use strict';
const logger = require('../../utils/logger');
const Handler = require('../accessory.handler');
const { getInputDeviceType, getInputSourceType, writeTvToCache } = require('../accessory.utils');
class Accessory {
constructor(api, accessory, bravia) {
this.api = api;
this.accessory = accessory;
this.tvCache = accessory.context.config.tvCache;
this.displayOrder = [];
this.inputs = this.configureInputs();
this.handler = new Handler(api, accessory, this.inputs, bravia);
this.getService();
this.api.on('shutdown', async () => {
await writeTvToCache(
this.accessory.displayName,
this.api.user.storagePath(),
this.accessory.context.config.tvCache
);
logger.debug(
`Televison cached: ${this.api.user.storagePath()}/bravia/${this.accessory.displayName}.json`,
this.accessory.displayName
);
});
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
// Services
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
configureInputs() {
const customDisplayOrder = this.accessory.context.config.displayOrder;
logger.debug(`Display Order: ${customDisplayOrder.toString()}`, this.accessory.displayName);
let inputs = [
...this.accessory.context.config[customDisplayOrder[0]],
...this.accessory.context.config[customDisplayOrder[1]],
...this.accessory.context.config[customDisplayOrder[2]],
...this.accessory.context.config[customDisplayOrder[3]],
...this.accessory.context.config[customDisplayOrder[4]],
];
inputs = inputs
.map((configInput) => {
if (configInput.type === 'apps') {
const app = this.tvCache.apps.find((tvApp) => tvApp.name === configInput.name);
const appIndex = this.tvCache.apps.findIndex((tvApp) => tvApp.name === configInput.name);
if (app) {
return {
...configInput,
...app,
origin: appIndex,
inputSourceType: getInputSourceType('app'),
inputDeviceType: getInputDeviceType('other'),
};
}
} else if (configInput.type === 'channels') {
const channel = this.tvCache.channels.find(
(tvChannel) => tvChannel.source === configInput.source && tvChannel.index === configInput.index
);
const channelIndex = this.tvCache.channels.findIndex(
(tvChannel) => tvChannel.source === configInput.source && tvChannel.index === configInput.index
);
if (channel) {
return {
...configInput,
...channel,
origin: channelIndex,
inputSourceType: getInputSourceType('channel'),
inputDeviceType: getInputDeviceType('channel'),
};
}
} else if (configInput.type === 'commands') {
const command = this.tvCache.commands.find(
(tvCommand) => tvCommand.name === configInput.value || tvCommand.value === configInput.value
);
const commandIndex = this.tvCache.commands.findIndex(
(tvCommand) => tvCommand.name === configInput.value || tvCommand.value === configInput.value
);
if (command) {
return {
...configInput,
...command,
origin: commandIndex,
inputSourceType: getInputSourceType('other'),
inputDeviceType: getInputDeviceType('tv'),
};
}
} else if (configInput.type === 'inputs') {
const exInput = this.tvCache.inputs.find(
(exInput) => exInput.source === configInput.source && exInput.name === configInput.name
);
const inputIndex = this.tvCache.inputs.findIndex(
(tvInput) => tvInput.source === configInput.source && tvInput.name === configInput.name
);
if (exInput) {
return {
...configInput,
...exInput,
origin: inputIndex,
inputSourceType: getInputSourceType(exInput.source),
inputDeviceType: getInputDeviceType(exInput.source),
};
}
} else if (configInput.type === 'macros') {
const macro = this.tvCache.macros.find((tvMacro) => tvMacro.name === configInput.name);
const macroIndex = this.tvCache.macros.findIndex((tvMacro) => tvMacro.name === configInput.name);
if (macro) {
return {
...configInput,
...macro,
origin: macroIndex,
inputSourceType: getInputSourceType('other'),
inputDeviceType: getInputDeviceType('tv'),
};
}
}
})
.filter((input) => input);
return inputs;
}
getService() {
//Service.Television
let televisionService = this.accessory.addService(
this.api.hap.Service.Television,
this.accessory.displayName,
this.accessory.context.config.subtype //television
);
televisionService
.setCharacteristic(this.api.hap.Characteristic.ConfiguredName, this.tvCache.name || this.accessory.displayName)
.setCharacteristic(this.api.hap.Characteristic.SleepDiscoveryMode, 1) //Always Discoverable
.setCharacteristic(this.api.hap.Characteristic.ClosedCaptions, 0); //Disabled
televisionService
.getCharacteristic(this.api.hap.Characteristic.ConfiguredName)
.onSet((name) => (this.accessory.context.config.tvCache.name = name));
televisionService
.getCharacteristic(this.api.hap.Characteristic.Active)
.onSet((state) => this.handler.setActive(state));
televisionService
.getCharacteristic(this.api.hap.Characteristic.ActiveIdentifier)
.onSet((state) => this.handler.setActiveIdentifier(state));
televisionService
.getCharacteristic(this.api.hap.Characteristic.RemoteKey)
.onSet((state) => this.handler.setRemoteKey(state));
televisionService
.getCharacteristic(this.api.hap.Characteristic.PowerModeSelection)
.onSet((state) => this.handler.setRemoteKey(state, 'SETTINGS'));
televisionService
.getCharacteristic(this.api.hap.Characteristic.TargetMediaState)
.onSet((state) => this.handler.setRemoteKey(state, 'MEDIA'));
//Service.InputSource
this.inputs.forEach((input, index) => {
const identifier = index + 1;
logger.debug(`Creating Input Source: ${input.inputName} (${identifier})`);
const InputService = this.accessory.addService(
this.api.hap.Service.InputSource,
input.inputName,
`${input.type}-${identifier}`
);
InputService.setCharacteristic(this.api.hap.Characteristic.Identifier, identifier)
.setCharacteristic(this.api.hap.Characteristic.ConfiguredName, input.inputName)
.setCharacteristic(this.api.hap.Characteristic.IsConfigured, 1) //Configured
.setCharacteristic(this.api.hap.Characteristic.CurrentVisibilityState, input.visibility || 0)
.setCharacteristic(this.api.hap.Characteristic.TargetVisibilityState, input.visibility || 0)
.setCharacteristic(this.api.hap.Characteristic.InputSourceType, input.inputSourceType)
.setCharacteristic(this.api.hap.Characteristic.InputDeviceType, input.inputDeviceType);
InputService.getCharacteristic(this.api.hap.Characteristic.CurrentVisibilityState).onGet(() => {
const inputs = this.accessory.context.config.tvCache[input.type][input.origin];
return inputs.visibility || 0;
});
InputService.getCharacteristic(this.api.hap.Characteristic.TargetVisibilityState)
.onGet(() => {
const inputs = this.accessory.context.config.tvCache[input.type][input.origin];
return inputs.visibility || 0;
})
.onSet((state) => {
this.accessory.context.config.tvCache[input.type][input.origin].visibility = state;
InputService.getCharacteristic(this.api.hap.Characteristic.CurrentVisibilityState).updateValue(state);
});
InputService.getCharacteristic(this.api.hap.Characteristic.ConfiguredName).onSet(
(name) => (this.accessory.context.config.tvCache[input.type][input.origin].inputName = name)
);
this.displayOrder.push(0x01, 0x04, identifier & 0xff, 0x00, 0x00, 0x00);
televisionService.addLinkedService(InputService);
});
//DisplayOrder
this.displayOrder.push(0x00, 0x00);
televisionService.setCharacteristic(
this.api.hap.Characteristic.DisplayOrder,
Buffer.from(this.displayOrder).toString('base64')
);
//Service.TelevisionSpeaker
let televisionSpeakerService = this.accessory.addService(
this.api.hap.Service.TelevisionSpeaker,
this.accessory.displayName,
'television-speaker'
);
televisionSpeakerService
.setCharacteristic(this.api.hap.Characteristic.Active, this.api.hap.Characteristic.Active.ACTIVE)
.setCharacteristic(this.api.hap.Characteristic.VolumeControlType, 3); //Absolute
televisionSpeakerService
.getCharacteristic(this.api.hap.Characteristic.Mute)
.onSet((state) => this.handler.setMute(state));
televisionSpeakerService
.getCharacteristic(this.api.hap.Characteristic.Volume)
.onSet((state) => this.handler.setVolume(state));
televisionSpeakerService
.getCharacteristic(this.api.hap.Characteristic.VolumeSelector)
.onSet((state) => this.handler.setVolumeSelector(state));
//Custom Speaker
if (this.accessory.context.config.speaker.active) {
this.accessory.context.speakerMute = true;
this.accessory.context.speakerVolume = 0;
let speakerType = this.accessory.context.config.speaker.accType;
if (speakerType === 'switch') {
let speakerService = this.accessory.addService(
this.api.hap.Service.Switch,
`${this.accessory.displayName} Speaker`,
'speaker'
);
speakerService
.getCharacteristic(this.api.hap.Characteristic.On)
.onSet((state) => this.handler.setMute(state, true));
} else if (speakerType === 'fan') {
let speakerService = this.accessory.addService(
this.api.hap.Service.Fanv2,
`${this.accessory.displayName} Speaker`,
'speaker'
);
speakerService.addCharacteristic(this.api.hap.Characteristic.RotationSpeed);
speakerService
.getCharacteristic(this.api.hap.Characteristic.Active)
.onSet((state) => this.handler.setMute(state, true));
speakerService
.getCharacteristic(this.api.hap.Characteristic.RotationSpeed)
.onSet((state) => this.handler.setVolume(state));
} else {
let speakerService = this.accessory.addService(
this.api.hap.Service.Lightbulb,
`${this.accessory.displayName} Speaker`,
'speaker'
);
speakerService.addCharacteristic(this.api.hap.Characteristic.Brightness);
speakerService
.getCharacteristic(this.api.hap.Characteristic.On)
.onSet((state) => this.handler.setMute(state, true));
speakerService
.getCharacteristic(this.api.hap.Characteristic.Brightness)
.onSet((state) => this.handler.setVolume(state));
}
}
setTimeout(() => this.handler.poll(), 1000);
}
}
module.exports = Accessory;