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}]