@senzil/cec-monitor
Version:
HDMI-CEC library with a simple monitor written on ES6 to make cec enabled apps.
877 lines (704 loc) • 29.3 kB
JavaScript
/**
* Created by pablo on 6/13/17.
*/
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _child_process = require("child_process");
var _events = require("events");
var _eventStream = _interopRequireDefault(require("event-stream"));
var _deasync = _interopRequireDefault(require("deasync"));
var _HDMICEC = _interopRequireDefault(require("./HDMI-CEC.1.4"));
var _death = _interopRequireDefault(require("death"));
var _CECAdapterNotReadyError = _interopRequireDefault(require("./CECAdapterNotReadyError"));
var _CECTimeoutError = _interopRequireDefault(require("./CECTimeoutError"));
var _StateManager = _interopRequireDefault(require("./StateManager"));
var _Convert = _interopRequireDefault(require("./Convert"));
var _Validate = _interopRequireDefault(require("./Validate"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
class CECMonitor extends _events.EventEmitter {
constructor(OSDName, options) {
super();
_defineProperty(this, "OSDName", void 0);
_defineProperty(this, "com_port", void 0);
_defineProperty(this, "debug", void 0);
_defineProperty(this, "client", void 0);
_defineProperty(this, "ready", void 0);
_defineProperty(this, "no_serial", void 0);
_defineProperty(this, "auto_restart", void 0);
_defineProperty(this, "auto_restarting", void 0);
_defineProperty(this, "reconnect_intent", void 0);
_defineProperty(this, "params", void 0);
_defineProperty(this, "state_manager", void 0);
_defineProperty(this, "cache", void 0);
_defineProperty(this, "command_timeout", void 0);
_defineProperty(this, "user_control_hold_interval", void 0);
_defineProperty(this, "user_control_hold_interval_ref", void 0);
_defineProperty(this, "GetState", function (address) {
address = _parseAddress.call(this, address, undefined); // Return copy of our state information
if (address === undefined) {
return JSON.parse(JSON.stringify(_getUpdatedStateManager.call(this)));
}
return JSON.parse(JSON.stringify(_getUpdatedDeviceState.call(this, address)));
}.bind(this));
_defineProperty(this, "GetPhysicalAddress", function () {
return this.state_manager.primary.route;
}.bind(this));
_defineProperty(this, "GetLogicalAddress", function () {
return this.state_manager.primary.logical;
}.bind(this));
_defineProperty(this, "GetLogicalAddresses", function () {
return this.state_manager.owns.map(S => S.logical);
}.bind(this));
_defineProperty(this, "Logical2Physical", function (logical) {
logical = _parseAddress.call(this, logical, null);
if (logical === null) {
return null;
}
return _getUpdatedDeviceState.call(this, logical).route;
}.bind(this));
_defineProperty(this, "Physical2Logical", function (physical) {
physical = _parseAddress.call(this, physical, null);
return physical;
}.bind(this));
_defineProperty(this, "GetOSDName", function (address) {
address = _parseAddress.call(this, address, null);
if (address === null) {
return '';
}
return _getUpdatedDeviceState.call(this, address).osdname;
}.bind(this));
_defineProperty(this, "GetPowerStatus", function (address) {
address = _parseAddress.call(this, address, null);
if (address === null) {
return null;
}
return _getUpdatedDeviceState.call(this, address).status;
}.bind(this));
_defineProperty(this, "GetPowerStatusName", function (address) {
address = _parseAddress.call(this, address, null);
if (address === null) {
return null;
}
return _getUpdatedDeviceState.call(this, address).power;
}.bind(this));
_defineProperty(this, "GetActiveSource", function () {
return this.state_manager.active_source;
}.bind(this));
_defineProperty(this, "WriteRawMessage", async function (raw) {
if (this.client && this.ready) {
try {
return this.client.stdin.write(raw + '\n');
} catch (e) {
console.log('Is not possible write messages to the cec adapter right now');
console.log(e);
throw new _CECAdapterNotReadyError.default(e);
}
}
console.log('the cec adapter is not ready');
throw new _CECAdapterNotReadyError.default();
}.bind(this));
_defineProperty(this, "WriteMessage", function (source, target, opcode, args) {
let msg = `tx ${[(source << 4) + target, opcode].concat(args || []).map(h => `0${h.toString(16)}`.substr(-2)).join(':')}`;
return this.WriteRawMessage(msg);
}.bind(this));
_defineProperty(this, "SendMessage", function (source, target, opcode, args) {
source = _parseAddress.call(this, source, this.GetLogicalAddress());
target = _parseAddress.call(this, target, _HDMICEC.default.LogicalAddress.BROADCAST);
if (typeof opcode === 'string') {
if (_Validate.default.isHexaNumber(opcode)) {
opcode = Number.parseInt(opcode, 16);
} else if (_HDMICEC.default.Opcode.hasOwnProperty(opcode.toLocaleUpperCase())) {
opcode = _HDMICEC.default.Opcode[opcode.toLocaleUpperCase()];
}
}
if (typeof args === 'string') {
// If a phyiscal address
if (_Validate.default.isRoute(args)) {
args = _Convert.default.routeToArgs(args);
} else if (_Validate.default.isHexaNumber(args)) {
args = Number.parseInt(args, 16);
} // Otherwise treat as string argument
else {
args = args.split('').map(s => s.charCodeAt(0));
}
} // todo: Create classes for complex operations (EG. SELECT_DIGITAL_SERVICE), that can be provided and generate their own arguments array
// else if(typeof args === 'object' && args instanceof Command)
return this.WriteMessage(source, target, opcode, args);
}.bind(this));
_defineProperty(this, "SendCommand", function (source, target, opcode, event, args) {
source = _parseAddress.call(this, source, this.GetLogicalAddress());
target = _parseAddress.call(this, target, _HDMICEC.default.LogicalAddress.BROADCAST);
return this.SendMessage(source, target, opcode, args).then(() => _eventPromise.call(this, target, event, this.command_timeout * 1000));
}.bind(this));
_defineProperty(this, "WaitForReady", async function () {
_deasync.default.loopWhile(() => !this.ready);
return this.ready;
}.bind(this));
_defineProperty(this, "Stop", function () {
if (this.client) {
this.client.kill('SIGINT');
_onClose.call(this);
}
}.bind(this));
this.setMaxListeners(300);
this.ready = false;
this.auto_restarting = false;
this.reconnect_intent = false;
this.no_serial = {
reconnect: false,
wait_time: 30,
//in seconds
trigger_stop: false
};
this.cache = {
enable: true,
autorefresh: true,
timeout: 30 //in seconds
};
this.OSDName = OSDName || 'cec-monitor';
this.com_port = options.com_port || '';
this.auto_restart = options.auto_restart ? options.auto_restart : true;
this.no_serial = Object.assign(this.no_serial, options.no_serial);
this.cache = Object.assign(this.cache, options.cache);
this.debug = options.debug;
this.command_timeout = options.command_timeout || 3;
this.user_control_hold_interval = options.user_control_hold_interval || 1000;
this.user_control_hold_interval_ref = null;
process.on('beforeExit', this.Stop);
process.on('exit', this.Stop);
if (!options.processManaged) {
(0, _death.default)({
uncaughtException: true
})((signal, err) => {
if (err) {
console.error(err);
}
return process.exit();
});
}
this.params = [];
let earlyPrimaryAddress;
if (options.recorder !== false) {
this.params.push('-t', 'r');
earlyPrimaryAddress = 1;
}
if (options.player === true) {
this.params.push('-t', 'p');
earlyPrimaryAddress = earlyPrimaryAddress || 4;
}
if (options.tuner === true) {
this.params.push('-t', 't');
earlyPrimaryAddress = earlyPrimaryAddress || 3;
}
if (options.audio === true) {
this.params.push('-t', 'a');
earlyPrimaryAddress = earlyPrimaryAddress || 5;
}
this.params.push('-o', this.OSDName, '-d', '31', '-p', options.hdmiport || 1, this.com_port); // Cache of data about logical addresses
this.state_manager = new _StateManager.default();
this.state_manager.primary = earlyPrimaryAddress;
_initCecClient.call(this);
}
static get EVENTS() {
return {
_DATA: '_data',
_DEBUG: '_debug',
_ERROR: '_error',
_NOTICE: '_notice',
_PACKET: '_packet',
_READY: '_ready',
_RECEIVED: '_received',
_SENDED: '_sended',
_STOP: '_stop',
_TRAFFIC: '_traffic',
_OPCODE: '_opcode',
_WARNING: '_warning',
_NOSERIALPORT: '_no_serial_port',
_NOHDMICORD: '_no_hdmi_cord',
_EXPIREDCACHE: '_expired_cache',
_UPDATEDCACHE: '_updated_cache',
_USERCONTROLHOLD: '_user_control_hold',
ABORT: 'ABORT',
ACTIVE_SOURCE: 'ACTIVE_SOURCE',
CEC_VERSION: 'CEC_VERSION',
CLEAR_ANALOGUE_TIMER: 'CLEAR_ANALOGUE_TIMER',
CLEAR_DIGITAL_TIMER: 'CLEAR_ANALOGUE_TIMER',
CLEAR_EXTERNAL_TIMER: 'CLEAR_EXTERNAL_TIMER',
DECK_CONTROL: 'DECK_CONTROL',
DECK_STATUS: 'DECK_STATUS',
DEVICE_VENDOR_ID: 'DEVICE_VENDOR_ID',
FEATURE_ABORT: 'FEATURE_ABORT',
GET_CEC_VERSION: 'GET_CEC_VERSION',
GET_MENU_LANGUAGE: 'GET_MENU_LANGUAGE',
GIVE_AUDIO_STATUS: 'GIVE_AUDIO_STATUS',
GIVE_DECK_STATUS: 'GIVE_DECK_STATUS',
GIVE_DEVICE_POWER_STATUS: 'GIVE_DEVICE_POWER_STATUS',
GIVE_DEVICE_VENDOR_ID: 'GIVE_DEVICE_VENDOR_ID',
GIVE_OSD_NAME: 'GIVE_OSD_NAME',
GIVE_PHYSICAL_ADDRESS: 'GIVE_PHYSICAL_ADDRESS',
GIVE_SYSTEM_AUDIO_MODE_STATUS: 'GIVE_SYSTEM_AUDIO_MODE_STATUS',
GIVE_TUNER_DEVICE_STATUS: 'GIVE_TUNER_DEVICE_STATUS',
IMAGE_VIEW_ON: 'IMAGE_VIEW_ON',
INACTIVE_SOURCE: 'INACTIVE_SOURCE',
MENU_REQUEST: 'MENU_REQUEST',
MENU_STATUS: 'MENU_STATUS',
PLAY: 'PLAY',
POLLING_MESSAGE: 'POLLING_MESSAGE',
RECORD_OFF: 'RECORD_OFF',
RECORD_ON: 'RECORD_ON',
RECORD_STATUS: 'RECORD_STATUS',
RECORD_TV_SCREEN: 'RECORD_TV_SCREEN',
REPORT_AUDIO_STATUS: 'REPORT_AUDIO_STATUS',
REPORT_PHYSICAL_ADDRESS: 'REPORT_PHYSICAL_ADDRESS',
REPORT_POWER_STATUS: 'REPORT_POWER_STATUS',
REQUEST_ACTIVE_SOURCE: 'REQUEST_ACTIVE_SOURCE',
ROUTING_CHANGE: 'ROUTING_CHANGE',
ROUTING_INFORMATION: 'ROUTING_INFORMATION',
SELECT_ANALOGUE_SERVICE: 'SELECT_ANALOGUE_SERVICE',
SELECT_DIGITAL_SERVICE: 'SELECT_DIGITAL_SERVICE',
SET_ANALOGUE_TIMER: 'SET_ANALOGUE_TIMER',
SET_AUDIO_RATE: 'SET_AUDIO_RATE',
SET_DIGITAL_TIMER: 'SET_DIGITAL_TIMER',
SET_EXTERNAL_TIMER: 'SET_EXTERNAL_TIMER',
SET_MENU_LANGUAGE: 'SET_MENU_LANGUAGE',
SET_OSD_NAME: 'SET_OSD_NAME',
SET_OSD_STRING: 'SET_OSD_STRING',
SET_STREAM_PATH: 'SET_STREAM_PATH',
SET_SYSTEM_AUDIO_MODE: 'SET_SYSTEM_AUDIO_MODE',
SET_TIMER_PROGRAM_TITLE: 'SET_TIMER_PROGRAM_TITLE',
STANDBY: 'STANDBY',
SYSTEM_AUDIO_MODE_REQUEST: 'SYSTEM_AUDIO_MODE_REQUEST',
SYSTEM_AUDIO_MODE_STATUS: 'SYSTEM_AUDIO_MODE_STATUS',
TEXT_VIEW_ON: 'TEXT_VIEW_ON',
TIMER_CLEARED_STATUS: 'TIMER_CLEARED_STATUS',
TIMER_STATUS: 'TIMER_STATUS',
TUNER_DEVICE_STATUS: 'TUNER_DEVICE_STATUS',
TUNER_STEP_DECREMENT: 'TUNER_STEP_DECREMENT',
TUNER_STEP_INCREMENT: 'TUNER_STEP_INCREMENT',
USER_CONTROL_PRESSED: 'USER_CONTROL_PRESSED',
USER_CONTROL_RELEASE: 'USER_CONTROL_RELEASE',
VENDOR_COMMAND: 'VENDOR_COMMAND',
VENDOR_COMMAND_WITH_ID: 'VENDOR_COMMAND_WITH_ID',
VENDOR_REMOTE_BUTTON_DOWN: 'VENDOR_REMOTE_BUTTON_DOWN',
VENDOR_REMOTE_BUTTON_UP: 'VENDOR_REMOTE_BUTTON_UP'
};
}
/**
* Get copy of internal state information on CEC bus
*
* @param {number|string} [address] Optional address to request state for.
* (physical, logical or CEC.LogicalAddress names)
* If omitted, return an array of all addresses indexed by logical address
*
* @return {object|array[object]} An object or array of objects
* @see WriteMessage
* with index as the logical device address and/or values an object
* representing state of the logical address
*/
}
/*** BEGIN INTERNAL FUNCTIONS
*
* THEY SHOULD NOT BE USED BY END USERS AND SHOULD NOT BE EXPORTED
*
* ***/
exports.default = CECMonitor;
const _getUpdatedStateManager = function () {
if (this.cache.enable && this.cache.timeout > 0 && this.state_manager.timestamp < Date.now() - this.cache.timeout * 1000) {
return this.state_manager.map(ds => _getUpdatedDeviceState.call(this, ds.logical));
}
return this.state_manager;
};
const _getUpdatedDeviceState = function (address) {
const currentState = this.state_manager[address];
if (this.cache.enable && this.cache.timeout > 0 && currentState.timestamp < Date.now() - this.cache.timeout * 1000) {
this.emit(CECMonitor.EVENTS._EXPIREDCACHE, currentState);
if (this.cache.autorefresh) {
let done = false;
_getUpdatedDeviceStateAsync.call(this, address).then(() => done = true);
_deasync.default.loopWhile(() => !done);
return this.state_manager[address];
}
}
return currentState;
};
const _getUpdatedDeviceStateAsync = function (address) {
const primary = this.GetLogicalAddress();
return this.SendCommand(primary, address, _HDMICEC.default.Opcode.GIVE_DEVICE_POWER_STATUS, CECMonitor.EVENTS.REPORT_POWER_STATUS).catch(() => this.state_manager[address].status = _HDMICEC.default.PowerStatus.UNKNOWN).then(() => {
this.emit(CECMonitor.EVENTS._UPDATEDCACHE, this.state_manager[address]);
return this.state_manager[address];
});
};
const _eventPromise = function (target, event, milisecondsToWait) {
let __listener, __timeout;
const __removeListener = function __removeListener() {
this.removeListener(event, __listener);
}.bind(this);
return new Promise((resolve, reject) => {
__listener = packet => {
if (packet.source === target) {
clearTimeout(__timeout);
__removeListener();
return resolve(packet);
}
};
__timeout = setTimeout(() => {
__removeListener();
return reject(new _CECTimeoutError.default(target, milisecondsToWait));
}, milisecondsToWait).unref();
this.on(event, __listener);
});
};
/**
* Start the cec-client process in monitor mode.
* @returns {spawn} cec-client process
* @private
*/
const _initCecClient = function () {
this.client = (0, _child_process.spawn)('cec-client', this.params);
this.client.stdout.pipe(_eventStream.default.split()).pipe(_eventStream.default.map(_processStdOut.bind(this)));
this.client.on('close', _onClose.bind(this));
return this.client;
};
/**
* On Close time. Prepare restart cec-client and/or launch _STOP event
* @type {function(this:CECMonitor)}
* @private
*/
const _onClose = function () {
this.client = null;
if (this.auto_restarting) {
setTimeout(_initCecClient.bind(this), 15000);
} else if (this.no_serial.trigger_stop || !this.reconnect_intent) {
clearInterval(this.user_control_hold_interval_ref);
return this.emit(CECMonitor.EVENTS._STOP);
} else if (this.reconnect_intent) {
setTimeout(_initCecClient.bind(this), this.no_serial.wait_time * 1000);
}
};
/**
* Process the standard out of cec-client
* @type {function(this:CECMonitor)}
* @private
*/
const _processStdOut = function (data, cb) {
if (/^TRAFFIC:.*/g.test(data)) {
_processTraffic.call(this, data);
} else if (/^DEBUG:.*/g.test(data)) {
_processDebug.call(this, data);
} else if (/^NOTICE:.*/g.test(data)) {
_processNotice.call(this, data);
} else if (/^waiting for input.*/g.test(data)) {
this.auto_restarting = false;
this.reconnect_intent = false;
this.ready = true;
this.emit(CECMonitor.EVENTS._READY);
} else if (/^WARNING:.*/g.test(data)) {
_processWarning.call(this, data);
} else if (/^ERROR:.*/g.test(data)) {
_processError.call(this, data);
} else if (/(^no serial port given\. trying autodetect: FAILED)|(^Connection lost)/gu.test(data)) {
if (this.no_serial.reconnect) {
this.reconnect_intent = true;
this.ready = false;
}
this.emit(CECMonitor.EVENTS._NOSERIALPORT);
}
this.emit(CECMonitor.EVENTS._DATA, data);
cb(null, data);
};
/**
* Parse the packet info from stdout
* @type {function(this:CECMonitor)}
* @param {String} plain stdout text
* @private
*/
const _readPacket = function (plain) {
const regex = /^(TRAFFIC|DEBUG):[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]\[[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]*([0-9]*)\][\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF](<<|>>)[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF](([0-9A-Z_a-z]{2}:?)+)$/gu;
let match = regex.exec(plain);
if (match) {
let tokens = match[4].split(':').map(h => parseInt(h, 16));
let packet = {
type: match[1],
number: match[2],
flow: match[3] === '>>' ? 'IN' : 'OUT',
source: (tokens[0] & 0xF0) >> 4,
target: tokens[0] & 0x0F,
opcode: tokens[1],
args: tokens.slice(2)
};
this.emit(CECMonitor.EVENTS._PACKET, packet);
return packet;
}
return null;
};
/**
*
* @type {function(this:CECMonitor)}
* @param {String} plain stdout text
* @private
*/
const _processTraffic = function (plain) {
this.emit(CECMonitor.EVENTS._TRAFFIC, plain);
let packet = _readPacket.call(this, plain);
if (packet) {
if (packet.flow === 'IN') {
this.emit(CECMonitor.EVENTS._RECEIVED, packet);
} else {
this.emit(CECMonitor.EVENTS._SENDED, packet);
}
if (!packet.opcode) {
this.emit(CECMonitor.EVENTS.POLLING_MESSAGE, packet);
} else {
_processEvents.call(this, packet);
}
}
};
/**
* Process all received events
* @type {function(this:CECMonitor)}
* @param {Object} packet
* @private
*/
const _processEvents = function (packet) {
let data = {};
let physical, source, status, from, to; // Store opcode name as event property
packet.event = _HDMICEC.default.OpcodeNames[packet.opcode];
switch (packet.opcode) {
case _HDMICEC.default.Opcode.ACTIVE_SOURCE:
if (packet.args.length !== 2) {
return this.emit(CECMonitor.EVENTS._ERROR, 'opcode command ACTIVE_SOURCE with bad formated address');
}
source = _Convert.default.argsToPhysical(packet.args);
physical = _Convert.default.physicalToRoute(source); // Update our records
this.state_manager.active_source = physical;
data = {
val: source,
str: physical
};
break;
case _HDMICEC.default.Opcode.CEC_VERSION:
if (packet.args.length !== 1) {
return this.emit(CECMonitor.EVENTS._ERROR, 'opcode command CEC_VERSION without version');
}
this.state_manager[packet.source].cec = packet.args[0];
data = {
val: this.state_manager[packet.source].cec,
str: this.state_manager[packet.source].cecversion
};
break;
case _HDMICEC.default.Opcode.DECK_STATUS:
if (packet.args.length !== 2) {
return this.emit(CECMonitor.EVENTS._ERROR, 'opcode command DECK_STATUS without Deck Info');
}
status = packet.args[0] << 8 | packet.args[1];
data = {
val: status,
str: _HDMICEC.default.DeckStatusNames[status]
};
break;
case _HDMICEC.default.Opcode.DEVICE_VENDOR_ID:
if (packet.args.length !== 3) {
return this.emit(CECMonitor.EVENTS._ERROR, 'opcode command DEVICE_VENDOR_ID with bad arguments');
}
this.state_manager[packet.source].vendorid = _Convert.default.argsToVendorID(packet.args);
data = {
val: this.state_manager[packet.source].vendorid,
str: this.state_manager[packet.source].vendor
};
break;
case _HDMICEC.default.Opcode.REPORT_PHYSICAL_ADDRESS:
if (packet.args.length !== 3) {
return this.emit(CECMonitor.EVENTS._ERROR, 'opcode command REPORT_PHYSICAL_ADDRESS with bad formated address or device type');
} // Update our records
this.state_manager[packet.source].physical = _Convert.default.argsToPhysical(packet.args);
data = {
val: this.state_manager[packet.source].physical,
str: this.state_manager[packet.source].route
};
break;
case _HDMICEC.default.Opcode.REPORT_POWER_STATUS:
if (packet.args.length !== 1) {
return this.emit(CECMonitor.EVENTS._ERROR, 'opcode command REPORT_POWER_STATUS with bad formated power status');
} // Update our records
this.state_manager[packet.source].status = packet.args[0];
data = {
val: this.state_manager[packet.source].status,
str: this.state_manager[packet.source].power
};
break;
case _HDMICEC.default.Opcode.ROUTING_CHANGE:
if (packet.args.length !== 4) {
return this.emit(CECMonitor.EVENTS._ERROR, 'opcode command ROUTING_CHANGE with bad formated addresses');
}
from = _Convert.default.argsToPhysical(packet.args.slice(0, 2));
to = _Convert.default.argsToPhysical(packet.args.slice(2, 4));
physical = _Convert.default.physicalToRoute(to); // Update our records
this.state_manager.active_source = physical;
data = {
from: {
val: from,
str: _Convert.default.physicalToRoute(from)
},
to: {
val: to,
str: physical
}
};
break;
case _HDMICEC.default.Opcode.SET_OSD_NAME:
if (!packet.args.length) {
return this.emit(CECMonitor.EVENTS._ERROR, 'opcode command SET_OSD_NAME without OSD NAME');
} // Update our records
this.state_manager[packet.source].osdname = _Convert.default.argsToOSDName(packet.args);
data = {
val: this.state_manager[packet.source].osdname,
str: this.state_manager[packet.source].osdname
};
break;
case _HDMICEC.default.Opcode.STANDBY:
if (packet.args.length !== 0) {
return this.emit(CECMonitor.EVENTS._ERROR, 'opcode command STANDBY with bad args');
}
break;
case _HDMICEC.default.Opcode.IMAGE_VIEW_ON:
case _HDMICEC.default.Opcode.TEXT_VIEW_ON:
if (packet.args.length !== 0) {
return this.emit(CECMonitor.EVENTS._ERROR, 'opcode command IMAGE_VIEW_ON with bad args');
}
break;
case _HDMICEC.default.Opcode.MENU_REQUEST:
if (packet.args.length !== 1) {
return this.emit(CECMonitor.EVENTS._ERROR, 'opcode command MENU_REQUEST with bad args');
}
data = {
val: packet.args[0],
str: _HDMICEC.default.MenuRequestTypeNames[packet.args[0]]
};
break;
case _HDMICEC.default.Opcode.USER_CONTROL_PRESSED:
if (packet.args.length !== 1) {
return this.emit(CECMonitor.EVENTS._ERROR, 'opcode command USER_CONTROL_PRESSED with bad args');
}
data = {
val: packet.args[0],
str: _HDMICEC.default.UserControlCodeNames[packet.args[0]]
};
clearInterval(this.user_control_hold_interval_ref);
this.user_control_hold_interval_ref = setInterval(() => this.emit(CECMonitor.EVENTS._USERCONTROLHOLD, data), this.user_control_hold_interval).unref();
break;
case _HDMICEC.default.Opcode.USER_CONTROL_RELEASE:
clearInterval(this.user_control_hold_interval_ref);
if (packet.args.length !== 0) {
return this.emit(CECMonitor.EVENTS._ERROR, 'opcode command USER_CONTROL_RELEASE with bad args');
}
break;
}
packet.data = data;
if (packet.event !== null) {
// Emit all OPCODE events to '_opcode' event
this.emit(CECMonitor.EVENTS._OPCODE, packet);
return this.emit(packet.event, packet);
}
};
/**
* Process notice text from cec-client
* @type {function(this:CECMonitor)}
* @param {String} data: notice stdout text
* @private
*/
const _processNotice = function (data) {
const regexLogical = /logical[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]address\(es\)[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]=[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]((Recorder|Playback|Tuner|Audio)[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF][0-9]?[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]?\([0-9]\)[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]){1,4}/gu;
let match = regexLogical.exec(data);
if (match) {
const regexAddresses = /(Recorder[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF][0-9][\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]|Playback[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF][0-9][\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]|Tuner[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF][0-9][\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]|Audio[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF])\(?([0-9])\)/gu;
let innerMatch = regexAddresses.exec(match[0]);
this.state_manager.primary = Number.parseInt(innerMatch[2], 10);
while (innerMatch) {
let logical_address = Number.parseInt(innerMatch[2], 10);
this.state_manager[logical_address].osdname = this.OSDName;
this.state_manager[logical_address].own = true;
innerMatch = regexAddresses.exec(match[0]);
}
}
const regexDevice = /base[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]device:[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF][0-9A-Z_a-z]+[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]\(([0-9]{1,2})\),[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]HDMI[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]port[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]number:[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]([0-9]{1,2}),/gu;
match = regexDevice.exec(data);
if (match) {
this.state_manager.base = parseInt(match[1], 10);
this.state_manager.hdmi = parseInt(match[2], 10);
}
const regexPhysical = /physical[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]address:[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]([\.0-9A-Z_a-z]+)/gu;
match = regexPhysical.exec(data);
if (match) {
this.state_manager.owns.forEach(S => {
S.route = match[1];
});
}
return this.emit(CECMonitor.EVENTS._NOTICE, data);
};
/**
* Process debug text from cec-client
* @type {function(this:CECMonitor)}
* @param {String} data: debug stdout text
* @private
*/
const _processDebug = function (data) {
if (/TRANSMIT_FAILED_ACK/gu.test(data)) {
return this.emit(CECMonitor.EVENTS._NOHDMICORD);
}
if (this.debug) {
return this.emit(CECMonitor.EVENTS._DEBUG, data);
}
};
/**
* Process warning text from cec-client
* @type {function(this:CECMonitor)}
* @param {String} data: warning stdout text
* @private
*/
const _processWarning = function (data) {
if (/COMMAND_REJECTED/gu.test(data)) {
this.ready = false;
this.auto_restarting = true;
this.Stop();
}
return this.emit(CECMonitor.EVENTS._WARNING, data);
};
/**
* Process error text from cec-client
* @type {function(this:CECMonitor)}
* @param {String} data: error stdout text
* @private
*/
const _processError = function (data) {
return this.emit(CECMonitor.EVENTS._ERROR, data);
};
/**
* Parse any address format and convert to logical address
* @param {number|string} address Address to parse/convert to logical address
* @param {number} def Default logical address if address invalid
*/
const _parseAddress = function (address, def) {
if (typeof address === 'string') {
if (_Validate.default.isHexaNumber(address)) {
address = Number.parseInt(address, 16);
} else if (_Validate.default.isRoute(address)) {
const state = this.state_manager.GetByRoute(address);
address = state ? state.logical : def;
} else if (_HDMICEC.default.LogicalAddress.hasOwnProperty(address.toLocaleUpperCase())) {
address = _HDMICEC.default.LogicalAddress[address.toLocaleUpperCase()];
} else {
address = Number.parseInt(address, 10);
}
}
if (Number.isNaN(address) || address === null) {
address = def;
} else if (address > 15 || address < 0) {
address = def;
}
return address;
};
/*** END INTERNAL FUNCTIONS***/