diff --git a/README.md b/README.md index 3440cba..f3e518e 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ Requires CEC capable device (e.g. Raspberry Pi or USB-CEC adapter).
Additionally `cec-client` must be installed. On Raspbian it is included in cec-utils package. +Controller scans devices on startup. It takes a while (scan is done async and result is returned in "ready" event). + ### Usage Examples ```javascript var CecController = require('cec-controller'); @@ -49,12 +51,36 @@ cecCtl.on('error', console.error); volumeUp: [Function: bound command], // Increase amplifier volume volumeDown: [Function: bound command], // Decrease amplifier volume mute: [Function: bound command], // Mute amplifier + getKeyNames: [Function: bound getNamesArray] // Returns array of supported keys (for use with sendKey()) command: [Function: command] // Send custom signal (arg is send as input to cec-client) } */ ``` +#### Send TV remote key presses +Send key press to your TV, player or receiver. Get the list of available key names with `cecCtl.getKeyNames()`. +```javascript +var CecController = require('cec-controller'); +var cecCtl = new CecController(); + +cecCtl.on('ready', readyHandler); +cecCtl.on('error', console.error); + +function readyHandler(controller) +{ + /* In this example dev1 is a satellite decoder */ + controller.dev1.sendKey('up').then((success) => + { + if(success) + console.log('Successfully send "up" key to decoder'); + else + console.error('Could not send input key!'); + }); +} +``` + #### Receive TV remote input +Use `keypress`, `keydown` or `keyup` events to implement code logic that depends on the pressed TV remote button. ```javascript var CecController = require('cec-controller'); var cecCtl = new CecController(); diff --git a/index.js b/index.js index 54635e7..89b47fc 100644 --- a/index.js +++ b/index.js @@ -233,6 +233,7 @@ module.exports = class Client else if(line.startsWith('TRAFFIC:') && line.includes('<<')) { var srcAddress = this.devices[this.myDevice].logicalAddress; + var destAddress = this.devices[this.controlledDevice].logicalAddress; if(line.includes(`<< ${srcAddress}0:04`)) { @@ -246,6 +247,14 @@ module.exports = class Client this.devices[this.myDevice].activeSource = 'no'; this.cec.emit(`${srcAddress}:activeSource`, 'no'); } + else if(line.includes(`<< ${srcAddress}${destAddress}:44`)) + { + var value = this._getLineValue(line).toUpperCase(); + var keyName = keymap.getName(value); + + debug(`Send "${keyName}" key to dev${destAddress}`); + this.cec.emit('sendKey', keyName); + } } }); } @@ -280,6 +289,62 @@ module.exports = class Client } } + if(this.devices[deviceId] !== this.devices[this.myDevice]) + { + func.sendKey = (keyName) => + { + this.controlledDevice = deviceId; + + var srcAddress = this.devices[this.myDevice].logicalAddress; + var destAddress = this.devices[deviceId].logicalAddress; + + return new Promise((resolve, reject) => + { + var keyHex = keymap.getHex(keyName); + + if(!keyHex) + { + debug(`Unknown key name: ${keyName}`); + resolve(null); + } + else + { + var sendKeyTimeout = setTimeout(() => + { + sendKeyTimeout = null; + debug(`dev${destAddress} send key timed out!`); + sendKeyHandler(null); + }, 1000); + + var sendKeyHandler = (value) => + { + if(value === keyName || value === null) + { + if(sendKeyTimeout) + clearTimeout(sendKeyTimeout); + + this.cec.removeListener('sendKey', sendKeyHandler); + + if(!value) + { + debug(`dev${destAddress} could not receive key!`); + resolve(null); + } + else + resolve(true); + } + } + + this.cec.on('sendKey', sendKeyHandler); + + var sendCmd = `tx ${srcAddress}${destAddress}:44:${keyHex}`; + debug(`Command: ${sendCmd}`); + this.command(sendCmd, null); + } + }); + } + } + return func; } @@ -291,6 +356,7 @@ module.exports = class Client volumeUp: this.command.bind(this, 'volup', null), volumeDown: this.command.bind(this, 'voldown', null), mute: this.command.bind(this, 'mute', null), + getKeyNames: keymap.getNamesArray.bind(this), command: (args) => { return this.command(args, null); } } } diff --git a/keymap.js b/keymap.js index 24db4cb..9ae2004 100644 --- a/keymap.js +++ b/keymap.js @@ -8,6 +8,11 @@ module.exports = getHex: function(name) { return Object.keys(keyNames).find(hex => keyNames[hex] === name); + }, + + getNamesArray: function() + { + return Object.values(keyNames); } } diff --git a/test/control-decoder.js b/test/control-decoder.js new file mode 100644 index 0000000..ba90545 --- /dev/null +++ b/test/control-decoder.js @@ -0,0 +1,62 @@ +/* + Use arrow keys from your PC as arrows buttons to control TV. +*/ + +const cecInit = require('./shared/init'); +cecInit().then(test); + +process.stdin.setRawMode(true); +process.stdin.setEncoding('utf8'); + +var enabled = true; + +function test(obj) +{ + console.log('--- Control dev1 with keyboard arrow keys, select with "Spacebar" ---\n'); + + process.stdin.on('data', async(key) => + { + if(!enabled) return; + + enabled = false; + + /* Read arrow keys */ + if(key.charCodeAt(0) === 27 && key.charCodeAt(1) === 91) + { + switch(key.charCodeAt(2)) + { + case 65: + await obj.controller.dev1.sendKey('up'); + break; + case 66: + await obj.controller.dev1.sendKey('down'); + break; + case 67: + await obj.controller.dev1.sendKey('right'); + break; + case 68: + await obj.controller.dev1.sendKey('left'); + break; + default: + break; + } + } + else + { + switch(key) + { + case '\u0020': // "Enter" key + await obj.controller.dev1.sendKey('select'); + break; + case '\u0003': // Close with "Ctrl+c" + /* In stdin RAW mode spawned process will not be closed on exit and must be closed manually */ + obj.cec.closeClient().then(() => process.exit(0)); + break; + default: + break; + } + } + + enabled = true; + }); +}