Skip to content

Commit

Permalink
Add ability to send key presses over HDMI
Browse files Browse the repository at this point in the history
Allows sending key press events to selected destination device (TV, receiver, player, etc.)
  • Loading branch information
Rafostar authored Sep 13, 2019
1 parent d0474cf commit 479ac17
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 0 deletions.
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
Requires CEC capable device (e.g. Raspberry Pi or USB-CEC adapter).<br>
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');
Expand Down Expand Up @@ -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();
Expand Down
66 changes: 66 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`))
{
Expand All @@ -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);
}
}
});
}
Expand Down Expand Up @@ -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;
}

Expand All @@ -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); }
}
}
Expand Down
5 changes: 5 additions & 0 deletions keymap.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ module.exports =
getHex: function(name)
{
return Object.keys(keyNames).find(hex => keyNames[hex] === name);
},

getNamesArray: function()
{
return Object.values(keyNames);
}
}

Expand Down
62 changes: 62 additions & 0 deletions test/control-decoder.js
Original file line number Diff line number Diff line change
@@ -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;
});
}

0 comments on commit 479ac17

Please sign in to comment.