From 7b334dad55e76d3707e4ef47b5edf75630633466 Mon Sep 17 00:00:00 2001 From: "AMEN-LAPTOP-2\\jamen" Date: Thu, 10 Dec 2020 16:12:14 -0600 Subject: [PATCH 1/2] Add funtion to save and restore the entire routing table to a disk file (text, easy to read). Useful to easily change the base configurations between two different use cases (mine is college baseball and pro baseball). Also incorporated the fall-back routing that allows a destination to revert to whatever its last source was before it was changed. Added a 20 value stack to this functionality. Can be used as a 'key-up' action to make a key "press to show a new route then on release but it back the way it was". --- HELP.md | 4 +- README.md | 3 ++ actions.js | 39 ++++++++++++++ index.js | 139 ++++++++++++++++++++++++++++++++++++++++++++++--- internalAPI.js | 16 ++++-- package.json | 2 +- presets.js | 41 +++++++++++++++ variables.js | 2 +- 8 files changed, 231 insertions(+), 15 deletions(-) diff --git a/HELP.md b/HELP.md index fb84788..ae72412 100644 --- a/HELP.md +++ b/HELP.md @@ -12,4 +12,6 @@ This module will connect to any Blackmagic Design VideoHub Device. * Select destination * Route source to selected destination * Take (when enabled) -* Clear (when enabled) \ No newline at end of file +* Clear (when enabled) +* Write routes to a disk file +* Read routes from a disk file \ No newline at end of file diff --git a/README.md b/README.md index 2c483d4..ed8e071 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # companion-module-bmd-videohub See HELP.md and LICENSE +**Changes in v1.3.0** +- Added ability to read and write routing table to a disk file. +- Added 'Return to previous Route' as a release action **Changes in v1.2.0** - Moved to split files structure for actions, feedback, presets, and variables. - **companion-module-bmd-multiview16 extends this module.** Future changes require testing against both modules. diff --git a/actions.js b/actions.js index b645d65..3730a7c 100644 --- a/actions.js +++ b/actions.js @@ -12,6 +12,45 @@ module.exports = { getActions() { var actions = {}; + actions['route_to_previous'] = { + label: 'Return to previous route', + options: [ + { + type: 'dropdown', + label: 'Destination', + id: 'destination', + default: '0', + choices: this.CHOICES_OUTPUTS + } + ] + }; + + actions['load_route_from_file'] = { + label: 'Load Routes File', + options: [ + + { + type: 'textinput', + label: 'Source File', + id: 'source_file', + default: "C:\\VideoHub.txt" + } + ] + }; + + actions['store_route_in_file'] = { + label: 'Save Routes File', + options: [ + + { + type: 'textinput', + label: 'Destination File', + id: 'destination_file', + default: "C:\\VideoHub.txt" + } + ] + }; + actions['rename_destination'] = { label: 'Rename destination', options: [ diff --git a/index.js b/index.js index c7eb4c0..e8da651 100644 --- a/index.js +++ b/index.js @@ -1,25 +1,31 @@ var tcp = require('../../tcp'); var instance_skel = require('../../instance_skel'); +var fs = require('fs'); var instance_api = require('./internalAPI'); var actions = require('./actions'); var feedback = require('./feedback'); var presets = require('./presets'); var variables = require('./variables'); +const { isNull } = require('util'); +const { getOutput } = require('./internalAPI'); var debug; var log; + /** * Companion instance class for the Blackmagic VideoHub Routers. * * !!! This class is being used by the bmd-multiview16 module, be careful !!! * * @extends instance_skel - * @version 1.2.0 + * @version 1.3.0 * @since 1.0.0 * @author William Viker * @author Keith Rocheck + * @author Peter Schuster + * @author Jim Amen */ class instance extends instance_skel { @@ -70,6 +76,7 @@ class instance extends instance_skel { ]; this.actions(); // export actions + } /** @@ -95,9 +102,79 @@ class instance extends instance_skel { action(action) { var cmd; var opt = action.options; + var string = ""; + var routes_text = []; + var data =[]; // String with route date that gets written to file + var routes = []; // disk data parse helper + var dest_source = []; // disk data parse helper switch (action.action) { + + case 'store_route_in_file': // added ability to store routes to a file v1.3.0 + + string = " : BMD uses zero based indexing when referencing source and destination so '0' in this file refernces port '1'. You may add your own text here after the colon. \n"; + string = "\nRouting history: \n" + + // this information in the disk file is more for debug than anything else. Its ignored on read + for (let index = 0; index < this.outputCount; index++) { + data[index] = index + ' ' + this.getOutput(index).route; + string = string + index + " " + this.getOutput(index).fallback + "\n" + } + try{ + // use the blocking version of file write here. + fs.writeFileSync(opt.destination_file, data + string, 'utf8'); + this.log('info',data.length + " Routes written to: " + opt.destination_file ); + } + catch (e) { + this.log('error',"File Write Error: " + e.message); + } + break; + + case 'load_route_from_file': // added ability to read routes from a file v1.3.0 + + try { + // use the blocking version of file read here. + var data = fs.readFileSync(opt.source_file, 'utf8'); + try{ + routes_text= data.split(':'); // strip out unwanted text after the ':' + routes = routes_text[0].split(','); // split routes into source - destination array + if((routes.length > 0) && (routes.length <= this.outputCount)) { + cmd = ''; + for (let index = 0; index < routes.length; index++) { + dest_source = routes[index].split(' '); + if(isNaN(dest_source[0])) { + throw routes[index] + " - " + dest_source[0] + " is not a valid Router Destination "; + } + if(dest_source[0] < 0 || dest_source[0] > (this.outputCount - 1)) { + throw dest_source[0] + " is an invalid destination. Remember, Router is zero based when indexing ports. Max Routes for this router = " + this.outputCount; + } + if(isNaN(dest_source[1])) { + throw routes[index] + " - " + dest_source[1] + " is not a valid Router Source "; + } + if(dest_source[1] < 0 || dest_source[1] > (this.outputCount - 1)) { + throw dest_source[1] + " is an invalid source. Remember, Router is zero based when indexing ports. Max Routes for this router = " +this.outputCount; + } + cmd = cmd + "VIDEO OUTPUT ROUTING:\n" + routes[index] + "\n\n"; // not sure if we need both \n here... + } + } + else { + throw "Invalid number of Routes: " + routes.length + ","; + } + this.log('info', routes.length + " Routes read from File: " + opt.source_file ); + } + catch (err) { + this.log('error', err + " in File:" + opt.source_file); + } + } + catch(e) { + this.log('error',"File Read Error: " + e.message); + } + break; + case 'route': + + var output = this.getOutput(parseInt(opt.destination)); + if (parseInt(opt.destination) >= this.outputCount) { cmd = "VIDEO MONITORING OUTPUT ROUTING:\n"+(parseInt(opt.destination)-this.outputCount)+" "+opt.source+"\n\n"; } @@ -105,13 +182,45 @@ class instance extends instance_skel { cmd = "VIDEO OUTPUT ROUTING:\n"+opt.destination+" "+opt.source+"\n\n"; } break; + + case 'route_to_previous': + + var output = this.getOutput(parseInt(opt.destination)); + var fallbackpop = -1; + + if(output !== undefined){ + fallbackpop = output.fallback.pop(); // The current route (i.e what the hardware is acctually set to) + // has already been pushed onto the stack at "updateRouting" so to + // get to the last route we have to first pop this one off + fallbackpop = output.fallback.pop(); // This now, is the route to fallback to + + if(output.fallback.length < 1 ){ + output.fallback.push(-1); + } + } + if (output !== undefined && fallbackpop >= 0) { + + if (parseInt(opt.destination) >= this.outputCount) { + cmd = "VIDEO MONITORING OUTPUT ROUTING:\n"+(parseInt(opt.destination)-this.outputCount) + " " + fallbackpop + "\n\n"; + } + else { + cmd = "VIDEO OUTPUT ROUTING:\n"+opt.destination + " " + fallbackpop + "\n\n"; + } + } + break; + case 'route_serial': + cmd = "SERIAL PORT ROUTING:\n"+opt.destination+" "+opt.source+"\n\n"; break; + case 'rename_source': + cmd = "INPUT LABELS:\n"+opt.source+" "+opt.label+"\n\n"; break; + case 'rename_destination': + if (parseInt(opt.destination) >= this.outputCount) { cmd = "MONITORING OUTPUT LABELS:\n"+(parseInt(opt.destination)-this.outputCount)+" "+opt.label+"\n\n"; } @@ -119,16 +228,22 @@ class instance extends instance_skel { cmd = "OUTPUT LABELS:\n"+opt.destination+" "+opt.label+"\n\n"; } break; + case 'rename_serial': + cmd = "SERIAL PORT LABELS:\n"+opt.serial+" "+opt.label+"\n\n"; break; + case 'select_destination': - this.selected = parseInt(opt.destination); + + this.selected = parseInt(opt.destination); this.checkFeedbacks('selected_destination'); this.checkFeedbacks('take_tally_source'); this.checkFeedbacks('selected_source'); break; + case 'route_source': + if (this.selected >= this.outputCount) { if (this.config.take === true) { this.queue = "VIDEO MONITORING OUTPUT ROUTING:\n"+(this.selected-this.outputCount)+" "+opt.source+"\n\n"; @@ -158,6 +273,7 @@ class instance extends instance_skel { } } break; + case 'take': cmd = this.queue; this.queue = ''; @@ -167,6 +283,8 @@ class instance extends instance_skel { this.checkFeedbacks('take_tally_source'); this.checkFeedbacks('take_tally_dest'); this.checkFeedbacks('take_tally_route'); + break; // why no break here originally? jla + case 'clear': this.queue = ''; this.queuedDest = -1; @@ -175,15 +293,21 @@ class instance extends instance_skel { this.checkFeedbacks('take_tally_source'); this.checkFeedbacks('take_tally_dest'); this.checkFeedbacks('take_tally_route'); + break; // why no break here originally? jla } if (cmd !== undefined) { - + // do the work of sending data to Videohub right here! jla if (this.socket !== undefined && this.socket.connected) { - this.socket.send(cmd); + try { + this.socket.send(cmd); + } catch (error) { + this.log('error',"TCP error " + error.message); + } } else { - this.debug('Socket not connected :('); + this.log('error',"Socket not connected "); /// changed to an 'error' instead of debug + init_tcp(); /// what the heck, lets try to reinitialize the tcp stack } } } @@ -225,7 +349,7 @@ class instance extends instance_skel { id: 'info', width: 12, label: 'Information', - value: 'This counts below will automatically populate from the device upon connection, however, can be set manually for offline programming.' + value: 'The counts below will automatically populate from the device upon connection, however, they can be set manually for offline programming.' }, { type: 'number', @@ -409,8 +533,7 @@ class instance extends instance_skel { this.initPresets(); } else if (key.match(/(VIDEO OUTPUT|VIDEO MONITORING OUTPUT|SERIAL PORT) ROUTING/)) { - this.updateRouting(key,data); - + this.updateRouting(key,data); this.checkFeedbacks('input_bg'); this.checkFeedbacks('selected_source'); } diff --git a/internalAPI.js b/internalAPI.js index ff74bcf..3cf9687 100644 --- a/internalAPI.js +++ b/internalAPI.js @@ -8,6 +8,7 @@ module.exports = { * @access protected * @since 1.1.0 */ + getInput(id) { if (this.inputs[id] === undefined) { @@ -38,10 +39,10 @@ module.exports = { name: 'Output ' + (id+1), route: id, status: 'BNC', - lock: 'U' + lock: 'U', + fallback: [-1] }; } - return this.outputs[id]; }, @@ -188,8 +189,9 @@ module.exports = { } }, + /** - * INTERNAL: Updates routing table based on data from the Videohub + * INTERNAL: Updates routing table based on data From the Videohub * * @param {string} labeltype - the command/data type being passed * @param {Object} object - the collected data @@ -208,7 +210,13 @@ module.exports = { case 'VIDEO MONITORING OUTPUT ROUTING': dest = dest + this.outputCount; case 'VIDEO OUTPUT ROUTING': - this.getOutput(dest).route = src; + var output = this.getOutput(parseInt(dest)); + // Lets not let the fallback array grow without bounds. is 20 enough? + if(output.fallback.length > 20){ + output.fallback = output.fallback.slice(2); + } + output.fallback.push(src); // push the route returned from the hardware into the fallback route + output.route = src; // now we set the route in the container to the new value this.setVariable('output_' + (dest+1) + '_input', this.getInput(src).name); break; case 'SERIAL PORT ROUTING': diff --git a/package.json b/package.json index 42c3709..337ed8b 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "legacy": [ "videohub" ], - "version": "1.2.0", + "version": "1.3.0", "api_version": "1.0.0", "keywords": [ "Matrix" ], "manufacturer": "Blackmagic Design", diff --git a/presets.js b/presets.js index bcd295b..395e899 100644 --- a/presets.js +++ b/presets.js @@ -177,6 +177,47 @@ module.exports = { } ] }); + + presets.push({ + category: 'Output ' + (out+1) + ' (momentary)', + label: 'Output ' + (out+1) + ' button for ' + this.getInput(i).name + ' with route back', + bank: { + style: 'text', + text: '$(videohub:input_' + (i+1) + ') (mom.)', + size: '18', + color: this.rgb(255,255,255), + bgcolor: this.rgb(0,0,0) + }, + feedbacks: [ + { + type: 'input_bg', + options: { + bg: this.rgb(255,255,0), + fg: this.rgb(0,0,0), + input: i, + output: out + } + } + ], + actions: [ + { + action: 'route', + options: { + source: i, + destination: out + } + } + ], + release_actions: [ + { + action: 'route_to_previous', + options: { + destination: out + } + } + ] + }); + } } diff --git a/variables.js b/variables.js index 2801f7e..059aeef 100644 --- a/variables.js +++ b/variables.js @@ -1,5 +1,5 @@ module.exports = { - + /** * INTERNAL: initialize variables. * From 8d537f72ed3c63427a40bfcc84243158c2e47432 Mon Sep 17 00:00:00 2001 From: BaseballJim <70243422+BaseballJim@users.noreply.github.com> Date: Sat, 19 Dec 2020 09:24:20 -0600 Subject: [PATCH 2/2] Add files via upload --- index.js | 1240 ++++++++++++++++++++++++------------------------ internalAPI.js | 562 +++++++++++----------- 2 files changed, 887 insertions(+), 915 deletions(-) diff --git a/index.js b/index.js index e8da651..fc1eba8 100644 --- a/index.js +++ b/index.js @@ -1,634 +1,608 @@ -var tcp = require('../../tcp'); -var instance_skel = require('../../instance_skel'); -var fs = require('fs'); - -var instance_api = require('./internalAPI'); -var actions = require('./actions'); -var feedback = require('./feedback'); -var presets = require('./presets'); -var variables = require('./variables'); -const { isNull } = require('util'); -const { getOutput } = require('./internalAPI'); - -var debug; -var log; - - -/** - * Companion instance class for the Blackmagic VideoHub Routers. - * - * !!! This class is being used by the bmd-multiview16 module, be careful !!! - * - * @extends instance_skel - * @version 1.3.0 - * @since 1.0.0 - * @author William Viker - * @author Keith Rocheck - * @author Peter Schuster - * @author Jim Amen - */ -class instance extends instance_skel { - - /** - * Create an instance of a videohub module. - * - * @param {EventEmitter} system - the brains of the operation - * @param {string} id - the instance ID - * @param {Object} config - saved user configuration parameters - * @since 1.0.0 - */ - constructor(system, id, config) { - super(system, id, config); - - this.stash = []; - this.command = null; - this.selected = 0; - this.deviceName = ''; - this.queue = ''; - this.queuedDest = -1; - this.queuedSource = -1; - - Object.assign(this, { - ...actions, - ...feedback, - ...presets, - ...variables, - ...instance_api - }); - - this.inputs = {}; - this.outputs = {}; - this.serials = {}; - - this.inputCount = parseInt(config.inputCount); - this.outputCount = parseInt(config.outputCount); - this.monitoringCount = parseInt(config.monitoringCount); - this.serialCount = parseInt(config.serialCount); - - this.CHOICES_INPUTS = []; - this.CHOICES_OUTPUTS = []; - this.CHOICES_SERIALS = []; - - this.CHOICES_SERIALDIRECTIONS = [ - { id: 'auto', label: 'Automatic' }, - { id: 'control', label: 'In (Workstation)' }, - { id: 'slave', label: 'Out (Deck)' } - ]; - - this.actions(); // export actions - - } - - /** - * Setup the actions. - * - * @param {EventEmitter} system - the brains of the operation - * @access public - * @since 1.0.0 - */ - actions(system) { - - this.setupChoices(); - this.setActions(this.getActions()); - } - - /** - * Executes the provided action. - * - * @param {Object} action - the action to be executed - * @access public - * @since 1.0.0 - */ - action(action) { - var cmd; - var opt = action.options; - var string = ""; - var routes_text = []; - var data =[]; // String with route date that gets written to file - var routes = []; // disk data parse helper - var dest_source = []; // disk data parse helper - - switch (action.action) { - - case 'store_route_in_file': // added ability to store routes to a file v1.3.0 - - string = " : BMD uses zero based indexing when referencing source and destination so '0' in this file refernces port '1'. You may add your own text here after the colon. \n"; - string = "\nRouting history: \n" - - // this information in the disk file is more for debug than anything else. Its ignored on read - for (let index = 0; index < this.outputCount; index++) { - data[index] = index + ' ' + this.getOutput(index).route; - string = string + index + " " + this.getOutput(index).fallback + "\n" - } - try{ - // use the blocking version of file write here. - fs.writeFileSync(opt.destination_file, data + string, 'utf8'); - this.log('info',data.length + " Routes written to: " + opt.destination_file ); - } - catch (e) { - this.log('error',"File Write Error: " + e.message); - } - break; - - case 'load_route_from_file': // added ability to read routes from a file v1.3.0 - - try { - // use the blocking version of file read here. - var data = fs.readFileSync(opt.source_file, 'utf8'); - try{ - routes_text= data.split(':'); // strip out unwanted text after the ':' - routes = routes_text[0].split(','); // split routes into source - destination array - if((routes.length > 0) && (routes.length <= this.outputCount)) { - cmd = ''; - for (let index = 0; index < routes.length; index++) { - dest_source = routes[index].split(' '); - if(isNaN(dest_source[0])) { - throw routes[index] + " - " + dest_source[0] + " is not a valid Router Destination "; - } - if(dest_source[0] < 0 || dest_source[0] > (this.outputCount - 1)) { - throw dest_source[0] + " is an invalid destination. Remember, Router is zero based when indexing ports. Max Routes for this router = " + this.outputCount; - } - if(isNaN(dest_source[1])) { - throw routes[index] + " - " + dest_source[1] + " is not a valid Router Source "; - } - if(dest_source[1] < 0 || dest_source[1] > (this.outputCount - 1)) { - throw dest_source[1] + " is an invalid source. Remember, Router is zero based when indexing ports. Max Routes for this router = " +this.outputCount; - } - cmd = cmd + "VIDEO OUTPUT ROUTING:\n" + routes[index] + "\n\n"; // not sure if we need both \n here... - } - } - else { - throw "Invalid number of Routes: " + routes.length + ","; - } - this.log('info', routes.length + " Routes read from File: " + opt.source_file ); - } - catch (err) { - this.log('error', err + " in File:" + opt.source_file); - } - } - catch(e) { - this.log('error',"File Read Error: " + e.message); - } - break; - - case 'route': - - var output = this.getOutput(parseInt(opt.destination)); - - if (parseInt(opt.destination) >= this.outputCount) { - cmd = "VIDEO MONITORING OUTPUT ROUTING:\n"+(parseInt(opt.destination)-this.outputCount)+" "+opt.source+"\n\n"; - } - else { - cmd = "VIDEO OUTPUT ROUTING:\n"+opt.destination+" "+opt.source+"\n\n"; - } - break; - - case 'route_to_previous': - - var output = this.getOutput(parseInt(opt.destination)); - var fallbackpop = -1; - - if(output !== undefined){ - fallbackpop = output.fallback.pop(); // The current route (i.e what the hardware is acctually set to) - // has already been pushed onto the stack at "updateRouting" so to - // get to the last route we have to first pop this one off - fallbackpop = output.fallback.pop(); // This now, is the route to fallback to - - if(output.fallback.length < 1 ){ - output.fallback.push(-1); - } - } - if (output !== undefined && fallbackpop >= 0) { - - if (parseInt(opt.destination) >= this.outputCount) { - cmd = "VIDEO MONITORING OUTPUT ROUTING:\n"+(parseInt(opt.destination)-this.outputCount) + " " + fallbackpop + "\n\n"; - } - else { - cmd = "VIDEO OUTPUT ROUTING:\n"+opt.destination + " " + fallbackpop + "\n\n"; - } - } - break; - - case 'route_serial': - - cmd = "SERIAL PORT ROUTING:\n"+opt.destination+" "+opt.source+"\n\n"; - break; - - case 'rename_source': - - cmd = "INPUT LABELS:\n"+opt.source+" "+opt.label+"\n\n"; - break; - - case 'rename_destination': - - if (parseInt(opt.destination) >= this.outputCount) { - cmd = "MONITORING OUTPUT LABELS:\n"+(parseInt(opt.destination)-this.outputCount)+" "+opt.label+"\n\n"; - } - else { - cmd = "OUTPUT LABELS:\n"+opt.destination+" "+opt.label+"\n\n"; - } - break; - - case 'rename_serial': - - cmd = "SERIAL PORT LABELS:\n"+opt.serial+" "+opt.label+"\n\n"; - break; - - case 'select_destination': - - this.selected = parseInt(opt.destination); - this.checkFeedbacks('selected_destination'); - this.checkFeedbacks('take_tally_source'); - this.checkFeedbacks('selected_source'); - break; - - case 'route_source': - - if (this.selected >= this.outputCount) { - if (this.config.take === true) { - this.queue = "VIDEO MONITORING OUTPUT ROUTING:\n"+(this.selected-this.outputCount)+" "+opt.source+"\n\n"; - this.queuedDest = (this.selected-this.outputCount); - this.queuedSource = parseInt(opt.source); - this.checkFeedbacks('take'); - this.checkFeedbacks('take_tally_source'); - this.checkFeedbacks('take_tally_dest'); - this.checkFeedbacks('take_tally_route'); - } - else { - cmd = "VIDEO MONITORING OUTPUT ROUTING:\n"+(this.selected-this.outputCount)+" "+opt.source+"\n\n"; - } - } - else { - if (this.config.take === true) { - this.queue = "VIDEO OUTPUT ROUTING:\n"+this.selected+" "+opt.source+"\n\n"; - this.queuedDest = this.selected; - this.queuedSource = parseInt(opt.source); - this.checkFeedbacks('take'); - this.checkFeedbacks('take_tally_source'); - this.checkFeedbacks('take_tally_dest'); - this.checkFeedbacks('take_tally_route'); - } - else { - cmd = "VIDEO OUTPUT ROUTING:\n"+this.selected+" "+opt.source+"\n\n"; - } - } - break; - - case 'take': - cmd = this.queue; - this.queue = ''; - this.queuedDest = -1; - this.queuedSource = -1; - this.checkFeedbacks('take'); - this.checkFeedbacks('take_tally_source'); - this.checkFeedbacks('take_tally_dest'); - this.checkFeedbacks('take_tally_route'); - break; // why no break here originally? jla - - case 'clear': - this.queue = ''; - this.queuedDest = -1; - this.queuedSource = -1; - this.checkFeedbacks('take'); - this.checkFeedbacks('take_tally_source'); - this.checkFeedbacks('take_tally_dest'); - this.checkFeedbacks('take_tally_route'); - break; // why no break here originally? jla - } - - if (cmd !== undefined) { - // do the work of sending data to Videohub right here! jla - if (this.socket !== undefined && this.socket.connected) { - try { - this.socket.send(cmd); - } catch (error) { - this.log('error',"TCP error " + error.message); - } - } - else { - this.log('error',"Socket not connected "); /// changed to an 'error' instead of debug - init_tcp(); /// what the heck, lets try to reinitialize the tcp stack - } - } - } - - /** - * Creates the configuration fields for web config. - * - * @returns {Array} the config fields - * @access public - * @since 1.0.0 - */ - config_fields() { - - return [ - { - type: 'text', - id: 'info', - width: 12, - label: 'Information', - value: 'This module will connect to any Blackmagic Design VideoHub Device.' - }, - { - type: 'textinput', - id: 'host', - label: 'Videohub IP', - width: 6, - default: '192.168.10.150', - regex: this.REGEX_IP - }, - { - type: 'checkbox', - id: 'take', - label: 'Enable Take? (XY only)', - width: 6, - default: false, - }, - { - type: 'text', - id: 'info', - width: 12, - label: 'Information', - value: 'The counts below will automatically populate from the device upon connection, however, they can be set manually for offline programming.' - }, - { - type: 'number', - id: 'inputCount', - label: 'Input Count', - default: 12, - width: 3, - min: 0, - max: 288, - required: true, - range: false - }, - { - type: 'number', - id: 'outputCount', - label: 'Output Count', - default: 12, - width: 3, - min: 0, - max: 288, - required: true, - range: false - }, - { - type: 'number', - id: 'monitoringCount', - label: 'Monitoring Output Count (when present)', - default: 0, - width: 3, - min: 0, - max: 288, - required: true, - range: false - }, - { - type: 'number', - id: 'serialCount', - label: 'Serial Port Count (when present)', - default: 0, - width: 3, - min: 0, - max: 288, - required: true, - range: false - } - ] - } - - /** - * Clean up the instance before it is destroyed. - * - * @access public - * @since 1.0.0 - */ - destroy() { - if (this.socket !== undefined) { - this.socket.destroy(); - } - - this.debug("destroy", this.id); - } - - /** - * Main initialization function called once the module - * is OK to start doing things. - * - * @access public - * @since 1.0.0 - */ - init() { - debug = this.debug; - log = this.log; - - this.initVariables(); - this.initFeedbacks(); - this.initPresets(); - this.checkFeedbacks('selected_destination'); - this.checkFeedbacks('selected_source'); - - this.init_tcp(); - } - - /** - * INTERNAL: use setup data to initalize the tcp socket object. - * - * @access protected - * @since 1.0.0 - */ - init_tcp() { - var receivebuffer = ''; - - if (this.socket !== undefined) { - this.socket.destroy(); - delete this.socket; - } - - if (this.config.port === undefined) { - this.config.port = 9990; - } - - if (this.config.host) { - this.socket = new tcp(this.config.host, this.config.port); - - this.socket.on('status_change', (status, message) => { - this.status(status, message); - }); - - this.socket.on('error', (err) => { - this.debug("Network error", err); - this.log('error',"Network error: " + err.message); - }); - - this.socket.on('connect', () => { - this.debug("Connected"); - }); - - // separate buffered stream into lines with responses - this.socket.on('data', (chunk) => { - var i = 0, line = '', offset = 0; - receivebuffer += chunk; - - while ( (i = receivebuffer.indexOf('\n', offset)) !== -1) { - line = receivebuffer.substr(offset, i - offset); - offset = i + 1; - this.socket.emit('receiveline', line.toString()); - } - - receivebuffer = receivebuffer.substr(offset); - }); - - this.socket.on('receiveline', (line) => { - - if (this.command === null && line.match(/:/) ) { - this.command = line; - } - else if (this.command !== null && line.length > 0) { - this.stash.push(line.trim()); - } - else if (line.length === 0 && this.command !== null) { - var cmd = this.command.trim().split(/:/)[0]; - - this.processVideohubInformation(cmd, this.stash); - - this.stash = []; - this.command = null; - } - else { - this.debug("weird response from videohub", line, line.length); - } - }); - } - } - - /** - * INTERNAL: initialize feedbacks. - * - * @access protected - * @since 1.1.0 - */ - initFeedbacks() { - // feedbacks - var feedbacks = this.getFeedbacks(); - - this.setFeedbackDefinitions(feedbacks); - } - - /** - * INTERNAL: Routes incoming data to the appropriate function for processing. - * - * @param {string} key - the command/data type being passed - * @param {Object} data - the collected data - * @access protected - * @since 1.0.0 - */ - processVideohubInformation(key,data) { - - if (key.match(/(INPUT|OUTPUT|MONITORING OUTPUT|SERIAL PORT) LABELS/)) { - this.updateLabels(key,data); - this.actions(); - this.initFeedbacks(); - this.initPresets(); - } - else if (key.match(/(VIDEO OUTPUT|VIDEO MONITORING OUTPUT|SERIAL PORT) ROUTING/)) { - this.updateRouting(key,data); - this.checkFeedbacks('input_bg'); - this.checkFeedbacks('selected_source'); - } - else if (key.match(/(VIDEO OUTPUT|VIDEO MONITORING OUTPUT|SERIAL PORT) LOCKS/)) { - this.updateLocks(key,data); - } - else if (key.match(/(VIDEO INPUT|VIDEO OUTPUT|SERIAL PORT) STATUS/)) { - this.updateStatus(key,data); - this.actions(); - this.initFeedbacks(); - this.initPresets(); - } - else if (key == 'SERIAL PORT DIRECTIONS') { - this.updateSerialDirections(key,data); - } - else if (key == 'VIDEOHUB DEVICE') { - this.updateDevice(key,data); - this.actions(); - this.initVariables(); - this.initFeedbacks(); - this.initPresets(); - } - else { - // TODO: find out more about the video hub from stuff that comes in here - } - } - - /** - * INTERNAL: use model data to define the choices for the dropdowns. - * - * @access protected - * @since 1.1.0 - */ - setupChoices() { - - this.CHOICES_INPUTS = []; - this.CHOICES_OUTPUTS = []; - this.CHOICES_SERIALS = []; - - if (this.inputCount > 0) { - for(var key = 0; key < this.inputCount; key++) { - if (this.getInput(key).status != 'None') { - this.CHOICES_INPUTS.push( { id: key, label: this.getInput(key).label } ); - } - } - } - - if (this.outputCount > 0) { - for(var key = 0; key < (this.outputCount + this.monitoringCount); key++) { - if (this.getOutput(key).status != 'None') { - this.CHOICES_OUTPUTS.push( { id: key, label: this.getOutput(key).label } ); - } - } - } - - if (this.serialCount > 0) { - for(var key = 0; key < this.serialCount; key++) { - if (this.getSerial(key).status != 'None') { - this.CHOICES_SERIALS.push( { id: key, label: this.getSerial(key).label } ); - } - } - } - } - - /** - * Process an updated configuration array. - * - * @param {Object} config - the new configuration - * @access public - * @since 1.0.0 - */ - updateConfig(config) { - var resetConnection = false; - - if (this.config.host != config.host) - { - resetConnection = true; - } - - this.config = config; - - this.inputCount = parseInt(this.config.inputCount); - this.outputCount = parseInt(this.config.outputCount); - this.monitoringCount = parseInt(this.config.monitoringCount); - this.serialCount = parseInt(this.config.serialCount); - - this.actions(); - this.initFeedbacks(); - this.initPresets(); - this.initVariables(); - - if (resetConnection === true || this.socket === undefined) { - this.init_tcp(); - } - } -} - +var tcp = require('../../tcp'); +var instance_skel = require('../../instance_skel'); +var fs = require('fs'); + +var instance_api = require('./internalAPI'); +var actions = require('./actions'); +var feedback = require('./feedback'); +var presets = require('./presets'); +var variables = require('./variables'); + +var debug; +var log; + +/** + * Companion instance class for the Blackmagic VideoHub Routers. + * + * !!! This class is being used by the bmd-multiview16 module, be careful !!! + * + * @extends instance_skel + * @version 1.3.0 + * @since 1.0.0 + * @author William Viker + * @author Keith Rocheck + * @author Peter Schuster + * @author Jim Amen + */ +class instance extends instance_skel { + + /** + * Create an instance of a videohub module. + * + * @param {EventEmitter} system - the brains of the operation + * @param {string} id - the instance ID + * @param {Object} config - saved user configuration parameters + * @since 1.0.0 + */ + constructor(system, id, config) { + super(system, id, config); + + this.stash = []; + this.command = null; + this.selected = 0; + this.deviceName = ''; + this.queue = ''; + this.queuedDest = -1; + this.queuedSource = -1; + + Object.assign(this, { + ...actions, + ...feedback, + ...presets, + ...variables, + ...instance_api + }); + + this.inputs = {}; + this.outputs = {}; + this.serials = {}; + + this.inputCount = parseInt(config.inputCount); + this.outputCount = parseInt(config.outputCount); + this.monitoringCount = parseInt(config.monitoringCount); + this.serialCount = parseInt(config.serialCount); + + this.CHOICES_INPUTS = []; + this.CHOICES_OUTPUTS = []; + this.CHOICES_SERIALS = []; + + this.CHOICES_SERIALDIRECTIONS = [ + { id: 'auto', label: 'Automatic' }, + { id: 'control', label: 'In (Workstation)' }, + { id: 'slave', label: 'Out (Deck)' } + ]; + + this.actions(); // export actions + } + + /** + * Setup the actions. + * + * @param {EventEmitter} system - the brains of the operation + * @access public + * @since 1.0.0 + */ + actions(system) { + + this.setupChoices(); + this.setActions(this.getActions()); + } + + /** + * Executes the provided action. + * + * @param {Object} action - the action to be executed + * @access public + * @since 1.0.0 + */ + action(action) { + var cmd; + var opt = action.options; + var string = ""; + var routes_text = []; + var data =[]; + var routes = []; + var dest_source = []; + + switch (action.action) { + + case 'store_route_in_file': + string = " : BMD uses zero based indexing when referencing source and destination so '0' in this file references port '1'. You may add your own text here after the colon. \n"; + string = string + "\nRouting history: \n" + + for (let index = 0; index < this.outputCount; index++) { + data[index] = index + ' ' + this.getOutput(index).route; + string = string + index + " " + this.getOutput(index).fallback + "\n" + } + try{ + fs.writeFileSync(opt.destination_file, data + string, 'utf8'); + this.log('info',data.length + " Routes written to: " + opt.destination_file ); + } + catch (e) { + this.log('error',"File Write Error: " + e.message); + } + break; + + case 'load_route_from_file': + try { + var data = fs.readFileSync(opt.source_file, 'utf8'); + try{ + routes_text= data.split(':'); + routes = routes_text[0].split(','); + if((routes.length > 0) && (routes.length <= this.outputCount)) { + cmd = ''; + for (let index = 0; index < routes.length; index++) { + dest_source = routes[index].split(' '); + if(isNaN(dest_source[0])) { + throw routes[index] + " - " + dest_source[0] + " is not a valid Router Destination "; + } + if(dest_source[0] < 0 || dest_source[0] > (this.outputCount - 1)) { + throw dest_source[0] + " is an invalid destination. Remember, Router is zero based when indexing ports. Max Routes for this router = " + this.outputCount; + } + if(isNaN(dest_source[1])) { + throw routes[index] + " - " + dest_source[1] + " is not a valid Router Source "; + } + if(dest_source[1] < 0 || dest_source[1] > (this.outputCount - 1)) { + throw dest_source[1] + " is an invalid source. Remember, Router is zero based when indexing ports. Max Routes for this router = " +this.outputCount; + } + cmd = cmd + "VIDEO OUTPUT ROUTING:\n" + routes[index] + "\n\n"; + } + } + else { + throw "Invalid number of Routes: " + routes.length + ","; + } + this.log('info', routes.length + " Routes read from File: " + opt.source_file ); + } + catch (err) { + this.log('error', err + " in File:" + opt.source_file); + } + } + catch(e) { + this.log('error',"File Read Error: " + e.message); + } + break; + + case 'route': + var output = this.getOutput(parseInt(opt.destination)); + + if (parseInt(opt.destination) >= this.outputCount) { + cmd = "VIDEO MONITORING OUTPUT ROUTING:\n"+(parseInt(opt.destination)-this.outputCount)+" "+opt.source+"\n\n"; + } + else { + cmd = "VIDEO OUTPUT ROUTING:\n"+opt.destination+" "+opt.source+"\n\n"; + } + break; + + case 'route_to_previous': + var output = this.getOutput(parseInt(opt.destination)); + var fallbackpop = -1; + + fallbackpop = output.fallback.pop(); // The current route (i.e what the hardware is actually set to) + // has already been pushed onto the stack at "updateRouting" so to + // get to the last route we have to first pop this one off. + fallbackpop = output.fallback.pop(); // This now, is the route to fallback to. + + if(output.fallback.length < 1 ){ + output.fallback.push(-1); + } + + if (fallbackpop >= 0) { + + if (parseInt(opt.destination) >= this.outputCount) { + cmd = "VIDEO MONITORING OUTPUT ROUTING:\n"+(parseInt(opt.destination)-this.outputCount) + " " + fallbackpop + "\n\n"; + } + else { + cmd = "VIDEO OUTPUT ROUTING:\n"+opt.destination + " " + fallbackpop + "\n\n"; + } + } + break; + + case 'route_serial': + cmd = "SERIAL PORT ROUTING:\n"+opt.destination+" "+opt.source+"\n\n"; + break; + case 'rename_source': + cmd = "INPUT LABELS:\n"+opt.source+" "+opt.label+"\n\n"; + break; + case 'rename_destination': + if (parseInt(opt.destination) >= this.outputCount) { + cmd = "MONITORING OUTPUT LABELS:\n"+(parseInt(opt.destination)-this.outputCount)+" "+opt.label+"\n\n"; + } + else { + cmd = "OUTPUT LABELS:\n"+opt.destination+" "+opt.label+"\n\n"; + } + break; + case 'rename_serial': + cmd = "SERIAL PORT LABELS:\n"+opt.serial+" "+opt.label+"\n\n"; + break; + case 'select_destination': + this.selected = parseInt(opt.destination); + this.checkFeedbacks('selected_destination'); + this.checkFeedbacks('take_tally_source'); + this.checkFeedbacks('selected_source'); + break; + case 'route_source': + if (this.selected >= this.outputCount) { + if (this.config.take === true) { + this.queue = "VIDEO MONITORING OUTPUT ROUTING:\n"+(this.selected-this.outputCount)+" "+opt.source+"\n\n"; + this.queuedDest = (this.selected-this.outputCount); + this.queuedSource = parseInt(opt.source); + this.checkFeedbacks('take'); + this.checkFeedbacks('take_tally_source'); + this.checkFeedbacks('take_tally_dest'); + this.checkFeedbacks('take_tally_route'); + } + else { + cmd = "VIDEO MONITORING OUTPUT ROUTING:\n"+(this.selected-this.outputCount)+" "+opt.source+"\n\n"; + } + } + else { + if (this.config.take === true) { + this.queue = "VIDEO OUTPUT ROUTING:\n"+this.selected+" "+opt.source+"\n\n"; + this.queuedDest = this.selected; + this.queuedSource = parseInt(opt.source); + this.checkFeedbacks('take'); + this.checkFeedbacks('take_tally_source'); + this.checkFeedbacks('take_tally_dest'); + this.checkFeedbacks('take_tally_route'); + } + else { + cmd = "VIDEO OUTPUT ROUTING:\n"+this.selected+" "+opt.source+"\n\n"; + } + } + break; + case 'take': + cmd = this.queue; + this.queue = ''; + this.queuedDest = -1; + this.queuedSource = -1; + this.checkFeedbacks('take'); + this.checkFeedbacks('take_tally_source'); + this.checkFeedbacks('take_tally_dest'); + this.checkFeedbacks('take_tally_route'); + break; + case 'clear': + this.queue = ''; + this.queuedDest = -1; + this.queuedSource = -1; + this.checkFeedbacks('take'); + this.checkFeedbacks('take_tally_source'); + this.checkFeedbacks('take_tally_dest'); + this.checkFeedbacks('take_tally_route'); + break; + } + + if (cmd !== undefined) { + if (this.socket !== undefined && this.socket.connected) { + try { + this.socket.send(cmd); + } catch (error) { + this.log('error',"TCP error " + error.message); + } + } + else { + this.log('error',"Socket not connected "); + this.init_tcp(); + } + } + } + + /** + * Creates the configuration fields for web config. + * + * @returns {Array} the config fields + * @access public + * @since 1.0.0 + */ + config_fields() { + + return [ + { + type: 'text', + id: 'info', + width: 12, + label: 'Information', + value: 'This module will connect to any Blackmagic Design VideoHub Device.' + }, + { + type: 'textinput', + id: 'host', + label: 'Videohub IP', + width: 6, + default: '192.168.10.150', + regex: this.REGEX_IP + }, + { + type: 'checkbox', + id: 'take', + label: 'Enable Take? (XY only)', + width: 6, + default: false, + }, + { + type: 'text', + id: 'info', + width: 12, + label: 'Information', + value: 'The counts below will automatically populate from the device upon connection, however, they can be set manually for offline programming.' + }, + { + type: 'number', + id: 'inputCount', + label: 'Input Count', + default: 12, + width: 3, + min: 0, + max: 288, + required: true, + range: false + }, + { + type: 'number', + id: 'outputCount', + label: 'Output Count', + default: 12, + width: 3, + min: 0, + max: 288, + required: true, + range: false + }, + { + type: 'number', + id: 'monitoringCount', + label: 'Monitoring Output Count (when present)', + default: 0, + width: 3, + min: 0, + max: 288, + required: true, + range: false + }, + { + type: 'number', + id: 'serialCount', + label: 'Serial Port Count (when present)', + default: 0, + width: 3, + min: 0, + max: 288, + required: true, + range: false + } + ] + } + + /** + * Clean up the instance before it is destroyed. + * + * @access public + * @since 1.0.0 + */ + destroy() { + if (this.socket !== undefined) { + this.socket.destroy(); + } + + this.debug("destroy", this.id); + } + + /** + * Main initialization function called once the module + * is OK to start doing things. + * + * @access public + * @since 1.0.0 + */ + init() { + debug = this.debug; + log = this.log; + + this.initVariables(); + this.initFeedbacks(); + this.initPresets(); + this.checkFeedbacks('selected_destination'); + this.checkFeedbacks('selected_source'); + + this.init_tcp(); + } + + /** + * INTERNAL: use setup data to initalize the tcp socket object. + * + * @access protected + * @since 1.0.0 + */ + init_tcp() { + var receivebuffer = ''; + + if (this.socket !== undefined) { + this.socket.destroy(); + delete this.socket; + } + + if (this.config.port === undefined) { + this.config.port = 9990; + } + + if (this.config.host) { + this.socket = new tcp(this.config.host, this.config.port); + + this.socket.on('status_change', (status, message) => { + this.status(status, message); + }); + + this.socket.on('error', (err) => { + this.debug("Network error", err); + this.log('error',"Network error: " + err.message); + }); + + this.socket.on('connect', () => { + this.debug("Connected"); + }); + + // separate buffered stream into lines with responses + this.socket.on('data', (chunk) => { + var i = 0, line = '', offset = 0; + receivebuffer += chunk; + + while ( (i = receivebuffer.indexOf('\n', offset)) !== -1) { + line = receivebuffer.substr(offset, i - offset); + offset = i + 1; + this.socket.emit('receiveline', line.toString()); + } + + receivebuffer = receivebuffer.substr(offset); + }); + + this.socket.on('receiveline', (line) => { + + if (this.command === null && line.match(/:/) ) { + this.command = line; + } + else if (this.command !== null && line.length > 0) { + this.stash.push(line.trim()); + } + else if (line.length === 0 && this.command !== null) { + var cmd = this.command.trim().split(/:/)[0]; + + this.processVideohubInformation(cmd, this.stash); + + this.stash = []; + this.command = null; + } + else { + this.debug("weird response from videohub", line, line.length); + } + }); + } + } + + /** + * INTERNAL: initialize feedbacks. + * + * @access protected + * @since 1.1.0 + */ + initFeedbacks() { + // feedbacks + var feedbacks = this.getFeedbacks(); + + this.setFeedbackDefinitions(feedbacks); + } + + /** + * INTERNAL: Routes incoming data to the appropriate function for processing. + * + * @param {string} key - the command/data type being passed + * @param {Object} data - the collected data + * @access protected + * @since 1.0.0 + */ + processVideohubInformation(key,data) { + + if (key.match(/(INPUT|OUTPUT|MONITORING OUTPUT|SERIAL PORT) LABELS/)) { + this.updateLabels(key,data); + this.actions(); + this.initFeedbacks(); + this.initPresets(); + } + else if (key.match(/(VIDEO OUTPUT|VIDEO MONITORING OUTPUT|SERIAL PORT) ROUTING/)) { + this.updateRouting(key,data); + this.checkFeedbacks('input_bg'); + this.checkFeedbacks('selected_source'); + } + else if (key.match(/(VIDEO OUTPUT|VIDEO MONITORING OUTPUT|SERIAL PORT) LOCKS/)) { + this.updateLocks(key,data); + } + else if (key.match(/(VIDEO INPUT|VIDEO OUTPUT|SERIAL PORT) STATUS/)) { + this.updateStatus(key,data); + this.actions(); + this.initFeedbacks(); + this.initPresets(); + } + else if (key == 'SERIAL PORT DIRECTIONS') { + this.updateSerialDirections(key,data); + } + else if (key == 'VIDEOHUB DEVICE') { + this.updateDevice(key,data); + this.actions(); + this.initVariables(); + this.initFeedbacks(); + this.initPresets(); + } + else { + // TODO: find out more about the video hub from stuff that comes in here + } + } + + /** + * INTERNAL: use model data to define the choices for the dropdowns. + * + * @access protected + * @since 1.1.0 + */ + setupChoices() { + + this.CHOICES_INPUTS = []; + this.CHOICES_OUTPUTS = []; + this.CHOICES_SERIALS = []; + + if (this.inputCount > 0) { + for(var key = 0; key < this.inputCount; key++) { + if (this.getInput(key).status != 'None') { + this.CHOICES_INPUTS.push( { id: key, label: this.getInput(key).label } ); + } + } + } + + if (this.outputCount > 0) { + for(var key = 0; key < (this.outputCount + this.monitoringCount); key++) { + if (this.getOutput(key).status != 'None') { + this.CHOICES_OUTPUTS.push( { id: key, label: this.getOutput(key).label } ); + } + } + } + + if (this.serialCount > 0) { + for(var key = 0; key < this.serialCount; key++) { + if (this.getSerial(key).status != 'None') { + this.CHOICES_SERIALS.push( { id: key, label: this.getSerial(key).label } ); + } + } + } + } + + /** + * Process an updated configuration array. + * + * @param {Object} config - the new configuration + * @access public + * @since 1.0.0 + */ + updateConfig(config) { + var resetConnection = false; + + if (this.config.host != config.host) + { + resetConnection = true; + } + + this.config = config; + + this.inputCount = parseInt(this.config.inputCount); + this.outputCount = parseInt(this.config.outputCount); + this.monitoringCount = parseInt(this.config.monitoringCount); + this.serialCount = parseInt(this.config.serialCount); + + this.actions(); + this.initFeedbacks(); + this.initPresets(); + this.initVariables(); + + if (resetConnection === true || this.socket === undefined) { + this.init_tcp(); + } + } +} + exports = module.exports = instance; \ No newline at end of file diff --git a/internalAPI.js b/internalAPI.js index 3cf9687..86cea9d 100644 --- a/internalAPI.js +++ b/internalAPI.js @@ -1,283 +1,281 @@ -module.exports = { - - /** - * INTERNAL: returns the desired input object. - * - * @param {number} id - the input to fetch - * @returns {Object} the desired input object - * @access protected - * @since 1.1.0 - */ - - getInput(id) { - - if (this.inputs[id] === undefined) { - this.inputs[id] = { - label: (id+1) + ': Input ' + (id+1), - name: 'Input ' + (id+1), - status: 'BNC', - lock: 'U' - }; - } - - return this.inputs[id]; - }, - - /** - * INTERNAL: returns the desired output object. - * - * @param {number} id - the output to fetch - * @returns {Object} the desired output object - * @access protected - * @since 1.1.0 - */ - getOutput(id) { - - if (this.outputs[id] === undefined) { - this.outputs[id] = { - label: (id+1) + ': Output ' + (id+1), - name: 'Output ' + (id+1), - route: id, - status: 'BNC', - lock: 'U', - fallback: [-1] - }; - } - return this.outputs[id]; - }, - - /** - * INTERNAL: returns the desired serial port object. - * - * @param {number} id - the serial port to fetch - * @returns {Object} the desired serial port object - * @access protected - * @since 1.1.0 - */ - getSerial(id) { - - if (this.serials[id] === undefined) { - this.serials[id] = { - label: (id+1) + ': Serial ' + (id+1), - name: 'Serial ' + (id+1), - route: id, - status: 'RS422', - lock: 'U', - directions: 'auto' - }; - } - - return this.serials[id]; - }, - - /** - * INTERNAL: Updates device data from the Videohub - * - * @param {string} labeltype - the command/data type being passed - * @param {Object} object - the collected data - * @access protected - * @since 1.1.0 - */ - updateDevice(labeltype, object) { - - for (var key in object) { - var parsethis = object[key]; - var a = parsethis.split(/: /); - var attribute = a.shift(); - var value = a.join(" "); - - switch (attribute) { - case 'Model name': - this.deviceName = value; - this.log('info', 'Connected to a ' + this.deviceName); - break; - case 'Video inputs': - this.config.inputCount = value; - break; - case 'Video outputs': - this.config.outputCount = value; - break; - case 'Video monitoring outputs': - this.config.monitoringCount = value; - break; - case 'Serial ports': - this.config.serialCount = value; - break; - } - } - - this.saveConfig(); - }, - - /** - * INTERNAL: Updates variables based on data from the Videohub - * - * @param {string} labeltype - the command/data type being passed - * @param {Object} object - the collected data - * @access protected - * @since 1.0.0 - */ - updateLabels(labeltype, object) { - - for (var key in object) { - var parsethis = object[key]; - var a = parsethis.split(/ /); - var num = parseInt(a.shift()); - var label = a.join(" "); - - switch (labeltype) { - case 'INPUT LABELS': - this.getInput(num).name = label; - this.getInput(num).label = (num+1).toString() + ': ' + label; - this.setVariable('input_' + (num+1), label); - break; - case 'MONITORING OUTPUT LABELS': - num = num + this.outputCount; - case 'OUTPUT LABELS': - this.getOutput(num).name = label; - this.getOutput(num).label = (num+1).toString() + ': ' + label; - this.setVariable('output_' + (num+1), label); - break; - case 'SERIAL PORT LABELS': - this.getSerial(num).name = label; - this.getSerial(num).label = (num+1).toString() + ': ' + label; - this.setVariable('serial_' + (num+1), label); - break; - } - } - - if (labeltype == 'INPUT LABELS') { - - for (var i = 0; i < (this.outputCount + this.monitoringCount); i++) { - - if (this.getOutput(i).status != 'None') { - - this.setVariable('output_' + (i+1) + '_input', this.getInput(this.getOutput(i).route).name); - } - } - - this.setVariable('selected_source', this.getInput(this.getOutput(this.selected).route).name); - } - }, - - /** - * INTERNAL: Updates lock states based on data from the Videohub - * - * @param {string} labeltype - the command/data type being passed - * @param {Object} object - the collected data - * @access protected - * @since 1.1.0 - */ - updateLocks(labeltype, object) { - - for (var key in object) { - var parsethis = object[key]; - var a = parsethis.split(/ /); - var num = parseInt(a.shift()); - var label = a.join(" "); - - switch (labeltype) { - case 'MONITORING OUTPUT LOCKS': - num = num + this.outputCount; - case 'VIDEO OUTPUT LOCKS': - this.getOutput(num).lock = label; - break; - case 'SERIAL PORT LOCKS': - this.getSerial(num).lock = label; - break; - } - } - }, - - - /** - * INTERNAL: Updates routing table based on data From the Videohub - * - * @param {string} labeltype - the command/data type being passed - * @param {Object} object - the collected data - * @access protected - * @since 1.0.0 - */ - updateRouting(labeltype, object) { - - for (var key in object) { - var parsethis = object[key]; - var a = parsethis.split(/ /); - var dest = parseInt(a.shift()); - var src = parseInt(a.join(" ")); - - switch (labeltype) { - case 'VIDEO MONITORING OUTPUT ROUTING': - dest = dest + this.outputCount; - case 'VIDEO OUTPUT ROUTING': - var output = this.getOutput(parseInt(dest)); - // Lets not let the fallback array grow without bounds. is 20 enough? - if(output.fallback.length > 20){ - output.fallback = output.fallback.slice(2); - } - output.fallback.push(src); // push the route returned from the hardware into the fallback route - output.route = src; // now we set the route in the container to the new value - this.setVariable('output_' + (dest+1) + '_input', this.getInput(src).name); - break; - case 'SERIAL PORT ROUTING': - this.getSerial(dest).route = src - this.setVariable('serial_' + (dest+1) + '_route', this.getSerial(src).name); - break; - } - } - }, - - /** - * INTERNAL: Updates serial port directions based on data from the Videohub - * - * @param {string} labeltype - the command/data type being passed - * @param {Object} object - the collected data - * @access protected - * @since 1.1.0 - */ - updateSerialDirections(labeltype, object) { - - for (var key in object) { - var parsethis = object[key]; - var a = parsethis.split(/ /); - var num = parseInt(a.shift()); - var type = a.join(" "); - - switch (labeltype) { - case 'SERIAL PORT DIRECTIONS': - this.getSerial(num).direction = type; - break; - } - } - }, - - /** - * INTERNAL: Updates variables based on data from the Videohub - * - * @param {string} labeltype - the command/data type being passed - * @param {Object} object - the collected data - * @access protected - * @since 1.1.0 - */ - updateStatus(labeltype, object) { - - for (var key in object) { - var parsethis = object[key]; - var a = parsethis.split(/ /); - var num = parseInt(a.shift()); - var label = a.join(" "); - - switch (labeltype) { - case 'VIDEO INPUT STATUS': - this.getInput(num).status = label; - break; - case 'VIDEO OUTPUT STATUS': - this.getOutput(num).status = label; - break; - case 'SERIAL PORT STATUS': - this.getSerial(num).status = label; - break; - } - } - } +module.exports = { + + /** + * INTERNAL: returns the desired input object. + * + * @param {number} id - the input to fetch + * @returns {Object} the desired input object + * @access protected + * @since 1.1.0 + */ + getInput(id) { + + if (this.inputs[id] === undefined) { + this.inputs[id] = { + label: (id+1) + ': Input ' + (id+1), + name: 'Input ' + (id+1), + status: 'BNC', + lock: 'U' + }; + } + + return this.inputs[id]; + }, + + /** + * INTERNAL: returns the desired output object. + * + * @param {number} id - the output to fetch + * @returns {Object} the desired output object + * @access protected + * @since 1.1.0 + */ + getOutput(id) { + + if (this.outputs[id] === undefined) { + this.outputs[id] = { + label: (id+1) + ': Output ' + (id+1), + name: 'Output ' + (id+1), + route: id, + status: 'BNC', + lock: 'U', + fallback: [-1] + }; + } + return this.outputs[id]; + }, + + /** + * INTERNAL: returns the desired serial port object. + * + * @param {number} id - the serial port to fetch + * @returns {Object} the desired serial port object + * @access protected + * @since 1.1.0 + */ + getSerial(id) { + + if (this.serials[id] === undefined) { + this.serials[id] = { + label: (id+1) + ': Serial ' + (id+1), + name: 'Serial ' + (id+1), + route: id, + status: 'RS422', + lock: 'U', + directions: 'auto' + }; + } + + return this.serials[id]; + }, + + /** + * INTERNAL: Updates device data from the Videohub + * + * @param {string} labeltype - the command/data type being passed + * @param {Object} object - the collected data + * @access protected + * @since 1.1.0 + */ + updateDevice(labeltype, object) { + + for (var key in object) { + var parsethis = object[key]; + var a = parsethis.split(/: /); + var attribute = a.shift(); + var value = a.join(" "); + + switch (attribute) { + case 'Model name': + this.deviceName = value; + this.log('info', 'Connected to a ' + this.deviceName); + break; + case 'Video inputs': + this.config.inputCount = value; + break; + case 'Video outputs': + this.config.outputCount = value; + break; + case 'Video monitoring outputs': + this.config.monitoringCount = value; + break; + case 'Serial ports': + this.config.serialCount = value; + break; + } + } + + this.saveConfig(); + }, + + /** + * INTERNAL: Updates variables based on data from the Videohub + * + * @param {string} labeltype - the command/data type being passed + * @param {Object} object - the collected data + * @access protected + * @since 1.0.0 + */ + updateLabels(labeltype, object) { + + for (var key in object) { + var parsethis = object[key]; + var a = parsethis.split(/ /); + var num = parseInt(a.shift()); + var label = a.join(" "); + + switch (labeltype) { + case 'INPUT LABELS': + this.getInput(num).name = label; + this.getInput(num).label = (num+1).toString() + ': ' + label; + this.setVariable('input_' + (num+1), label); + break; + case 'MONITORING OUTPUT LABELS': + num = num + this.outputCount; + case 'OUTPUT LABELS': + this.getOutput(num).name = label; + this.getOutput(num).label = (num+1).toString() + ': ' + label; + this.setVariable('output_' + (num+1), label); + break; + case 'SERIAL PORT LABELS': + this.getSerial(num).name = label; + this.getSerial(num).label = (num+1).toString() + ': ' + label; + this.setVariable('serial_' + (num+1), label); + break; + } + } + + if (labeltype == 'INPUT LABELS') { + + for (var i = 0; i < (this.outputCount + this.monitoringCount); i++) { + + if (this.getOutput(i).status != 'None') { + + this.setVariable('output_' + (i+1) + '_input', this.getInput(this.getOutput(i).route).name); + } + } + + this.setVariable('selected_source', this.getInput(this.getOutput(this.selected).route).name); + } + }, + + /** + * INTERNAL: Updates lock states based on data from the Videohub + * + * @param {string} labeltype - the command/data type being passed + * @param {Object} object - the collected data + * @access protected + * @since 1.1.0 + */ + updateLocks(labeltype, object) { + + for (var key in object) { + var parsethis = object[key]; + var a = parsethis.split(/ /); + var num = parseInt(a.shift()); + var label = a.join(" "); + + switch (labeltype) { + case 'MONITORING OUTPUT LOCKS': + num = num + this.outputCount; + case 'VIDEO OUTPUT LOCKS': + this.getOutput(num).lock = label; + break; + case 'SERIAL PORT LOCKS': + this.getSerial(num).lock = label; + break; + } + } + }, + + /** + * INTERNAL: Updates Companion's routing table based on data sent from the Videohub + * + * @param {string} labeltype - the command/data type being passed + * @param {Object} object - the collected data + * @access protected + * @since 1.0.0 + */ + updateRouting(labeltype, object) { + + for (var key in object) { + var parsethis = object[key]; + var a = parsethis.split(/ /); + var dest = parseInt(a.shift()); + var src = parseInt(a.join(" ")); + + switch (labeltype) { + case 'VIDEO MONITORING OUTPUT ROUTING': + dest = dest + this.outputCount; + case 'VIDEO OUTPUT ROUTING': + var output = this.getOutput(parseInt(dest)); + // Lets not let the fallback array grow without bounds. is 20 enough? + if(output.fallback.length > 20){ + output.fallback = output.fallback.slice(2); + } + output.fallback.push(src); // push the route returned from the hardware into the fallback route + output.route = src; // now we set the route in the container to the new value + this.setVariable('output_' + (dest+1) + '_input', this.getInput(src).name); + break; + case 'SERIAL PORT ROUTING': + this.getSerial(dest).route = src + this.setVariable('serial_' + (dest+1) + '_route', this.getSerial(src).name); + break; + } + } + }, + + /** + * INTERNAL: Updates serial port directions based on data from the Videohub + * + * @param {string} labeltype - the command/data type being passed + * @param {Object} object - the collected data + * @access protected + * @since 1.1.0 + */ + updateSerialDirections(labeltype, object) { + + for (var key in object) { + var parsethis = object[key]; + var a = parsethis.split(/ /); + var num = parseInt(a.shift()); + var type = a.join(" "); + + switch (labeltype) { + case 'SERIAL PORT DIRECTIONS': + this.getSerial(num).direction = type; + break; + } + } + }, + + /** + * INTERNAL: Updates variables based on data from the Videohub + * + * @param {string} labeltype - the command/data type being passed + * @param {Object} object - the collected data + * @access protected + * @since 1.1.0 + */ + updateStatus(labeltype, object) { + + for (var key in object) { + var parsethis = object[key]; + var a = parsethis.split(/ /); + var num = parseInt(a.shift()); + var label = a.join(" "); + + switch (labeltype) { + case 'VIDEO INPUT STATUS': + this.getInput(num).status = label; + break; + case 'VIDEO OUTPUT STATUS': + this.getOutput(num).status = label; + break; + case 'SERIAL PORT STATUS': + this.getSerial(num).status = label; + break; + } + } + } } \ No newline at end of file