From 66bf06b5d3bcd82a313b103b2595619ddedcd914 Mon Sep 17 00:00:00 2001 From: Jos van Eijndhoven Date: Wed, 14 Oct 2026 19:12:46 -1000 Subject: [PATCH] [hdmi_cec] CEC message decoder from the original branch The decoder sources as they were before they were taken out of #9189 to keep the review small (commit 11ded9dfbf on the author's branch). They turn a frame into a one-line text with the opcode name and its operands, for the log. They are not wired into the merged bus core yet: the original called the decoder from Frame::to_text() behind HDMI_CEC_USE_DECODER, with a decode_messages option in the configuration. --- esphome/components/hdmi_cec/cec_decoder.cpp | 484 ++++++++++++++++++++ esphome/components/hdmi_cec/cec_decoder.h | 196 ++++++++ 2 files changed, 680 insertions(+) create mode 100644 esphome/components/hdmi_cec/cec_decoder.cpp create mode 100644 esphome/components/hdmi_cec/cec_decoder.h diff --git a/esphome/components/hdmi_cec/cec_decoder.cpp b/esphome/components/hdmi_cec/cec_decoder.cpp new file mode 100644 index 00000000000..35830589863 --- /dev/null +++ b/esphome/components/hdmi_cec/cec_decoder.cpp @@ -0,0 +1,484 @@ +#include +#include +#include +#include +#include + +#include "esphome/core/defines.h" +#include "esphome/core/log.h" + +#ifdef HDMI_CEC_USE_DECODER +#include "hdmi_cec.h" +#include "cec_decoder.h" + +namespace esphome { +namespace hdmi_cec { + +const std::array Decoder::UI_COMMANDS = { + /* 0x00 = */ "Select", + "Up", + "Down", + "Left", + "Right", + "Right-Up", + "Right-Down", + "Left-Up", + /* 0x08 = */ "Left-Down", + "Root Menu", + "Setup Menu", + "Contents Menu", + "Favorite Menu", + "Exit", + "Reserved", + "Reserved", + /* 0x10 = */ "Media Top Menu", + "Media Context-sensitive Menu", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + /* 0x18 = */ "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Number Entry Mode", + "11", + "12", + /* 0x20 = */ "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + /* 0x28 = */ "8", + "9", + "Dot", + "Enter", + "Clear", + "Reserved", + "Reserved", + "Next Favorite", + /* 0x30 = */ "Channel Up", + "Channel Down", + "Previous Channel", + "Sound Select", + "Input Select", + "Display Information", + "Help", + "Page Up", + /* 0x38 = */ "Page Down", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + /* 0x40 = */ "Power", + "Volume Up", + "Volume Down", + "Mute", + "Play", + "Stop", + "Pause", + "Record", + /* 0x48 = */ "Rewind", + "Fast forward", + "Eject", + "Forward", + "Backward", + "Stop-Record", + "Pause-Record", + "Reserved", + /* 0x50 = */ "Angle", + "Sub picture", + "Video on Demand", + "Electronic Program Guide", + "Timer Programming", + "Initial Configuration", + "Select Broadcast Type", + "Select Sound Presentation", + /* 0x58 = */ "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + /* 0x60 = */ "Play Function", + "Pause-Play Function", + "Record Function", + "Pause-Record Function", + "Stop Function", + "Mute Function", + "Restore Volume Function", + "Tune Function", + /* 0x68 = */ "Select Media Function", + "Select A/V Input Function", + "Select Audio Input Function", + "Power Toggle Function", + "Power Off Function", + "Power On Function", + "Reserved", + "Reserved", + /* 0x70 = */ "Reserved", + "F1 (Blue)", + "F2 (Red)", + "F3 (Green)", + "F4 (Yellow)", + "F5", + "Data"}; + +// See 'short audio descriptor' in https://en.wikipedia.org/wiki/Extended_Display_Identification_Data +const std::array Decoder::AUDIO_FORMATS = { + "reserved", "LPCM", "AC3", "MPEG-1", "MP3", "MPEG-2", "AAC", "DTS", "ATRAC", + "DSD", "DD+", "DTS-HD", "MAT/Dolby TrueHD", "DST Audio", "WMA Pro", "Extension?"}; + +const std::array Decoder::AUDIO_SAMPLERATES = {"32", "44.1", "48", "88", + "96", "176", "192", "Reserved"}; + +template bool Decoder::do_operand_() { + if (OPERANDS <= 0xFF) { + // generic function called for single operand of unkown type and length + return append_operand_("."); + } else { + // generic function to handle two or more operands + return do_operand_() && do_operand_<(OPERANDS >> 8u)>(); + } +} + +#define APPEND_LINE(...) (length_ = buf_append_printf(line_, size_, length_, __VA_ARGS__)) + +/** + * List of specialised operand decode functions, one function per operand type. + * (Not fully complete, might be extended later) + */ +template<> bool Decoder::do_operand_() { return false; } + +template<> bool Decoder::do_operand_() { + const static std::array NAMES = { + "Unrecognized opcode", "Not in correct mode to respond", "Cannot provide source", "Invalid operand", "Refused", + "Unable to determine"}; + return append_operand_(NAMES); +} + +template<> bool Decoder::do_operand_() { + // this type of operand comes in a sequence, until exhausted + bool ok = true; + while (ok && (offset_ < frame_.size())) { + ok = append_operand_(AUDIO_FORMATS); + } + return ok; +} + +template<> bool Decoder::do_operand_() { + char line[20]; + if (offset_ < frame_.size()) { + uint8_t field = frame_[offset_]; + std::snprintf(line, sizeof(line), "Mute=%d,Vol=%02X", (field >> 7), (field & 0x7f)); + return append_operand_(line); + } else { + return append_operand_("?"); + } +} + +template<> bool Decoder::do_operand_() { + const static std::array NAMES = {"TV", "Recording Device", "Reserved", + "Tuner", "Playback Device", "Audio System", + "CEC Switch", "Video Processor"}; + return append_operand_(NAMES); +} + +template<> bool Decoder::do_operand_() { + const static std::array NAMES = {"Default time", "Until cleared", "Clear previous", "Reserved"}; + return append_operand_(NAMES); +} + +template<> bool Decoder::do_operand_() { + if (offset_ >= frame_.size()) { + return false; + } + uint8_t opcode = frame_[offset_]; + return append_operand_(find_opcode_name_(opcode)); +} + +template<> bool Decoder::do_operand_() { + char line[20]; // frame size() is at most 16 + unsigned int i; + for (i = 0; i < frame_.size() - offset_; i++) { + line[i] = (char) (frame_[offset_ + i]); + } + line[i] = '\0'; // append '\0' termination: that is likely NOT part of the frame + offset_ = frame_.size(); // no useful data further in this frame + return append_operand_(line); +} + +template<> bool Decoder::do_operand_() { + // Exception: if this is an operand of 0x70, then this operand is + // merely optional, and its absence means 'Off' + if (frame_.at(1) == 0x70 && offset_ >= frame_.size()) { + return append_operand_("Off"); + } + if (offset_ + 1 >= frame_.size()) { + return append_operand_("?", 2); + } + char line[12]; + std::snprintf(line, sizeof(line), "%1x.%1x.%1x.%1x", (frame_[offset_] >> 4) & 0xF, frame_[offset_] & 0xF, + (frame_[offset_ + 1] >> 4) & 0xF, frame_[offset_ + 1] & 0xF); + return append_operand_(line, 2); +} + +template<> bool Decoder::do_operand_() { + const static std::array NAMES = {"On", "Standby", "Standby->On", "On->Standby"}; + return append_operand_(NAMES); +} + +template<> bool Decoder::do_operand_() { + // The frame can have a sequence of these operands, count is not fixed; + // each such operand takes 3 bytes in the frame. + // For a specification of this "Short Audio descriptor" format, see the Wikipedia page on + // "Extended Display Identification Data (EDID)", section "Audio Data Blocks". + std::array line; + uint32_t pos = 0; + bool ok = true; + while (ok && (offset_ + 2 < frame_.size())) { + const uint8_t descriptor = frame_[offset_]; + uint8_t format = (descriptor >> 3) & 0x0F; + pos = buf_append_printf(&line[0], line.size(), 0, "%s", AUDIO_FORMATS[format]); + pos = buf_append_printf(&line[0], line.size(), pos, ",num_channels=%d", (descriptor & 0x07)); + uint8_t rates = frame_[offset_ + 1]; + for (int bit = 0; rates; bit++, rates >>= 1) { + if (rates & 0x1) { + // show support of various audio sample rates + pos = buf_append_printf(&line[0], line.size(), pos, ",%skHz", AUDIO_SAMPLERATES[bit]); + } + } + if (format == 1) { + // for LPCM format + uint8_t widths = frame_[offset_ + 2] & 0x7; + for (int i = 0; widths; i++, widths >>= 1) { + if (widths & 0x1) { + // show support of audio samble bit widths of 16, 20, and/or 24 + pos = buf_append_printf(&line[0], line.size(), pos, ",%dbits", (16 + 4 * i)); + } + } + } + ok = append_operand_(line.data(), 3); + } + // Note: Further descriptor 'extensions' not yet decoded + return ok; +} + +template<> bool Decoder::do_operand_() { + const static std::array NAMES = {"Off", "On"}; + return append_operand_(NAMES); +} + +template<> bool Decoder::do_operand_() { + uint8_t command = frame_[offset_]; + bool ok = append_operand_(UI_COMMANDS); + if (!ok) { + return false; + } + // out of the 100+ UI commands, a few exceptional UI commands have appended an extra parameter: + switch (command) { + case 0x56: + return do_operand_(); + case 0x57: + return do_operand_(); + case 0x60: + return do_operand_(); + case 0x67: + return do_operand_(); + case 0x68: + return do_operand_(); + case 0x69: + return do_operand_(); + case 0x6A: + return do_operand_(); + default: + return ok; + } +} + +template<> bool Decoder::do_operand_() { + if (offset_ + 2 >= frame_.size()) { + return append_operand_("?", 3); + } + uint32_t id = (uint32_t) (frame_[offset_]) << 16 | (uint32_t) (frame_[offset_ + 1]) << 8 | frame_[offset_ + 2]; + const char *const *name_lookup = VENDOR_IDS.find(id); + const char *vendor_name = name_lookup ? (*name_lookup) : nullptr; + if (!vendor_name) { + // if the hdmi-cec vendor id is not in our list, the id value itself is printed. + char line[12]; + std::snprintf(line, sizeof(line), "ID=%06x", id); + return append_operand_(line, 3); + } + return append_operand_(vendor_name, 3); +} + +template<> bool Decoder::do_operand_() { + const static std::array NAMES = {"?", "1.2", "1.2a", "1.3", "1.3a", "1.4", "2.0", "2.x", "2.x"}; + return append_operand_(NAMES); +} + +const Decoder::CecOpcodeTable Decoder::CEC_OPCODE_TABLE = { + {// Must be given sorted on increasing opcode: + // opcode, name, function pointer with specification of its operands + {0x00, {"Feature Abort", &Decoder::do_operand_}}, + {0x04, {"Image View On", &Decoder::do_operand_}}, + {0x05, {"Tuner Step Increment", &Decoder::do_operand_}}, + {0x06, {"Tuner Step Decrement", &Decoder::do_operand_}}, + {0x07, {"Tuner Device Status", &Decoder::do_operand_}}, + {0x08, {"Give Tuner Device Status", &Decoder::do_operand_}}, + {0x09, {"Record On", &Decoder::do_operand_}}, + {0x0A, {"Record Status", &Decoder::do_operand_}}, + {0x0B, {"Record Off", &Decoder::do_operand_}}, + {0x0D, {"Text View On", &Decoder::do_operand_}}, + {0x0F, {"Record TV Screen", &Decoder::do_operand_}}, + {0x1A, {"Give Deck Status", &Decoder::do_operand_}}, + {0x1B, {"Deck Status", &Decoder::do_operand_}}, + {0x32, {"Set Menu Language", &Decoder::do_operand_}}, + {0x33, {"Clear Analogue Timer", &Decoder::do_operand_}}, + {0x34, {"Set Analogue Timer", &Decoder::do_operand_}}, + {0x35, {"Timer Status", &Decoder::do_operand_}}, + {0x36, {"Standby", &Decoder::do_operand_}}, + {0x41, {"Play", &Decoder::do_operand_}}, + {0x42, {"Deck Control", &Decoder::do_operand_}}, + {0x43, {"Timer Cleared Status", &Decoder::do_operand_}}, + {0x44, {"User Control Pressed", &Decoder::do_operand_}}, + {0x45, {"User Control Released", &Decoder::do_operand_}}, + {0x46, {"Give OSD Name", &Decoder::do_operand_}}, + {0x47, {"Set OSD Name", &Decoder::do_operand_}}, + {0x64, {"Set OSD String", &Decoder::do_operand_}}, + {0x67, {"Set Timer Program Title", &Decoder::do_operand_}}, + {0x70, {"System Audio Mode Request", &Decoder::do_operand_}}, + {0x71, {"Give Audio Status", &Decoder::do_operand_}}, + {0x72, {"Set System Audio Mode", &Decoder::do_operand_}}, + {0x7A, {"Report Audio Status", &Decoder::do_operand_}}, + {0x7D, {"Give System Audio Mode Status", &Decoder::do_operand_}}, + {0x7E, {"System Audio Mode Status", &Decoder::do_operand_}}, + {0x80, {"Routing Change", &Decoder::do_operand_}}, + {0x81, {"Routing Information", &Decoder::do_operand_}}, + {0x82, {"Active Source", &Decoder::do_operand_}}, + {0x83, {"Give Physical Address", &Decoder::do_operand_}}, + {0x84, {"Report Physical Address", &Decoder::do_operand_}}, + {0x85, {"Request Active Source", &Decoder::do_operand_}}, + {0x86, {"Set Stream Path", &Decoder::do_operand_}}, + {0x87, {"Device Vendor ID", &Decoder::do_operand_}}, + {0x89, {"Vendor Command", &Decoder::do_operand_}}, + {0x8A, {"Vendor Remote Button Down", &Decoder::do_operand_}}, + {0x8B, {"Vendor Remote Button Up", &Decoder::do_operand_}}, + {0x8C, {"Give Device Vendor ID", &Decoder::do_operand_}}, + {0x8D, {"Menu Request", &Decoder::do_operand_}}, + {0x8E, {"Menu Status", &Decoder::do_operand_}}, + {0x8F, {"Give Device Power Status", &Decoder::do_operand_}}, + {0x90, {"Report Power Status", &Decoder::do_operand_}}, + {0x91, {"Get Menu Language", &Decoder::do_operand_}}, + {0x92, + {"Select Analogue Service", + &Decoder::do_operand_}}, + {0x93, {"Select Digital Service", &Decoder::do_operand_}}, + {0x9A, {"Set Audio Rate", &Decoder::do_operand_}}, + {0x9D, {"Inactive Source", &Decoder::do_operand_}}, + {0x97, {"Set Digital Timer", &Decoder::do_operand_}}, + {0x99, {"Clear Digital Timer", &Decoder::do_operand_}}, + {0x9E, {"CEC Version", &Decoder::do_operand_}}, + {0x9F, {"Get CEC Version", &Decoder::do_operand_}}, + {0xA0, {"Vendor Command With ID", &Decoder::do_operand_}}, + {0xA1, {"Clear External Timer", &Decoder::do_operand_}}, + {0xA2, {"Set External Timer", &Decoder::do_operand_}}, + {0xA3, {"Report Short Audio Descriptor", &Decoder::do_operand_}}, + {0xA4, {"Request Short Audio Descriptor", &Decoder::do_operand_}}, + {0xC0, {"Initiate ARC", &Decoder::do_operand_}}, + {0xC1, {"Report ARC Initiated", &Decoder::do_operand_}}, + {0xC2, {"Report ARC Terminated", &Decoder::do_operand_}}, + {0xC3, {"Request ARC Initiation", &Decoder::do_operand_}}, + {0xC4, {"Request ARC Termination", &Decoder::do_operand_}}, + {0xC5, {"Terminate ARC", &Decoder::do_operand_}}, + {0xF8, {"CDC Message", &Decoder::do_operand_}}, + {0xFF, {"Abort", &Decoder::do_operand_}}}}; + +const Decoder::VendorIdTable Decoder::VENDOR_IDS = { + {// Must be given sorted on increasing id: + {0x000039, "Toshiba"}, {0x0000F0, "Samsung"}, {0x0005CD, "Denon"}, {0x000678, "Maranz"}, + {0x000982, "Loewe"}, {0x0009B0, "Onkyo"}, {0x000CB8, "Medion"}, {0x000CE7, "Toshiba"}, + {0x0010FA, "Apple"}, {0x001582, "Pulse Eight"}, {0x001950, "Harman Kardon"}, {0x001A11, "Google"}, + {0x0020C7, "Akai"}, {0x002467, "AOC"}, {0x008045, "Panasonic"}, {0x00903E, "Philips"}, + {0x009053, "Daewoo"}, {0x00A0DE, "Yamaha"}, {0x00D0D5, "Grundig"}, {0x00E036, "Pioneer"}, + {0x00E091, "LG"}, {0x08001F, "Sharp"}, {0x080046, "Sony"}, {0x18C086, "Broadcom"}, + {0x534850, "Sharp"}, {0x6B746D, "Vizio"}, {0x8065E9, "Benq"}, {0x9C645E, "Harman Kardon"}}}; + +void Decoder::address_decode_() { + const static std::array NAMES = {"TV", "RecordingDev1", "RecordingDev2", "Tuner1", + "PlaybackDev1", "AudioSystem", "Tuner2", "Tuner3", + "PlaybackDev2", "RecordingDev3", "Tuner4", "PlaybackDev3", + "Reserved", "Reserved", "SpecificUse", "Unregistered"}; + const char *dst = (frame_.is_broadcast()) ? "All" : NAMES[frame_.destination_addr()]; + const char *src = NAMES[frame_.initiator_addr()]; + APPEND_LINE("%s to %s: ", src, dst); +} + +const char *Decoder::find_opcode_name_(uint32_t opcode) const { + const FrameType *frametype = CEC_OPCODE_TABLE.find(opcode); + + if (!frametype) + return "?"; + + return frametype->name_; +} + +/** + * Helper function to implement the 'do_operand' methods, to gather a textual representation. + * @return true if a further operand can be decoded, false otherwise + */ +bool Decoder::append_operand_(const char *word, uint8_t offset_incr /* default 1 */) { + APPEND_LINE("[%s]", word); + offset_ += offset_incr; + return (length_ < size_) && (offset_ < frame_.size()); +} + +/** + * Entry function 'decode' to call for full decode of a CEC frame + */ +int Decoder::decode() { + // prepare text buffer state variables for an empty line + length_ = 0; // currently accumulated length of text of operands + line_[length_] = '\0'; // initialise output text to empty string + + if (!line_ || !size_) + return 0; + + // print src and dest fields into 'line_' + address_decode_(); + + // print opcode field + const FrameType *frametype = nullptr; + if (frame_.size() <= 1) { + // Missing frame operation field? + APPEND_LINE("Ping"); + } else { + // find operation name + frametype = CEC_OPCODE_TABLE.find(frame_.opcode()); + const char *opname = frametype ? frametype->name_ : "?"; + APPEND_LINE("<%s>", opname); + } + + // convert operand fields to text: + if (frametype) { + offset_ = 2; // location in frame of first operand, after address-byte and opcode-byte + OperandDecode_f f = frametype->decode_f_; + (this->*f)(); // call one of the above 'do_operand_' methods + } + return length_; +} + +} // namespace hdmi_cec +} // namespace esphome +#endif // HDMI_CEC_USE_DECODER diff --git a/esphome/components/hdmi_cec/cec_decoder.h b/esphome/components/hdmi_cec/cec_decoder.h new file mode 100644 index 00000000000..c17ff4f9076 --- /dev/null +++ b/esphome/components/hdmi_cec/cec_decoder.h @@ -0,0 +1,196 @@ +#pragma once + +#include +#include +#include +#include + +#include "hdmi_cec.h" + +namespace esphome::hdmi_cec { + +/** + * This LookupTable is const-initialised and only provides a simple 'find()'. + * This creates a compact (smaller code) alternative to an std::map; + */ +template class LookupTable { + public: + struct KeyValue { + KeyT key_; + ValueT value_; + }; + + const ValueT *find(KeyT key) const { + int lo_inx = 0; + int hi_inx = SIZE - 1; + while (lo_inx <= hi_inx) { + // Search for 'key' in the array range lo_inx..hi_inx, bounds included. + // Rely on the array being sorted on increasing opcode! + int mid = (lo_inx + hi_inx) / 2; + if (key == table_[mid].key_) { + return &table_[mid].value_; + } + if (key < table_[mid].key_) { + hi_inx = mid - 1; + } else { + lo_inx = mid + 1; + } + } + return nullptr; // requested 'key' not found in table + } + + const KeyValue table_[SIZE]; +}; + +/** + * This Decoder class interprets a binary CEC Frame to create a textual representation. + * The information to create this decoder is mostly extracted from the HDMI 1.3a standard document, + * from its section "Supplement 1 Consumer Electronics Control (CEC)". + * Some further details were found in the Linux kernel source code of the "v4l-utils" repository, + * such as the "ARC" related functionality of HDMI-CEC 1.4, and the HDMI vendor ID names. + * Details on the digital audio format decoding came from the wikipedia page on + * "Extended Display Identification Data (EDID)" + */ +class Decoder { + public: + Decoder(const Frame &frame, char *line, unsigned int size) + : frame_(frame), line_(line), size_(size), length_(0), offset_(2) {} + + /** + * Create a one-line textual representation of the Frame content for logging and debugging + * Store the text in the earlier provided 'line' buffer + * @return the length of the written text, excluding the terminating null character + */ + int decode(); + + protected: + const char *find_opcode_name_(uint32_t opcode) const; + void address_decode_(); + + /** + * Generic operand decode method, later specialised with operand-type-specific methods + * @return true if further conversions can continue, false when to stop. + */ + template bool do_operand_(); + + /** + * The CecOpcodeTable is extracted from the HDMI CEC standard (1.4): + * It lists all Frame opcodes with their and their expected [operand argument type(s)] + */ + using OperandDecode_f = bool (Decoder::*)(); + struct FrameType { + const char *name_; // name of the operation (of the op_code) + const OperandDecode_f decode_f_; // a pointer to the corresponding 'do_operand_()' method + }; + + using CecOpcodeTable = LookupTable; + using VendorIdTable = LookupTable; + + const Frame &frame_; + char *const line_; // buffer to hold the text of the decoded frame + const unsigned int size_; // size of the 'line_' buffer + unsigned int length_; // currently accumulated length of output text in 'line' + unsigned int offset_; // current offset in frame to process next operand byte(s) (frame[0] and [1] are skipped) + + /** + * The HDMI CEC standard specifies a set of distinct operand (parameter) types, + * used across the frame opcodes, denoted with "[operand type name]". + * These specified operand types are enumerated here for later type-specific decoding to text + */ + enum Operand : uint8_t { + NONE, + ABORT_REASON, + ANALOG_BROADCAST_TYPE, + ANALOG_FREQUENCY, + ASCII_DIGIT, + ASCII, + AUDIO_FORMAT, + AUDIO_RATE, + AUDIO_STATUS, + BOOLEAN, + BROADCAST_SYSTEM, + CEC_VERSION, + CHANNEL_IDENTIFIER, + // DayOfMonth, integrated in StartDateTime + DECK_CONTROL_MODE, + DECK_INFO, + DEVICE_TYPE, + DIGITAL_SERVICE_IDENTIFICATION, + DISPLAY_CONTROL, + DURATION, + EXTERNAL_PHYSICAL_ADDRESS, + EXTERNAL_PLUG, + EXTERNAL_SOURCE_SPECIFIER, + HOUR, + FEATURE_OPCODE, + LANGUAGE, + MENU_REQUEST_TYPE, + MENU_STATE, + MINUTE, + // MonthOfYear, integrated in StartDateTime + NEW_ADDRESS, + ORIGINAL_ADDRESS, + OSD_NAME, + OSD_STRING = OSD_NAME, + PHYSICAL_ADDRESS, + PLAY_MODE, + POWER_STATUS, + PROGRAM_TITLE_STRING, + RECORD_SOURCE, + RECORD_STATUS_INFO, + RECORDING_SEQUENCE, + SHORT_AUDIO_DESCRIPTOR, + STATUS_REQUEST, + START_DATE_TIME, + SYSTEM_AUDIO_STATUS, + TIME, + TIMER_CLEARED_STATUS_DATA, + TIMER_STATUS_DATA, + TUNER_DEVICE_INFO, + UI_BROADCAST_TYPE, + UI_COMMAND, + UI_FUNCTION_MEDIA, + UI_FUNCTION_SELECT_AV_INPUT, + UI_FUNCTION_SELECT_AUDIO_INPUT, + UI_SOUND_PRESENTATION_CONTROL, + VENDOR_ID, + VENDOR_SPECIFIC_DATA, + VENDOR_SPECIFIC_RC_CODE, + }; + /** + * The plain 'operand types' are uint8. + * Further uint32 'operand type' values are used to encode a sequence of upto 4 (potentially different) operands + * in the MSB bytes of an uint32 value. + */ + constexpr static uint32_t two(uint32_t first, uint32_t secnd) { return first | (secnd << 8); } + constexpr static uint32_t three(uint32_t first, uint32_t secnd, uint32_t third) { + return first | (secnd << 8) | (third << 16); + } + + /** + * Helper function to implement the 'do_operand' methods: + * Append one word + * @return true if a further operand shall be decoded, false to terminate operand decoding + */ + bool append_operand_(const char *word, uint8_t offset_incr = 1); + + /** + * Append one word, determined by the operand value as index to an array of names + * @return true if a further operand shall be decoded, false to terminate operand decoding + */ + template bool append_operand_(const std::array &strings) { + uint32_t operand_value = frame_[offset_]; + const char *s = (operand_value < N_STRINGS) ? strings[operand_value] : "?"; + return append_operand_(s); + } + + /** + * String tables used in the subsequent 'do_operand' decode functions + */ + const static std::array UI_COMMANDS; + const static std::array AUDIO_FORMATS; + const static std::array AUDIO_SAMPLERATES; + const static CecOpcodeTable CEC_OPCODE_TABLE; + const static VendorIdTable VENDOR_IDS; +}; // class Decoder +} // namespace esphome::hdmi_cec