Support for HDMI CEC protocol (#19434)

This commit is contained in:
s-hadinger 2023-09-01 21:46:18 +02:00 committed by GitHub
parent 48cf04db3a
commit f7c4c16a9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 1560 additions and 3 deletions

View File

@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file.
## [13.1.0.2]
### Added
- Support for HDMI CEC protocol
### Breaking Changed

View File

@ -771,6 +771,13 @@
// Commands xdrv_60_shift595.ino - 74x595 family shift register driver
#define D_CMND_SHIFT595_DEVICE_COUNT "Shift595DeviceCount"
// Commands xdrv_70_hdmi_cec.ino
#define D_PRFX_HDMI "Hdmi"
#define D_CMND_HDMI_SEND "Send"
#define D_CMND_HDMI_SEND_RAW "SendRaw"
#define D_CMND_HDMI_TYPE "Type"
#define D_CMND_HDMI_ADDR "Addr"
// Commands xdrv_89_dali.ino
#define D_CMND_DALI_POWER "power"
#define D_CMND_DALI_DIMMER "dim"

View File

@ -211,6 +211,7 @@ enum UserSelectablePins {
GPIO_LOX_O2_RX, // LOX-O2 RX
GPIO_GM861_TX, GPIO_GM861_RX, // GM861 Serial interface
GPIO_DINGTIAN_OE, // New version of Dingtian relay board where PL is not shared with OE
GPIO_HDMI_CEC, // Support for HDMI CEC
GPIO_SENSOR_END };
// Error as warning to rethink GPIO usage with max 2045
@ -469,6 +470,7 @@ const char kSensorNames[] PROGMEM =
D_SENSOR_LOX_O2_RX "|"
D_SENSOR_GM861_TX "|" D_SENSOR_GM861_RX "|"
D_GPIO_DINGTIAN_OE "|"
D_SENSOR_HDMI_CEC "|"
;
const char kSensorNamesFixed[] PROGMEM =
@ -697,6 +699,10 @@ const uint16_t kGpioNiceList[] PROGMEM = {
AGPIO(GPIO_MCP23XXX_INT) + MAX_MCP23XXX,
#endif
#ifdef USE_HDMI_CEC
AGPIO(GPIO_HDMI_CEC), // HDMI CEC bus
#endif
AGPIO(GPIO_TXD), // Serial interface
AGPIO(GPIO_RXD), // Serial interface

View File

@ -742,7 +742,7 @@ typedef struct {
uint16_t modbus_sbaudrate; // 736
uint16_t shutter_motorstop; // 738
uint8_t battery_level_percent; // 73A
uint8_t free_73B[2]; // 73B
uint8_t hdmi_addr[2]; // 73B HDMI CEC physical address - warning this is a non-aligned uint16
uint8_t novasds_startingoffset; // 73D
uint8_t web_color[18][3]; // 73E
@ -845,7 +845,7 @@ typedef struct {
uint8_t tcp_config; // F5F
uint8_t light_step_pixels; // F60
uint8_t ex_modbus_sbaudrate; // F61 - v12.2.0.5
uint8_t hdmi_cec_device_type; // F61 - v13.1.0.1 (was ex_modbus_sbaudrate v12.2.0.5)
uint8_t modbus_sconfig; // F62

View File

@ -655,6 +655,7 @@
#define D_SENSOR_I2S_BCLK_IN "I2S WS IN"
#define D_SENSOR_I2S_DIN "I2S DIN"
#define D_SENSOR_I2S_DOUT "I2S DOUT"
#define D_SENSOR_HDMI_CEC "HDMI CEC"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "MP3 Speler"
#define D_SENSOR_DFR562_BUSY "MP3 Bezet"

View File

@ -655,6 +655,7 @@
#define D_SENSOR_I2S_BCLK_IN "I2S WS IN"
#define D_SENSOR_I2S_DIN "I2S DIN"
#define D_SENSOR_I2S_DOUT "I2S DOUT"
#define D_SENSOR_HDMI_CEC "HDMI CEC"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "MP3 Player"
#define D_SENSOR_DFR562_BUSY "MP3 Busy"

View File

@ -655,6 +655,7 @@
#define D_SENSOR_I2S_BCLK_IN "I2S WS IN"
#define D_SENSOR_I2S_DIN "I2S DIN"
#define D_SENSOR_I2S_DOUT "I2S DOUT"
#define D_SENSOR_HDMI_CEC "HDMI CEC"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "Reproductor MP3"
#define D_SENSOR_DFR562_BUSY "MP3 Ocupat"

View File

@ -655,6 +655,7 @@
#define D_SENSOR_I2S_BCLK_IN "I2S WS IN"
#define D_SENSOR_I2S_DIN "I2S DIN"
#define D_SENSOR_I2S_DOUT "I2S DOUT"
#define D_SENSOR_HDMI_CEC "HDMI CEC"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "MP3 Player"
#define D_SENSOR_DFR562_BUSY "MP3 Busy"

View File

@ -655,6 +655,7 @@
#define D_SENSOR_I2S_BCLK_IN "I2S WS IN"
#define D_SENSOR_I2S_DIN "I2S DIN"
#define D_SENSOR_I2S_DOUT "I2S DOUT"
#define D_SENSOR_HDMI_CEC "HDMI CEC"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "MP3 Player"
#define D_SENSOR_DFR562_BUSY "MP3 Busy"

View File

@ -655,6 +655,7 @@
#define D_SENSOR_I2S_BCLK_IN "I2S WS IN"
#define D_SENSOR_I2S_DIN "I2S DIN"
#define D_SENSOR_I2S_DOUT "I2S DOUT"
#define D_SENSOR_HDMI_CEC "HDMI CEC"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "MP3 Player"
#define D_SENSOR_DFR562_BUSY "MP3 Busy"

View File

@ -655,6 +655,7 @@
#define D_SENSOR_I2S_BCLK_IN "I2S WS IN"
#define D_SENSOR_I2S_DIN "I2S DIN"
#define D_SENSOR_I2S_DOUT "I2S DOUT"
#define D_SENSOR_HDMI_CEC "HDMI CEC"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "MP3 Player"
#define D_SENSOR_DFR562_BUSY "MP3 Busy"

View File

@ -655,6 +655,7 @@
#define D_SENSOR_I2S_BCLK_IN "I2S WS IN"
#define D_SENSOR_I2S_DIN "I2S DIN"
#define D_SENSOR_I2S_DOUT "I2S DOUT"
#define D_SENSOR_HDMI_CEC "HDMI CEC"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "MP3 Player"
#define D_SENSOR_DFR562_BUSY "MP3 Busy"

View File

@ -655,6 +655,7 @@
#define D_SENSOR_I2S_BCLK_IN "I2S WS In"
#define D_SENSOR_I2S_DIN "I2S DIn"
#define D_SENSOR_I2S_DOUT "I2S DOut"
#define D_SENSOR_HDMI_CEC "HDMI CEC"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "MP3 Player"
#define D_SENSOR_DFR562_BUSY "MP3 Busy"

View File

@ -655,6 +655,7 @@
#define D_SENSOR_I2S_BCLK_IN "I2S WS IN"
#define D_SENSOR_I2S_DIN "I2S DIN"
#define D_SENSOR_I2S_DOUT "I2S DOUT"
#define D_SENSOR_HDMI_CEC "HDMI CEC"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "MP3 Speler"
#define D_SENSOR_DFR562_BUSY "MP3 Bezet"

View File

@ -655,6 +655,7 @@
#define D_SENSOR_I2S_BCLK_IN "I2S WS IN"
#define D_SENSOR_I2S_DIN "I2S DIN"
#define D_SENSOR_I2S_DOUT "I2S DOUT"
#define D_SENSOR_HDMI_CEC "HDMI CEC"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "נגן מוזיקה"
#define D_SENSOR_DFR562_BUSY "MP3 Busy"

View File

@ -655,6 +655,7 @@
#define D_SENSOR_I2S_BCLK_IN "I2S WS IN"
#define D_SENSOR_I2S_DIN "I2S DIN"
#define D_SENSOR_I2S_DOUT "I2S DOUT"
#define D_SENSOR_HDMI_CEC "HDMI CEC"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "MP3 lejátszó"
#define D_SENSOR_DFR562_BUSY "MP3 elfoglalt"

View File

@ -655,6 +655,7 @@
#define D_SENSOR_I2S_BCLK_IN "I2S - WS IN"
#define D_SENSOR_I2S_DIN "I2S - DIN"
#define D_SENSOR_I2S_DOUT "I2S - DOUT"
#define D_SENSOR_HDMI_CEC "HDMI CEC"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "Riproduttore MP3"
#define D_SENSOR_DFR562_BUSY "MP3 occupato"

View File

@ -655,6 +655,7 @@
#define D_SENSOR_I2S_BCLK_IN "I2S WS IN"
#define D_SENSOR_I2S_DIN "I2S DIN"
#define D_SENSOR_I2S_DOUT "I2S DOUT"
#define D_SENSOR_HDMI_CEC "HDMI CEC"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "MP3 Player"
#define D_SENSOR_DFR562_BUSY "MP3 Busy"

View File

@ -655,6 +655,7 @@
#define D_SENSOR_I2S_BCLK_IN "I2S WS IN"
#define D_SENSOR_I2S_DIN "I2S DIN"
#define D_SENSOR_I2S_DOUT "I2S DOUT"
#define D_SENSOR_HDMI_CEC "HDMI CEC"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "MP3 Speler"
#define D_SENSOR_DFR562_BUSY "MP3 Bezet"

View File

@ -655,6 +655,7 @@
#define D_SENSOR_I2S_BCLK_IN "I2S WS IN"
#define D_SENSOR_I2S_DIN "I2S DIN"
#define D_SENSOR_I2S_DOUT "I2S DOUT"
#define D_SENSOR_HDMI_CEC "HDMI CEC"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "Odtwarzacz MP3"
#define D_SENSOR_DFR562_BUSY "MP3 zajęty"

View File

@ -655,6 +655,7 @@
#define D_SENSOR_I2S_BCLK_IN "I2S WS IN"
#define D_SENSOR_I2S_DIN "I2S DIN"
#define D_SENSOR_I2S_DOUT "I2S DOUT"
#define D_SENSOR_HDMI_CEC "HDMI CEC"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "MP3 Player"
#define D_SENSOR_DFR562_BUSY "MP3 Busy"

View File

@ -655,6 +655,7 @@
#define D_SENSOR_I2S_BCLK_IN "I2S WS IN"
#define D_SENSOR_I2S_DIN "I2S DIN"
#define D_SENSOR_I2S_DOUT "I2S DOUT"
#define D_SENSOR_HDMI_CEC "HDMI CEC"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "Leitor de MP3"
#define D_SENSOR_DFR562_BUSY "MP3 Ocupado"

View File

@ -655,6 +655,7 @@
#define D_SENSOR_I2S_BCLK_IN "I2S WS IN"
#define D_SENSOR_I2S_DIN "I2S DIN"
#define D_SENSOR_I2S_DOUT "I2S DOUT"
#define D_SENSOR_HDMI_CEC "HDMI CEC"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "MP3 Player"
#define D_SENSOR_DFR562_BUSY "MP3 Busy"

View File

@ -656,6 +656,7 @@
#define D_SENSOR_I2S_BCLK_IN "I2S WS IN"
#define D_SENSOR_I2S_DIN "I2S DIN"
#define D_SENSOR_I2S_DOUT "I2S DOUT"
#define D_SENSOR_HDMI_CEC "HDMI CEC"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "MP3 Player"
#define D_SENSOR_DFR562_BUSY "MP3 Busy"

View File

@ -655,6 +655,7 @@
#define D_SENSOR_I2S_BCLK_IN "I2S WS IN"
#define D_SENSOR_I2S_DIN "I2S DIN"
#define D_SENSOR_I2S_DOUT "I2S DOUT"
#define D_SENSOR_HDMI_CEC "HDMI CEC"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "MP3 Player"
#define D_SENSOR_DFR562_BUSY "MP3 Busy"

View File

@ -655,6 +655,7 @@
#define D_SENSOR_I2S_BCLK_IN "I2S WS IN"
#define D_SENSOR_I2S_DIN "I2S DIN"
#define D_SENSOR_I2S_DOUT "I2S DOUT"
#define D_SENSOR_HDMI_CEC "HDMI CEC"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "MP3 spelare"
#define D_SENSOR_DFR562_BUSY "MP3 upptaget"

View File

@ -655,6 +655,7 @@
#define D_SENSOR_I2S_BCLK_IN "I2S WS IN"
#define D_SENSOR_I2S_DIN "I2S DIN"
#define D_SENSOR_I2S_DOUT "I2S DOUT"
#define D_SENSOR_HDMI_CEC "HDMI CEC"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "MP3 Player"
#define D_SENSOR_DFR562_BUSY "MP3 Busy"

View File

@ -655,6 +655,7 @@
#define D_SENSOR_I2S_BCLK_IN "I2S WS IN"
#define D_SENSOR_I2S_DIN "I2S DIN"
#define D_SENSOR_I2S_DOUT "I2S DOUT"
#define D_SENSOR_HDMI_CEC "HDMI CEC"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "MP3 Player"
#define D_SENSOR_DFR562_BUSY "MP3 Busy"

View File

@ -655,6 +655,7 @@
#define D_SENSOR_I2S_BCLK_IN "I2S WS IN"
#define D_SENSOR_I2S_DIN "I2S DIN"
#define D_SENSOR_I2S_DOUT "I2S DOUT"
#define D_SENSOR_HDMI_CEC "HDMI CEC"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "MP3 Player"
#define D_SENSOR_DFR562_BUSY "MP3 Busy"

View File

@ -655,6 +655,7 @@
#define D_SENSOR_I2S_BCLK_IN "I2S WS IN"
#define D_SENSOR_I2S_DIN "I2S DIN"
#define D_SENSOR_I2S_DOUT "I2S DOUT"
#define D_SENSOR_HDMI_CEC "HDMI CEC"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "MP3 Player"
#define D_SENSOR_DFR562_BUSY "MP3 Busy"

View File

@ -655,6 +655,7 @@
#define D_SENSOR_I2S_BCLK_IN "I2S WS IN"
#define D_SENSOR_I2S_DIN "I2S DIN"
#define D_SENSOR_I2S_DOUT "I2S DOUT"
#define D_SENSOR_HDMI_CEC "HDMI CEC"
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "MP3 Player"
#define D_SENSOR_DFR562_BUSY "MP3 Busy"

View File

@ -783,6 +783,9 @@
#endif // USE_SPI
// -- One wire sensors ----------------------------
// #define USE_HDMI_CEC // Add support for HDMI CEC bus (+7k code)
// -- Serial sensors ------------------------------
//#define USE_MHZ19 // Add support for MH-Z19 CO2 sensor (+2k code)
//#define USE_SENSEAIR // Add support for SenseAir K30, K70 and S8 CO2 sensor (+2k3 code)

View File

@ -1731,7 +1731,7 @@ void SettingsDelta(void) {
Settings->energy_current_calibration2 = Settings->energy_current_calibration;
}
if (Settings->version < 0x0C020005) { // 12.2.0.5
Settings->modbus_sbaudrate = Settings->ex_modbus_sbaudrate;
Settings->modbus_sbaudrate = Settings->hdmi_cec_device_type; // was ex_modbus_sbaudrate
Settings->param[P_SERIAL_SKIP] = 0;
}
if (Settings->version < 0x0C030102) { // 12.3.1.2

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,324 @@
/*
xdrv_70_hdmi_cec.ino - support for HDMI CEC bus (control TV via HDMI)
Copyright (C) 2021 Theo Arends, Stephan Hadinger
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef USE_HDMI_CEC
/*********************************************************************************************\
* HDMI CEC send and receive using lib https://github.com/lucadentella/ArduinoLib_CEClient
\*********************************************************************************************/
#define XDRV_70 70
const char kHDMICommands[] PROGMEM = D_PRFX_HDMI "|"
D_CMND_HDMI_SEND_RAW "|" D_CMND_HDMI_SEND "|"
D_CMND_HDMI_TYPE "|" D_CMND_HDMI_ADDR;
void (* const HDMICommand[])(void) PROGMEM = {
&CmndHDMISendRaw, CmndHDMISend,
&CmndHDMIType, &CmndHDMIAddr,
};
// This is called after the logical address has been allocated
void HDMI_OnReady(class CEC_Device* self, int logical_address) {
int physical_address = self->getPhysicalAddress();
AddLog(LOG_LEVEL_INFO, PSTR("CEC: HDMI CEC initialized on GPIO %i, Logical address %d, Physical address 0x%04X"), self->getGPIO(), logical_address, physical_address);
}
void HDMI_OnReceive(class CEC_Device *self, int32_t from, int32_t to, uint8_t* buf, size_t len, bool ack)
{
AddLog(LOG_LEVEL_DEBUG, "CEC: Packet received: (%1X->%1X) %1X%1X%*_H %s", from, to, from, to, len, buf, ack ? PSTR("ACK") : PSTR("NAK"));
Response_P(PSTR("{\"HdmiReceived\":{\"From\":%i,\"To\":%i,\"Data\":\"%*_H\"}}"), from, to, len, buf);
if (to == self->getLogicalAddress() || to == 0x0F) {
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings->flag.mqtt_sensor_retain);
}
XdrvRulesProcess(0); // apply rules
}
void HDMI_OnTransmit(class CEC_Device *self, uint8_t* buf, size_t len, bool ack)
{
// This is called after a frame is transmitted.
AddLog(LOG_LEVEL_DEBUG, "CEC: Packet sent: %*_H %s", len, buf, ack ? PSTR("ACK") : PSTR("NAK"));
}
// singleton for HDMI CEC object, could be expanded if we manage multiple HDMI in parallel
CEC_Device *HDMI_CEC_device = nullptr;
void HdmiCecInit(void)
{
// CEC device type
CEC_Device::CEC_DEVICE_TYPE device_type = (CEC_Device::CEC_DEVICE_TYPE) Settings->hdmi_cec_device_type;
if (device_type == CEC_Device::CDT_TV || device_type >= CEC_Device::CDT_LAST) {
// if type in Settings is invalid, default to PLAYBACK_DEVICE
device_type = CEC_Device::CDT_PLAYBACK_DEVICE;
Settings->hdmi_cec_device_type = (uint8_t) device_type;
SettingsSaveAll();
}
// GPIO configuration
int32_t cec_gpio = Pin(GPIO_HDMI_CEC);
if (cec_gpio >= 0) {
HDMI_CEC_device = new CEC_Device(cec_gpio, device_type, true); // Promiscuous mode
if (HDMI_CEC_device == nullptr) {
AddLog(LOG_LEVEL_ERROR, PSTR("CEC: HDMI_CEC_device init failed"));
return;
}
HDMI_CEC_device->setOnReceiveCB(&HDMI_OnReceive);
HDMI_CEC_device->setOnTransmitCB(&HDMI_OnTransmit);
HDMI_CEC_device->setOnReadyCB(&HDMI_OnReady);
HDMI_CEC_device->start(); // start the protocol
}
}
/*********************************************************************************************\
* Interrupt management
\*********************************************************************************************/
void IRAM_ATTR CEC_Run(void *self) {
CEC_Device *cec_device = (CEC_Device*)self;
cec_device->serviceGpioISR();
}
/*********************************************************************************************\
* Commands
\*********************************************************************************************/
//
// Command HdmiSendRaw
//
// HdmiSendRaw <hex>
// Send the HEX sequence as-is with no control
//
void CmndHDMISendRaw(void) {
if (HDMI_CEC_device) {
RemoveSpace(XdrvMailbox.data);
SBuffer buf = SBuffer::SBufferFromHex(XdrvMailbox.data, strlen(XdrvMailbox.data));
if (buf.len() > 0 && buf.len() < 16) {
HDMI_CEC_device->transmitRaw(buf.buf(), buf.len());
ResponseCmndDone();
} else {
ResponseCmndChar_P(PSTR("Buffer too large"));
}
} else {
ResponseCmndError();
}
}
//
// Command HdmiSend
//
// HdmiSend <hex>
// HdmiSend { ["To":<to>,] "Data":"<hex>"}
// Send the HEX payload to the target (unicast of broadcast)
// "To": 0-15 (optional) target logical address, defaults to 0 (TV)
// "Hex": payload without the first byte (source/dst) which is inferred
//
// Examples:
// HdmiSend 8F -- ask TV its power state
// or HdmiSend {"Data":"8F"}
// or HdmiSend {"To":0, "Data":"8F"}
//
//
// HdmiSend 8C -- ask TV its vendor id
// or HdmiSend {"Data":"8C"}
// or HdmiSend {"To":0, "Data":"8C"}
//
void CmndHDMISend(void) {
if (HDMI_CEC_device) {
RemoveSpace(XdrvMailbox.data);
if (XdrvMailbox.data[0] == '{') {
// JSON
JsonParser parser(XdrvMailbox.data);
JsonParserObject root = parser.getRoot();
if (!parser || !(root.isObject())) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
JsonParserToken val;
uint32_t to = root.getUInt(PSTR("To"), 0);
const char * payload = root.getStr(PSTR("Data"));
SBuffer buf = SBuffer::SBufferFromHex(payload, strlen(payload));
if (buf.len() > 0 && buf.len() < 15) {
HDMI_CEC_device->transmitFrame(to, buf.buf(), buf.len());
ResponseCmndDone();
} else {
if (buf.len() == 0) {
ResponseCmndChar_P(PSTR("Buffer empty"));
} else {
ResponseCmndChar_P(PSTR("Buffer too large"));
}
}
} else {
// Hex
SBuffer buf = SBuffer::SBufferFromHex(XdrvMailbox.data, strlen(XdrvMailbox.data));
if (buf.len() > 0 && buf.len() < 15) {
HDMI_CEC_device->transmitFrame(0, buf.buf(), buf.len());
ResponseCmndDone();
} else {
if (buf.len() == 0) {
ResponseCmndChar_P(PSTR("Buffer empty"));
} else {
ResponseCmndChar_P(PSTR("Buffer too large"));
}
}
}
} else {
ResponseCmndError();
}
}
//
// Command CmndHDMIType
//
//
void CmndHDMIType(void) {
if (XdrvMailbox.data_len > 0) {
if ((XdrvMailbox.payload < 1) && (XdrvMailbox.payload >= CEC_Device::CDT_LAST)) {
uint8_t type = XdrvMailbox.payload;
if (type != Settings->hdmi_cec_device_type) {
Settings->hdmi_cec_device_type = XdrvMailbox.payload;
SettingsSaveAll();
}
}
}
ResponseCmndNumber(Settings->hdmi_cec_device_type);
}
#define HDMI_EDID_ADDRESS 0x50 // HDMI EDID address is 0x50
// Read FULL EDID 256 bytes from address 0x50
// Return true if failed
// The buffer must be allocated to uint8_t[256] by caller
// Only checksum is checked
bool ReadEdid256(uint8_t *buf) {
if (!TasmotaGlobal.i2c_enabled) { return true; } // abort if I2C is not started
if (I2cReadBuffer(HDMI_EDID_ADDRESS, 0, buf , 128)) { return true; }
if (I2cReadBuffer(HDMI_EDID_ADDRESS, 128, buf + 128, 128)) { return true; }
// verify checksum for block 0
uint8_t chk0 = 0;
for (uint32_t i = 0; i < 128; i++) {
chk0 += buf[i];
}
if (chk0 != 0) { return true; }
// verify checksum for block 1
uint8_t chk1 = 0;
for (uint32_t i = 128; i < 256; i++) {
chk1 += buf[i];
}
if (chk1 != 0) { return true; }
// check prefix
uint32_t * buf32 = (uint32_t*) buf;
if (buf32[0] != 0xFFFFFF00 || buf32[1] != 0x00FFFFFF) { return true; }
return false; // OK
}
// HDMI get physical address
// This is done by reading EDID via I2C, and looking for a vendor specific extension
//
// Return 0x0000 if not found
uint16_t HDMIGetPhysicalAddress(void) {
uint8_t buf[256] = {0};
AddLog(LOG_LEVEL_DEBUG, PSTR("CEC: trying to read physical address"));
if (ReadEdid256(buf)) { return 0x0000; } // unable to get an address
uint8_t edid_extensions = buf[126];
if (HighestLogLevel() >= LOG_LEVEL_DEBUG) {
AddLog(LOG_LEVEL_DEBUG, PSTR("CEC: successfully read EDID 256 bytes, extensions count %i"), edid_extensions);
AddLog(LOG_LEVEL_DEBUG, PSTR("CEC: EDID: %*_H"), sizeof(buf),buf);
}
if (edid_extensions == 0) {
AddLog(LOG_LEVEL_INFO, PSTR("CEC: Error: EDID has no extension"));
}
// Read first extension which is mandatory for HDMI
if (buf[128] != 0x02 || buf[129] < 0x03) { return 0x0000; } // invalid extension
uint32_t extensions_first_byte = 128 + 4;
uint32_t extensions_last_byte = 128 + buf[130];
uint32_t idx = extensions_first_byte;
while (idx < extensions_last_byte) {
uint8_t data_block_header = buf[idx];
uint32_t type = (data_block_header >> 5);
uint32_t number_of_bytes = (data_block_header & 0x1F);
// AddLog(LOG_LEVEL_DEBUG, "CEC: idx %i extension type %i, number of bytes %i", idx, type, number_of_bytes);
if (type == 3) {
// Vendor specific extension
// 030C00 for "HDMI Licensing, LLC"
if (buf[idx+1] == 0x03 && buf[idx+2] == 0x0C && buf[idx+3] == 0x00) {
uint16_t addr = (buf[idx+4] << 8) | buf[idx+5];
AddLog(LOG_LEVEL_DEBUG, "CEC: physical address found: 0x%04X", addr);
return addr;
}
}
idx += 1 + number_of_bytes;
}
AddLog(LOG_LEVEL_DEBUG, "CEC: physical address not found");
return 0x0000; // TODO
}
void CmndHDMIAddr(void) {
if (XdrvMailbox.data_len > 0) {
if ((XdrvMailbox.payload < 1)) {
uint16_t hdmi_addr = XdrvMailbox.payload;
Settings->hdmi_addr[0] = (hdmi_addr) & 0xFF;
Settings->hdmi_addr[1] = (hdmi_addr >> 8) & 0xFF;
}
}
uint16_t hdmi_addr = HDMIGetPhysicalAddress();
Response_P(PSTR("{\"%s\":\"0x%04X\"}"), XdrvMailbox.command, hdmi_addr);
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
bool Xdrv70(uint32_t function)
{
bool result = false;
switch (function) {
case FUNC_INIT:
HdmiCecInit();
break;
case FUNC_LOOP:
case FUNC_SLEEP_LOOP:
if (HDMI_CEC_device) {
HDMI_CEC_device->run();
}
break;
case FUNC_COMMAND:
if (HDMI_CEC_device) {
result = DecodeCommand(kHDMICommands, HDMICommand);
}
break;
}
return result;
}
#endif // USE_HDMI_CEC