From 22868cc05be97c166eb302212bb1cad35d480a19 Mon Sep 17 00:00:00 2001 From: tetele Date: Fri, 21 Jun 2024 12:46:51 +0300 Subject: [PATCH] Latest developments --- esphome/components/es8388/__init__.py | 65 +++++- .../components/es8388/es8388_component.cpp | 190 ++++++++++++------ esphome/components/es8388/es8388_component.h | 47 ++++- 3 files changed, 232 insertions(+), 70 deletions(-) diff --git a/esphome/components/es8388/__init__.py b/esphome/components/es8388/__init__.py index 3884755cb1..cc6ab767e5 100644 --- a/esphome/components/es8388/__init__.py +++ b/esphome/components/es8388/__init__.py @@ -1,15 +1,48 @@ +from esphome import automation +from esphome.automation import maybe_simple_id import esphome.codegen as cg import esphome.config_validation as cv - from esphome.components import i2c from esphome.const import CONF_ID +from esphome.util import Registry + +CONF_ES8388_ID = "es8388_id" + +CONF_PRESET = "preset" +CONF_INIT_INSTRUCTIONS = "init_instructions" +CONF_MACROS = "macros" +CONF_INSTRUCTIONS = "instructions" + +ES8388_MACROS = Registry() es8388_ns = cg.esphome_ns.namespace("es8388") ES8388Component = es8388_ns.class_("ES8388Component", cg.Component, i2c.I2CDevice) +Presets = es8388_ns.enum("ES8388Preset") +Macro = es8388_ns.class_("Macro") +MacroAction = es8388_ns.class_("ES8388MacroAction", automation.Action) + +ES8388_PRESETS = { + "raspiaudio_muse_luxe": Presets.RASPIAUDIO_MUSE_LUXE, + "raspiaudio_radio": Presets.RASPIAUDIO_RADIO, +} + +def validate_instruction_list(): + return cv.ensure_list( + cv.Length(min=2, max=2), + cv.ensure_list(int) + ) CONFIG_SCHEMA = ( - cv.Schema({cv.GenerateID(): cv.declare_id(ES8388Component)}) + cv.Schema({ + cv.GenerateID(): cv.declare_id(ES8388Component), + cv.Optional(CONF_PRESET): cv.enum(ES8388_PRESETS, lower=True), + cv.Optional(CONF_INIT_INSTRUCTIONS): validate_instruction_list(), + cv.Optional(CONF_MACROS): cv.ensure_list({ + cv.Required(CONF_ID): cv.string, + cv.Required(CONF_INSTRUCTIONS): validate_instruction_list(), + }), + }) .extend(i2c.i2c_device_schema(0x10)) .extend(cv.COMPONENT_SCHEMA) ) @@ -19,3 +52,31 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) + if CONF_PRESET in config: + cg.add(var.set_preset(config[CONF_PRESET])) + if CONF_INIT_INSTRUCTIONS in config: + cg.add(var.set_init_instructions(config[CONF_INIT_INSTRUCTIONS])) + if CONF_MACROS in config: + for macro in config[CONF_MACROS]: + ES8388_MACROS.register(macro[CONF_ID], Macro, { + cv.Required(CONF_ID): cv.declare_id(Macro), + cv.Required(CONF_INSTRUCTIONS): validate_instruction_list(), + }) + cg.add(var.register_macro(macro[CONF_ID], macro[CONF_INSTRUCTIONS])) + +@automation.register_action( + "es8388.execute_macro", + MacroAction, + maybe_simple_id( + { + cv.GenerateID(CONF_ES8388_ID): cv.use_id(ES8388Component), + cv.Required(CONF_ID): cv.string, + }, + ), +) +async def execute_macro_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ES8388_ID]) + + cg.add(var.set_id(config[CONF_ID])) + return var \ No newline at end of file diff --git a/esphome/components/es8388/es8388_component.cpp b/esphome/components/es8388/es8388_component.cpp index 0cf77307e7..12e002272c 100644 --- a/esphome/components/es8388/es8388_component.cpp +++ b/esphome/components/es8388/es8388_component.cpp @@ -7,19 +7,120 @@ namespace esphome { namespace es8388 { +static const char *const TAG = "es8388"; + #define ES8388_CLK_MODE_SLAVE 0 #define ES8388_CLK_MODE_MASTER 1 void ES8388Component::setup() { - int zerooo = 0; - int val = 2 / zerooo; - ESP_LOGW("ES8388", "Writing I2C registers"); - // PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1); // PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, 1); // WRITE_PERI_REG(PIN_CTRL, READ_PERI_REG(PIN_CTRL) & 0xFFFFFFF0); - this->setup_raspiaudio_radio(); + switch (this->preset_) { + case ES8388Preset::RASPIAUDIO_MUSE_LUXE: + this->setup_raspiaudio_muse_luxe(); + break; + + case ES8388Preset::RASPIAUDIO_RADIO: + this->setup_raspiaudio_radio(); + break; + + default: + if (!this->init_instructions_.empty()) { + for (std::array instruction : this->init_instructions_) { + if (this->write_byte(instruction[0], instruction[1])) + ESP_LOGW(TAG, "Error writing I2C instructions to ES8388: %#04x, %#04x", instruction[0], instruction[1]); + } + } + break; + } +} + +void ES8388Component::dump_config() { + ESP_LOGCONFIG(TAG, "ES8388:"); + if (this->preset_) { + ESP_LOGCONFIG(TAG, " Preset loaded: %#04x", this->preset_); + } else { + if (!this->init_instructions_.empty()) { + ESP_LOGCONFIG(TAG, " %d initialization instructions found", this->init_instructions_.size()); + } else { + ESP_LOGCONFIG(TAG, " No initialization instructions found"); + } + } +} + +void ES8388Component::setup_raspiaudio_muse_luxe() { + bool error = false; + + // mute + error = error || not this->write_byte(0x19, 0x04); + + // powerup + error = error || not this->write_byte(0x01, 0x50); // LPVrefBuf - low power + error = error || not this->write_byte(0x02, 0x00); // power up DAC/ADC without resetting DMS, DEM, filters & serial + + // set CLK mode to slave + error = error || not this->write_byte(0x08, 0x00); + + // DAC powerdown + error = error || not this->write_byte(0x04, 0xC0); + // vmidsel/500k ADC/DAC idem + error = error || not this->write_byte(0x00, 0x12); + + // i2s 16 bits + error = error || not this->write_byte(0x17, 0x18); + // sample freq 256 + error = error || not this->write_byte(0x18, 0x02); + // LIN2/RIN2 for mixer + error = error || not this->write_byte(0x26, 0x00); + // left DAC to left mixer + error = error || not this->write_byte(0x27, 0x90); + // right DAC to right mixer + error = error || not this->write_byte(0x2A, 0x90); + // DACLRC ADCLRC idem + error = error || not this->write_byte(0x2B, 0x80); + error = error || not this->write_byte(0x2D, 0x00); + // DAC volume max + error = error || not this->write_byte(0x1B, 0x00); + error = error || not this->write_byte(0x1A, 0x00); + + // ADC poweroff + error = error || not this->write_byte(0x03, 0xFF); + // ADC amp 24dB + error = error || not this->write_byte(0x09, 0x88); + // LINPUT1/RINPUT1 + error = error || not this->write_byte(0x0A, 0x00); + // ADC mono left + error = error || not this->write_byte(0x0B, 0x02); + // i2S 16b + error = error || not this->write_byte(0x0C, 0x0C); + // MCLK 256 + error = error || not this->write_byte(0x0D, 0x02); + // ADC Volume + error = error || not this->write_byte(0x10, 0x00); + error = error || not this->write_byte(0x11, 0x00); + // ALC OFF + error = error || not this->write_byte(0x03, 0x09); + error = error || not this->write_byte(0x2B, 0x80); + + error = error || not this->write_byte(0x02, 0xF0); + delay(1); + error = error || not this->write_byte(0x02, 0x00); + // DAC power-up LOUT1/ROUT1 enabled + error = error || not this->write_byte(0x04, 0x30); + error = error || not this->write_byte(0x03, 0x00); + // DAC volume max + error = error || not this->write_byte(0x2E, 0x1C); + error = error || not this->write_byte(0x2F, 0x1C); + // unmute + error = error || not this->write_byte(0x19, 0x00); + + if (error) { + ESP_LOGE(TAG, "Error writing I2C registers for preset Raspiaudio Muse Luxe"); + } else { + ESP_LOGD(TAG, "I2C registers written successfully for preset Raspiaudio Muse Luxe"); + } } void ES8388Component::setup_raspiaudio_radio() { @@ -119,74 +220,31 @@ void ES8388Component::setup_raspiaudio_radio() { error = error || not this->write_byte(49, 33); if (error) { - ESP_LOGE("ES8388", "Error writing I2C registers!"); + ESP_LOGE(TAG, "Error writing I2C registers for preset Raspiaudio Radio"); } else { - ESP_LOGW("ES8388", "I2C registers written successfully"); + ESP_LOGD(TAG, "I2C registers written successfully for preset Raspiaudio Radio"); } } -void ES8388Component::setup_raspiaudio_muse_luxe() { - // mute - this->mute(); +void ES8388Component::register_macro(std::string name, Instructions instructions) { + Macro macro; + macro.name = name; + macro.instructions = instructions; + this->macros_[name] = macro; +} - // powerup - this->powerup(); - this->clock_mode(ES8388_CLK_MODE_SLAVE); +void ES8388Component::execute_macro(std::string name) { + if (this->macros_.count(name) == 0) { + ESP_LOGE(TAG, "Unable to execute macro `%s`: not found", name); + return; + } - bool error = false; + ESP_LOGD(TAG, "Calling ES8388 macro `%s` with %d I2C instructions", name, this->macros_[name].instructions.size()); - // DAC powerdown - this->powerdown_dac(); - // vmidsel/500k ADC/DAC idem - error = error || not this->write_byte(0x00, 0x12); - - // i2s 16 bits - error = error || not this->write_byte(0x17, 0x18); - // sample freq 256 - error = error || not this->write_byte(0x18, 0x02); - // LIN2/RIN2 for mixer - error = error || not this->write_byte(0x26, 0x00); - // left DAC to left mixer - error = error || not this->write_byte(0x27, 0x90); - // right DAC to right mixer - error = error || not this->write_byte(0x2A, 0x90); - // DACLRC ADCLRC idem - error = error || not this->write_byte(0x2B, 0x80); - error = error || not this->write_byte(0x2D, 0x00); - // DAC volume max - error = error || not this->write_byte(0x1B, 0x00); - error = error || not this->write_byte(0x1A, 0x00); - - // ADC poweroff - error = error || not this->write_byte(0x03, 0xFF); - // ADC amp 24dB - error = error || not this->write_byte(0x09, 0x88); - // LINPUT1/RINPUT1 - error = error || not this->write_byte(0x0A, 0x00); - // ADC mono left - error = error || not this->write_byte(0x0B, 0x02); - // i2S 16b - error = error || not this->write_byte(0x0C, 0x0C); - // MCLK 256 - error = error || not this->write_byte(0x0D, 0x02); - // ADC Volume - error = error || not this->write_byte(0x10, 0x00); - error = error || not this->write_byte(0x11, 0x00); - // ALC OFF - error = error || not this->write_byte(0x03, 0x09); - error = error || not this->write_byte(0x2B, 0x80); - - error = error || not this->write_byte(0x02, 0xF0); - delay(1); - error = error || not this->write_byte(0x02, 0x00); - // DAC power-up LOUT1/ROUT1 enabled - error = error || not this->write_byte(0x04, 0x30); - error = error || not this->write_byte(0x03, 0x00); - // DAC volume max - error = error || not this->write_byte(0x2E, 0x1C); - error = error || not this->write_byte(0x2F, 0x1C); - // unmute - error = error || not this->write_byte(0x19, 0x00); + for (std::array instruction : this->macros_[name].instructions) { + if (this->write_byte(instruction[0], instruction[1])) + ESP_LOGW(TAG, "Error writing I2C instructions to ES8388: %#04x, %#04x", instruction[0], instruction[1]); + } } void ES8388Component::powerup_dac() { this->write_byte(0x04, 0x3B); } diff --git a/esphome/components/es8388/es8388_component.h b/esphome/components/es8388/es8388_component.h index 476ace2e94..4fc57ee5e9 100644 --- a/esphome/components/es8388/es8388_component.h +++ b/esphome/components/es8388/es8388_component.h @@ -1,19 +1,35 @@ #pragma once +#include + #include "esphome/components/i2c/i2c.h" #include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "esphome/core/helpers.h" namespace esphome { namespace es8388 { +enum ES8388Preset : uint8_t { NONE = 0x00, RASPIAUDIO_MUSE_LUXE = 0x01, RASPIAUDIO_RADIO = 0x02 }; +typedef std::vector> Instructions; +struct Macro { + std::string name; + Instructions instructions; +}; +typedef std::map Macros; + class ES8388Component : public Component, public i2c::I2CDevice { public: void setup() override; - void setup_raspiaudio_radio(); - void setup_raspiaudio_muse_luxe(); + void dump_config() override; float get_setup_priority() const override { return setup_priority::LATE - 1; } + void set_preset(ES8388Preset preset) { this->preset_ = preset; } + void set_init_instructions(Instructions instructions) { this->init_instructions_ = instructions; } + void register_macro(std::string name, Instructions instructions); + void execute_macro(std::string name); + void powerup_dac(); // void powerup_adc(); void powerup(); @@ -25,6 +41,33 @@ class ES8388Component : public Component, public i2c::I2CDevice { void clock_mode(uint8_t mode); void mute(); + + protected: + void setup_raspiaudio_radio(); + void setup_raspiaudio_muse_luxe(); + ES8388Preset preset_; + Instructions init_instructions_; + Macros macros_; +}; + +template class ES8388MacroAction : public Action, public Parented { + public: + // TEMPLATABLE_VALUE(int8_t, hw_frontend_reset) + TEMPLATABLE_VALUE(std::string, macro_id) + // TEMPLATABLE_VALUE(int, sensing_distance) + // TEMPLATABLE_VALUE(int, poweron_selfcheck_time) + // TEMPLATABLE_VALUE(int, power_consumption) + // TEMPLATABLE_VALUE(int, protect_time) + // TEMPLATABLE_VALUE(int, trigger_base) + // TEMPLATABLE_VALUE(int, trigger_keep) + // TEMPLATABLE_VALUE(int, stage_gain) + + void play(Ts... x) { + if (this->macro_id_.has_value()) { + std::string macro_id = this->macro_.value(x...); + this->parent_->execute_macro(macro_id); + } + } }; } // namespace es8388