Blackmagic ATEM to Home Assistant

This flow will add entities from a Blackmagic ATEM switcher into Home Assistant.

[{"id":"19314f19fb237662","type":"subflow","name":"Home Assistant Event","info":"","category":"","in":[],"out":[{"x":1040,"y":220,"wires":[{"id":"b1dc0a0f26f5713a","port":0}]}],"env":[{"name":"Event","type":"str","value":"","ui":{"type":"select","opts":{"opts":[{"l":{"en-US":"Loaded"},"v":"loaded"},{"l":{"en-US":"Script Update"},"v":"scriptUpdate"}]}}}],"meta":{},"color":"#DDAA99"},{"id":"53b0710ef31acff2","type":"function","z":"19314f19fb237662","name":"function 14","func":"switch (env.get(\"Event\")) {\n    case \"loaded\": {\n        if (msg.topic == \"home_assistant_client:ready\" && msg.payload == \"ready\") {\n            msg.delay = 1;\n            return msg;\n        }\n        break;\n    }\n    case \"scriptUpdate\": {\n        if (msg.topic == \"call_service\" && msg.payload.event.domain == \"script\" && msg.payload.event.service == \"reload\") {\n            return msg;\n        }\n        break;\n    }\n    case \"disconnected\": {\n        if (msg.topic == \"home_assistant_client\" && msg.payload == \"disconnected\") {\n            return msg;\n        }\n        break;\n    }\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":730,"y":220,"wires":[["b1dc0a0f26f5713a"]]},{"id":"4138ef0b7ad4ecdd","type":"server-events","z":"19314f19fb237662","name":"","server":"19cf4248.b7861e","version":2,"eventType":"home_assistant_client","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"waitForRunning":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\"eventData\").event_type","valueType":"jsonata"}],"x":500,"y":220,"wires":[["53b0710ef31acff2"]]},{"id":"b1dc0a0f26f5713a","type":"delay","z":"19314f19fb237662","name":"","pauseType":"delayv","timeout":"0","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":900,"y":220,"wires":[[]]},{"id":"2fd3616cd7d46d84","type":"server-events","z":"19314f19fb237662","name":"","server":"19cf4248.b7861e","version":2,"eventType":"call_service","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"waitForRunning":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\"eventData\").event_type","valueType":"jsonata"}],"x":530,"y":260,"wires":[["53b0710ef31acff2"]]},{"id":"a6bd71149af45dda","type":"atem-atem","z":"015b0090692f04b3","name":"Auditorium ATEM","network":"f32424bdd0787633","outputMode":"supported","sendTime":"yes","sendInitialData":"yes","sendStatusUpdates":"yes","x":570,"y":860,"wires":[["f84e6d94cc784554"]]},{"id":"de39182a3483ebdf","type":"comment","z":"015b0090692f04b3","name":"ATEM","info":"This will convert the ATEM to HASS.\n\nFor program/preview inputs add custom input select entities with the following id\ninput_select.<NAME>_me1_preview\ninput_select.<NAME>_me2_program\netc\n\n","x":130,"y":780,"wires":[]},{"id":"bcc8e30d085dc52f","type":"ha-api","z":"015b0090692f04b3","name":"","server":"19cf4248.b7861e","version":1,"debugenabled":false,"protocol":"http","method":"post","path":"/api/states/{{entity_id}}","data":"","dataType":"json","responseType":"json","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"results"}],"x":1110,"y":860,"wires":[[]]},{"id":"f84e6d94cc784554","type":"change","z":"015b0090692f04b3","name":"Set name","rules":[{"t":"set","p":"name","pt":"msg","to":"auditorium","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":695,"y":860,"wires":[["f2607202ebbf45d7"]],"l":false},{"id":"ea82666fd7c05a03","type":"server-events","z":"015b0090692f04b3","name":"","server":"19cf4248.b7861e","version":2,"eventType":"call_service","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"waitForRunning":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\"eventData\").event_type","valueType":"jsonata"}],"x":170,"y":860,"wires":[["efb371f3791a44b2"]]},{"id":"efb371f3791a44b2","type":"function","z":"015b0090692f04b3","name":"Set","func":"var getInputId = function(name, inputName) {\n    for(var i in flow.get(\"atemConfig\")[name][\"inputs\"]) {\n        if(flow.get(\"atemConfig\")[name][\"inputs\"][i] == inputName) {\n            return parseInt(i);\n        }\n    }\n}\n\nif(typeof msg.payload.event.service_data.entity_id == \"string\"){msg.payload.event.service_data.entity_id = [msg.payload.event.service_data.entity_id];}\nvar msgs = [];\nfor(var i in msg.payload.event.service_data.entity_id) {\n    var entity_id = msg.payload.event.service_data.entity_id[i];\n    if(entity_id.includes(\"atem_\")){\n        var entityIdSplits = entity_id.split(\".\")[1].split(\"_\");\n        \n        switch(entityIdSplits[2]) {\n            case \"previewinput\":\n            case \"programinput\": {\n                if(msg.payload.event.domain != \"input_select\" || msg.payload.event.service != \"select_option\") {return;}\n                var inputId = parseInt(msg.payload.event.service_data.option) || getInputId(entityIdSplits[1], msg.payload.event.service_data.option);\n                msgs.push({\n                    \"name\": entityIdSplits[1],\n                    \"payload\": {\n                        \"cmd\": entityIdSplits[2].split(\"input\")[0] + \"Input\",\n                        \"data\": {\n                            \"ME\": parseInt(entityIdSplits[3]) - 1,\n                            \"videoSource\": {\n                                \"id\": inputId\n                            }\n                        }\n                    }\n                });\n                break;\n            }\n            case \"macro\": {\n                if(msg.payload.event.domain != \"button\" || msg.payload.event.service != \"press\") {return;}\n                msgs.push({\n                    \"name\": entityIdSplits[1],\n                    \"payload\": {\n                        \"cmd\": \"macroAction\",\n                        \"data\": {\n                            \"macroId\": parseInt(entityIdSplits[3]),\n                            \"action\": \"run\"\n                        }\n                    }\n                });\n                break;\n            }\n            case \"downstreamkeyer\":\n            case \"upstreamkeyer\": {\n                if(msg.payload.event.domain != \"input_boolean\"){return;}\n                var cmd = entityIdSplits[2].split(\"keyer\")[0] + \"Keyer\";\n                var ret = {\n                    \"name\": entityIdSplits[1],\n                    \"payload\": {\n                        \"cmd\": cmd,\n                        \"data\": {\n                            \"ME\": parseInt(entityIdSplits[3]) - 1,\n                            \"id\": parseInt(entityIdSplits[4]) - 1\n                        }\n                    }\n                }\n                switch(msg.payload.event.service) {\n                    case \"turn_on\": {\n                        ret.payload.data.state = true;\n                        msgs.push(ret);\n                        break;\n                    }\n                    case \"turn_off\": {\n                        ret.payload.data.state = false;\n                        msgs.push(ret);\n                        break;\n                    }\n                    case \"toggle\": {\n                        ret.payload.data.state = !flow.get(\"atemStates\")[entityIdSplits[1]][cmd][ret.payload.data.id].state;\n                        msgs.push(ret);\n                        break;\n                    }\n                }\n                break;\n            }\n            case \"auxsource\": {\n                if(msg.payload.event.domain != \"input_select\" || msg.payload.event.service != \"select_option\") {return;}\n                var inputId = parseInt(msg.payload.event.service_data.option) || getInputId(entityIdSplits[1], msg.payload.event.service_data.option);\n                msgs.push({\n                    \"name\": entityIdSplits[1],\n                    \"payload\": {\n                        \"cmd\": \"auxSource\",\n                        \"data\": {\n                            \"id\": parseInt(entityIdSplits[3]) - 1,\n                            \"videoSource\": {\n                                \"id\": inputId\n                            },\n                            \"mask\": true\n                        }\n                    }\n                });\n                break;\n            }\n        }\n    }\n}\nreturn [msgs];","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":330,"y":860,"wires":[["589b82629fb25959"]]},{"id":"589b82629fb25959","type":"switch","z":"015b0090692f04b3","name":"","property":"name","propertyType":"msg","rules":[{"t":"eq","v":"auditorium","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":445,"y":860,"wires":[["a6bd71149af45dda"]],"l":false},{"id":"7ba5761a91b7d29d","type":"delay","z":"015b0090692f04b3","name":"","pauseType":"delayv","timeout":"0","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":975,"y":860,"wires":[["bcc8e30d085dc52f"]],"l":false},{"id":"a62ba966f34e8ed4","type":"function","z":"015b0090692f04b3","name":"Request","func":"var supportedCommands = [\"version\", \"inputProperty\", \"programInput\", \"previewInput\", \"downstreamKeyer\", \"upstreamKeyer\", \"auxSource\", \"macroProperties\"];\nvar msgs = [];\nfor (var i in supportedCommands) {\n    msgs.push({\n        \"payload\": {\n            \"cmd\": supportedCommands[i],\n            \"data\": {}\n        }\n    });\n}\nreturn [msgs];","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":340,"y":900,"wires":[["a6bd71149af45dda"]]},{"id":"5e0dc13363dd4bc3","type":"function","z":"015b0090692f04b3","name":"Process","func":"var setATEMConfig = function (name, configName, value) {\n    if (!flow.get(\"atemConfig\")) { flow.set(\"atemConfig\", {});}\n    if (!flow.get(\"atemConfig\")[name]) { flow.get(\"atemConfig\")[name] = {}; }\n    flow.get(\"atemConfig\")[name][configName] = value;\n}\nvar getATEMConfig = function (name, configName) {\n    if (!flow.get(\"atemConfig\") || !flow.get(\"atemConfig\")[name]) { return undefined; }\n    return flow.get(\"atemConfig\")[name][configName];\n}\nvar getInputName = function (name, id) {\n    return getATEMConfig(name, \"inputs\")[id];\n}\nvar getInputId = function (name, inputName) {\n    for (var i in getATEMConfig(name, \"inputs\")) {\n        if (getATEMConfig(name, \"inputs\")[i] == inputName) {\n            return i;\n        }\n    }\n}\nvar generateInputOptions = function (name) {\n    var inputs = [];\n    for (var i in getATEMConfig(name, \"inputs\")) {\n        inputs.push(getATEMConfig(name, \"inputs\")[i]);\n    }\n    return inputs;\n}\n\n//Program/Preview input\nvar input = function (name, ME, type, inputNumber) {\n    return {\n        \"entity_id\": `input_select.atem_${name}_${type}_${ME}`,\n        \"payload\": {\n            \"data\": {\n                \"state\": getInputName(name, inputNumber) || \"unknown\",\n                \"attributes\": {\n                    \"friendly_name\": `${name.charAt(0).toUpperCase() + name.slice(1)} ATEM ME ${ME} ${type.charAt(0).toUpperCase() + type.slice(1).split(\"input\")[0]} Input`,\n                    \"options\": generateInputOptions(name),\n                    \"editable\": true\n                }\n            }\n        }\n    }\n}\n\n//Up/Downstream keyer\nvar keyer = function (name, ME, type, id, state, fillSource, keySource) {\n    return {\n        \"entity_id\": `input_boolean.atem_${name}_${type}_${ME}_${id}`,\n        \"payload\": {\n            \"data\": {\n                \"state\": state ? \"on\" : \"off\",\n                \"attributes\": {\n                    \"friendly_name\": `${name.charAt(0).toUpperCase() + name.slice(1)} ATEM ME ${ME} ${type.charAt(0).toUpperCase() + type.slice(1).split(\"keyer\")[0]} Keyer ${id}`,\n                    \"ME\": ME,\n                    \"id\": id,\n                    \"fillSource\": fillSource,\n                    \"keySource\": keySource,\n                    \"editable\": true\n                }\n            }\n        }\n    }\n}\n\nswitch (msg.topic) {\n    case \"status\": {\n        return {\n            \"entity_id\": `sensor.atem_${msg.name}_status`,\n            \"payload\": {\n                \"data\": {\n                    \"state\": msg.payload.connectionStatus,\n                    \"attributes\": {\n                        \"friendly_name\": `${msg.name.charAt(0).toUpperCase() + msg.name.slice(1)} ATEM Status`\n                    }\n                }\n            }\n        }\n    }\n    case \"command\": {\n        switch (msg.payload.cmd) {\n            case \"version\": {\n                return {\n                    \"entity_id\": `sensor.atem_${msg.name}_version`,\n                    \"payload\": {\n                        \"data\": {\n                            \"state\": msg.payload.data.version,\n                            \"attributes\": {\n                                \"friendly_name\": `${msg.name.charAt(0).toUpperCase() + msg.name.slice(1)} ATEM Version`\n                            }\n                        }\n                    }\n                }\n            }\n            case \"inputProperty\": {\n                if (!msg.payload.data.inputs) { return; }\n                var inputs = {};\n                for (var i in msg.payload.data.inputs) {\n                    inputs[msg.payload.data.inputs[i].id] = msg.payload.data.inputs[i].longName.replace(/[^a-zA-Z ]/g, '');\n                }\n\n                setATEMConfig(msg.name, \"inputs\", inputs);\n                break;\n            }\n            case \"previewInput\":\n            case \"programInput\": {\n                setATEMConfig(msg.name, \"MECount\", msg.payload.data.length);\n                var msgs = [];\n                for (var i in msg.payload.data) {\n                    msgs.push(input(msg.name, parseInt(i) + 1, msg.payload.cmd.toLowerCase(), msg.payload.data[i].inputNumber));\n                }\n                return [msgs];\n            }\n            case \"downstreamKeyer\":\n            case \"upstreamKeyer\": {\n                var msgs = [];\n                for (var i in msg.payload.data) {\n                    if (msg.payload.cmd == \"downstreamKeyer\") {\n                        msg.payload.data[i].ME = 0;\n                        msg.payload.data[i].id = parseInt(i);\n                    }\n                    if (!msg.payload.data[i].fillSource) { msg.payload.data[i].fillSource = { \"longName\": \"unknown\" }; }\n                    if (!msg.payload.data[i].keySource) { msg.payload.data[i].keySource = { \"longName\": \"unknown\" }; }\n                    msgs.push(keyer(msg.name, msg.payload.data[i].ME + 1, msg.payload.cmd.toLowerCase(), msg.payload.data[i].id + 1,\n                        msg.payload.data[i].state, msg.payload.data[i].fillSource.longName, msg.payload.data[i].keySource.longName));\n                }\n                return [msgs];\n            }\n            case \"auxSource\": {\n                var msgs = [];\n                var delay = 0;\n                for (var i in msg.payload.data) {\n                    msgs.push({\n                        \"delay\": delay += 100,\n                        \"entity_id\": `input_select.atem_${msg.name}_auxsource_${parseInt(i) + 1}`,\n                        \"payload\": {\n                            \"data\": {\n                                \"state\": getInputName(msg.name, msg.payload.data[i].inputNumber) || \"unknown\",\n                                \"attributes\": {\n                                    \"friendly_name\": `${msg.name.charAt(0).toUpperCase() + msg.name.slice(1)} ATEM Aux Source ${parseInt(i) + 1}`,\n                                    \"options\": generateInputOptions(msg.name),\n                                    \"editable\": true\n                                }\n                            }\n                        }\n                    });\n                }\n                return [msgs];\n            }\n            case \"macroProperties\": {\n                var msgs = [];\n                var delay = 0;\n                for (var i in msg.payload.data) {\n                    if (msg.payload.data[i].isUsed == true) {\n                        msgs.push({\n                            \"delay\": delay += 100,\n                            \"entity_id\": `button.atem_${msg.name}_macro_${msg.payload.data[i].macroId}`,\n                            \"payload\": {\n                                \"data\": {\n                                    \"state\": \"unknown\",\n                                    \"attributes\": {\n                                        \"friendly_name\": `${msg.name.charAt(0).toUpperCase() + msg.name.slice(1)} ATEM Macro ${msg.payload.data[i].name}`,\n                                        \"editable\": true\n                                    }\n                                }\n                            }\n                        });\n                    }\n                }\n                return [msgs];\n            }\n        }\n    }\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":880,"y":860,"wires":[["7ba5761a91b7d29d"]]},{"id":"f2607202ebbf45d7","type":"q-gate","z":"015b0090692f04b3","name":"","controlTopic":"control","defaultState":"closed","openCmd":"open","closeCmd":"close","toggleCmd":"toggle","queueCmd":"queue","defaultCmd":"default","triggerCmd":"trigger","flushCmd":"flush","resetCmd":"reset","peekCmd":"peek","dropCmd":"drop","statusCmd":"status","maxQueueLength":"100","keepNewest":false,"qToggle":false,"persist":false,"storeName":"memory","x":785,"y":860,"wires":[["5e0dc13363dd4bc3"]],"l":false},{"id":"fe7d1ca426a89aa4","type":"change","z":"015b0090692f04b3","name":"","rules":[{"t":"set","p":"topic","pt":"msg","to":"control","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"open","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":955,"y":920,"wires":[["f2607202ebbf45d7"]],"l":false},{"id":"bc6703dd39e0c715","type":"delay","z":"015b0090692f04b3","name":"","pauseType":"delay","timeout":"6","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":235,"y":900,"wires":[["a62ba966f34e8ed4"]],"l":false},{"id":"ef414f80fad7ef41","type":"delay","z":"015b0090692f04b3","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":895,"y":920,"wires":[["fe7d1ca426a89aa4"]],"l":false},{"id":"beb1452c125244e3","type":"subflow:19314f19fb237662","z":"015b0090692f04b3","name":"Load","env":[{"name":"Event","value":"loaded","type":"str"}],"x":810,"y":920,"wires":[["ef414f80fad7ef41"]]},{"id":"b5b9c0d45d732fcb","type":"subflow:19314f19fb237662","z":"015b0090692f04b3","name":"Load","env":[{"name":"Event","value":"loaded","type":"str"}],"x":130,"y":900,"wires":[["bc6703dd39e0c715"]]},{"id":"f32424bdd0787633","type":"atem-network","name":"Auditorium","ipAddress":"10.4.25.14"},{"id":"19cf4248.b7861e","type":"server","name":"Home Assistant","version":5,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true,"heartbeat":false,"heartbeatInterval":30,"areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":"at: ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m","enableGlobalContextStore":true}]

Flow Info

Created 2 years, 10 months ago
Updated 2 years, 8 months ago
Rating: not yet rated

Owner

Actions

Rate:

Node Types

Core
  • change (x2)
  • comment (x1)
  • delay (x4)
  • function (x4)
  • switch (x1)
Other
  • atem-atem (x1)
  • atem-network (x1)
  • ha-api (x1)
  • q-gate (x1)
  • server (x1)
  • server-events (x3)
  • subflow (x1)
  • subflow:19314f19fb237662 (x2)

Tags

  • blackmagic
  • atem
  • homeassistant
  • home
  • assistant
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option