casparcg-connect

casparcg-connect (subflow)

Node-Red Subflow to build up an amcp and osc connection to a casparcg server

casparcg

casparcg.config

be shure to have these tags included:

  <controllers>
    <tcp>
      <port>5250</port>
      <protocol>AMCP</protocol>
    </tcp>
  </controllers>
  <amcp>
    <media-server>
      <host>localhost</host>
      <port>8000</port>
    </media-server>
  </amcp>
  <osc>
    <default-port>6250</default-port>
    <disable-send-to-amcp-clients>false</disable-send-to-amcp-clients>
    <predefined-clients>
      <predefined-client>
        <address>127.0.0.1</address>
        <port>5253</port>
      </predefined-client>
    </predefined-clients>
  </osc>

clip

In the flow the control of a clip AMB.mp4 is done AMB If the clip doesn't come with the server download then you could probably use another mp4 file with the same name.

flow

image

subflow

image

connection settings

  • udp:
    image
  • tcp:
    image
[{"id":"8a17dcfe.d933b","type":"tab","label":"AMCP","disabled":false,"info":""},{"id":"e0e6e559.8f30d8","type":"subflow","name":"scan","info":"","category":"ui-table","in":[{"x":51,"y":85,"wires":[{"id":"ffa780bb.8fd62"}]}],"out":[{"x":357,"y":85,"wires":[{"id":"ffa780bb.8fd62","port":0}]}],"env":[{"name":"ipString","type":"str","value":"http://192.168.2.","ui":{"icon":"font-awesome/fa-feed","type":"input","opts":{"types":["str"]},"label":{}}},{"name":"start","type":"num","value":"1","ui":{"icon":"font-awesome/fa-arrow-circle-right","type":"input","opts":{"types":["num"]},"label":{}}},{"name":"end","type":"num","value":"255","ui":{"icon":"font-awesome/fa-arrow-circle-left","type":"input","opts":{"types":["num"]},"label":{}}},{"name":"suffix","type":"str","value":"/json","ui":{"icon":"font-awesome/fa-address-card-o","type":"input","opts":{"types":["str"]},"label":{}}}],"color":"#A6BBCF","icon":"font-awesome/fa-stethoscope"},{"id":"2924702c.b33a7","type":"subflow","name":"ui-table handler","info":"# ui-table handler\nUniversal handler for ui-table.\n## features\n- buffer table data\n- add or update individual rows or cells of the table\n- delete rows\n- clear tableData\n- handle column width\n- handle column order\n\n## configuration\n- `tabulator` json formatted object containing configuration of the table. See ui-table for more details.\n- `property` property of the msg object that contains the data to be passed to ui-table. I.e. *state* `msg.state`\n- `index` index column to identify individual rows. Each message containing data must have a unique `msg.topic` to identify the row. Messages without this `msg.topic` will be droped. It is not nessesary but possible to display the index column in the table. Do not enable editing on this column otherwise you will loose the connection and another row will be added to the table as soon as a new message arrives!\n\n   Defaults to *$topic* `msg.state.$topic`\n- `dashboard` name of the dashboard tab to only update the table if the dashboard is visible. If empty the table will be updated on every tab change and connect.\n- `context` configuration of context data. The subflow will save or cache data in the flows context using `$parent.`. \n   **tableData** caches the incoming data to restore it on `ui-control´ *change* messages.\n   **tableConfig** saves column width and order to save the interactive table layot\n   **tableEdit** saves edits on the table data otherwise it would be overwritten when new data arrives\n```json\n{\n    \"tableData\": {\n        \"name\": \"tableData\"\n    },\n    \"tableConfig\": {\n        \"name\": \"tableConfig\",\n        \"storage\": \"file\"\n    },\n    \"tableEdit\": {\n        \"name\": \"tableEdit\",\n        \"storage\": \"file\"\n    }\n}\n```\n\n## commands\ncommands can be passed by sending a object as `msg.payload`\n- delete custom colum order and restore colum order from the tabulator json. This is important if you add or delete columns in the tabulator config otherwise the columns most likely don`t show up\n```json\n{\n    \"command\": \"delete\",\n    \"object\": \"columnOrder\"\n}\n```\n- delete custom column widths\n```json\n{\n    \"command\": \"delete\",\n    \"object\": \"columnWidth\"\n}\n```\n- delete tableCache\n```json\n{\n    \"command\": \"delete\",\n    \"object\": \"tableCache\"\n}\n```\n\n## background\nui-table warps the powerfull tabluator library. This subflow makes it easier to unleash the powerfull features of ui-table","category":"dashboard","in":[{"x":54,"y":85,"wires":[{"id":"5eb0bd6b.74b794"}]}],"out":[{"x":360,"y":85,"wires":[{"id":"5eb0bd6b.74b794","port":1}]},{"x":360,"y":136,"wires":[{"id":"5eb0bd6b.74b794","port":2}]}],"env":[{"name":"tabulator","type":"json","value":"{\"tabulator\":{\"responsiveLayout\":\"collapse\",\"responsiveLayoutCollapseStartOpen\":false,\"index\":\"$name\",\"layout\":\"fitColumns\",\"movableColumns\":true,\"groupBy\":\"\",\"columnResized\":\"function(column){     var newColumn = {         field: column._column.field,         visible: column._column.visible,         width: column._column.width,         widthFixed: column._column.widthFixed,         widthStyled: column._column.widthStyled     }; this.send({topic:this.config.topic,ui_control:{callback:'columnResized',columnWidths:newColumn}}); }\",\"columnMoved\":\"function(column, columns){     var newColumns=[];     columns.forEach(function (column) {         newColumns.push({'field': column._column.definition.field, 'title': column._column.definition.title});     });     this.send({topic:this.config.topic,ui_control:{callback:'columnMoved',columns:newColumns}}); }\",\"rowFormatter\":\"function(row){     var data = row.getData();     switch (data.$state) {         case \\\"lost\\\":             row.getElement().style.backgroundColor = \\\"#9e2e66\\\";             row.getElement().style.color = \\\"#a6a6a6\\\";             break;         case \\\"sleeping\\\":             row.getElement().style.backgroundColor = \\\"#336699\\\";             break;         case \\\"disconnected\\\":             row.getElement().style.backgroundColor = \\\"#cc3300\\\";             row.getElement().style.color = \\\"#a6a6a6\\\";             break;         case \\\"alert\\\":             row.getElement().style.backgroundColor = \\\"#A6A6DF\\\";             break;         case \\\"init\\\":             row.getElement().style.backgroundColor = \\\"#f2f20d\\\";             break;         case \\\"ready\\\":             row.getElement().style.backgroundColor = \\\"\\\";             row.getElement().style.color = \\\"\\\";             break;         } }\",\"columns\":[{\"formatter\":\"responsiveCollapse\",\"width\":30,\"minWidth\":30,\"align\":\"center\",\"resizable\":false,\"headerSort\":false,\"frozen\":true,\"title\":\"expand\",\"field\":\"expand\",\"headerVertical\":\"flip\"},{\"formatter\":\"function(cell, formatterParams, onRendered) {      var html = cell.getValue(); return html;  }\",\"title\":\"State\",\"field\":\"$stateIcon\",\"width\":100,\"frozen\":true,\"headerVertical\":\"flip\"},{\"formatter\":\"function(cell, formatterParams, onRendered) {     var html = cell.getValue(); return html;  }\",\"title\":\"Signal\",\"field\":\"signalIcon\",\"width\":100,\"frozen\":true,\"headerVertical\":\"flip\"},{\"title\":\"Name\",\"field\":\"$name\",\"width\":100,\"frozen\":true,\"headerVertical\":\"flip\"},{\"title\":\"State\",\"field\":\"$state\",\"width\":100,\"align\":\"center\",\"headerVertical\":\"flip\"},{\"title\":\"last-ready\",\"field\":\"lastSeenreadyFormatted\",\"width\":100,\"align\":\"left\",\"headerVertical\":\"flip\"},{\"title\":\"Homie\",\"field\":\"$homie\",\"width\":100,\"align\":\"left\",\"headerVertical\":\"flip\"},{\"title\":\"Platform\",\"field\":\"$implementation\",\"width\":100,\"align\":\"left\",\"headerVertical\":\"flip\"},{\"title\":\"Statistics\",\"columns\":[{\"title\":\"Interval\",\"field\":\"interval\",\"width\":100,\"headerVertical\":\"flip\"},{\"formatterParams\":{\"outputFormat\":\"d hh:mm:ss\",\"inputFormat\":\"seconds\",\"invalidPlaceholder\":\"(unknown)\"},\"title\":\"Uptime\",\"field\":\"uptime\",\"formatter\":\"function(cell, formatterParams, onRendered){     var pad = function (num) {         return (\\\"0\\\"+num).slice(-2);     };     var secs = Number(cell.getValue());     if (Number.isNaN(secs)) return;     var minutes = Math.floor(secs / 60);     secs = secs%60;     var hours = Math.floor(minutes/60);     minutes = minutes%60;     var days = Math.floor(hours/24);     hours = hours%24;     if (days>0)         return days+\\\"d \\\"+pad(hours)+\\\":\\\"+pad(minutes);     else         return pad(hours)+\\\":\\\"+pad(minutes)+\\\":\\\"+pad(secs); }\",\"width\":100,\"headerVertical\":\"flip\"},{\"formatterParams\":{\"min\":0,\"max\":100,\"color\":[\"red\",\"orange\",\"green\"],\"legend\":\"function (value) {if (value>0) return \\\"<span style='color:#FFFFFF;'>\\\"+value+\\\" %</span>\\\"; else return; }\",\"legendColor\":\"#FFFFFF\",\"legendAlign\":\"center\"},\"title\":\"Signal\",\"field\":\"signal\",\"formatter\":\"progress\",\"width\":100,\"headerVertical\":\"flip\"},{\"formatterParams\":{\"min\":2.5,\"max\":3.5,\"color\":[\"red\",\"green\",\"red\"],\"legend\":\"function (value) { if (value>0) return \\\"<span style='color:#FFFFFF;'>\\\"+value+\\\" V</span>\\\"; else return; }\",\"legendColor\":\"#101010\",\"legendAlign\":\"center\"},\"title\":\"Supply\",\"field\":\"supply\",\"formatter\":\"progress\",\"width\":100,\"headerVertical\":\"flip\"},{\"formatterParams\":{\"min\":0,\"max\":100,\"color\":[\"red\",\"orange\",\"green\"],\"legend\":\"function (value) {     if (value>0)         return \\\"<span style='color:#FFFFFF;'>\\\"+value+\\\" %</span>\\\";     else         return; }\",\"legendColor\":\"#101010\",\"legendAlign\":\"center\"},\"title\":\"Battery\",\"field\":\"battery\",\"formatter\":\"progress\",\"width\":100,\"headerVertical\":\"flip\"},{\"formatterParams\":{\"min\":0,\"max\":100000,\"color\":[\"red\",\"orange\",\"green\"],\"legend\":\"function (value) { if (value>0) return \\\"<span style='color:#FFFFFF;'>\\\"+(value/1024).toFixed(2)+\\\" kB</span>\\\"; else return; }\",\"legendColor\":\"#101010\",\"legendAlign\":\"center\"},\"title\":\"Memory\",\"field\":\"freeheap\",\"formatter\":\"progress\",\"width\":100,\"headerVertical\":\"flip\"},{\"formatterParams\":{\"target\":\"_blank\",\"min\":0,\"max\":100,\"color\":[\"red\",\"orange\",\"green\"],\"legend\":\"function (value) {     if (value>0)         return \\\"<span style='color:#FFFFFF;'>\\\"+value+\\\" %</span>\\\";     else         return; }\",\"legendColor\":\"#101010\",\"legendAlign\":\"center\"},\"title\":\"CPU load\",\"field\":\"cpuload\",\"formatter\":\"progress\",\"width\":100,\"headerVertical\":\"flip\"},{\"formatterParams\":{\"min\":20,\"max\":60,\"color\":[\"green\",\"orange\",\"red\"],\"legend\":\"function (value) { if (value>0) return \\\"<span style='color:#FFFFFF;'>\\\"+value+\\\" °C</span>\\\"; else return; }\",\"legendColor\":\"#101010\",\"legendAlign\":\"center\"},\"title\":\"CPU temp\",\"field\":\"cputemp\",\"formatter\":\"progress\",\"width\":100,\"headerVertical\":\"flip\"}]},{\"title\":\"Firmware\",\"columns\":[{\"formatter\":\"link\",\"formatterParams\":{\"labelField\":\"$localip\",\"urlPrefix\":\"http://\",\"target\":\"_blank\"},\"title\":\"IP\",\"field\":\"$localip\",\"width\":100},{\"title\":\"mac\",\"field\":\"$mac\",\"width\":100},{\"title\":\"Accsess Point\",\"field\":\"SSID\",\"width\":100},{\"title\":\"Firmware\",\"field\":\"name\",\"width\":100},{\"title\":\"Version\",\"field\":\"version\",\"width\":100},{\"title\":\"Last Boot Cause\",\"field\":\"lastBootCause\",\"width\":100},{\"title\":\"Reset Reason\",\"field\":\"resetReason\",\"width\":100}]}]},\"customHeight\":12}","ui":{"icon":"font-awesome/fa-table","label":{"en-US":"Tabulator"},"type":"input","opts":{"types":["json","env"]}}},{"name":"tableDataProp","type":"str","value":"row","ui":{"icon":"font-awesome/fa-tag","label":{"en-US":"rowProperty"},"type":"input","opts":{"types":["str","num","bool","json","bin","env"]}}},{"name":"tableIndex","type":"str","value":"$topic","ui":{"icon":"font-awesome/fa-indent","label":{"en-US":"Index"},"type":"input","opts":{"types":["str","json","env"]}}},{"name":"dashboard","type":"str","value":"Remote Device Table","ui":{"icon":"font-awesome/fa-dashboard","label":{"en-US":"Dashboard"},"type":"input","opts":{"types":["str","env"]}}},{"name":"tableContext","type":"json","value":"{\"tableData\":{\"name\":\"tableData\"},\"tableConfig\":{\"name\":\"tableConfig\",\"storage\":\"file\"},\"tableEdit\":{\"name\":\"tableEdit\",\"storage\":\"file\"}}","ui":{"icon":"font-awesome/fa-database","label":{"en-US":"Context"},"type":"input","opts":{"types":["json","env"]}}}],"color":"#3FADB5","icon":"node-red-dashboard/ui_slider.png","status":{"x":360,"y":34,"wires":[{"id":"5eb0bd6b.74b794","port":0}]}},{"id":"82403a972ac7824f","type":"subflow","name":"CasparCG Server","info":"","category":"","in":[{"x":40,"y":580,"wires":[{"id":"db08daa3c753706c"}]}],"out":[{"x":1920,"y":140,"wires":[{"id":"b776547b196b83a9","port":0}]},{"x":1920,"y":440,"wires":[{"id":"e87fce072a8ca15e","port":0}]},{"x":1920,"y":240,"wires":[{"id":"cfbe7b9ffeeb6cc9","port":0}]}],"env":[],"meta":{},"color":"#DDAA99","inputLabels":["amcp"],"outputLabels":["osc","amcp response","connection state"],"icon":"node-red/bridge.svg","status":{"x":1920,"y":320,"wires":[{"id":"2b64fa22837eca67","port":0}]}},{"id":"1c156b8964aa2067","type":"ui_base","theme":{"name":"theme-light","lightTheme":{"default":"#0094CE","baseColor":"#0094CE","baseFont":"-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif","edited":false},"darkTheme":{"default":"#097479","baseColor":"#097479","baseFont":"-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif","edited":false},"customTheme":{"name":"Untitled Theme 1","default":"#4B7930","baseColor":"#4B7930","baseFont":"-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif"},"themeState":{"base-color":{"default":"#0094CE","value":"#0094CE","edited":false},"page-titlebar-backgroundColor":{"value":"#0094CE","edited":false},"page-backgroundColor":{"value":"#fafafa","edited":false},"page-sidebar-backgroundColor":{"value":"#ffffff","edited":false},"group-textColor":{"value":"#1bbfff","edited":false},"group-borderColor":{"value":"#ffffff","edited":false},"group-backgroundColor":{"value":"#ffffff","edited":false},"widget-textColor":{"value":"#111111","edited":false},"widget-backgroundColor":{"value":"#0094ce","edited":false},"widget-borderColor":{"value":"#ffffff","edited":false},"base-font":{"value":"-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif"}},"angularTheme":{"primary":"indigo","accents":"blue","warn":"red","background":"grey","palette":"light"}},"site":{"name":"Node-RED Dashboard","hideToolbar":"false","allowSwipe":"false","lockMenu":"false","allowTempTheme":"true","dateFormat":"DD.MM.YYYY","sizes":{"sx":48,"sy":48,"gx":6,"gy":6,"cx":6,"cy":6,"px":0,"py":0}}},{"id":"bf419011.bb583","type":"ui_tab","name":"poor man's newsroom","icon":"track_changes","order":1,"disabled":false,"hidden":false},{"id":"232420a9.9fd56","type":"ui_group","name":"Group 1","tab":"bf419011.bb583","order":1,"disp":true,"width":"19","collapse":false},{"id":"b4b2ea51.f976b8","type":"ui_group","name":"Group 2","tab":"bf419011.bb583","order":2,"disp":true,"width":6},{"id":"ffa780bb.8fd62","type":"function","z":"e0e6e559.8f30d8","name":"scan subnet","func":"var ipString = env.get('ipString') || '';\nvar start = env.get('start') || 1;\nvar end = env.get('end') || 255;\nvar suffix = env.get('suffix') || '/json';\n\nif (!msg.payload.hasOwnProperty(\"command\")) { // if no command scan complete subnet\n    for (let i=start; i<end+1; i++){\n        node.send({\"url\":ipString+i+\"/json\"})\n    }\n} else { // message form popup menue\n    switch (msg.payload.command){\n        case \"updateDevice\":\n            if (msg.payload.hasOwnProperty(\"$localip\") && \n                msg.payload.name===\"ESP Easy Mega\") {\n                    node.status({fill:\"green\", shape:\"dot\", text:\"scan \"+msg.payload.$localip})\n                    return({\"url\":\"http://\"+msg.payload.$localip+\"/json\"});\n            }\n            break;\n        case \"updateDevices\":\n            for (let i=start; i<end+1; i++){\n                node.send({\"url\":ipString+i+\"/json\"})\n            }\n            break;\n    }\n}","outputs":1,"noerr":0,"x":206,"y":85,"wires":[[]],"icon":"node-red/white-globe.svg"},{"id":"5eb0bd6b.74b794","type":"function","z":"2924702c.b33a7","name":"handle tableData","func":"var status = {fill:\"red\",shape:\"dot\",text: \"payload \"};\nvar tableIndex = env.get(\"tableIndex\") || \"$topic\";\nvar tableDataProp = env.get(\"tableDataProp\") || \"row\";\nvar tableContext = env.get(\"tableContext\");\nvar dashboard = env.get(\"dashboard\");\nif (!tableContext.hasOwnProperty(\"tableData\") || !tableContext.hasOwnProperty(\"tableConfig\")) {\n    status.text=\"tableContext not defined\";\n    node.error(status.text);\n    return [{payload:status},null];\n}\n\n// context store to cache table data (memoryOnly prefered)\nvar tableData = flow.get(\"$parent.\"+tableContext.tableData.name,tableContext.tableData.storage);\nif (tableData===undefined) {\n    node.warn(\"[ui-table handler] tableData initialized!\");\n    tableData={};\n    flow.set(\"$parent.\"+tableContext.tableData.name,tableData,tableContext.tableData.storage);\n}\n\n// context Store to save table configuration (file)\nvar tableConfig = flow.get(\"$parent.\"+tableContext.tableConfig.name,tableContext.tableConfig.storage);\nif (tableConfig===undefined) {\n    node.warn(\"[ui-table handler] tableConfig initialized!\");\n    tableConfig={ResponsiveLayout:true};\n    flow.set(\"$parent.\"+tableContext.tableConfig.name,tableConfig,tableContext.tableConfig.storage);\n}\n\n// context Store to save table configuration (file)\nvar tableEdit;\nif (tableContext.hasOwnProperty(\"tableEdit\")) {\n    tableEdit = flow.get(\"$parent.\"+tableContext.tableEdit.name,tableContext.tableEdit.storage);\n    if (tableEdit===undefined) {\n        node.warn(\"[ui-table handler] tableEdit initialized!\");\n        tableEdit={};\n        flow.set(\"$parent.\"+tableContext.tableEdit.name,tableEdit,tableContext.tableEdit.storage);\n    }\n}\n\n// function to merge partial data into existing table row\nvar mergeObject = function (destination, source, filter) {\n    for (let currentSource in source) {\n        if (source.hasOwnProperty(currentSource)) {\n            if (filter!==undefined && tableEdit && tableEdit.hasOwnProperty(filter) && tableEdit[filter].hasOwnProperty(currentSource)) {\n                destination[currentSource]= tableEdit[filter][currentSource];\n                source[currentSource]=tableEdit[filter][currentSource];\n            } else {\n                destination[currentSource]= source[currentSource];\n            }\n        }    \n    }\n    return source;\n};\n    \n// deep search for a column\nvar searchTabulatorColumn = function (columns,key,match) {\n    var result;\n    for (let column of columns) {\n        if (column.hasOwnProperty(\"columns\")) {\n            result = searchTabulatorColumn(column.columns,key,match);\n            if (result!==undefined) return result;\n        } else if (column.hasOwnProperty(key) && column[key]===match) {\n            return column;\n        }\n    }\n};\n\n// command message to update add or update data on ui-table\nvar msgToTable={};\nmsgToTable.payload={\n    \"command\":\"updateOrAddData\",\n    \"arguments\": [],\n    \"returnPromise\": false\n};\n\nif (msg.hasOwnProperty(tableDataProp)) {\n    // store data for later recover\n    if (!msg.hasOwnProperty(\"topic\")) { // check if index existst\n        status.text=\"msg.topic not defined!\";\n        return [{payload:status},null];\n    }\n    if (!tableData.hasOwnProperty(msg.topic)){ // first seen\n        tableData[msg.topic]={};\n        if (tableEdit && tableEdit.hasOwnProperty(msg.topic)) { // table edits available!\n            Object.keys(tableEdit[msg.topic]).forEach((key) => {\n                msg[tableDataProp][key]=tableEdit[msg.topic][key];\n                tableData[msg.topic][key]=tableEdit[msg.topic][key];\n            })\n        }\n    }\n    if (!tableData[msg.topic].hasOwnProperty(tableIndex)) tableData[msg.topic][tableIndex]=msg.topic;\n    msg[tableDataProp]=mergeObject(tableData[msg.topic],msg[tableDataProp],msg.topic);\n    msg[tableDataProp][tableIndex]=msg.topic;\n    msgToTable.payload.arguments=[[msg[tableDataProp]]];\n    status.fill=\"green\";\n    status.text=msg.topic+\" updated\";\n    return [{payload:status},msgToTable,null];\n} if (msg.payload===\"connect\" || (msg.payload===\"change\" && msg.name===dashboard) || (msg.hasOwnProperty(\"payload\") && msg.payload.hasOwnProperty(\"command\"))) { \n    if (!msg.hasOwnProperty(\"ui_control\")) {\n        msg.ui_control = env.get('tabulator');\n        status.text+=\" ui_control added\";\n    }\n    //process commands\n    //node.warn({\"command\":msg.payload.command,\"msg\":msg,\"object\":msg.payload.object})\n    if (msg.payload.hasOwnProperty(\"command\")) {\n        status.fill=\"blue\";\n        switch(msg.payload.command) {\n            case 'deleteTable':\n                flow.set(\"$parent.\"+tableContext.tableData.name,undefined,tableContext.tableData.storage);\n                tableData={};\n                status.text=\"tabledata deleted\";\n                node.warn(\"[ui-table handler] \"+\"tabledata deleted\");\n                break;\n            case 'deleteDevice':\n                if (tableData.hasOwnProperty(msg.payload.object)) {\n                    delete tableData[msg.payload.object];\n                    status.text=msg.payload.object+\" deleted\";\n                } else {\n                    status.fill=\"yellow\";\n                    status.text=msg.payload.object+\" undefined\";\n                }\n                break;\n            case 'ignoreDevice':\n                if (tableData.hasOwnProperty(msg.payload.object)) {\n                    delete tableData[msg.payload.object];\n                    status.text=msg.payload.object+\" will be ignored\";\n                    if (!tableConfig.hasOwnProperty('ignoreDevice')) tableConfig.ignoreDevice={};\n                    tableConfig.ignoreDevice[msg.payload.object]=true;\n                }\n                break;\n            case 'unIgnoreDevice':\n                if (tableConfig.hasOwnProperty('ignoreDevice')) {\n                    delete tableConfig.ignoreDevice[msg.payload.object];\n                }\n                break;\n            case 'unIgnoreDevices':\n                delete tableConfig.ignoreDevice;\n                break;\n            case 'updateData':\n                status.text=\"column \"+msg.payload.column+\" updated\";\n                return [{payload:status},msg];\n            case 'updateTable':\n                status.text=msg.payload.command+\": \";\n                break;\n            case 'columnHide':\n                if (!tableConfig.hasOwnProperty('columnVisible')) tableConfig.columnVisible={};\n                tableConfig.columnVisible[msg.payload.object]=false;\n                break;\n            case 'columnUnHide':\n                if (!tableConfig.hasOwnProperty('columnVisible')) tableConfig.columnVisible={};\n                tableConfig.columnVisible[msg.payload.object]=true;\n                break;\n            case 'columnsUnHide':\n                for (let column in tableConfig.columnVisible) {\n                    if (tableConfig.columnVisible.hasOwnProperty(column)) tableConfig.columnVisible[column]=true;\n                }\n                break;\n            case 'refreshTable':\n                break;\n            case 'restoreColumnOrder':\n                delete tableConfig.columns;\n                break;\n            case 'resetColumnWidth':\n                delete tableConfig.columnWidths;\n                break;\n            case 'setResponsiveLayout':\n                tableConfig.ResponsiveLayout=!tableConfig.ResponsiveLayout;\n                break;\n            default:\n                status.fill=\"red\";\n                status.text=\"unknown command \"+msg.payload.command;\n                node.warn(\"[ui-table handler] \"+status.text);\n                break;\n        }\n        flow.set(\"$parent.\"+tableContext.tableConfig.name,tableConfig,tableContext.tableConfig.storage);\n        node.send([{payload:status},null,null]);\n    }\n\n    // crawl through tabulator arrays and updated user defined values\n    var crawlTabulator = function (columns,match,config,property) {\n        for (let column of columns) {\n            if (column.hasOwnProperty(\"columns\")) {\n                crawlTabulator(column.columns,match,config,property);\n            } else if (config.hasOwnProperty(column[match])) column[property]=config[column.field];\n        }\n    };\n    \n    // restore custom column width\n    if (tableConfig.hasOwnProperty(\"columnWidths\") && msg.hasOwnProperty(\"ui_control\")) {\n        crawlTabulator(msg.ui_control.tabulator.columns,\"field\",tableConfig.columnWidths,\"width\");\n    }\n    \n    // restore custom column hide/show\n    if (tableConfig.hasOwnProperty(\"columnVisible\") && msg.hasOwnProperty(\"ui_control\")) {\n        crawlTabulator(msg.ui_control.tabulator.columns,\"field\",tableConfig.columnVisible,\"visible\");\n    }\n    \n    // restore custom responsive / standard view\n    if (tableConfig.hasOwnProperty(\"ResponsiveLayout\")) {\n        if (!tableConfig.ResponsiveLayout) {\n            msg.ui_control.tabulator.responsiveLayout=false;\n        }\n        msg.ui_control.tabulator.columns.forEach((column,index) => {\n            if (column.formatter===\"responsiveCollapse\") { // hide expand column on any position\n                column.visible=tableConfig.ResponsiveLayout;\n                return;\n            }\n        });\n    }\n\n    // sort columns\n    if (tableConfig.hasOwnProperty(\"columns\") && msg.hasOwnProperty(\"ui_control\") && msg.ui_control.hasOwnProperty(\"tabulator\")) {\n        var addedColumns = 0;\n        var sortColumnsByLayout = function (sortColumns, columnsLayout, targetColumns) {\n            for (var layoutColumn=0;  layoutColumn<columnsLayout.length; layoutColumn++) {\n                for (var sortColumn in sortColumns) {\n                    if (sortColumns[sortColumn].hasOwnProperty(\"columns\")) {\n                        targetColumns.push({\"title\":sortColumns[sortColumn].title, \"columns\":[]});\n                        sortColumnsByLayout(sortColumns[sortColumn].columns,columnsLayout,targetColumns[targetColumns.length-1].columns);\n                        layoutColumn=addedColumns; // jump forward after childes added\n                    } else {\n                        if (columnsLayout[layoutColumn].field===sortColumns[sortColumn].field){\n                            targetColumns.push(sortColumns[sortColumn]);\n                            addedColumns++;\n                            break;\n                        }\n                    }\n                }\n            }\n        };                 \n        var newColumns=[];\n        sortColumnsByLayout(msg.ui_control.tabulator.columns,tableConfig.columns,newColumns);\n        msg.ui_control.tabulator.columns=newColumns;\n    }\n\n    // restore stored lines after connect\n    msg.payload=[];\n    for (let device in tableData) {\n        if (tableConfig && tableConfig.hasOwnProperty(\"ignoreDevice\") && tableConfig.ignoreDevice[device]) {\n            continue;\n        }\n        // merge edits into table\n        if (tableEdit && tableEdit.hasOwnProperty(device)) {\n            let tableRow = RED.util.cloneMessage(tableData[device]);\n            Object.keys(tableEdit[device]).forEach((field) => {\n                tableRow[field]=tableEdit[device][field];\n            });\n            msg.payload.push(tableRow);\n        } else {\n            msg.payload.push(tableData[device]);\n        }\n    }\n    \n    status.fill=\"blue\";\n    status.text+=\" \"+msg.payload.length+\" rows restored\";\n    return [{payload:status},msg];\n} if (msg.hasOwnProperty(\"ui_control\")) {\n    // callback from tabulator\n    status.fill=\"blue\";\n    status.text=\"unknown callback \"+msg.ui_control.callback;\n    switch(msg.ui_control.callback) {\n        case \"columnResized\": // save new column width\n            if (tableConfig.columnWidths===undefined) tableConfig.columnWidths={};\n            tableConfig.columnWidths[msg.ui_control.columnWidths.field]=msg.ui_control.columnWidths.width;\n            flow.set(\"$parent.\"+tableContext.tableConfig.name,tableConfig,tableContext.tableConfig.storage);\n            status.text=msg.ui_control.columnWidths.field+\"=\"+msg.ui_control.columnWidths.width+\"px\";\n            break;\n        case \"columnMoved\": // save new column order\n            if (tableConfig.columns===undefined) tableConfig.columns=[];\n            tableConfig.columns=msg.ui_control.columns;\n            flow.set(\"$parent.\"+tableContext.tableConfig.name,tableConfig,tableContext.tableConfig.storage);\n            status.text=\"new column order\";\n            break;\n        case \"cellEdited\":\n            if (tableEdit) {\n                if (!tableEdit.hasOwnProperty(msg[tableIndex])) tableEdit[msg[tableIndex]]={};\n                tableEdit[msg[tableIndex]][msg.field] = msg.payload; // save data and mark es edited field\n                flow.set(\"$parent.\"+tableContext.tableEdit.name,tableEdit,tableContext.tableEdit.storage);\n                status.text=msg[tableIndex]+\" \"+msg.field+\" edited to \"+msg.payload;\n                msg[tableDataProp]={};\n                msg[tableDataProp][tableIndex]=msg[tableIndex];\n                msg[tableDataProp][msg.field]=msg.payload;\n                msgToTable.payload.arguments=[[msg[tableDataProp]]];\n                node.send([{payload:status},msgToTable,msg]);\n            } else {\n                node.error(\"[ui-table handler] no tableEdit store defined!\")\n            }\n            break;\n        case \"rowContext\":\n            msg.ignoredDevices=[];\n            for (let device in tableConfig.ignoreDevice) {\n                if (tableConfig.ignoreDevice.hasOwnProperty(device)) {\n                    msg.ignoredDevices.push({\"text\":device,\"icon\":\"fa fa-plug\",\"topic\":\"unIgnoreDevice\",\"payload\":device})  \n                }\n            }\n            break;\n        case \"headerContext\":\n            msg.hiddenColumns=[];\n            let tabulatorConfig = env.get('tabulator');\n            for (let column in tableConfig.columnVisible) {\n                if (tableConfig.columnVisible.hasOwnProperty(column) &&\n                    !tableConfig.columnVisible[column]) {\n                    let configColumn=searchTabulatorColumn(tabulatorConfig.tabulator.columns,\"field\",column);\n                    let icon;\n                    if (configColumn.hasOwnProperty('title') && configColumn.title.toLowerCase().includes('</i>')) {\n                        // <i class='fa fa-star-half-o'></i> State\n                        let start=configColumn.title.indexOf(\"'fa \");\n                        let end=configColumn.title.indexOf(\"'\",start+1);\n                        icon=configColumn.title.substring(start+4,end);\n                    }\n                    msg.hiddenColumns.push({\"text\":column,\"icon\":icon,\"topic\":\"columnUnHide\",\"payload\":configColumn.field})  \n                }\n            }\n            break;\n        default:\n            // if rowIndex exists pass complete object\n            if (msg.hasOwnProperty(tableIndex)) {\n                msg.rowData=tableData[msg[tableIndex]];\n            }\n            status.text=\"pass message\";\n    }\n    return [{payload:status},null,msg];\n}\n// nothing to do bejond this point\nstatus.text+=\" [\"+msg.payload+\"]\";\nreturn [{payload:status},null];\n","outputs":3,"noerr":0,"x":192,"y":85,"wires":[[],[],[]],"icon":"font-awesome/fa-table"},{"id":"c05e719861f26e66","type":"osc","z":"82403a972ac7824f","name":"","path":"","metadata":false,"x":530,"y":160,"wires":[["68c6ae0726cc1b53","57acdaafa9b92d84"]]},{"id":"5d71eb4fc12fc649","type":"udp in","z":"82403a972ac7824f","name":"UDP (OSC)","iface":"","port":"5253","ipv":"udp4","multicast":"false","group":"","datatype":"buffer","x":370,"y":160,"wires":[["c05e719861f26e66"]]},{"id":"68c6ae0726cc1b53","type":"trigger","z":"82403a972ac7824f","name":"Trigger if 1 second no message","op1":"","op2":"","op1type":"nul","op2type":"date","duration":"1000","extend":true,"overrideDelay":false,"units":"ms","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":830,"y":200,"wires":[["bee4abd1000c7f01"]]},{"id":"4d180bf3fa58518f","type":"catch","z":"82403a972ac7824f","name":"Catch Connection Errors","scope":["5d71eb4fc12fc649","5eb3a0b5c12ce197"],"uncaught":false,"x":850,"y":280,"wires":[["bee4abd1000c7f01"]]},{"id":"57acdaafa9b92d84","type":"function","z":"82403a972ac7824f","name":"OSC Gate","func":"if(msg.connected==false){\n    context.set(\"connection\", false);\n    msg.payload = \"disconnected\";\n    return [null, msg];\n}else{\n    if(context.get('connection')==false){\n        context.set(\"connection\", true);\n        return [msg, {payload:\"connected\"}];\n    }else{\n        return [msg, null];\n    }\n}","outputs":2,"noerr":0,"initialize":"// Der Code hier wird ausgeführt,\n// wenn der Node gestartet wird\ncontext.set('connection', false);","finalize":"// Der Code hier wird ausgeführt,\n// wenn der Node gestoppt wird\ncontext.set('connection', false);","libs":[],"x":1330,"y":160,"wires":[["b776547b196b83a9"],["1c5cb376266f10f6","754e50d92a79f144","cfbe7b9ffeeb6cc9","2b64fa22837eca67"]],"outputLabels":["Osc","Connection"],"info":"### all messages\nare put through to output 1 (Osc)\n\n### msg.connected (boolean)\n - **false** disables output 1\n - **true** enables output 1\n - generates a new message on output 2 (Connection)<br>\n    **payload** is `connected` or `disconnected`\n"},{"id":"5eb3a0b5c12ce197","type":"tcp request","z":"82403a972ac7824f","server":"127.0.0.1","port":"5250","out":"sit","splitc":" ","name":"TCP (AMCP)","x":1110,"y":440,"wires":[["1c5cb376266f10f6"]],"info":"offen gehaltene AMCP-Connection"},{"id":"bee4abd1000c7f01","type":"function","z":"82403a972ac7824f","name":"disconnected","func":"const message = {connected:false}\nreturn message;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1110,"y":240,"wires":[["57acdaafa9b92d84"]]},{"id":"754e50d92a79f144","type":"function","z":"82403a972ac7824f","name":"Command Gate","func":"if(msg.payload == 'connected'){\n    context.set('connected', true);\n    return [null, {reset:true}];\n}else if(msg.payload == 'disconnected'){\n    context.set('connected', false);\n    return [null, {reset:true}];\n}\nif(context.get('connected')==true){\n    return [msg, null];\n}else{\n    return [null, {reset:true}];\n}","outputs":2,"noerr":0,"initialize":"// Der Code hier wird ausgeführt,\n// wenn der Node gestartet wird\ncontext.set('connected', false);","finalize":"// Der Code hier wird ausgeführt,\n// wenn der Node gestoppt wird\ncontext.set('connected', false);","libs":[],"x":880,"y":440,"wires":[["5eb3a0b5c12ce197"],["a24ee4a1eac91f81"]],"outputLabels":["Data","Reset"]},{"id":"a24ee4a1eac91f81","type":"simple-queue","z":"82403a972ac7824f","name":"","firstMessageBypass":true,"bypassInterval":"0","x":710,"y":580,"wires":[["754e50d92a79f144"]]},{"id":"1c5cb376266f10f6","type":"function","z":"82403a972ac7824f","name":"Response Gate","func":"if(msg.payload == 'connected'){\n    context.set('connected', true);\n    return [{reset:true}, null];\n}else if(msg.payload == 'disconnected'){\n    context.set('connected', false);\n    return [{reset:true}, null];\n}\nif(context.get('connected')==true){\n    return [msg, {trigger:true}];\n}else{\n    return [{reset:true}, null];\n}","outputs":2,"noerr":0,"initialize":"// Der Code hier wird ausgeführt,\n// wenn der Node gestartet wird\ncontext.set('connected', false);","finalize":"// Der Code hier wird ausgeführt,\n// wenn der Node gestoppt wird\ncontext.set('connected', false);","libs":[],"x":1340,"y":440,"wires":[["ccdc13643b29a2d5"],["a24ee4a1eac91f81"]],"outputLabels":["Data / Reset","Trigger"]},{"id":"ccdc13643b29a2d5","type":"join","z":"82403a972ac7824f","name":"","mode":"auto","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":"false","timeout":"","count":"","reduceRight":false,"x":1570,"y":440,"wires":[["e87fce072a8ca15e"]]},{"id":"622aa063938f3563","type":"split","z":"82403a972ac7824f","name":"","splt":"\\r\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":350,"y":580,"wires":[["9840e699f4618782"]]},{"id":"9840e699f4618782","type":"function","z":"82403a972ac7824f","name":"add \\r\\n","func":"msg.payload = msg.payload + '\\r\\n';\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":520,"y":580,"wires":[["a24ee4a1eac91f81"]]},{"id":"db08daa3c753706c","type":"function","z":"82403a972ac7824f","name":"kill last \\r\\n","func":"msg.requestid = msg._msgid;\nmsg.payload = msg.payload.slice(0, msg.payload.length-2);\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":170,"y":580,"wires":[["622aa063938f3563"]]},{"id":"cfbe7b9ffeeb6cc9","type":"change","z":"82403a972ac7824f","name":"set topic","rules":[{"t":"set","p":"topic","pt":"msg","to":"CCG connection","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1740,"y":240,"wires":[[]]},{"id":"b776547b196b83a9","type":"change","z":"82403a972ac7824f","name":"set topic","rules":[{"t":"set","p":"topic","pt":"msg","to":"CCG osc","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1740,"y":140,"wires":[[]]},{"id":"e87fce072a8ca15e","type":"change","z":"82403a972ac7824f","name":"delete items","rules":[{"t":"delete","p":"_queueCount","pt":"msg"},{"t":"delete","p":"_queuetimestamp","pt":"msg"},{"t":"delete","p":"ttl","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1740,"y":440,"wires":[[]]},{"id":"2b64fa22837eca67","type":"function","z":"82403a972ac7824f","name":"State","func":"if(msg.payload == \"disconnected\"){\n    msg.payload = ({fill:\"red\",shape:\"ring\",text:\"disconnected\"});\n}else{\n    msg.payload = ({fill:\"green\",shape:\"ring\",text:\"connected\"});\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1580,"y":320,"wires":[[]]},{"id":"38173d12604dd3d5","type":"inject","z":"8a17dcfe.d933b","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":280,"y":460,"wires":[["40ca99bfdc1c857e"]]},{"id":"5bfc7fbdd76c9ec0","type":"subflow:82403a972ac7824f","z":"8a17dcfe.d933b","name":"","env":[],"x":870,"y":640,"wires":[["8db1268c6c939438"],["71c84f86fd1a5db4"],["94153612411662b5"]]},{"id":"71c84f86fd1a5db4","type":"debug","z":"8a17dcfe.d933b","name":"RESPONSE","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1310,"y":640,"wires":[]},{"id":"8db1268c6c939438","type":"debug","z":"8a17dcfe.d933b","name":"OSC","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1290,"y":580,"wires":[]},{"id":"94153612411662b5","type":"debug","z":"8a17dcfe.d933b","name":"CONNECTION","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1320,"y":700,"wires":[]},{"id":"a50f7c101c4944f2","type":"inject","z":"8a17dcfe.d933b","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payloadType":"date","x":280,"y":700,"wires":[["de6e3ba4fe252c5a"]]},{"id":"40ca99bfdc1c857e","type":"function","z":"8a17dcfe.d933b","name":"LoadBG & Play","func":"msg.topic = 'CCG: LoadBG & Play AMB';\nmsg.payload = 'LoadBG 1-1 \"AMB.mp4\" loop\\r\\nPlay 1-1\\r\\n';\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":480,"y":460,"wires":[["5bfc7fbdd76c9ec0"]]},{"id":"de6e3ba4fe252c5a","type":"function","z":"8a17dcfe.d933b","name":"Clear Channel","func":"msg.topic = 'CCG: Clear Channel';\nmsg.payload = 'Clear 1\\r\\n';\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":480,"y":700,"wires":[["5bfc7fbdd76c9ec0"]]},{"id":"48ef5be5ed54a316","type":"inject","z":"8a17dcfe.d933b","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payloadType":"date","x":280,"y":780,"wires":[["5ce3aa119a26fb2e"]]},{"id":"5ce3aa119a26fb2e","type":"function","z":"8a17dcfe.d933b","name":"Clip Search","func":"msg.topic = 'CCG: Clip Search';\nmsg.payload = 'CLS\\r\\n';\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":490,"y":780,"wires":[["5bfc7fbdd76c9ec0"]]},{"id":"bc621849707f62fb","type":"inject","z":"8a17dcfe.d933b","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payloadType":"date","x":280,"y":860,"wires":[["b3cbbc7de63eb6b1"]]},{"id":"b3cbbc7de63eb6b1","type":"function","z":"8a17dcfe.d933b","name":"Clip Info","func":"msg.topic = 'CCG: Clip Info AMB';\nmsg.payload = 'CINF \"AMB\"\\r\\n';\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":500,"y":860,"wires":[["5bfc7fbdd76c9ec0"]]},{"id":"ed5a048103eeb1cf","type":"function","z":"8a17dcfe.d933b","name":"Fill & Rotate","func":"msg.topic = 'CCG: Fill & Rotate';\nmsg.payload = 'MIXER 1-1 FILL 0 0 0.5 0.5 25 DEFER\\r\\nMIXER 1-1 ROTATION 90 25 easeinsine DEFER\\r\\nMIXER 1-1 COMMIT\\r\\n';\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":490,"y":540,"wires":[["5bfc7fbdd76c9ec0"]]},{"id":"0f166ac10a38c183","type":"inject","z":"8a17dcfe.d933b","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":280,"y":540,"wires":[["ed5a048103eeb1cf"]]},{"id":"69e3754cc67097c0","type":"function","z":"8a17dcfe.d933b","name":"Clear Mixer","func":"msg.topic = 'CCG: Clear Mixer';\nmsg.payload = 'MIXER 1-1 CLEAR\\r\\n';\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":490,"y":620,"wires":[["5bfc7fbdd76c9ec0"]]},{"id":"9a63f9a68e847df6","type":"inject","z":"8a17dcfe.d933b","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payloadType":"date","x":280,"y":620,"wires":[["69e3754cc67097c0"]]}]

Flow Info

Created 3 years, 6 months ago
Rating: not yet rated

Owner

Actions

Rate:

Node Types

Core
  • catch (x1)
  • change (x3)
  • debug (x3)
  • function (x15)
  • inject (x6)
  • join (x1)
  • split (x1)
  • tcp request (x1)
  • trigger (x1)
  • udp in (x1)
Other
  • osc (x1)
  • simple-queue (x1)
  • subflow (x3)
  • subflow:82403a972ac7824f (x1)
  • tab (x1)
  • ui_base (x1)
  • ui_group (x2)
  • ui_tab (x1)

Tags

  • casparcg
  • amcp
  • osc
  • caspar
  • subflow
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option