diff --git a/plugins/scs/icon.png b/plugins/scs/icon.png new file mode 100644 index 0000000..383037a Binary files /dev/null and b/plugins/scs/icon.png differ diff --git a/plugins/scs/info.html b/plugins/scs/info.html new file mode 100644 index 0000000..9863b00 --- /dev/null +++ b/plugins/scs/info.html @@ -0,0 +1,4 @@ +

Connection Requirements

+ diff --git a/plugins/scs/main.js b/plugins/scs/main.js new file mode 100644 index 0000000..638a545 --- /dev/null +++ b/plugins/scs/main.js @@ -0,0 +1,187 @@ +exports.config = { + defaultName: 'Show Cue Systems', + connectionType: 'TCPsocket', + defaultPort: 58000, + mayChangePort: true, + heartbeatInterval: 5000, + searchOptions: { + type: 'TCPport', + searchBuffer: Buffer.from('/_status', 'ascii'), + testPort: 58000, + validateResponse(msg, info) { + return msg.toString().includes('/_status'); + }, + }, + fields: [], +}; + +const typeMap = { + A: 'Video/Image', + E: 'Memo', + F: 'Audio File', + G: 'GoTo Cue', + H: 'Multi-File Cue', + I: 'Live Input', + J: 'Enable/Disable', + K: 'Lighting', + L: 'Level Change', + M: 'Control Send', + N: 'Note', + P: 'Playlist', + Q: 'Call Cue', + R: 'Run Program', + S: 'SFR', + T: 'Set Position', + U: 'MIDI Time Code', +}; + +const versionMap = { + 10: 'LITE', + 20: 'STD', + 30: 'PRO', + 40: 'PLUS', + 45: 'DEMO', + 50: 'PLAT', +}; + +exports.ready = function ready(_device) { + const device = _device; + console.log('Show Cue Systems ready'); + device.data.cues = []; + device.send(Buffer.from('/_prod/gettitle')); +}; + +exports.data = function data(_device, message) { + const device = _device; + this.deviceInfoUpdate(device, 'status', 'ok'); + const msg = message.toString(); + const messagePath = msg.substring(0, msg.indexOf(' ')); + if (messagePath === '/_prod/gettitle') { + const title = msg.replace(messagePath, '').replaceAll('"', '').trim(); + this.deviceInfoUpdate(device, 'defaultName', title); + device.send(Buffer.from('/_info/scsversion')); + } else if (messagePath === '/_info/scsversion') { + const versionInfo = msg.replace(messagePath, '').trim().split(' '); + const versionNumber = versionInfo[0]; + const versionMajor = versionNumber.substring(0, 2).replace('0', ''); + const versionMinor = versionNumber.substring(2, 4).replace('0', ''); + const versionPatch = versionNumber.substring(4, 6).replace('0', ''); + const versionString = `${versionMajor}.${versionMinor}.${versionPatch}`; + + const versionType = versionMap[versionInfo[1]]; + device.data.version = { + number: versionString, + type: versionType, + }; + device.send(Buffer.from('/_info/finalcue', 'ascii')); + } else if (messagePath === '/_info/finalcue') { + const cueCount = parseInt(msg.replace(messagePath, '').trim(), 10); + device.data.cues = new Array(cueCount).fill(0).map(() => ({ + label: '', + page: '', + description: '', + type: { code: '', display: '' }, + state: '', + activation: '', + file_info: '', + length: '', + colors: ['', ''], + })); + device.send(Buffer.from(`/_cue/getitemsn 1 QNTCLSPARZ`)); + } else if (messagePath === '/_info/getcue') { + const cueLabelInfo = msg.replace(messagePath, '').trim().split(' '); + const cueNumber = parseInt(cueLabelInfo[0], 10); + const cueLabel = cueLabelInfo[1].replaceAll('"', ''); + + device.data.cues[cueNumber - 1].label = cueLabel; + device.draw(); + } else if (messagePath === '/_cue/getpage') { + const pageInfo = msg.replace(messagePath, '').trim().match(/".*?"/g); + const cueLabel = pageInfo[0].replaceAll('"', ''); + const cueIndex = device.data.cues.map((cue) => cue.label).indexOf(cueLabel); + device.data.cues[cueIndex].page = pageInfo[1].replaceAll('"', ''); + if (cueIndex < device.data.cues.length - 1) { + device.send(Buffer.from(`/_cue/getpage ${device.data.cues[cueIndex + 1].label}`)); + } else { + // console.log('done loading cue pages'); + device.draw(); + device.send(Buffer.from('/_status')); + } + } else if (messagePath === '/_cue/getname') { + const nameInfo = msg.replace(messagePath, '').trim().match(/".*?"/g); + const cueLabel = nameInfo[0].replaceAll('"', ''); + const cueIndex = device.data.cues.map((cue) => cue.label).indexOf(cueLabel); + + device.data.cues[cueIndex].description = nameInfo[1].replaceAll('"', ''); + device.draw(); + } else if (messagePath === '/_cue/gettype') { + const typeInfo = msg.replace(messagePath, '').trim().match(/".*?"/g); + const cueLabel = typeInfo[0].replaceAll('"', ''); + const cueIndex = device.data.cues.map((cue) => cue.label).indexOf(cueLabel); + + device.data.cues[cueIndex].type = { + code: typeInfo[1].replaceAll('"', ''), + display: typeMap[typeInfo[1].replaceAll('"', '')], + }; + device.draw(); + } else if (messagePath === '/_cue/getitemsn') { + const itemParts = msg + .replace(messagePath, '') + .trim('') + .match(/".*?"|\d+/g); + if (itemParts.length === 12) { + const cueIndex = itemParts[0] - 1; + device.data.cues[cueIndex].label = sanitizeSCSString(itemParts[2]); + device.data.cues[cueIndex].description = sanitizeSCSString(itemParts[3]); + device.data.cues[cueIndex].type = { + code: sanitizeSCSString(itemParts[4]), + display: typeMap[sanitizeSCSString(itemParts[4])], + }; + device.data.cues[cueIndex].colors = sanitizeSCSString(itemParts[5]).split(', '); + device.data.cues[cueIndex].length = millisToString(itemParts[6]); + device.data.cues[cueIndex].state = itemParts[7]; + device.data.cues[cueIndex].position = itemParts[8]; + device.data.cues[cueIndex].activation = sanitizeSCSString(itemParts[9]); + device.data.cues[cueIndex].repeat = itemParts[10]; + device.data.cues[cueIndex].loop = itemParts[11]; + device.draw(); + + if (cueIndex < device.data.cues.length - 1) { + device.send(Buffer.from(`/_cue/getitemsn ${cueIndex + 2} QNTCLSPARZ`)); + } else { + // console.log('done loading cue items'); + device.send(Buffer.from(`/_cue/getpage ${device.data.cues[0].label}`)); + } + } else { + console.error('bad response from getitems'); + } + } else if (messagePath === '/_status') { + const status = parseInt(msg.split(' ')[1].trim(), 10); + console.log(`status: ${status}`); + } else { + console.error(`unhandled message path: ${messagePath}`); + } +}; + +function sanitizeSCSString(string) { + return string.substring(1, string.length - 1); +} + +function millisToString(duration) { + const milliseconds = parseInt(duration % 1000, 10); + let seconds = parseInt((duration / 1000) % 60, 10); + const minutes = parseInt((duration / (1000 * 60)) % 60, 10); + + if (minutes === 0) { + return `${seconds}.${milliseconds}`; + } + if (seconds === 0) { + return `0.${milliseconds}`; + } + if (seconds < 10) { + seconds = `0${seconds}`; + } + return `${minutes}:${seconds}.${milliseconds}`; +} + +exports.heartbeat = function heartbeat(device) {}; diff --git a/plugins/scs/styles.css b/plugins/scs/styles.css new file mode 100644 index 0000000..35b357a --- /dev/null +++ b/plugins/scs/styles.css @@ -0,0 +1,12 @@ +table { + background: white; +} + +th { + text-align: left; + font-weight: normal; +} + +.header-row { + background: white; +} diff --git a/plugins/scs/template.ejs b/plugins/scs/template.ejs new file mode 100644 index 0000000..0d4ac32 --- /dev/null +++ b/plugins/scs/template.ejs @@ -0,0 +1,25 @@ +
+

<%= listName %>

+
+ + + + + + + + + + + <% data.cues.forEach((cue)=>{ %> + + + + + + + + + + <% }) %> +
LabelPageDescriptionCue TypeStateActivationLength
<%=cue.label%><%=cue.page%><%=cue.description%><%=cue.type.display%><%=cue.state%><%=cue.activation%><%=cue.length%>
\ No newline at end of file