@senzil/cec-monitor
Version:
HDMI-CEC library with a simple monitor written on ES6 to make cec enabled apps.
354 lines (318 loc) • 11.4 kB
JavaScript
/**
* cli.js
*
* cec-monitor
*
* 12/8/17
*
* Created by Damien Clark (damo.clarky@gmail.com)
*/
var c = require('../index')
var CEC = c.CEC
var CECMonitor = c.CECMonitor
var readline = require('readline')
///////////////////////////////////////////////////////////////////////////////
// cli commands - see man cec-client
///////////////////////////////////////////////////////////////////////////////
var commands = [
// cli.js commands
'active', // Output state on current active source
'quit', // Quit cli.js
'physical', // Give physical address of given logical address
'logical', // Give primary logical address of given physical address
'address', // Give state of cli.js logical and physical address
'addresses', // Output current state information for all logical addresses
'osdname', // Output state of name for logical or physical address
'power', // Output state of power for logical or physical address
'state', // Get current state cache
'cp', // Get current power state
'hdmi', // Set HDMI port
// cec-client commands
'ad',
'as',
'at',
'bl',
'is',
'la',
'lad',
'lang',
'log',
'mon',
'mute',
'name',
'on',
'osd',
'p',
'pa',
'ping',
'poll',
'pow',
'scan',
'self',
'sp',
'spl',
'standby',
'tx',
'txn',
'ven',
'ver',
'voldown',
'volup'
]
///////////////////////////////////////////////////////////////////////////////
// cli.js local functions
///////////////////////////////////////////////////////////////////////////////
var functions = {
addresses: function () {
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14].forEach(function (t) {
var output = [
lpad(t, 2, '0'),
rpad(monitor.GetOSDName(t), 20),
rpad(monitor.Logical2Physical(t), 7),
rpad('power: '+monitor.GetPowerStatusName(t), 35)
]
console.log('['+output.join('] [')+']')
})
},
address: function () {
console.log('My address:')
console.log(lpad('logical addresses: ', 19)+monitor.GetLogicalAddresses().join(', '))
console.log(lpad('primary logical: ', 19)+monitor.GetLogicalAddress())
console.log(lpad('physical: ', 19)+monitor.GetPhysicalAddress())
},
active: function () {
console.log('active source: '+monitor.GetActiveSource())
},
logical: function (physical) {
console.log('primary logical address: '+monitor.Physical2Logical(physical))
},
physical: function (logical) {
console.log('physical address: '+monitor.Logical2Physical(logical))
},
power: function (address) {
console.log('power status: '+monitor.GetPowerStatusName(address)+' ('+monitor.GetPowerStatus(address)+')')
},
osdname: function (address) {
console.log('OSD name: '+monitor.GetOSDName(address))
},
state: function () {
console.log(monitor.GetState())
},
cp: function(address) {
monitor.SendCommand(null, address, CEC.Opcode.GIVE_DEVICE_POWER_STATUS, CECMonitor.EVENTS.REPORT_POWER_STATUS)
.then(function(packet) {
console.log('POWER', packet.data.str)
})
.catch(function(error) {
console.log('POWER UNKNOWN')
console.log(error)
})
},
hdmi: function(port) {
monitor.state_manager.hdmi = Number.parseInt(port, 10)
},
quit: function () {
monitor.Stop()
process.exit(0)
}
}
///////////////////////////////////////////////////////////////////////////////
// instantiate cec-monitor
///////////////////////////////////////////////////////////////////////////////
//All config options are optionals
var monitor = new CECMonitor('cec-mon-cli', {
debug: false, // enable/disabled debug events from cec-client
hdmiport: 1, // set inital hdmi port
processManaged: false, // set/unset handlers to avoid unclear process exit.
recorder: true, //enable cec-client as recorder device
player: false, //enable cec-client as player device
tuner: false, //enable cec-client as tuner device
audio: false, //enable cec-client as audio system device
autorestart: true, //enable autorestart cec-client to avoid some wierd conditions
command_timeout: 3, //set timeout to invalidate status cache and search current power state
no_serial: { //controls if the monitor restart cec-client when that stop after the usb was unplugged
reconnect: true, //enable reconnection attempts when usb is unplugged
wait_time: 30, //in seconds - time to do the attempt
trigger_stop: false //avoid trigger stop event
},
cache: {
enable: true,
timeout: 30,
autorefresh: true
}
})
monitor.once(CECMonitor.EVENTS._READY, function() {
functions.address()
console.log( ' -- READY -- ' )
// cec-client commands
// monitor.WriteRawMessage('on 0');
// monitor.WriteRawMessage('scan');
// monitor.WriteRawMessage('standby 0');
// cec-client tx commands
// monitor.SendMessage('0x4', '0xF', CEC.Opcode.SET_OSD_NAME,'Frisbee');
// monitor.SendMessage('playbackdevice1', 'broadcast', 'set_osd_name','Frisbee');
// monitor.SendMessage('PLAYBACKDEVICE1', 'BROADCAST', 'SET_OSD_NAME','Frisbee');
// monitor.SendMessage(null,null, 'set_osd_name','Frisbee');
// monitor.SendMessage(monitor.GetLogicalAddress(), CEC.LogicalAddress.BROADCAST, CEC.Opcode.SET_OSD_NAME,'Frisbee');
// monitor.SendMessage(CEC.LogicalAddress.PLAYBACKDEVICE1, CEC.LogicalAddress.BROADCAST, CEC.Opcode.SET_OSD_NAME,[70,114,105,115,98,101,101]);
// monitor.SendMessage(CEC.LogicalAddress.PLAYBACKDEVICE1, CEC.LogicalAddress.BROADCAST, CEC.Opcode.SET_OSD_NAME,[0x46,0x72,0x69,0x73,0x62,0x65,0x65]);
// monitor.SendMessage(CEC.LogicalAddress.UNREGISTERED, CEC.LogicalAddress.BROADCAST, CEC.Opcode.ACTIVE_SOURCE,'2.0.0.0');
// monitor.SendMessage(CEC.LogicalAddress.UNREGISTERED, CEC.LogicalAddress.BROADCAST, CEC.Opcode.ACTIVE_SOURCE,[0x20,0x0);
rl.prompt()
})
function displayPayload(packet) {
console.log(JSON.stringify(packet))
rl.prompt()
}
// Any Traffic containing an opcode
monitor.on(CECMonitor.EVENTS._OPCODE, displayPayload)
// If the cec-client pipe is closed
monitor.on(CECMonitor.EVENTS._STOP, function () {
console.log('cec-client exit')
rl.prompt()
})
// Debug messages from cec-client
monitor.on(CECMonitor.EVENTS._DEBUG, function(data) {
// set 'debug: true' on new CECMonitor
console.log( '[DEBUG] '+JSON.stringify(data))
rl.prompt()
})
// monitor.on(CECMonitor.EVENTS.ACTIVE_SOURCE,displayPayload);
//
// monitor.on(CECMonitor.EVENTS.CEC_VERSION,displayPayload);
//
// monitor.on(CECMonitor.EVENTS.DECK_STATUS,displayPayload);
//
// monitor.on(CECMonitor.EVENTS.DEVICE_VENDOR_ID,displayPayload);
//
// monitor.on(CECMonitor.EVENTS.REPORT_PHYSICAL_ADDRESS,displayPayload);
//
// monitor.on(CECMonitor.EVENTS.REPORT_POWER_STATUS,displayPayload);
//
// monitor.on(CECMonitor.EVENTS.ROUTING_CHANGE,displayPayload);
//
// monitor.on(CECMonitor.EVENTS.STANDBY,displayPayload);
//
// monitor.on(CECMonitor.EVENTS.SET_OSD_NAME,displayPayload);
//
// monitor.on(CECMonitor.EVENTS.ROUTING_INFORMATION,displayPayload);
//
// monitor.on(CECMonitor.EVENTS.IMAGE_VIEW_ON,displayPayload);
// Capture lowercase labels for opcodes
const opcode = toLowerObject(CEC.Opcode)
// Capture lowercase labels for addresses
const logicaladdress = toLowerObject(CEC.LogicalAddress)
// Use readline to provide cli interface, with tab autocompletion
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: 'CEC-MON> ',
completer: function(line) {
var tokens = line.split(/\s+/) // /(?<!\\)\s+/
const level = tokens.length
var hits
if (level === 1) { // complete top-level commands
// test for matching commands
hits = commands.filter(function(c) { return c.startsWith(tokens[0].toLocaleLowerCase())})
// Advance a space so user can continue with next option
if (hits.length === 1) hits[0] += ' '
return [hits.length ? hits : commands, tokens[level-1]]
}
if ((level === 2 || level === 3) && tokens[0] === 'tx') { // complete logical addresses for tx command
// test for matching logical address names
hits = Object.keys(logicaladdress).filter(function(c) {
return c.startsWith(tokens[level-1].toLocaleLowerCase())
})
if (hits.length === 1) hits[0] += ' '
return [hits.length ? hits.sort() : Object.keys(logicaladdress).sort(), tokens[level-1]]
}
if (level === 4 && tokens[0] === 'tx') { // complete opcode for tx command
// test for matching opcodes
hits = Object.keys(opcode).filter(function (c) { return c.startsWith(tokens[level-1].toLocaleLowerCase()) })
// Advance a space so user can continue with next option
if (hits.length === 1) hits[0] += ' '
return [hits.length ? hits.sort() : Object.keys(opcode).sort(), tokens[level-1]]
}
return [[], '']
}
})
rl.prompt()
// Process entered command
rl.on('line', function(line) {
line = line.trim()
if (line === '') {
rl.prompt()
return
}
// Split commands by whitespace
var tokens = line.split(/\s+/)
if (tokens.length > 2 && tokens[0] === 'tx') { // If tx, then resolve text labels for addresses and opcodes to bytes
tokens.shift() //Throw away tx
if (logicaladdress.hasOwnProperty(tokens[0])) { // src
tokens[0] = logicaladdress[tokens[0]]
}
// else {
// tokens[0] = parseInt(tokens[0],16);
// }
if (logicaladdress.hasOwnProperty(tokens[1])) { // dest
tokens[1] = logicaladdress[tokens[1]]
}
// else {
// tokens[1] = parseInt(tokens[1],16);
// }
if (opcode.hasOwnProperty(tokens[2])) { // opcode - minimum required params for tx - we are going to send
tokens[2] = CEC.Opcode[tokens[2].toLocaleUpperCase()]
}
// else {
// tokens[2] = parseInt(tokens[2],16);
// }
var args = tokens.slice(3) // Capture remaining tokens as args for opcode
// If only one arg and not numeric, then assume its not a literal code byte and don't store as array
// let cec-monitor process
if (args.length === 1 && !args[0].toString(10).match(/^\d+$/)) {
args = args[0]
}
tokens = tokens.slice(0, 3) // Strip args from tokens array
tokens.push(args) // Append args as a single value or an array reference
console.log('Calling: monitor.SendMessage('+JSON.stringify(tokens)+')')
monitor.SendMessage.apply(monitor, tokens)
rl.prompt()
return
}
if (functions.hasOwnProperty(tokens[0])) { // not cec-client commands
functions[tokens[0]].apply(null, tokens.slice(1))
rl.prompt()
return
}
console.log('Sending to cec-client: "'+line+'"')
monitor.WriteRawMessage(line)
rl.prompt()
}).on('close', function() {
console.log('cli.js terminating')
})
function toLowerObject(obj) {
var data = {}
Object.keys(obj).forEach(function (k) {
data[k.toLocaleLowerCase()] = obj[k]
})
return data
}
function rpad(str, size, pad) {
var s = padding(size, str, pad)
return str+s
}
function padding(size, str, pad) {
if (pad === undefined)
pad = ' '
if (str === null)
str = 'null'
else if (str === undefined)
str = 'undefined'
var s = new Array(size - str.toString().length).fill(pad).join('')
return s
}
function lpad(str, size, pad) {
var s = padding(size, str, pad)
return s+str
}