homebridge-saphi-tv
Version:
This plugin allows you to control your SaphiOS PhilipsTV with support for Apps, Sources and Channels as well as Ambilight, Ambilight+Hue and a CC Remote.
543 lines • 25.9 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TelevisionAccessory = void 0;
const ping_1 = __importDefault(require("ping"));
const inputType_1 = require("./inputType");
/**
* Platform Accessory
* An instance of this class is created for each accessory your platform registers
* Each accessory may expose multiple services of different service types.
*/
class TelevisionAccessory {
constructor(utilities, tvAccessory, remoteAccessory) {
var _a;
this.utilities = utilities;
this.tvAccessory = tvAccessory;
this.remoteAccessory = remoteAccessory;
this.TvState = {
TvActive: false,
AmbiHueActive: false,
AmbilightActive: false,
};
this.power_on_body = { powerstate: 'On' };
this.power_off_body = { powerstate: 'Standby' };
this.ambihue_on_body = { power: 'On' };
this.ambihue_off_body = { power: 'Off' };
this.ambilight_on_body = { power: 'On' };
this.ambilight_off_body = { power: 'Off' };
this.config = utilities.config;
this.platform = utilities.platform;
if (this.config.inputs) {
this.config.inputs = (_a = this.config.inputs) === null || _a === void 0 ? void 0 : _a.filter(x => x.name && x.type && (x.position || x.position === 0 || x.type === inputType_1.InputType.TV));
}
this.platform.log.debug('times: ', this.config.startup_time, this.config.polling_interval, this.config.input_delay, this.config.timeout);
this.platform.log.debug('inputs: ', this.config.inputs);
this.platform.log.debug('powerURL: ', this.config.power_url);
this.platform.log.debug('inputURL: ', this.config.input_url);
this.platform.log.debug('ambihueURL: ', this.config.ambihue_url);
this.platform.log.debug('multirow apps: ', this.config.multirow_apps);
this.platform.log.debug('vertical inputs: ', this.config.vertical_inputs);
this.tvService = this.tvAccessory.addService(this.platform.Service.Television, 'ActiveInput');
// Add AmbiHue switch to remote accessory
if (this.config.has_ambilight) {
// TODO: implement get & set with reference to old project.
// TODO: implement brightness with reference to old project.
// this.ambilightService = this.remoteAccessory.addService(this.platform.Service.Outlet, 'Ambilight', 'ambilight');
// this.ambilightService.getCharacteristic(this.platform.Characteristic.On)
// .on('get', (callback) => {
// this.GetAmbilight(callback);
// this.platform.log.debug('Get AmbiLight');
// })
// .on('set', (newValue, callback) => {
// this.SetAmbilight(newValue);
// callback(null);
// this.platform.log.debug('Set AmbiLight => ' + newValue);
// });
if (this.config.has_ambihue) {
this.ambihueService = this.remoteAccessory.addService(this.platform.Service.Switch, 'Ambilight Plus Hue', 'ambihue');
this.ambihueService.getCharacteristic(this.platform.Characteristic.On)
.on('get', (callback) => {
this.GetAmbiHue(callback);
this.platform.log.debug('Get AmbiHue');
})
.on('set', (newValue, callback) => {
this.SetAmbiHue(newValue);
callback(null, this.TvState.AmbiHueActive);
this.platform.log.debug('set AmbiHue => ' + newValue);
});
}
}
// TODO: implement get & set with reference to old project.
// this.audioService = this.remoteAccessory.addService(this.platform.Service.Lightbulb, 'Volume', 'tvVolume');
// this.audioService.getCharacteristic(this.platform.Characteristic.Brightness)
// .on('get', (callback) => {
// // this.GetVolume(callback);
// callback();
// this.platform.log.debug('Get Volume');
// })
// .on('set', (newValue, callback) => {
// const volume = newValue / 100 * 60;
// // this.SetVolume(volume);
// callback(null);
// this.platform.log.debug('Set Volume => ' + volume);
// });
this.AddInputServices();
// eslint-disable-next-line @typescript-eslint/no-var-requires
const pjson = require('../package.json');
// set accessory information
this.tvAccessory.category = 31 /* TELEVISION */;
this.tvAccessory.getService(this.platform.Service.AccessoryInformation)
.setCharacteristic(this.platform.Characteristic.Manufacturer, pjson['author'])
.setCharacteristic(this.platform.Characteristic.Model, pjson['name'])
.setCharacteristic(this.platform.Characteristic.Name, this.config.name)
.setCharacteristic(this.platform.Characteristic.FirmwareRevision, pjson['version'])
.setCharacteristic(this.platform.Characteristic.SerialNumber, pjson['version']);
//TODO: Check if Categories.TARGET_CONTROLLER will work in the future!
this.remoteAccessory.category = 36 /* TV_STREAMING_STICK */;
this.remoteAccessory.getService(this.platform.Service.AccessoryInformation)
.setCharacteristic(this.platform.Characteristic.Manufacturer, pjson['author'])
.setCharacteristic(this.platform.Characteristic.Model, pjson['name'])
.setCharacteristic(this.platform.Characteristic.Name, this.config.name + ' Remote')
.setCharacteristic(this.platform.Characteristic.ConfiguredName, this.config.name + ' Remote')
.setCharacteristic(this.platform.Characteristic.FirmwareRevision, pjson['version'])
.setCharacteristic(this.platform.Characteristic.SerialNumber, pjson['version']);
// set the tv name
this.tvService.setCharacteristic(this.platform.Characteristic.Name, this.config.name);
this.tvService.setCharacteristic(this.platform.Characteristic.ConfiguredName, this.config.name);
// set sleep discovery characteristic
this.tvService.setCharacteristic(this.platform.Characteristic.SleepDiscoveryMode, this.platform.Characteristic.SleepDiscoveryMode.ALWAYS_DISCOVERABLE);
// handle on / off events using the Active characteristic
this.tvService.getCharacteristic(this.platform.Characteristic.Active)
.on('set', (newValue, callback) => {
callback(null);
this.SetActive(newValue);
this.platform.log.debug('set Active => ' + newValue);
})
.on('get', (callback) => {
this.GetActive(callback);
this.platform.log.debug('Get Active');
});
this.tvService.setCharacteristic(this.platform.Characteristic.ActiveIdentifier, 0);
// handle input source changes
this.tvService.getCharacteristic(this.platform.Characteristic.ActiveIdentifier)
.on('set', (newValue, callback) => {
callback(null);
this.SetActiveIdentifier(newValue);
this.platform.log.debug('set Active Identifier => setNewValue: ' + newValue);
});
// handle remote control input
this.tvService.getCharacteristic(this.platform.Characteristic.RemoteKey)
.on('set', (newValue, callback) => {
callback(null);
this.SendRemoteInput(newValue);
this.platform.log.debug('Sending RemoteInput: ' + newValue);
});
const speakerService = this.tvAccessory.addService(this.platform.Service.TelevisionSpeaker);
speakerService
.setCharacteristic(this.platform.Characteristic.Active, this.platform.Characteristic.Active.ACTIVE)
.setCharacteristic(this.platform.Characteristic.VolumeControlType, this.platform.Characteristic.VolumeControlType.ABSOLUTE);
// handle volume control
speakerService
.getCharacteristic(this.platform.Characteristic.VolumeSelector)
.on('set', (newValue, callback) => {
callback(null, null);
this.platform.log.debug('set VolumeSelector => setNewValue: ' + newValue);
if (newValue === this.platform.Characteristic.VolumeSelector.DECREMENT) {
this.SendRemoteInput('VolumeDown');
}
else {
this.SendRemoteInput('VolumeUp');
}
});
setInterval(() => {
this.platform.log.debug('Triggering interval');
ping_1.default.sys.probe(this.config.ip_address, (isAlive) => {
const msg = isAlive ? 'TV is alive' : 'TV is dead';
this.platform.log.debug(msg);
});
this.GetActive(null);
if (this.config.has_ambihue) {
this.GetAmbiHue(null);
}
}, this.config.polling_interval);
}
async AddInputServices() {
await this.utilities.waitFor(1000);
if (this.config.inputs && this.config.inputs.length > 0) {
// Add inputs to TV accessory
this.config.inputs.forEach((input, index) => {
const inputService = this.tvAccessory.addService(this.platform.Service.InputSource, input.name, 'input' + input.name + input.position || 0);
inputService
.setCharacteristic(this.platform.Characteristic.ConfiguredName, input.name)
.setCharacteristic(this.platform.Characteristic.Identifier, index)
.setCharacteristic(this.platform.Characteristic.CurrentVisibilityState, this.platform.Characteristic.CurrentVisibilityState.SHOWN)
.setCharacteristic(this.platform.Characteristic.IsConfigured, this.platform.Characteristic.IsConfigured.CONFIGURED)
.setCharacteristic(this.platform.Characteristic.InputSourceType, this.platform.Characteristic.InputSourceType.APPLICATION);
this.tvService.addLinkedService(inputService);
// Add inputs to remote accessory
const switchService = this.remoteAccessory.addService(this.platform.Service.Outlet, input.name, 'switchInput' + input.name + input.position || 0);
switchService
.setCharacteristic(this.platform.Characteristic.Name, input.name)
.getCharacteristic(this.platform.Characteristic.On)
.on('set', (newValue, callback) => {
callback(null, false);
if (newValue === true) {
if (this.TvState.TvActive === false) {
this.SetActive(this.platform.Characteristic.Active.ACTIVE);
}
this.SetActiveIdentifier(index);
switchService.updateCharacteristic(this.platform.Characteristic.On, false);
}
})
.on('get', (callback) => {
callback(1);
});
});
}
}
async GetActive(callback) {
await this.utilities.GET(this.config.power_url)
.then((response) => {
if (!response.ok) {
throw Error(response.status);
}
return response;
})
.then(response => response.json())
.then(result => {
this.platform.log.debug('Success:', result);
if (JSON.stringify(result) === JSON.stringify(this.power_on_body)) {
this.TvState.TvActive = true;
}
else if (JSON.stringify(result) === JSON.stringify(this.power_off_body)) {
this.TvState.TvActive = false;
}
})
.catch(error => {
if (error.response && error.response.status !== 200) {
this.TvState.TvActive = false;
}
if (error.code === 'EHOSTUNREACH') {
this.TvState.TvActive = false;
}
this.platform.log.debug('Error getPowerState : ', error);
})
.finally(() => {
this.platform.log.debug('Now updating PowerState to:', this.TvState.TvActive);
this.tvService.updateCharacteristic(this.platform.Characteristic.Active, this.TvState.TvActive);
if (callback) {
callback(null, this.TvState.TvActive);
}
});
}
async SetActive(value) {
const newPowerState = value === this.platform.Characteristic.Active.ACTIVE;
this.platform.log.debug('Setting power to: ', newPowerState);
if (newPowerState) {
if (this.config.has_ambihue && this.config.ambi_poweron) {
await this.utilities.wolRequest(this.config.wol_url);
await this.utilities.POST(this.config.power_url, this.power_on_body);
await this.utilities.waitFor(this.config.startup_time)
.then(() => {
this.platform.log.debug('Setting AmbiHue after ', this.config.startup_time);
if (this.ambihueService) {
this.ambihueService.getCharacteristic(this.platform.Characteristic.On).setValue(true);
}
});
}
else {
await this.utilities.wolRequest(this.config.wol_url);
await this.utilities.POST(this.config.power_url, this.power_on_body);
}
}
else {
if (this.config.has_ambihue && this.config.ambi_poweroff) {
await this.utilities.POST(this.config.ambihue_url, this.ambihue_off_body)
.then(async () => {
await this.utilities.waitFor(this.config.input_delay)
.then(async () => {
await this.utilities.POST(this.config.input_url, { key: 'Standby' });
});
});
}
else {
await this.utilities.POST(this.config.power_url, this.power_off_body);
}
}
}
async GetAmbiHue(callback) {
await this.utilities.GET(this.config.ambihue_url)
.then((response) => {
if (!response.ok) {
throw Error(response.status);
}
return response;
})
.then(response => response.json())
.then(result => {
this.platform.log.debug('Success:', result);
if (JSON.stringify(result) === JSON.stringify(this.ambihue_on_body)) {
this.TvState.AmbiHueActive = true;
}
else if (JSON.stringify(result) === JSON.stringify(this.ambihue_off_body)) {
this.TvState.AmbiHueActive = false;
}
})
.catch(error => {
if (error.response && error.response.status !== 200) {
this.TvState.AmbiHueActive = false;
}
if (error.code === 'EHOSTUNREACH') {
this.TvState.AmbiHueActive = false;
}
this.platform.log.debug('Error getAmbihueState : ', error);
})
.finally(() => {
this.platform.log.debug('Now updating AmbiHueState to:', this.TvState.AmbiHueActive);
if (this.ambihueService) {
this.ambihueService.updateCharacteristic(this.platform.Characteristic.On, this.TvState.AmbiHueActive);
}
if (callback) {
callback(null, this.TvState.AmbiHueActive);
}
});
}
async SetAmbiHue(value) {
const newPowerState = value;
this.platform.log.debug('Setting ambihue to: ', newPowerState);
if (this.TvState.TvActive === false) {
this.platform.log.debug('Waiting for TV to turn on');
await this.utilities.waitFor(this.config.startup_time);
}
if (newPowerState) {
await this.utilities.POST(this.config.ambihue_url, this.ambihue_on_body);
}
else {
await this.utilities.POST(this.config.ambihue_url, this.ambihue_off_body);
}
}
async GetAmbilight(callback) {
await this.utilities.GET(this.config.ambilight_url)
.then((response) => {
if (!response.ok) {
throw Error(response.status);
}
return response;
})
.then(response => response.json())
.then(result => {
this.platform.log.debug('Success:', result);
if (JSON.stringify(result) === JSON.stringify(this.ambilight_on_body)) {
this.TvState.AmbilightActive = true;
}
else if (JSON.stringify(result) === JSON.stringify(this.ambilight_off_body)) {
this.TvState.AmbilightActive = false;
}
})
.catch(error => {
if (error.response && error.response.status !== 200) {
this.TvState.AmbilightActive = false;
}
if (error.code === 'EHOSTUNREACH') {
this.TvState.AmbilightActive = false;
}
this.platform.log.debug('Error getAmbilightState : ', error);
})
.finally(() => {
this.platform.log.debug('Now updating AmbilightState to:', this.TvState.AmbilightActive);
if (this.ambihueService) {
this.ambihueService.updateCharacteristic(this.platform.Characteristic.On, this.TvState.AmbilightActive);
}
if (callback) {
callback(null, this.TvState.AmbilightActive);
}
});
}
async SetAmbilight(value) {
const newPowerState = value;
this.platform.log.debug('Setting ambihue to: ', newPowerState);
if (this.TvState.TvActive === false) {
this.platform.log.debug('Waiting for TV to turn on');
await this.utilities.waitFor(this.config.startup_time);
}
if (newPowerState) {
await this.utilities.POST(this.config.ambilight_url, this.ambilight_on_body);
}
else {
await this.utilities.POST(this.config.ambilight_url, this.ambilight_off_body);
}
}
async SetActiveIdentifier(value) {
const input = this.config.inputs[value];
this.platform.log.debug('Setting input to: ', input.name, input.type, inputType_1.InputType.App);
if (this.TvState.TvActive === false) {
this.platform.log.debug('Waiting for TV to turn on');
await this.utilities.waitFor(this.config.startup_time);
}
if (input.type === inputType_1.InputType.TV) {
await this.utilities.POST(this.config.input_url, { key: 'WatchTV' })
.then(response => response.text())
.then(data => this.platform.log.debug('response: ', data))
.then(async () => await this.utilities.waitFor(this.config.input_delay))
.then(() => {
this.platform.log.debug('finished WatchTV');
}).catch(() => {
this.platform.log.debug('could not finish WatchTV');
});
}
else {
let stepsToMake = input.position;
const moves = [];
// Build the moves[]
if (input.type === inputType_1.InputType.App) {
moves.push({ key: 'Home' });
if (this.config.multirow_apps === true) {
let rowToReach = input.row;
this.platform.log.debug('setting row: ', rowToReach);
if (rowToReach > 0) {
moves.push({ key: 'CursorDown' });
rowToReach--;
}
}
}
if (input.type === inputType_1.InputType.Source) {
moves.push({ key: 'WatchTV' });
moves.push({ key: 'Source' });
if (this.config.vertical_inputs === false) {
moves.push({ key: 'CursorDown' });
}
}
if (input.type === inputType_1.InputType.Channel) {
moves.push({ key: 'WatchTV' });
const num = Math.abs(input.position);
const digits = num.toString().split('');
digits.forEach(digit => {
if (digit === '.') {
moves.push({ key: 'Dot' });
}
else {
moves.push({ key: 'Digit' + digit });
}
});
}
else {
while (Math.abs(stepsToMake) !== 0) {
if (stepsToMake > 0) {
if (this.config.vertical_inputs === true && input.type === inputType_1.InputType.Source) {
moves.push({ key: 'CursorDown' });
}
else {
moves.push({ key: 'CursorRight' });
}
stepsToMake--;
}
if (stepsToMake < 0) {
if (this.config.vertical_inputs === true && input.type === inputType_1.InputType.Source) {
moves.push({ key: 'CursorUp' });
}
else {
moves.push({ key: 'CursorLeft' });
}
stepsToMake++;
}
}
}
moves.push({ key: 'Confirm' });
this.platform.log.debug('Moves: ', moves);
// Execute moves[] one-by-one
for (const move of moves) {
await this.utilities.POST(this.config.input_url, move)
.then(response => response.text())
.then(data => this.platform.log.debug('response: ', data))
.then(async () => await this.utilities.waitFor(this.config.input_delay))
.then(() => {
this.platform.log.debug('finished move ', move);
}).catch(() => {
this.platform.log.debug('could not finish move ', move);
});
// Aditional delays because WatchTV takes time to load and even more if there are no channels installed.
if (move === { key: 'WatchTV' }) {
if (this.config.has_tv_channels === false) {
this.platform.log.debug('waiting ' + this.config.channel_setup_popup_time + ' ms for channel popup');
await this.utilities.waitFor(this.config.channel_setup_popup_time);
}
else {
await this.utilities.waitFor(this.config.input_delay);
}
}
}
this.platform.log.debug('finished moves!');
}
}
async SendRemoteInput(newValue) {
let KeyToPress = { key: 'Home' };
switch (newValue) {
case this.platform.Characteristic.RemoteKey.REWIND: {
KeyToPress = { key: 'Rewind' };
break;
}
case this.platform.Characteristic.RemoteKey.FAST_FORWARD: {
KeyToPress = { key: 'FastForward' };
break;
}
case this.platform.Characteristic.RemoteKey.NEXT_TRACK: {
KeyToPress = { key: 'Next' };
break;
}
case this.platform.Characteristic.RemoteKey.PREVIOUS_TRACK: {
KeyToPress = { key: 'Previous' };
break;
}
case this.platform.Characteristic.RemoteKey.ARROW_UP: {
KeyToPress = { key: 'CursorUp' };
break;
}
case this.platform.Characteristic.RemoteKey.ARROW_DOWN: {
KeyToPress = { key: 'CursorDown' };
break;
}
case this.platform.Characteristic.RemoteKey.ARROW_LEFT: {
KeyToPress = { key: 'CursorLeft' };
break;
}
case this.platform.Characteristic.RemoteKey.ARROW_RIGHT: {
KeyToPress = { key: 'CursorRight' };
break;
}
case this.platform.Characteristic.RemoteKey.SELECT: {
KeyToPress = { key: 'Confirm' };
break;
}
case this.platform.Characteristic.RemoteKey.BACK: {
KeyToPress = { key: 'Back' };
break;
}
case this.platform.Characteristic.RemoteKey.EXIT: {
KeyToPress = { key: 'Exit' };
break;
}
case this.platform.Characteristic.RemoteKey.PLAY_PAUSE: {
KeyToPress = { key: 'PlayPause' };
break;
}
case this.platform.Characteristic.RemoteKey.INFORMATION: {
KeyToPress = { key: 'Options' };
break;
}
case 'VolumeUp': {
KeyToPress = { key: 'VolumeUp' };
break;
}
case 'VolumeDown': {
KeyToPress = { key: 'VolumeDown' };
break;
}
}
await this.utilities.POST(this.config.input_url, KeyToPress);
}
}
exports.TelevisionAccessory = TelevisionAccessory;
//# sourceMappingURL=tvAccessory.js.map