homebridge-tv-cec
Version:
Homebridge support for HomeKit TV accessory, using HDMI-CEC
157 lines (143 loc) • 5.14 kB
JavaScript
const events = require('events');
const { spawn } = require('child_process');
const cecClient = spawn('cec-client', [ '-d', '8' ]);
const tvEvent = new events.EventEmitter();
module.exports = homebridge => {
const { Service, Characteristic } = homebridge.hap;
class TV {
constructor(log, config) {
this.log = log;
this.config = config;
this.tvService = new Service.Television(this.config.name, 'television');
this.tvService
.setCharacteristic(Characteristic.ConfiguredName, this.config.name);
this.tvService.getCharacteristic(Characteristic.Active)
.on('get', this.getPowerStatus.bind(this))
.on('set', this.setPowerStatus.bind(this));
this.tvService
.getCharacteristic(Characteristic.ActiveIdentifier)
.on('set', this.setInput.bind(this));
this.inputs = Object.entries(this.config.devices).map(([port, name]) => {
const input = new Service.InputSource(name, `inputSource${port}`);
input
.setCharacteristic(Characteristic.Identifier, port)
.setCharacteristic(Characteristic.ConfiguredName, name)
.setCharacteristic(Characteristic.IsConfigured, Characteristic.IsConfigured.CONFIGURED)
.setCharacteristic(Characteristic.InputSourceType, Characteristic.InputSourceType.APPLICATION);
this.tvService.addLinkedService(input);
return input;
});
this.informationService = new Service.AccessoryInformation();
cecClient.stdout.on('data', data => {
const traffic = data.toString();
if (traffic.indexOf('<< 10:47:43:45:43') !== -1) {
cecClient.stdin.write('tx 10:47:52:50:69\n'); // Set OSD String to 'RPi'
}
if (traffic.indexOf('>> 0f:36') !== -1) {
tvEvent.emit('POWER_OFF');
}
if (traffic.indexOf('>> 01:90:00') !== -1) {
tvEvent.emit('POWER_ON');
}
const match = />> (0f:80:\d0:00|0f:86):(\d)0:00/.exec(traffic);
if (match) {
tvEvent.emit('INPUT_SWITCHED', match[2]);
}
});
let justSwitched = false;
tvEvent.on('POWER_ON', () => {
if (!justSwitched) {
this.log.debug('CEC: Power on');
this.tvService.getCharacteristic(Characteristic.Active).updateValue(true);
justSwitched = true;
setTimeout(() => {
justSwitched = false;
}, 5000);
}
});
tvEvent.on('POWER_OFF', () => {
if (!justSwitched) {
this.log.debug('CEC: Power off');
this.tvService.getCharacteristic(Characteristic.Active).updateValue(false);
justSwitched = true;
setTimeout(() => {
justSwitched = false;
}, 5000);
}
});
tvEvent.on('INPUT_SWITCHED', port => {
this.log.debug(`CEC: Input switched to HDMI${port}`);
this.tvService.getCharacteristic(Characteristic.ActiveIdentifier).updateValue(parseInt(port));
});
}
getServices() {
return [ this.informationService, this.tvService, ...this.inputs ];
}
getPowerStatus(callback) {
this.log.info(`Checking TV power status`);
cecClient.stdin.write('tx 10:8f\n'); // 'pow 0'
const handler = () => {
handler.activated = true;
callback(null, true);
this.log.info('TV is on');
};
tvEvent.once('POWER_ON', handler);
setTimeout(() => {
tvEvent.removeListener('POWER_ON', handler);
if (!handler.activated) {
callback(null, false);
this.log.info('TV is off');
}
}, 1000);
}
setPowerStatus(value, callback) {
this.log.info(`Turning TV ${value ? 'on' : 'fff'}`);
if (value === this.tvService.getCharacteristic(Characteristic.Active).value) {
callback();
this.log.info(`TV is already ${value ? 'on' : 'off'}`);
return;
}
const handler = () => {
handler.activated = true;
callback();
this.log.info(`TV is turned ${value ? 'on' : 'off'}`);
};
tvEvent.once(value ? 'POWER_ON' : 'POWER_OFF', handler);
setTimeout(() => {
tvEvent.removeListener(value ? 'POWER_ON' : 'POWER_OFF', handler);
if (!handler.activated) {
callback(`TV is not turning ${value ? 'on' : 'off'}`);
this.log.info(`TV is not turning ${value ? 'on' : 'off'}`);
}
}, 30000);
// Send on or off signal
cecClient.stdin.write(value ? 'tx 10:04\n' : 'tx 10:36\n');
}
setInput(value, callback) {
this.log.info(`Switching to HDMI${value}`);
if (!this.tvService.getCharacteristic(Characteristic.Active).value) {
this.log.info(`TV is off; Retrying to switch input after TV turns on`);
tvEvent.once('POWER_ON', () => { this.setInput(value, callback); });
return;
}
// const handler = () => {
// handler.activated = true;
// callback();
// this.log.info(`TV is switched to HDMI${value}`);
// };
// tvEvent.once('INPUT_SWITCHED', handler);
// setTimeout(() => {
// tvEvent.removeListener('INPUT_SWITCHED', handler);
// if (!handler.activated) {
// callback(`TV is not switching to HDMI${value}`);
// this.log.info(`TV is not switching to HDMI${value}`);
// }
// }, 30000);
cecClient.stdin.write(`tx 1f:82:${value}0:00\n`);
cecClient.stdin.write(`is\n`);
callback();
this.log.info(`Sent CEC command to switch to HDMI${value}`);
}
}
homebridge.registerAccessory('homebridge-tv-cec', 'TV-CEC', TV);
};