HDMI CEC message translator

Flow to translate HDMI CEC messages received via MQTT. Check out the demo here: https://www.facebook.com/61550246612741/videos/986448103239859/?app=fbl and here: https://youtu.be/hJY-innhytc

[{"id":"d8a9416f72d5d03f","type":"tab","label":"HDMI CEC","disabled":false,"info":"","env":[]},{"id":"e060dae6ccd1eeeb","type":"mqtt in","z":"d8a9416f72d5d03f","name":"","topic":"cec_messages","qos":"2","datatype":"auto","broker":"","nl":false,"rap":false,"inputs":0,"x":120,"y":80,"wires":[["8c3fa7a850836170","26cda5d2fbbf16bc"]]},{"id":"8c3fa7a850836170","type":"function","z":"d8a9416f72d5d03f","name":"HDMI CEC translation","func":"// Translation of device numbers (0-15) to devices\nfunction getDeviceName(nibble) {\n    switch (nibble) {\n        case \"0\":\n            return \"TV\"; // \"TV\"\n        case \"1\":\n            return \"Recorder 1\"; // \"Recorder 1\"\n        case \"2\":\n            return \"Recorder 2\"; // \"Recorder 2\"\n        case \"3\":\n            return \"Tuner 1\"; // \"Tuner 1\"\n        case \"4\":\n            return \"Playback 1\"; // \"Playback 1\"\n        case \"5\":\n            return \"Audio system\"; // \"Audio system\"\n        case \"6\":\n            return \"Tuner 2\"; // \"Tuner 2\"\n        case \"7\":\n            return \"Tuner 3\"; // \"Tuner 3\"\n        case \"8\":\n            return \"Playback 2\"; // \"Playback 2\"\n        case \"9\":\n            return \"Recorder 3\"; // \"Recorder 3\"\n        case \"A\":\n            return \"Tuner 4\"; // \"Tuner 4\"\n        case \"B\":\n            return \"Playback 3\"; // \"Playback 3\"\n        case \"C\":\n            return \"Reserved 1\"; // \"Reserved 1\"\n        case \"D\":\n            return \"Reserved 2\"; // \"Reserved 2\"\n        case \"E\":\n            return \"Free Use\"; // \"Free Use\"\n        case \"F\":\n            return \"Broadcast\"; // \"Broadcast\"\n        default:\n            return \"Unknown device\"; // \"Unknown device\"\n    }\n}\n\n// Translation of the first byte (sender and receiver devices)\nfunction parseDeviceAndMessageType(byte) {\n    const senderNibble = byte[0]; // First nibble is the sender\n    const receiverNibble = byte[1]; // Second nibble is the receiver\n\n    const senderDevice = getDeviceName(senderNibble);\n    const receiverDevice = getDeviceName(receiverNibble);\n\n    let messageType = '';\n\n    // Identify message type based on the receiver nibble\n    if (receiverNibble === \"F\") {\n        messageType = \"broadcasting:\";\n    } else {\n        messageType = `to ${receiverDevice}:`;\n    }\n\n    return `${senderDevice} ${messageType}`;\n}\n\n// Function to format a 4-digit number with periods\nconst formatWithPeriods = number => number.toString().split('').join('.');\n\n// Translation of actions (second and third byte)\nfunction parseAction(bytes) {\n    let action = '';\n    const byte1 = bytes[0];\n    const byte2 = bytes[1] || null;\n    const byte3 = bytes[2] || null;\n\n    // Format byte2 and byte3\n    const formattedByte2And3 = formatWithPeriods(byte2 + byte3);\n    \n    // Convert byte2 to decimal, subtract 2, and use the updated value\n    const volume = parseInt(byte2, 16) - 2;\n    \n    switch (byte1) {\n        case \"0D\":\n            action = \"I am the active source\";\n            break; \n        case \"04\":\n            action = \"I am the active source\";\n            break; \n        case \"A0\":\n            action = \"Routing information\";\n            break;  \n        case \"1A\":\n            action = \"My state is\";\n            if (byte2 === \"02\") {\n                action += \" (Off)\";\n            } else if (byte2 === \"01\") {\n                action += \" (On)\";\n            }\n            break;        \n        case \"7A\":\n            action = `Audio volume (${volume})`;\n            break;\n        case \"7D\":\n            action = \"Request audio mode status\";\n            break;\n        case \"7E\":\n            action = `Audio mode`;\n            if (byte2 === \"00\") {\n                action += \" (Off)\";\n            } else if (byte2 === \"01\") {\n                action += \" (On)\";\n            }\n            break;\n        case \"8C\":\n            action = \"Requesting vendor ID\";\n            break;\n        case \"8E\":\n            action = \"CEC routing control command\";\n            break;\n        case \"8F\":\n            action = \"Requesting power state\";\n            break;\n        case \"9D\":\n            action = `Inactive source with physical address (${formattedByte2And3})`;\n            break;\n        case \"9E\":\n            action = 'Reporting CEC version (not translated)';\n            break;\n        case \"9F\":\n            action = 'Requesting CEC version';\n            break;\n        case \"36\":\n            action = \"Standby\";\n            break;\n        case \"44\":\n            action = \"Button pressed\";\n            if (byte2 === \"00\") {\n                action += \" (Select)\";\n            } else if (byte2 === \"01\") {\n                action += \" (Up)\";\n            } else if (byte2 === \"02\") {\n                action += \" (Down)\";\n            } else if (byte2 === \"03\") {\n                action += \" (Left)\";\n            } else if (byte2 === \"04\") {\n                action += \" (Right)\";\n            } else if (byte2 === \"41\") {\n                action += \" (Volume up)\";\n            } else if (byte2 === \"42\") {\n                action += \" (Volume down)\";\n            } else if (byte2 === \"46\") {\n                action += \" (Play/Pause)\";\n            } else {\n                action += ` (Unknown control: ${byte2}${byte3})`;\n            }\n            break;\n        case \"45\":\n            action = \"Button released\";\n            break;\n        case \"47\":\n            action = \"Set OSD display name (not translated)\";\n            break;\n        case \"70\":\n            action = `Set active audio source (${formattedByte2And3})`;\n            break;\n        case \"71\":\n            action = \"Requesting system audio mode\";\n            break;\n        case \"72\":\n            action = \"Set system audio mode\";\n            if (byte2 === \"00\") {\n                action += \" (Off)\";\n            } else if (byte2 === \"01\") {\n                action += \" (On)\";\n            } else {\n                action += ` (Unknown value: ${byte2})`;\n            }\n            break;\n        case \"82\":\n            action = `Set active source (${formattedByte2And3})`;\n            break;\n        case \"83\":\n            action = \"What is your physical address?\";\n            break;\n        case \"84\":\n            action = `My physical address is (${formattedByte2And3})`;\n            break;\n        case \"85\":\n            action = `Requesting active source`;\n            break;\n        case \"87\":\n            action = `Reporting device vendor ID (not translated)`;\n            break;\n        case \"89\":\n            action = `Vendor command (not translated)`;\n            break;\n        case \"90\":\n            action = \"Reporting power status\";\n            if (byte2 === \"00\") {\n                action += \" (Power On)\";\n            } else if (byte2 === \"01\") {\n                action += \" (Standby)\";\n            } else if (byte2 === \"02\") {\n                action += \" (In transition from Standby to On)\";\n            } else if (byte2 === \"03\") {\n                action += \" (Power Off)\";\n            }\n            break;\n        default:\n            action = `Unknown action (${byte1})`;\n    }\n    return action;\n}\n\n// Dynamic translation function for HDMI CEC commands\nfunction translateHdmiCecCommand(command) {\n    const parts = command.split(\":\").map(part => part.toUpperCase());\n\n    if (parts.length < 2) {\n        return \"Invalid command\";\n    }\n\n    const deviceAndMessageType = parseDeviceAndMessageType(parts[0]);\n    const action = parseAction(parts.slice(1)); // Send the remaining bytes to parseAction\n\n    return `${deviceAndMessageType} ${action}`;\n}\n\n// Receive input from msg.payload\nlet command = msg.payload;\n\n// Call the translation function\nmsg.payload = translateHdmiCecCommand(command);\n\nmsg.raw = command;\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":220,"y":150,"wires":[["7aec172e58613f8c"]]},{"id":"50e372ca8991d0cc","type":"debug","z":"d8a9416f72d5d03f","name":"translated HDMI CEC message","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":710,"y":150,"wires":[]},{"id":"7aec172e58613f8c","type":"change","z":"d8a9416f72d5d03f","name":"Custom device names","rules":[{"t":"change","p":"payload","pt":"msg","from":"Playback 1","fromt":"str","to":"Apple TV","tot":"str"},{"t":"change","p":"payload","pt":"msg","from":"Playback 2","fromt":"str","to":"PlayStation","tot":"str"},{"t":"change","p":"payload","pt":"msg","from":"Audio system","fromt":"str","to":"Denon AVR","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":450,"y":150,"wires":[["50e372ca8991d0cc"]]},{"id":"26cda5d2fbbf16bc","type":"debug","z":"d8a9416f72d5d03f","name":"raw HDMI CEC message","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":370,"y":80,"wires":[]}]

Flow Info

Created 1 year, 5 months ago
Rating: not yet rated

Owner

Actions

Rate:

Node Types

Core
  • change (x1)
  • debug (x2)
  • function (x1)
  • mqtt in (x1)
Other
  • tab (x1)

Tags

  • HDMI
  • CEC
  • HDMICEC
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option