From 8033ef48a858987d589a36c6402db7a9e4612510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Poczkodi?= Date: Tue, 15 Oct 2024 03:38:55 +0200 Subject: [PATCH] si4713 commands, properties --- esphome/components/si4713_i2c/__init__.py | 131 ++ .../components/si4713_i2c/number/__init__.py | 51 + .../si4713_i2c/number/frequency_number.cpp | 12 + .../si4713_i2c/number/frequency_number.h | 18 + .../components/si4713_i2c/select/__init__.py | 42 + esphome/components/si4713_i2c/si4713.cpp | 400 ++++++ esphome/components/si4713_i2c/si4713.h | 130 ++ esphome/components/si4713_i2c/si4713defs.h | 1266 +++++++++++++++++ .../components/si4713_i2c/switch/__init__.py | 61 + .../si4713_i2c/switch/mono_switch.cpp | 12 + .../si4713_i2c/switch/mono_switch.h | 18 + .../si4713_i2c/switch/mute_switch.cpp | 12 + .../si4713_i2c/switch/mute_switch.h | 18 + .../si4713_i2c/switch/rds_enable_switch.cpp | 12 + .../si4713_i2c/switch/rds_enable_switch.h | 18 + .../components/si4713_i2c/text/__init__.py | 154 ++ .../si4713_i2c/text/rds_station_text.cpp | 12 + .../si4713_i2c/text/rds_station_text.h | 18 + .../si4713_i2c/text/rds_text_text.cpp | 12 + .../si4713_i2c/text/rds_text_text.h | 18 + 20 files changed, 2415 insertions(+) create mode 100644 esphome/components/si4713_i2c/__init__.py create mode 100644 esphome/components/si4713_i2c/number/__init__.py create mode 100644 esphome/components/si4713_i2c/number/frequency_number.cpp create mode 100644 esphome/components/si4713_i2c/number/frequency_number.h create mode 100644 esphome/components/si4713_i2c/select/__init__.py create mode 100644 esphome/components/si4713_i2c/si4713.cpp create mode 100644 esphome/components/si4713_i2c/si4713.h create mode 100644 esphome/components/si4713_i2c/si4713defs.h create mode 100644 esphome/components/si4713_i2c/switch/__init__.py create mode 100644 esphome/components/si4713_i2c/switch/mono_switch.cpp create mode 100644 esphome/components/si4713_i2c/switch/mono_switch.h create mode 100644 esphome/components/si4713_i2c/switch/mute_switch.cpp create mode 100644 esphome/components/si4713_i2c/switch/mute_switch.h create mode 100644 esphome/components/si4713_i2c/switch/rds_enable_switch.cpp create mode 100644 esphome/components/si4713_i2c/switch/rds_enable_switch.h create mode 100644 esphome/components/si4713_i2c/text/__init__.py create mode 100644 esphome/components/si4713_i2c/text/rds_station_text.cpp create mode 100644 esphome/components/si4713_i2c/text/rds_station_text.h create mode 100644 esphome/components/si4713_i2c/text/rds_text_text.cpp create mode 100644 esphome/components/si4713_i2c/text/rds_text_text.h diff --git a/esphome/components/si4713_i2c/__init__.py b/esphome/components/si4713_i2c/__init__.py new file mode 100644 index 0000000000..6be0a3fd31 --- /dev/null +++ b/esphome/components/si4713_i2c/__init__.py @@ -0,0 +1,131 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation, pins +from esphome.components import i2c, sensor, text_sensor +from esphome.const import ( + CONF_ID, + CONF_FREQUENCY, + UNIT_EMPTY, + DEVICE_CLASS_VOLTAGE, + STATE_CLASS_MEASUREMENT, + ICON_CHIP, + ENTITY_CATEGORY_DIAGNOSTIC, +) + +CODEOWNERS = ["@gabest11"] +DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["sensor", "text_sensor", "number", "switch", "select", "text"] +MULTI_CONF = True + +UNIT_MEGA_HERTZ = "MHz" +UNIT_KILO_HERTZ = "kHz" +UNIT_MILLI_VOLT = "mV" +UNIT_MICRO_AMPERE = "mA" +UNIT_DECIBEL_MICRO_VOLT = "dBµV" + +ICON_VOLUME_MUTE = "mdi:volume-mute" +ICON_EAR_HEARING = "mdi:ear-hearing" +ICON_RADIO_TOWER = "mdi:radio-tower" +ICON_SLEEP = "mdi:sleep" +ICON_SINE_WAVE = "mdi:sine-wave" +ICON_RESISTOR = "mdi:resistor" +ICON_FORMAT_TEXT = "mdi:format-text" + +si4713_ns = cg.esphome_ns.namespace("si4713") +Si4713Component = si4713_ns.class_( + "Si4713Component", cg.PollingComponent, i2c.I2CDevice +) + +CONF_SI4713_ID = "si4713_id" +CONF_RESET_PIN = "reset_pin" +CONF_FREQUENCY_DEVIATION = "frequency_deviation" +CONF_MUTE = "mute" +CONF_MONO = "mono" +CONF_TX_ENABLE = "tx_enable" +CONF_TX_PILOT = "tx_pilot" +CONF_T1M_SEL = "t1m_sel" +CONF_PRIV_EN = "priv_en" +CONF_PRE_EMPHASIS = "pre_emphasis" +CONF_XTAL_SOURCE = "xtal_source" +CONF_XTAL_CURRENT = "xtal_current" +CONF_XTAL_FREQUENCY = "xtal_frequency" +CONF_INPUT_IMPEDANCE = "input_impedance" +CONF_INPUT_GAIN = "input_gain" +CONF_DIGITAL_GAIN = "digital_gain" +CONF_POWER_TARGET = "power_target" +CONF_RDS_ENABLE = "rds_enable" +CONF_RDS_FREQUENCY_DEVIATION = "rds_frequency_deviation" +CONF_RDS_STATION = "rds_station" +CONF_RDS_TEXT = "rds_text" +CONF_AUD_PK = "aud_pk" +CONF_FSM = "fsm" +CONF_CHIP_ID = "chip_id" +CONF_REG30 = "reg30" + +SetFrequencyAction = si4713_ns.class_( + "SetFrequencyAction", automation.Action, cg.Parented.template(Si4713Component) +) + +#T1mSel = si4713_ns.enum("T1mSel", True) +#T1M_SEL = { +# "58s": T1mSel.T1M_SEL_58S, +# "59s": T1mSel.T1M_SEL_59S, +# "60s": T1mSel.T1M_SEL_60S, +# "Never": T1mSel.T1M_SEL_NEVER, +#} + + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(Si4713Component), + cv.Required(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_FREQUENCY, default=87.50): cv.float_range(76, 108), + cv.Optional(CONF_MUTE, default=False): cv.boolean, + cv.Optional(CONF_MONO, default=False): cv.boolean, +# cv.Optional(CONF_T1M_SEL, default="60s"): cv.enum(T1M_SEL), + cv.Optional(CONF_RDS_ENABLE, default=False): cv.boolean, + cv.Optional(CONF_RDS_STATION): cv.string, + cv.Optional(CONF_RDS_TEXT): cv.string, + cv.Optional(CONF_CHIP_ID): text_sensor.text_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + icon=ICON_CHIP, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x63)) +) + + +async def set_var(config, id, setter): + if c := config.get(id): + cg.add(setter(c)) + + +async def set_sensor(config, id, setter): + if c := config.get(id): + s = await sensor.new_sensor(c) + cg.add(setter(s)) + + +async def set_text_sensor(config, id, setter): + if c := config.get(id): + s = await text_sensor.new_text_sensor(c) + cg.add(setter(s)) + + +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) + reset_pin = await cg.gpio_pin_expression(config[CONF_RESET_PIN]) + cg.add(var.set_reset_pin(reset_pin)) + await set_var(config, CONF_FREQUENCY, var.set_frequency) + await set_var(config, CONF_MUTE, var.set_mute) + await set_var(config, CONF_MONO, var.set_mono) +# await set_var(config, CONF_T1M_SEL, var.set_t1m_sel) + await set_var(config, CONF_RDS_ENABLE, var.set_rds_enable) + await set_var(config, CONF_RDS_STATION, var.set_rds_station) + await set_var(config, CONF_RDS_TEXT, var.set_rds_text) + await set_text_sensor(config, CONF_CHIP_ID, var.set_chip_id_text_sensor) diff --git a/esphome/components/si4713_i2c/number/__init__.py b/esphome/components/si4713_i2c/number/__init__.py new file mode 100644 index 0000000000..41b6dea463 --- /dev/null +++ b/esphome/components/si4713_i2c/number/__init__.py @@ -0,0 +1,51 @@ +import esphome.codegen as cg +from esphome.components import number +import esphome.config_validation as cv +from esphome.const import ( + CONF_FREQUENCY, + UNIT_PERCENT, + UNIT_DECIBEL, + DEVICE_CLASS_FREQUENCY, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_SIGNAL_STRENGTH, + DEVICE_CLASS_EMPTY, + ENTITY_CATEGORY_CONFIG, +) +from .. import ( + CONF_SI4713_ID, + Si4713Component, + si4713_ns, +# CONF_, + UNIT_MEGA_HERTZ, + UNIT_KILO_HERTZ, + UNIT_MICRO_AMPERE, + UNIT_DECIBEL_MICRO_VOLT, +) + +FrequencyNumber = si4713_ns.class_("FrequencyNumber", number.Number) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_SI4713_ID): cv.use_id(Si4713Component), + cv.Optional(CONF_FREQUENCY): number.number_schema( + FrequencyNumber, + unit_of_measurement=UNIT_MEGA_HERTZ, + device_class=DEVICE_CLASS_FREQUENCY, + entity_category=ENTITY_CATEGORY_CONFIG, + ), + } +) + + +async def new_number(config, id, setter, min_value, max_value, step, *args): + if c := config.get(id): + n = await number.new_number( + c, *args, min_value=min_value, max_value=max_value, step=step + ) + await cg.register_parented(n, config[CONF_SI4713_ID]) + cg.add(setter(n)) + + +async def to_code(config): + c = await cg.get_variable(config[CONF_SI4713_ID]) + await new_number(config, CONF_FREQUENCY, c.set_frequency_number, 76, 108, 0.05) diff --git a/esphome/components/si4713_i2c/number/frequency_number.cpp b/esphome/components/si4713_i2c/number/frequency_number.cpp new file mode 100644 index 0000000000..bf65a04f7e --- /dev/null +++ b/esphome/components/si4713_i2c/number/frequency_number.cpp @@ -0,0 +1,12 @@ +#include "frequency_number.h" + +namespace esphome { +namespace si4713 { + +void FrequencyNumber::control(float value) { + this->publish_state(value); + this->parent_->set_frequency(value); +} + +} // namespace si4713 +} // namespace esphome diff --git a/esphome/components/si4713_i2c/number/frequency_number.h b/esphome/components/si4713_i2c/number/frequency_number.h new file mode 100644 index 0000000000..2d2936b410 --- /dev/null +++ b/esphome/components/si4713_i2c/number/frequency_number.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/number/number.h" +#include "../si4713.h" + +namespace esphome { +namespace si4713 { + +class FrequencyNumber : public number::Number, public Parented { + public: + FrequencyNumber() = default; + + protected: + void control(float value) override; +}; + +} // namespace si4713 +} // namespace esphome diff --git a/esphome/components/si4713_i2c/select/__init__.py b/esphome/components/si4713_i2c/select/__init__.py new file mode 100644 index 0000000000..e34e0d5797 --- /dev/null +++ b/esphome/components/si4713_i2c/select/__init__.py @@ -0,0 +1,42 @@ +import esphome.codegen as cg +from esphome.components import select +import esphome.config_validation as cv +from esphome.const import ( + ENTITY_CATEGORY_CONFIG, + ICON_PULSE, +) +from .. import ( + CONF_SI4713_ID, + Si4713Component, + si4713_ns, + CONF_, + ICON_SLEEP, + ICON_SINE_WAVE, + ICON_RESISTOR, +# T1M_SEL, +) + +#T1mSelSelect = si4713_ns.class_("T1mSelSelect", select.Select) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_SI4713_ID): cv.use_id(Si4713Component), +# cv.Optional(CONF_T1M_SEL): select.select_schema( +# T1mSelSelect, +# entity_category=ENTITY_CATEGORY_CONFIG, +# icon=ICON_SLEEP, +# ), + } +) + + +async def new_select(config, id, setter, options): + if c := config.get(id): + s = await select.new_select(c, options=list(options.keys())) + await cg.register_parented(s, config[CONF_SI4713_ID]) + cg.add(setter(s)) + + +async def to_code(config): + c = await cg.get_variable(config[CONF_SI4713_ID]) +# await new_select(config, CONF_T1M_SEL, c.set_t1m_sel_select, T1M_SEL) diff --git a/esphome/components/si4713_i2c/si4713.cpp b/esphome/components/si4713_i2c/si4713.cpp new file mode 100644 index 0000000000..a6976aec07 --- /dev/null +++ b/esphome/components/si4713_i2c/si4713.cpp @@ -0,0 +1,400 @@ +#include "si4713.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include +#include + +namespace esphome { +namespace si4713 { + +// TODO: std::clamp isn't here yet +#define clamp(v, lo, hi) std::max(std::min(v, hi), lo) + +static const char *const TAG = "si4713"; + +Si4713Component::Si4713Component() { + this->reset_pin_ = nullptr; + this->reset_ = false; + // memset(&this->state_, 0, sizeof(this->state_)); + this->rds_station_pos_ = 0; + this->rds_text_pos_ = 0; + // this->state_. = ; + // our defaults + // this->state_. = ; // start with tx enabled +} +/* +void Si4713Component::write_reg_(uint8_t addr) { + switch (addr) { + case 0x00: // REG_.. + break; + default: + ESP_LOGE(TAG, "write_reg_(0x%02X) invalid register address", addr); + return; + } + + if (this->reset_) { + uint8_t value = this->regs_[addr]; + ESP_LOGV(TAG, "write_reg_(0x%02X) = 0x%02X", addr, value); + this->write_byte(addr, value); + } else { + if (this->get_component_state() & COMPONENT_STATE_LOOP) { + ESP_LOGE(TAG, "write_reg_(0x%02X) device was not reset", addr); + } + } +} + +bool Si4713Component::read_reg_(uint8_t addr) { + switch (addr) { + case 0x00: // REG_.. + break; + default: + ESP_LOGE(TAG, "read_reg_(0x%02X) trying to read invalid register", addr); + return false; + } + + if (auto b = this->read_byte(addr)) { + this->regs_[addr] = *b; + return true; + } + + ESP_LOGE(TAG, "read_reg_(0x%02X) cannot read register", addr); + return false; +} +*/ +bool Si4713Component::send_cmd(const void *cmd, size_t cmd_size, void *res, size_t res_size) { + const uint8_t *buff = (const uint8_t *) cmd; + + if (!this->reset_) { + if (this->get_component_state() & COMPONENT_STATE_LOOP) { + ESP_LOGE(TAG, "send_cmd(0x%02X, %d) device was not reset", buff[0], cmd_size); + } + return false; + } + + i2c::ErrorCode err = this->write(buff, cmd_size); + if (err != i2c::ERROR_OK) { + ESP_LOGE(TAG, "send_cmd(0x%02X, %d) write error", buff[0], cmd_size); + this->mark_failed(); + return false; + } + + uint8_t status = 0; + while (!(status & SI4710_STATUS_CTS)) { + err = this->read(&status, 1); // TODO: read res_size into res here? + if (err != i2c::ERROR_OK) { + ESP_LOGE(TAG, "send_cmd(0x%02X, %d) read status error", buff[0], cmd_size); + this->mark_failed(); + return false; + } + } + + if (res != nullptr) { + //((uint8_t*) res)[0] = status; + err = this->read((uint8_t *) res, res_size); + if (err != i2c::ERROR_OK) { + ESP_LOGE(TAG, "send_cmd(0x%02X, %d) read response error", buff[0], cmd_size); + this->mark_failed(); + return false; + } + } + + return true; +} + +bool Si4713Component::power_up() { + // Note: In FMTX component 1.0 and 2.0, a reset is required when the system controller writes a command other than + // POWER_UP when in powerdown mode + this->reset_pin_->digital_write(true); + delay_microseconds_safe(10); + this->reset_pin_->digital_write(false); + delay_microseconds_safe(10); + this->reset_pin_->digital_write(true); + + CmdPowerUp cmd; + cmd.FUNC = 2; + cmd.XOSCEN = 1; // TODO: external oscillator + cmd.OPMODE = 0x50; // TODO: digital + cmd.GPO2OEN = 0; // TODO: GPIO2 enable + return this->send_cmd(cmd); +} + +bool Si4713Component::power_down() { return this->send_cmd(CmdPowerDown()); } + +bool Si4713Component::detect_chip_id() { + ResGetRev res; + if (!this->send_cmd(CmdGetRev(), res)) { + return false; + } + + char buff[32] = {0}; + snprintf(buff, sizeof(buff), "Si47%02d Rev %d", res.PN, res.CHIPREV); + this->chip_id_ = buff; + + // TODO: support all transmitters 10/11/12/13/20/21 + + if (res.PN != 10 && res.PN != 11 && res.PN != 12 && res.PN != 13) { + ESP_LOGE(TAG, "Si47%02d is not supported", res.PN); + this->mark_failed(); + return false; + } + + return true; +} + +bool Si4713Component::tune_power(uint8_t power, uint8_t antcap) { + if (!this->send_cmd(CmdTxTunePower(power, antcap))) { + return false; + } + return this->tune_ready(); +} + +bool Si4713Component::tune_freq(uint16_t freq) { + if (!this->send_cmd(CmdTxTuneFreq(freq))) { + return false; + } + return this->tune_ready(); +} + +bool Si4713Component::tune_ready() { + ResGetIntStatus res; + while (res.CTS != 1 || res.STCINT != 1) { + if (!this->send_cmd(CmdGetIntStatus(), res)) { + return false; + } + } + return true; +} + +void Si4713Component::rds_update_() {} + +// overrides + +void Si4713Component::setup() { + if (this->reset_pin_ == nullptr) { + ESP_LOGE(TAG, "setup cannot reset device, reset pin is not set"); + this->mark_failed(); + return; + } + + // reset + + this->reset_ = true; + this->reset_pin_->setup(); + + if (!this->power_up()) { + return; + } + + if (!this->detect_chip_id()) { + return; + } + + this->set_prop(PropRefClkFreq(32768)); + this->set_prop(PropTxPreEmphasis()); + /* + // configuration! see page 254 + setProperty(SI4713_PROP_REFCLK_FREQ, 32768); // crystal is 32.768 + setProperty(SI4713_PROP_TX_PREEMPHASIS, 0); // 74uS pre-emph (USA std) + setProperty(SI4713_PROP_TX_ACOMP_GAIN, 10); // max gain? + // setProperty(SI4713_PROP_TX_ACOMP_ENABLE, 0x02); // turn on limiter, but no + // dynamic ranging + setProperty(SI4713_PROP_TX_ACOMP_ENABLE, 0x0); // turn on limiter and AGC + */ + + // TODO: set properties + + this->tune_power(115); + this->tune_freq(8750); + + // + + this->publish_frequency(); + this->publish_mute(); + this->publish_mono(); + this->publish_rds_enable(); + this->publish_rds_station(); + this->publish_rds_text(); + + this->set_interval(1000, [this]() { this->rds_update_(); }); +} + +void Si4713Component::dump_config() { + ESP_LOGCONFIG(TAG, "Si4713:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "failed!"); + } + ESP_LOGCONFIG(TAG, " Chip: %s", this->chip_id_.c_str()); + ESP_LOGCONFIG(TAG, " Frequency: %.2f MHz", this->get_frequency()); + ESP_LOGCONFIG(TAG, " RDS station: %s", this->rds_station_.c_str()); + ESP_LOGCONFIG(TAG, " RDS text: %s", this->rds_text_.c_str()); + // TODO: ...and everything else... + LOG_UPDATE_INTERVAL(this); +} + +void Si4713Component::update() { + /* + if (this->read_reg_(REG_)) { + this->publish_(); + } else { + ESP_LOGE(TAG, "update cannot read the status register"); + } + */ +} + +void Si4713Component::loop() {} + +// config + +void Si4713Component::set_reset_pin(InternalGPIOPin *pin) { this->reset_pin_ = pin; } + +void Si4713Component::set_frequency(float value) { + if (!(CH_FREQ_MIN <= value && value <= CH_FREQ_MAX)) { + ESP_LOGE(TAG, "set_frequency(%.2f) invalid (%.2f - %.2f)", value, CH_FREQ_MIN, CH_FREQ_MAX); + return; + } + + // int f = clamp((int) std::lround((value - 76) * 20), CH_FREQ_RAW_MIN, CH_FREQ_RAW_MAX); + // this->state_.CH_UPPER = (uint8_t) (f >> 8); + // this->state_.CH_LOWER = (uint8_t) (f & 0xff); + // this->write_reg_(REG_SYSTEM_ADDR); + // this->write_reg_(REG_CH1_ADDR); + + this->publish_frequency(); +} + +float Si4713Component::get_frequency() { + // uint16_t ch = ((uint16_t) this->state_.CH_UPPER << 8) | this->state_.CH_LOWER; + // return (float) ch / 20 + 76; + return 0; +} + +void Si4713Component::set_mute(bool value) { + // this->state_.MUTE = value ? 1 : 0; + // this->write_reg_(REG_SYSTEM_ADDR); + + this->publish_mute(); +} + +bool Si4713Component::get_mute() { + // return this->state_.MUTE == 1; + return false; +} + +void Si4713Component::set_mono(bool value) { + // this->state_.MONO = value ? 1 : 0; + // this->write_reg_(REG_SYSTEM_ADDR); + + this->publish_mono(); +} + +bool Si4713Component::get_mono() { + // return this->state_.MONO == 1; + return false; +} + +void Si4713Component::set_rds_enable(bool value) { + // TODO + + this->publish_rds_enable(); +} + +bool Si4713Component::get_rds_enable() { + // return this->state_.RDSEN == 1; + return false; +} + +void Si4713Component::set_rds_station(const std::string &value) { + this->rds_station_ = value; + this->rds_station_pos_ = 0; + if (this->rds_station_.size() > RDS_STATION_MAX) { + ESP_LOGW(TAG, "rds station too long '%s' (max %d characters)", value.c_str(), RDS_STATION_MAX); + this->rds_station_.resize(RDS_STATION_MAX); + } + + this->publish_rds_station(); +} + +void Si4713Component::set_rds_text(const std::string &value) { + this->rds_text_ = value; + this->rds_text_pos_ = 0; + if (this->rds_text_.size() > RDS_TEXT_MAX) { + ESP_LOGW(TAG, "rds text to long '%s' (max %d characters)", value.c_str(), RDS_TEXT_MAX); + this->rds_text_.resize(RDS_TEXT_MAX); + } + + this->publish_rds_text(); +} + +// publish + +void Si4713Component::publish_() { + // this->publish(this->_sensor_, this->state_.); +} + +void Si4713Component::publish_chip_id() { this->publish(this->chip_id_text_sensor_, this->chip_id_); } + +void Si4713Component::publish_frequency() { this->publish(this->frequency_number_, this->get_frequency()); } + +void Si4713Component::publish_mute() { this->publish(this->mute_switch_, this->get_mute()); } + +void Si4713Component::publish_mono() { this->publish(this->mono_switch_, this->get_mono()); } + +void Si4713Component::publish_rds_enable() { this->publish(this->rds_enable_switch_, this->get_rds_enable()); } + +void Si4713Component::publish_rds_station() { this->publish(this->rds_station_text_, this->rds_station_); } + +void Si4713Component::publish_rds_text() { this->publish(this->rds_text_text_, this->rds_text_); } + +void Si4713Component::publish(text_sensor::TextSensor *s, const std::string &state) { + if (s != nullptr) { + if (!s->has_state() || s->state != state) { + s->publish_state(state); + } + } +} + +void Si4713Component::publish(sensor::Sensor *s, float state) { + if (s != nullptr) { + if (!s->has_state() || s->state != state) { + s->publish_state(state); + } + } +} + +void Si4713Component::publish(number::Number *n, float state) { + if (n != nullptr) { + if (!n->has_state() || n->state != state) { + n->publish_state(state); + } + } +} + +void Si4713Component::publish(switch_::Switch *s, bool state) { + if (s != nullptr) { + if (s->state != state) { // ? + s->publish_state(state); + } + } +} + +void Si4713Component::publish(select::Select *s, size_t index) { + if (s != nullptr) { + if (auto state = s->at(index)) { + if (!s->has_state() || s->state != *state) { + s->publish_state(*state); + } + } + } +} + +void Si4713Component::publish(text::Text *t, const std::string &state) { + if (t != nullptr) { + if (!t->has_state() || t->state != state) { + t->publish_state(state); + } + } +} + +} // namespace si4713 +} // namespace esphome diff --git a/esphome/components/si4713_i2c/si4713.h b/esphome/components/si4713_i2c/si4713.h new file mode 100644 index 0000000000..6e8be2a7d8 --- /dev/null +++ b/esphome/components/si4713_i2c/si4713.h @@ -0,0 +1,130 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "esphome/core/gpio.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/text_sensor/text_sensor.h" +#include "esphome/components/number/number.h" +#include "esphome/components/switch/switch.h" +#include "esphome/components/select/select.h" +#include "esphome/components/text/text.h" +#include +#include "si4713defs.h" + +namespace esphome { +namespace si4713 { + +#ifndef SUB_TEXT +#define SUB_TEXT(name) \ + protected: \ + text::Text *name##_text_{nullptr}; \ +\ + public: \ + void set_##name##_text(text::Text *text) { this->name##_text_ = text; } +#endif + +class Si4713Component : public PollingComponent, public i2c::I2CDevice { + std::string chip_id_; + InternalGPIOPin *reset_pin_; + bool reset_; + /* + union { + struct Si4713State state_; + uint8_t regs_[sizeof(struct Si4713State)]; + }; + + void write_reg_(uint8_t addr); + bool read_reg_(uint8_t addr); + */ + bool send_cmd(const void *cmd, size_t cmd_size, void *res, size_t res_size); + + template bool send_cmd(const CMD &cmd) { + return this->send_cmd((const void *) &cmd, sizeof(cmd), nullptr, 0); + } + + template bool send_cmd(CMD cmd, RES &res) { + return this->send_cmd((const void *) &cmd, sizeof(cmd), (void *) &res, sizeof(res)); + } + + template bool set_prop(P p) { + CmdSetProperty cmd = CmdSetProperty(p.PROP, p.PROPD); + return this->send_cmd(cmd); + } + + template bool get_prop(P &p) { + ResGetProperty res; + if (this->send_cmd(CmdGetProperty(p.PROP), res)) { + p.PROPD = ((uint16_t) res.PROPDH << 8) | res.PROPDL; + return true; + } + + return false; + } + + bool power_up(); + bool power_down(); + bool detect_chip_id(); + bool tune_power(uint8_t power = 0, uint8_t antcap = 0); + bool tune_freq(uint16_t freq); + bool tune_ready(); + + std::string rds_station_; + std::string rds_text_; + uint8_t rds_station_pos_; + uint8_t rds_text_pos_; + uint8_t rds_upd_; + + void rds_update_(); + + SUB_TEXT_SENSOR(chip_id) + // TODO: sensors TX_TUNE_STATUS / FREQ, RFuV, ANTCAP, NL + // TODO: sensors TX_ASQ_STATUS / OVERMOD, IALH, IALL, INLEVEL + SUB_NUMBER(frequency) + SUB_SWITCH(mute) + SUB_SWITCH(mono) + SUB_SWITCH(rds_enable) + SUB_TEXT(rds_station) + SUB_TEXT(rds_text) + // TODO: GPIO switches + + void publish_(); + void publish_chip_id(); + void publish_frequency(); + void publish_mute(); + void publish_mono(); + void publish_rds_enable(); + void publish_rds_station(); + void publish_rds_text(); + void publish(sensor::Sensor *s, float state); + void publish(text_sensor::TextSensor *s, const std::string &state); + void publish(number::Number *n, float state); + void publish(switch_::Switch *s, bool state); + void publish(select::Select *s, size_t index); + void publish(text::Text *t, const std::string &state); + + public: + Si4713Component(); + + // float get_setup_priority() const override { return setup_priority::HARDWARE; } + void setup() override; + void dump_config() override; + void update() override; + void loop() override; + + void set_reset_pin(InternalGPIOPin *pin); + void set_frequency(float value); + float get_frequency(); + void set_mute(bool value); + bool get_mute(); + void set_mono(bool value); + bool get_mono(); + void set_rds_enable(bool value); + bool get_rds_enable(); + void set_rds_station(const std::string &value); + void set_rds_text(const std::string &value); +}; + +} // namespace si4713 +} // namespace esphome diff --git a/esphome/components/si4713_i2c/si4713defs.h b/esphome/components/si4713_i2c/si4713defs.h new file mode 100644 index 0000000000..b3edb81259 --- /dev/null +++ b/esphome/components/si4713_i2c/si4713defs.h @@ -0,0 +1,1266 @@ +#pragma once + +namespace esphome { +namespace si4713 { + +static const uint8_t SI4710_STATUS_CTS = 0x80; + +static const float CH_FREQ_MIN = 76; +static const float CH_FREQ_MAX = 108; + +static const uint8_t RDS_STATION_MAX = 8; +static const uint8_t RDS_TEXT_MAX = 64; + +enum class CmdType : uint8_t { + POWER_UP = 0x01, // Power up device and mode selection. + GET_REV = 0x10, // Returns revision information on the device. + POWER_DOWN = 0x11, // Power down device. + SET_PROPERTY = 0x12, // Sets the value of a property. + GET_PROPERTY = 0x13, // Retrieves a property’s value. + GET_INT_STATUS = 0x14, // Read interrupt status bits. + PATCH_ARGS = 0x15, // Reserved command used for patch file downloads. + PATCH_DATA = 0x16, // Reserved command used for patch file downloads. + TX_TUNE_FREQ = 0x30, // Tunes to given transmit frequency. + TX_TUNE_POWER = 0x31, // Sets the output power level and tunes the antenna capacitor + TX_TUNE_MEASURE = 0x32, // Measure the received noise level at the specified frequency. + TX_TUNE_STATUS = 0x33, // Queries the status of a previously sent TX Tune Freq, Power, or Measure command. + TX_ASQ_STATUS = 0x34, // Queries the TX status and input audio signal metrics. + TX_RDS_BUFF = 0x35, // Si4713 Only. Queries the status of the RDS Group Buffer and loads new data into buffer. + TX_RDS_PS = 0x36, // Si4713 Only. Set up default PS strings. + GPIO_CTL = 0x80, // Configures GPO3 as output or Hi-Z. + GPIO_SET = 0x81, // Sets GPO3 output level (low or high). +}; + +enum class PropType : uint16_t { + // Enables interrupt sources. + // Default: 0x0000 + GPO_IEN = 0x0001, + // Configures the digital input format. + // Default: 0x0000 + DIGITAL_INPUT_FORMAT = 0x0101, + // Configures the digital input sample rate in 10 Hz steps. Default is 0. + // Default: 0x0000 + DIGITAL_INPUT_SAMPLE_RATE = 0x0103, + // Sets frequency of the reference clock in Hz. The range is 31130 to 34406 Hz, or 0 to disable the AFC. Default is + // 32768 Hz. + // Default: 0x8000 + REFCLK_FREQ = 0x0201, + // Sets the prescaler value for the reference clock. + // Default: 0x0001 + REFCLK_PRESCALE = 0x0202, + // Enable transmit multiplex signal components. Default has pilot and L-R enabled. + // Default: 0x0003 + TX_COMPONENT_ENABLE = 0x2100, + // Configures audio frequency deviation level. Units are in 10 Hz increments. Default is 6285 (68.25 kHz). + // Default: 0x1AA9 + TX_AUDIO_DEVIATION = 0x2101, + // Configures pilot tone frequency deviation level. Units are in 10 Hz increments. Default is 675 (6.75 kHz) + // Default: 0x02A3 + TX_PILOT_DEVIATION = 0x2102, + // Si4713 Only. Configures the RDS/RBDS frequency deviation level. Units are in 10 Hz increments. Default is 2 kHz. + // Default: 0x00C8 + TX_RDS_DEVIATION = 0x2103, + // Configures maximum analog line input level to the LIN/RIN pins to reach the maximum deviation level programmed + // into the audio deviation property TX Audio Deviation. Default is 636 mVPK. + // Default: 0x327C + TX_LINE_INPUT_LEVEL = 0x2104, + // Sets line input mute. L and R inputs may be independently muted. Default is not muted. + // Default: 0x0000 + TX_LINE_INPUT_MUTE = 0x2105, + // Configures pre-emphasis time constant. Default is 0 (75 µS). + // Default: 0x0000 + TX_PREEMPHASIS = 0x2106, + // Configures the frequency of the stereo pilot. Default is 19000 Hz. + // Default: 0x4A38 + TX_PILOT_FREQUENCY = 0x2107, + // Enables audio dynamic range control. Default is 0 (disabled). + // Default: 0x0002 + TX_ACOMP_ENABLE = 0x2200, + // Sets the threshold level for audio dynamic range control. Default is –40 dB. + // Default: 0xFFD8 + TX_ACOMP_THRESHOLD = 0x2201, + // Sets the attack time for audio dynamic range control. Default is 0 (0.5 ms). + // Default: 0x0000 + TX_ACOMP_ATTACK_TIME = 0x2202, + // Sets the release time for audio dynamic range control. Default is 4 (1000 ms). + // Default: 0x0004 + TX_ACOMP_RELEASE_TIME = 0x2203, + // Sets the gain for audio dynamic range control. Default is 15 dB. + // Default: 0x000F + TX_ACOMP_GAIN = 0x2204, + // Sets the limiter release time. Default is 102 (5.01 ms) + // Default: 0x0066 + TX_LIMITER_RELEASE_TIME = 0x2205, + // Configures measurements related to signal quality metrics. Default is none selected. + // Default: 0x0000 + TX_ASQ_INTERRUPT_SOURCE = 0x2300, + // Configures low audio input level detection threshold. This threshold can be used to detect silence on the + // incoming audio. + // Default: 0x0000 + TX_ASQ_LEVEL_LOW = 0x2301, + // Configures the duration which the input audio level must be below the low threshold in order to detect a low + // audio condition. + // Default: 0x0000 + TX_ASQ_DURATION_LOW = 0x2302, + // Configures high audio input level detection threshold. This threshold can be used to detect activity on the + // incoming audio. + // Default: 0x0000 + TX_ASQ_LEVEL_HIGH = 0x2303, + // Configures the duration which the input audio level must be above the high threshold in order to detect a high + // audio condition. + // Default: 0x0000 + TX_ASQ_DURATION_HIGH = 0x2304, + // Si4713 Only. Configure RDS interrupt sources. Default is none selected. + // Default: 0x0000 + TX_RDS_INTERRUPT_SOURCE = 0x2C00, + // Si4713 Only. Sets transmit RDS program identifier. + // Default: 0x40A7 + TX_RDS_PI = 0x2C01, + // Si4713 Only. Configures mix of RDS PS Group with RDS Group Buffer. + // Default: 0x0003 + TX_RDS_PS_MIX = 0x2C02, + // Si4713 Only. Miscellaneous bits to transmit along with RDS_PS Groups. + // Default: 0x1008 + TX_RDS_PS_MISC = 0x2C03, + // Si4713 Only. Number of times to repeat transmission of a PS message before transmitting the next PS message. + // Default: 0x0003 + TX_RDS_PS_REPEAT_COUNT = 0x2C04, + // Si4713 Only. Number of PS messages in use. + // Default: 0x0001 + TX_RDS_PS_MESSAGE_COUNT = 0x2C05, + // Si4713 Only. RDS Program Service Alternate Frequency. This provides the ability to inform the receiver of a + // single alternate frequency using AF Method A coding and is transmitted along with the RDS_PS Groups. + // Default: 0xE0E0 + TX_RDS_PS_AF = 0x2C06, + // Si4713 Only. Number of blocks reserved for the FIFO. Note that the value written must be one larger than the + // desired FIFO size. + // Default: 0x0000 + TX_RDS_FIFO_SIZE = 0x2C07, +}; + +// COMMANDS + +struct CmdBase { + CmdType CMD; +}; + +struct ResBase { + union { + uint8_t STATUS; + struct { + uint8_t STCINT : 1; // Tune complete has been triggered + uint8_t ASQINT : 1; // Signal quality measurement has been triggered + uint8_t RDSINT : 1; // RDS interrupt has been triggered + uint8_t RSQINT : 1; + uint8_t _D4 : 1; + uint8_t _D5 : 1; + uint8_t ERR : 1; // Error + uint8_t CTS : 1; // Clear to Send + }; + }; + + ResBase() { this->STATUS = 0; } +}; + +struct CmdPowerUp : CmdBase { + union { + uint8_t ARG1; + struct { + uint8_t FUNC : 4; // Function (2 transmit, 15 query library) + uint8_t XOSCEN : 1; // Crystal Oscillator Enable + uint8_t PATCH : 1; // Patch Enable + uint8_t GPO2OEN : 1; // GPO2 Output Enable + uint8_t CTSIEN : 1; // CTS Interrupt Enable + }; + }; + union { + uint8_t ARG2; + struct { + uint8_t OPMODE : 8; // Application Setting (0x50 analog, 0x0F digital) + }; + }; + + CmdPowerUp() { + this->CMD = CmdType::POWER_UP; + this->ARG1 = 0; + this->ARG1 = 0; + } +}; + +struct ResPowerUpTX : ResBase {}; + +struct ResPowerUpQueryLibrary : ResBase { + union { + uint8_t RESP1; + struct { + uint8_t PN : 8; // Final 2 digits of part number + }; + }; + union { + uint8_t RESP2; + struct { + uint8_t FWMAJOR : 8; // Firmware Major Revision + }; + }; + union { + uint8_t RESP3; + struct { + uint8_t FWMINOR : 8; // Firmware Minor Revision + }; + }; + uint8_t RESP4; // Reserved + uint8_t RESP5; // Reserved + union { + uint8_t RESP6; + struct { + uint8_t CHIPREV : 8; // Chip Revision + }; + }; + union { + uint8_t RESP7; + struct { + uint8_t LIBRARYID : 8; // Library Revision + }; + }; +}; + +struct CmdGetRev : CmdBase { + CmdGetRev() { this->CMD = CmdType::GET_REV; } +}; + +struct ResGetRev : ResBase { + union { + uint8_t RESP1; + struct { + uint8_t PN : 8; // Final 2 digits of part number + }; + }; + union { + uint8_t RESP2; + struct { + uint8_t FWMAJOR : 8; // Firmware Major Revision + }; + }; + union { + uint8_t RESP3; + struct { + uint8_t FWMINOR : 8; // Firmware Minor Revision + }; + }; + union { + uint8_t RESP4; + struct { + uint8_t PATCHH : 8; // Patch ID High Byte + }; + }; + union { + uint8_t RESP5; + struct { + uint8_t PATCHL : 8; // Patch ID Low Byte + }; + }; + union { + uint8_t RESP6; + struct { + uint8_t CMPMAJOR : 8; // Component Major Revision + }; + }; + union { + uint8_t RESP7; + struct { + uint8_t CMPMINOR : 8; // Component Minor Revision + }; + }; + union { + uint8_t RESP8; + struct { + uint8_t CHIPREV : 8; // Chip Revision + }; + }; +}; + +struct CmdPowerDown : CmdBase { + CmdPowerDown() { this->CMD = CmdType::POWER_DOWN; } +}; + +struct ResPowerDown : ResBase {}; + +struct CmdSetProperty : CmdBase { + uint8_t ARG1; // zero + union { + uint8_t ARG2; + struct { + uint8_t PROPH : 8; + }; + }; + union { + uint8_t ARG3; + struct { + uint8_t PROPL : 8; + }; + }; + union { + uint8_t ARG4; + struct { + uint8_t PROPDH : 8; + }; + }; + union { + uint8_t ARG5; + struct { + uint8_t PROPDL : 8; + }; + }; + + CmdSetProperty() { + this->CMD = CmdType::SET_PROPERTY; + this->ARG1 = 0; + this->ARG2 = 0; + this->ARG3 = 0; + this->ARG4 = 0; + this->ARG5 = 0; + } + + CmdSetProperty(PropType prop, uint16_t value) { + this->CMD = CmdType::SET_PROPERTY; + this->ARG1 = 0; + this->ARG2 = ((uint16_t) prop) >> 8; + this->ARG3 = ((uint16_t) prop) & 0xff; + this->ARG4 = value >> 8; + this->ARG5 = value & 0xff; + } +}; + +struct ResSetProperty : ResBase {}; + +struct CmdGetProperty : CmdBase { + uint8_t ARG1; // zero + union { + uint8_t ARG2; + struct { + uint8_t PROPH : 8; + }; + }; + union { + uint8_t ARG3; + struct { + uint8_t PROPL : 8; + }; + }; + + CmdGetProperty() { + this->CMD = CmdType::GET_PROPERTY; + this->ARG1 = 0; + this->ARG2 = 0; + this->ARG3 = 0; + } + + CmdGetProperty(PropType prop) { + this->CMD = CmdType::GET_PROPERTY; + this->ARG1 = 0; + this->ARG2 = (uint16_t) prop >> 8; + this->ARG3 = (uint16_t) prop & 0xff; + } +}; + +struct ResGetProperty : ResBase { + uint8_t RESP1; // zero + union { + uint8_t RESP2; + struct { + uint8_t PROPDH : 8; // Property Get High Byte + }; + }; + union { + uint8_t RESP3; + struct { + uint8_t PROPDL : 8; // Property Get Low Byte + }; + }; + + operator uint16_t() const { return ((uint16_t) PROPDH << 8) | PROPDL; } +}; + +struct CmdGetIntStatus : CmdBase { + CmdGetIntStatus() { this->CMD = CmdType::GET_INT_STATUS; } +}; + +struct ResGetIntStatus : ResBase {}; + +struct CmdTxTuneFreq : CmdBase { + uint8_t ARG1; // zero + union { + uint8_t ARG2; + struct { + uint8_t FREQH : 8; // Tune Frequency High Byte + }; + }; + union { + uint8_t ARG3; + struct { + uint8_t FREQL : 8; // Tune Frequency Low Byte + }; + }; + + CmdTxTuneFreq(uint16_t freq = 0) { // 7600 to 10800, 10 kHz resolution, multiples of 50 kHz + this->CMD = CmdType::TX_TUNE_FREQ; + this->ARG1 = 0; + this->ARG2 = (uint16_t) freq >> 8; + this->ARG3 = (uint16_t) freq & 0xff; + } +}; + +struct ResTxTuneFreq : ResBase {}; + +struct CmdTxTunePower : CmdBase { + uint8_t ARG1; // zero + uint8_t ARG2; // zero + union { + uint8_t ARG3; + struct { + uint8_t RFdBuV : 8; // Tune Power Byte, 88 to 115 dBuV + }; + }; + union { + uint8_t ARG4; + struct { + uint8_t ANTCAP : 8; // Antenna Tuning Capacitor, 0.25 pF x 0 to 191 + }; + }; + + CmdTxTunePower(uint8_t power = 0, uint8_t antcap = 0) { + this->CMD = CmdType::TX_TUNE_POWER; + this->ARG1 = 0; + this->ARG2 = 0; + this->ARG3 = power; + this->ARG4 = antcap; + } +}; + +struct ResTxTunePower : ResBase {}; + +struct CmdTxTuneMeasure : CmdBase { + uint8_t ARG1; // zero + union { + uint8_t ARG2; + struct { + uint8_t FREQH : 8; // Tune Frequency High Byte + }; + }; + union { + uint8_t ARG3; + struct { + uint8_t FREQL : 8; // Tune Frequency Low Byte + }; + }; + union { + uint8_t ARG4; + struct { + uint8_t ANTCAP : 8; // Antenna Tuning Capacitor, 0.25 pF x 0 to 191 + }; + }; + + CmdTxTuneMeasure(uint8_t freq = 0, uint8_t antcap = 0) { + this->CMD = CmdType::TX_TUNE_MEASURE; + this->ARG1 = 0; + this->ARG2 = (uint16_t) freq >> 8; + this->ARG3 = (uint16_t) freq & 0xff; + this->ARG4 = antcap; + } +}; + +struct ResTxTuneMeasure : ResBase {}; + +struct CmdTxTuneStatus : CmdBase { + uint8_t ARG1; // zero + union { + uint8_t ARG2; + struct { + uint8_t INTACK : 1; // Clears the seek/tune complete interrupt status indicator + uint8_t RESERVED : 7; + }; + }; + + CmdTxTuneStatus() { + this->CMD = CmdType::TX_TUNE_STATUS; + this->ARG1 = 0; + this->ARG2 = 0; + } +}; + +struct ResTxTuneStatus : ResBase { + uint8_t RESP1; + union { + uint8_t RESP2; + struct { + uint8_t READFREQH : 8; // Read Frequency High Byte + }; + }; + union { + uint8_t RESP3; + struct { + uint8_t READFREQL : 8; // Read Frequency Low Byte + }; + }; + uint8_t RESP4; + union { + uint8_t RESP5; + struct { + uint8_t RFdBuV : 8; // Read Power + }; + }; + union { + uint8_t RESP6; + struct { + uint8_t ANTCAP : 8; // Read Antenna Tuning Capacitor + }; + }; + union { + uint8_t RESP7; + struct { + uint8_t RNL : 8; // Read Received Noise Level (Si4712/13 Only) + }; + }; +}; + +struct CmdTxAsqStatus : CmdBase { + uint8_t ARG1; // zero + union { + uint8_t ARG2; + struct { + uint8_t INTACK : 1; // Clears the seek/tune complete interrupt status indicator + uint8_t RESERVED : 7; + }; + }; + + CmdTxAsqStatus() { + this->CMD = CmdType::TX_ASQ_STATUS; + this->ARG1 = 0; + this->ARG2 = 0; + } +}; + +struct ResTxAsqStatus : ResBase { + union { + uint8_t RESP1; + struct { + uint8_t OVERMOD : 1; // Overmodulation Detection + uint8_t IALH : 1; // Input Audio Level Threshold Detect High + uint8_t IALL : 1; // Input Audio Level Threshold Detect Low + uint8_t RESERVED : 5; + }; + }; + uint8_t RESP2; + uint8_t RESP3; + union { + uint8_t RESP4; + struct { + int8_t INLEVEL : 8; // Input Audio Level + }; + }; +}; + +struct CmdTxRdsBuff : CmdBase { + union { + uint8_t ARG1; + struct { + uint8_t INTACK : 1; // Clear RDS Group buffer interrupt + uint8_t MTBUFF : 1; // Empty RDS Group Buffer + uint8_t LDBUFF : 1; // Load RDS Group Buffer + uint8_t RESERVED : 4; + uint8_t FIFO : 1; // Operate on FIFO + }; + }; + union { + uint8_t ARG2; + struct { + uint8_t RDSBH; // RDS Block B High Byte + }; + }; + union { + uint8_t ARG3; + struct { + uint8_t RDSBL; // RDS Block B Low Byte + }; + }; + union { + uint8_t ARG4; + struct { + uint8_t RDSCH; // RDS Block C High Byte + }; + }; + union { + uint8_t ARG5; + struct { + uint8_t RDSCL; // RDS Block C Low Byte + }; + }; + union { + uint8_t ARG6; + struct { + uint8_t RDSDH; // RDS Block D High Byte + }; + }; + union { + uint8_t ARG7; + struct { + uint8_t RDSDL; // RDS Block D Low Byte + }; + }; + + CmdTxRdsBuff() { + this->CMD = CmdType::TX_RDS_BUFF; + this->ARG1 = 0; + this->ARG2 = 0; + this->ARG3 = 0; + this->ARG4 = 0; + this->ARG5 = 0; + this->ARG6 = 0; + this->ARG7 = 0; + } +}; + +struct ResTxRdsBuff : ResBase { + union { + uint8_t RESP1; + struct { + uint8_t FIFOMT : 1; // Interrupt source: RDS Group FIFO Buffer is empty + uint8_t CBUFWRAP : 1; // Interrupt source: RDS Group Circular Buffer has wrapped + uint8_t FIFOXMIT : 1; // Interrupt source: RDS Group has been transmitted from the circular buffer + uint8_t CBUFXMIT : 1; // Interrupt source: RDS Group has been transmitted from the FIFO buffer + uint8_t RDSPSXMIT : 1; // Interrupt source: RDS PS Group has been transmitted + uint8_t RESERVED : 3; + }; + }; + union { + uint8_t RESP2; + struct { + uint8_t CBAVAIL : 8; // Returns the number of available Circular Buffer blocks + }; + }; + union { + uint8_t RESP3; + struct { + uint8_t CBUSED : 8; // Returns the number of used Circular Buffer blocks + }; + }; + union { + uint8_t RESP4; + struct { + uint8_t FIFOAVAIL : 8; // Returns the number of available FIFO blocks + }; + }; + union { + uint8_t RESP5; + struct { + uint8_t FIFOUSED : 8; // Returns the number of used FIFO blocks + }; + }; +}; + +struct CmdTxRdsPs : CmdBase { + union { + uint8_t ARG1; + struct { + // Selects which PS data to load (0-23) + // 0 = First 4 characters of PS0 + // 1 = Last 4 characters of PS0 + // 2 = First 4 characters of PS1 + // 3 = Last 4 characters of PS1 + // ... + // 22 = First 4 characters of PS11 + // 23 = Last 4 characters of PS11 + uint8_t PSID : 4; + uint8_t RESERVED : 4; + }; + }; + union { + uint8_t ARG2; + struct { + uint8_t PSCHAR0; // RDS PSID CHAR0 + }; + }; + union { + uint8_t ARG3; + struct { + uint8_t PSCHAR1; // RDS PSID CHAR1 + }; + }; + union { + uint8_t ARG4; + struct { + uint8_t PSCHAR2; // RDS PSID CHAR2 + }; + }; + union { + uint8_t ARG5; + struct { + uint8_t PSCHAR3; // RDS PSID CHAR3 + }; + }; + + CmdTxRdsPs() { + this->CMD = CmdType::TX_RDS_PS; + this->ARG1 = 0; + this->ARG2 = 0; + this->ARG3 = 0; + this->ARG4 = 0; + this->ARG5 = 0; + } +}; + +struct ResTxRdsPs : ResBase {}; + +struct CmdGpioCtl : CmdBase { + union { + uint8_t ARG1; + struct { + uint8_t RESERVED1 : 1; + uint8_t GPO1OEN : 1; // GPO1 Output Enable + uint8_t GPO2OEN : 1; // GPO2 Output Enable + uint8_t GPO3OEN : 1; // GPO3 Output Enable + }; + }; + + CmdGpioCtl() { + this->CMD = CmdType::GPIO_CTL; + this->ARG1 = 0; + } +}; + +struct ResGpioCtl : ResBase {}; + +struct CmdGpioSet : CmdBase { + union { + uint8_t ARG1; + struct { + uint8_t RESERVED1 : 1; + uint8_t GPO1OEN : 1; // GPO1 Output Level + uint8_t GPO2OEN : 1; // GPO2 Output Level + uint8_t GPO3OEN : 1; // GPO3 Output Level + uint8_t RESERVED2 : 4; + }; + }; + + CmdGpioSet() { + this->CMD = CmdType::GPIO_SET; + this->ARG1 = 0; + } +}; + +struct ResGpioSet : ResBase {}; + +// PROPERTIES + +// TODO: init to datasheet defaults in the constructors + +struct PropBase { + PropType PROP; +}; + +struct PropGpoIen : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t STCIEN : 1; // Seek/Tune Complete Interrupt Enable + uint16_t ASQIEN : 1; // Audio Signal Quality Interrupt Enable + uint16_t RDSIEN : 1; // RDS Interrupt Enable (Si4711/13/21 Only) + uint16_t RESERVED1 : 3; // + uint16_t ERRIEN : 1; // ERR Interrupt Enable + uint16_t CTSIEN : 1; // CTS Interrupt Enable + uint16_t STCREP : 1; // STC Interrupt Repeat + uint16_t ASQREP : 1; // ASQ Interrupt Repeat + uint16_t RDSREP : 1; // RDS Interrupt Repeat. (Si4711/13/21 Only) + uint16_t RESERVED2 : 5; + }; + }; + + PropGpoIen() { + this->PROP = PropType::GPO_IEN; + this->PROPD = 0x0000; + } +}; + +struct PropDigitalInputFormat : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t ISIZE : 2; // Digital Audio Sample Precision + uint16_t IMONO : 1; // Mono Audio Mode + uint16_t IMODE : 4; // Digital Mode + uint16_t IFALL : 1; // DCLK Falling Edge + }; + }; + + PropDigitalInputFormat() { + this->PROP = PropType::DIGITAL_INPUT_FORMAT; + this->PROPD = 0x0000; + } +}; + +// TX_TUNE_FREQ command must be sent to start the internal clocking before setting this +struct PropDigitalInputSampleRate : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t DISR : 16; // Digital Input Sample Rate (32000 to 48000 Hz, 0 Disabled) + }; + }; + + PropDigitalInputSampleRate(uint16_t disr = 0) { + this->PROP = PropType::DIGITAL_INPUT_SAMPLE_RATE; + // this->PROPD = 0x0000; + this->DISR = disr; + } +}; + +struct PropRefClkFreq : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t REFCLKF : 16; // Frequency of Reference Clock (31130 to 34406 Hz, 0 Disabled) + }; + }; + + PropRefClkFreq(uint16_t refclkf = 32768) { + this->PROP = PropType::REFCLK_FREQ; + // this->PROPD = 0x8000; + this->REFCLKF = refclkf; + } +}; + +struct PropRefClkPreScale : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t RCLKP : 12; // Prescaler for Reference Clock (31130 and 34406 Hz, 0 Disabled) + uint16_t RCLKSEL : 1; // RCLK/DCLK pin is clock source + uint16_t RESERVED : 3; + }; + }; + + PropRefClkPreScale() { + this->PROP = PropType::REFCLK_PRESCALE; + this->PROPD = 0x0001; + } +}; + +struct PropTxComponentEnable : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t PILOT : 1; // Pilot Tone + uint16_t LMR : 1; // Left Minus Right + uint16_t RDS : 1; // RDS Enable + uint16_t RESERVED : 13; + }; + }; + + PropTxComponentEnable() { + this->PROP = PropType::TX_COMPONENT_ENABLE; + this->PROPD = 0x0003; + } +}; + +struct PropTxAudioDeviation : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t TXADEV : 16; // Transmit Audio Frequency Deviation (0 Hz to 90 kHz in 10 Hz units) + }; + }; + + PropTxAudioDeviation(uint16_t txadev = 6825) { + this->PROP = PropType::TX_AUDIO_DEVIATION; + this->PROPD = 0x1AA9; + this->TXADEV = txadev; + } +}; + +struct PropTxPilotDeviation : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t TXPDEV : 16; // Transmit Pilot Frequency Deviation (0 Hz to 90 kHz in 10 Hz units) + }; + }; + + PropTxPilotDeviation(uint16_t txpdev = 675) { + this->PROP = PropType::TX_PILOT_DEVIATION; + this->PROPD = 0x02A3; + this->TXPDEV = txpdev; + } +}; + +struct PropTxRdsDeviation : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t TXRDEV : 16; // Transmit RDS Frequency Deviation (0 Hz to 90 kHz in 10 Hz units) + }; + }; + + PropTxRdsDeviation(uint16_t txrdev = 200) { + this->PROP = PropType::TX_RDS_DEVIATION; + this->PROPD = 0x00C8; + this->TXRDEV = txrdev; + } +}; + +struct PropTxLineInputLevel : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t LILEVEL : 10; // Line Level (mV) + uint16_t RESERVED1 : 2; + uint16_t LIATTEN : 2; // Line Attenuation (for 190, 301, 416, 636 mV) + uint16_t RESERVED2 : 2; + }; + }; + + PropTxLineInputLevel() { + this->PROP = PropType::TX_LINE_INPUT_LEVEL; + this->PROPD = 0x327C; + } +}; + +struct PropTxLineInputMute : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t RIMUTE : 1; // Mutes R Line Input + uint16_t LIMUTE : 1; // Mutes L Line Input + uint16_t RESERVED : 14; + }; + }; + + PropTxLineInputMute() { + this->PROP = PropType::TX_LINE_INPUT_MUTE; + this->PROPD = 0x0000; + } +}; + +struct PropTxPreEmphasis : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t FMPE : 2; // FM Pre-Emphasis (75us, 50us, Disabled, Reserved) + uint16_t RESERVED : 14; + }; + }; + + PropTxPreEmphasis() { + this->PROP = PropType::TX_PREEMPHASIS; + this->PROPD = 0x0000; + } +}; + +struct PropTxPilotFrequency : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t FREQ : 16; // Stereo Pilot Frequency (0 to 19000 Hz) + }; + }; + + PropTxPilotFrequency() { + this->PROP = PropType::TX_PILOT_FREQUENCY; + this->PROPD = 0x4A38; + } +}; + +struct PropTxAcompEnable : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t ACEN : 1; // Transmit Audio Dynamic Range Control Enable + uint16_t LIMITEN : 1; // Audio Limiter + uint16_t RESERVED : 14; + }; + }; + + PropTxAcompEnable() { + this->PROP = PropType::TX_ACOMP_ENABLE; + this->PROPD = 0x0002; + } +}; + +struct PropTxAcompThreshold : PropBase { + union { + uint16_t PROPD; + struct { + int16_t THRESHOLD : 16; // Transmit Audio Dynamic Range Control Threshold (-40 to 0 dBFS) + }; + }; + + PropTxAcompThreshold() { + this->PROP = PropType::TX_ACOMP_THRESHOLD; + this->PROPD = 0xFFD8; + } +}; + +struct PropTxAcompAttackTime : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t ATTACK : 4; // Transmit Audio Dynamic Range Control Attack Time (0.5 (=0) to 5ms (=9)) + uint16_t RESERVED : 12; + }; + }; + + PropTxAcompAttackTime() { + this->PROP = PropType::TX_ACOMP_ATTACK_TIME; + this->PROPD = 0x0000; + } +}; + +struct PropTxAcompReleaseTime : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t RELEASE : 3; // Transmit Audio Dynamic Range Control Release Time (100, 200, 350, 525, 1000 ms) + uint16_t RESERVED : 13; + }; + }; + + PropTxAcompReleaseTime() { + this->PROP = PropType::TX_ACOMP_RELEASE_TIME; + this->PROPD = 0x0004; + } +}; + +struct PropTxAcompGain : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t GAIN : 6; // Transmit Audio Dynamic Range Control Gain (0 to 20 dB) + uint16_t RESERVED : 10; + }; + }; + + PropTxAcompGain() { + this->PROP = PropType::TX_ACOMP_GAIN; + this->PROPD = 0x000F; + } +}; +struct PropTxLimiterReleaseTime : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t LMITERTC : 16; // Sets the limiter release time + }; + }; + + PropTxLimiterReleaseTime() { + this->PROP = PropType::TX_LIMITER_RELEASE_TIME; + this->PROPD = 0x0066; + } +}; + +struct PropTxAsqInterruptSource : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t IALLIEN : 1; // Input Audio Level Detection Low Threshold Enable + uint16_t IALHIEN : 1; // Input Audio Level Detection High Threshold Enable + uint16_t OVERMODIEN : 1; // Overmodulation Detection Enable + uint16_t RESERVED : 13; + }; + }; + + PropTxAsqInterruptSource() { + this->PROP = PropType::TX_ASQ_INTERRUPT_SOURCE; + this->PROPD = 0x0000; + } +}; + +struct PropTxAsqLevelLow : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t IALLTH : 8; // Input Audio Level Low Threshold (-70 to 0 dB) + uint16_t RESERVED : 8; + }; + }; + + PropTxAsqLevelLow() { + this->PROP = PropType::TX_ASQ_LEVEL_LOW; + this->PROPD = 0x0000; + } +}; + +struct PropTxAsqDurationLow : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t IALLDUR : 16; // Input Audio Level Duration Low (0 to 65535 ms) + }; + }; + + PropTxAsqDurationLow() { + this->PROP = PropType::TX_ASQ_DURATION_LOW; + this->PROPD = 0x0000; + } +}; + +struct PropTxAsqDurationHigh : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t IALHTH : 8; // Input Audio Level High Threshold (-70 to 0 dB) + uint16_t RESERVED : 8; + }; + }; + + PropTxAsqDurationHigh() { + this->PROP = PropType::TX_ASQ_LEVEL_HIGH; + this->PROPD = 0x0000; + } +}; + +struct PropTxAsqDurationHigh : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t IALHDUR : 16; // Input Audio Level Duration High (0 to 65535 ms) + }; + }; + + PropTxAsqDurationHigh() { + this->PROP = PropType::TX_ASQ_DURATION_HIGH; + this->PROPD = 0x0000; + } +}; + +struct PropTxRdsInterruptSource : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t RDSFIFOMT : 1; // Interrupt when the RDS Group FIFO Buffer is empty + uint16_t RDSCBUFWRAP : 1; // Interrupt when the RDS Group Circular Buffer has wrapped + uint16_t RDSFIFOXMIT : 1; // Interrupt when a RDS Group has been transmitted from the FIFO Buffer + uint16_t RDSCBUFXMIT : 1; // Interrupt when a RDS Group has been transmitted from the Circular Buffer + uint16_t RDSPSXMIT : 1; // Interrupt when a RDS PS Group has been transmitted + uint16_t RESERVED : 11; + }; + }; + + PropTxRdsInterruptSource() { + this->PROP = PropType::TX_RDS_INTERRUPT_SOURCE; + this->PROPD = 0x0000; + } +}; + +struct PropTxRdsPi : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t RDSPI : 16; // Transmit RDS Program Identifier + }; + }; + + PropTxRdsPi() { + this->PROP = PropType::TX_RDS_PI; + this->PROPD = 0x40A7; + } +}; + +struct PropTxRdsPsMix : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t RDSPSMIX : 3; // Transmit RDS Mix + uint16_t RESERVED : 13; + }; + }; + + PropTxRdsPsMix() { + this->PROP = PropType::TX_RDS_PS_MIX; + this->PROPD = 0x0003; + } +}; + +struct PropTxRdsPsMisc : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t RESERVED : 3; + uint16_t RDSMS : 1; // Music/Speech Switch Code + uint16_t RDSTA : 1; // Traffic Announcement Code + uint16_t RDSPTY : 1; // Program Type Code + uint16_t RDSTP : 5; // Traffic Program Code + uint16_t FORCEB : 1; // Use the PTY and TP set here in all block B data + uint16_t RDSD0 : 1; // Mono/Stereo code + uint16_t RDSD1 : 1; // Artificial Head code + uint16_t RDSD2 : 1; // Compressed code + uint16_t RDSD3 : 1; // Dynamic PTY code + }; + }; + + PropTxRdsPsMisc() { + this->PROP = PropType::TX_RDS_PS_MISC; + this->PROPD = 0x1008; + } +}; + +struct PropTxRdsRepeatCount : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t RDSPSRC : 8; // Transmit RDS PS Repeat Count + uint16_t RESERVED : 8; + }; + }; + + PropTxRdsRepeatCount() { + this->PROP = PropType::TX_RDS_PS_REPEAT_COUNT; + this->PROPD = 0x0003; + } +}; + +struct PropTxRdsMessageCount : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t RDSPSMC : 4; // Transmit RDS PS Message Count. + uint16_t RESERVED : 12; + }; + }; + + PropTxRdsMessageCount() { + this->PROP = PropType::TX_RDS_PS_MESSAGE_COUNT; + this->PROPD = 0x0001; + } +}; + +struct PropTxRdsPsAf : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t RDSAF : 16; // Transmit RDS Program Service Alternate Frequency + }; + }; + + PropTxRdsPsAf() { + this->PROP = PropType::TX_RDS_PS_AF; + this->PROPD = 0xE0E0; + } +}; + +struct PropTxRdsFifoSize : PropBase { + union { + uint16_t PROPD; + struct { + uint16_t RDSFIFOSZ : 8; // Transmit RDS FIFO Size (0 = FIFO disabled) + uint16_t RESERVED : 8; + }; + }; + + PropTxRdsFifoSize() { + this->PROP = PropType::TX_RDS_FIFO_SIZE; + this->PROPD = 0x0000; + } +}; + +} // namespace si4713 +} // namespace esphome diff --git a/esphome/components/si4713_i2c/switch/__init__.py b/esphome/components/si4713_i2c/switch/__init__.py new file mode 100644 index 0000000000..9971203f5c --- /dev/null +++ b/esphome/components/si4713_i2c/switch/__init__.py @@ -0,0 +1,61 @@ +import esphome.codegen as cg +from esphome.components import switch +import esphome.config_validation as cv +from esphome.const import ( + DEVICE_CLASS_SWITCH, + ENTITY_CATEGORY_CONFIG, + ICON_SECURITY, +) +from .. import ( + CONF_SI4713_ID, + Si4713Component, + si4713_ns, + CONF_MUTE, + CONF_MONO, + CONF_RDS_ENABLE, + ICON_VOLUME_MUTE, + ICON_EAR_HEARING, + ICON_FORMAT_TEXT, +) + +MuteSwitch = si4713_ns.class_("MuteSwitch", switch.Switch) +MonoSwitch = si4713_ns.class_("MonoSwitch", switch.Switch) +RDSEnableSwitch = si4713_ns.class_("RDSEnableSwitch", switch.Switch) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_SI4713_ID): cv.use_id(Si4713Component), + cv.Optional(CONF_MUTE): switch.switch_schema( + MuteSwitch, + device_class=DEVICE_CLASS_SWITCH, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_VOLUME_MUTE, + ), + cv.Optional(CONF_MONO): switch.switch_schema( + MonoSwitch, + device_class=DEVICE_CLASS_SWITCH, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_EAR_HEARING, + ), + cv.Optional(CONF_RDS_ENABLE): switch.switch_schema( + RDSEnableSwitch, + device_class=DEVICE_CLASS_SWITCH, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_FORMAT_TEXT, + ), + } +) + + +async def new_switch(config, id, setter): + if c := config.get(id): + s = await switch.new_switch(c) + await cg.register_parented(s, config[CONF_SI4713_ID]) + cg.add(setter(s)) + + +async def to_code(config): + c = await cg.get_variable(config[CONF_SI4713_ID]) + await new_switch(config, CONF_MUTE, c.set_mute_switch) + await new_switch(config, CONF_MONO, c.set_mono_switch) + await new_switch(config, CONF_RDS_ENABLE, c.set_rds_enable_switch) diff --git a/esphome/components/si4713_i2c/switch/mono_switch.cpp b/esphome/components/si4713_i2c/switch/mono_switch.cpp new file mode 100644 index 0000000000..f6d9068669 --- /dev/null +++ b/esphome/components/si4713_i2c/switch/mono_switch.cpp @@ -0,0 +1,12 @@ +#include "mono_switch.h" + +namespace esphome { +namespace si4713 { + +void MonoSwitch::write_state(bool value) { + this->publish_state(value); + this->parent_->set_mono(value); +} + +} // namespace si4713 +} // namespace esphome diff --git a/esphome/components/si4713_i2c/switch/mono_switch.h b/esphome/components/si4713_i2c/switch/mono_switch.h new file mode 100644 index 0000000000..450131e037 --- /dev/null +++ b/esphome/components/si4713_i2c/switch/mono_switch.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/switch/switch.h" +#include "../si4713.h" + +namespace esphome { +namespace si4713 { + +class MonoSwitch : public switch_::Switch, public Parented { + public: + MonoSwitch() = default; + + protected: + void write_state(bool value) override; +}; + +} // namespace si4713 +} // namespace esphome diff --git a/esphome/components/si4713_i2c/switch/mute_switch.cpp b/esphome/components/si4713_i2c/switch/mute_switch.cpp new file mode 100644 index 0000000000..425b0f3fcf --- /dev/null +++ b/esphome/components/si4713_i2c/switch/mute_switch.cpp @@ -0,0 +1,12 @@ +#include "mute_switch.h" + +namespace esphome { +namespace si4713 { + +void MuteSwitch::write_state(bool value) { + this->publish_state(value); + this->parent_->set_mute(value); +} + +} // namespace si4713 +} // namespace esphome diff --git a/esphome/components/si4713_i2c/switch/mute_switch.h b/esphome/components/si4713_i2c/switch/mute_switch.h new file mode 100644 index 0000000000..a4f69a4d57 --- /dev/null +++ b/esphome/components/si4713_i2c/switch/mute_switch.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/switch/switch.h" +#include "../si4713.h" + +namespace esphome { +namespace si4713 { + +class MuteSwitch : public switch_::Switch, public Parented { + public: + MuteSwitch() = default; + + protected: + void write_state(bool value) override; +}; + +} // namespace si4713 +} // namespace esphome diff --git a/esphome/components/si4713_i2c/switch/rds_enable_switch.cpp b/esphome/components/si4713_i2c/switch/rds_enable_switch.cpp new file mode 100644 index 0000000000..a42b20e0b5 --- /dev/null +++ b/esphome/components/si4713_i2c/switch/rds_enable_switch.cpp @@ -0,0 +1,12 @@ +#include "rds_enable_switch.h" + +namespace esphome { +namespace si4713 { + +void RDSEnableSwitch::write_state(bool value) { + this->publish_state(value); + this->parent_->set_rds_enable(value); +} + +} // namespace si4713 +} // namespace esphome diff --git a/esphome/components/si4713_i2c/switch/rds_enable_switch.h b/esphome/components/si4713_i2c/switch/rds_enable_switch.h new file mode 100644 index 0000000000..5622af1662 --- /dev/null +++ b/esphome/components/si4713_i2c/switch/rds_enable_switch.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/switch/switch.h" +#include "../si4713.h" + +namespace esphome { +namespace si4713 { + +class RDSEnableSwitch : public switch_::Switch, public Parented { + public: + RDSEnableSwitch() = default; + + protected: + void write_state(bool value) override; +}; + +} // namespace si4713 +} // namespace esphome diff --git a/esphome/components/si4713_i2c/text/__init__.py b/esphome/components/si4713_i2c/text/__init__.py new file mode 100644 index 0000000000..169818b458 --- /dev/null +++ b/esphome/components/si4713_i2c/text/__init__.py @@ -0,0 +1,154 @@ +from typing import Optional +import esphome.codegen as cg +from esphome.components import text +from esphome.components import mqtt, web_server +import esphome.config_validation as cv +from esphome.const import ( + CONF_ID, + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_MQTT_ID, + CONF_WEB_SERVER, + ENTITY_CATEGORY_CONFIG, +) +from esphome.core import CORE +from esphome.cpp_generator import MockObjClass +from esphome.cpp_helpers import setup_entity +from .. import ( + CONF_SI4713_ID, + Si4713Component, + si4713_ns, + CONF_RDS_STATION, + CONF_RDS_TEXT, + ICON_FORMAT_TEXT, +) + +RDSStationText = si4713_ns.class_("RDSStationText", text.Text) +RDSTextText = si4713_ns.class_("RDSTextText", text.Text) + +# The text component isn't implemented the same way as switch, select, number. +# It is not possible to create our own text platform as it is now, so I adopted +# the necessary code from those. + +_TEXT_SCHEMA = ( + cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) + .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA) + .extend( + { + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextComponent), + } + ) +) + +_UNDEF = object() + + +def text_schema( + class_: MockObjClass = _UNDEF, + *, + entity_category: str = _UNDEF, + device_class: str = _UNDEF, + icon: str = _UNDEF, +): + schema = _TEXT_SCHEMA + if class_ is not _UNDEF: + schema = schema.extend({cv.GenerateID(): cv.declare_id(class_)}) + if entity_category is not _UNDEF: + schema = schema.extend( + { + cv.Optional( + CONF_ENTITY_CATEGORY, default=entity_category + ): cv.entity_category + } + ) + if icon is not _UNDEF: + schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon}) + return schema + + +TEXT_SCHEMA = text_schema() # for compatibility + + +async def setup_text_core_( + var, + config, + *, + min_length: Optional[int], + max_length: Optional[int], + pattern: Optional[str], +): + await setup_entity(var, config) + cg.add(var.traits.set_min_length(min_length)) + cg.add(var.traits.set_max_length(max_length)) + if pattern is not None: + cg.add(var.traits.set_pattern(pattern)) + + if (mqtt_id := config.get(CONF_MQTT_ID)) is not None: + mqtt_ = cg.new_Pvariable(mqtt_id, var) + await mqtt.register_mqtt_component(mqtt_, config) + + if web_server_config := config.get(CONF_WEB_SERVER): + await web_server.add_entity_config(var, web_server_config) + + +async def register_text( + var, + config, + min_length: Optional[int] = 0, + max_length: Optional[int] = 255, + pattern: Optional[str] = None, +): + if not CORE.has_id(config[CONF_ID]): + var = cg.Pvariable(config[CONF_ID], var) + cg.add(cg.App.register_text(var)) + await setup_text_core_( + var, config, min_length=min_length, max_length=max_length, pattern=pattern + ) + + +async def new_text( + config, + *args, + min_length: Optional[int] = 0, + max_length: Optional[int] = 255, + pattern: Optional[str] = None, +): + var = cg.new_Pvariable(config[CONF_ID], *args) + await register_text( + var, config, min_length=min_length, max_length=max_length, pattern=pattern + ) + return var + + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_SI4713_ID): cv.use_id(Si4713Component), + cv.Optional(CONF_RDS_STATION): text_schema( + RDSStationText, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_FORMAT_TEXT, + ), + cv.Optional(CONF_RDS_TEXT): text_schema( + RDSTextText, + entity_category=ENTITY_CATEGORY_CONFIG, + icon=ICON_FORMAT_TEXT, + ), + } +) + + +async def new_text_simple(config, id, setter, min_length, max_length, *args): + if c := config.get(id): + t = await new_text(c, *args, min_length=min_length, max_length=max_length) + await cg.register_parented(t, config[CONF_SI4713_ID]) + cg.add(setter(t)) + + +async def to_code(config): + c = await cg.get_variable(config[CONF_SI4713_ID]) + await new_text_simple( + config, CONF_RDS_STATION, c.set_rds_station_text, 0, si4713_ns.RDS_STATION_MAX + ) + await new_text_simple( + config, CONF_RDS_TEXT, c.set_rds_text_text, 0, si4713_ns.RDS_TEXT_MAX + ) diff --git a/esphome/components/si4713_i2c/text/rds_station_text.cpp b/esphome/components/si4713_i2c/text/rds_station_text.cpp new file mode 100644 index 0000000000..5049443b82 --- /dev/null +++ b/esphome/components/si4713_i2c/text/rds_station_text.cpp @@ -0,0 +1,12 @@ +#include "rds_station_text.h" + +namespace esphome { +namespace si4713 { + +void RDSStationText::control(const std::string &value) { + this->publish_state(value); + this->parent_->set_rds_station(value); +} + +} // namespace si4713 +} // namespace esphome diff --git a/esphome/components/si4713_i2c/text/rds_station_text.h b/esphome/components/si4713_i2c/text/rds_station_text.h new file mode 100644 index 0000000000..d49a23c66f --- /dev/null +++ b/esphome/components/si4713_i2c/text/rds_station_text.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/text/text.h" +#include "../si4713.h" + +namespace esphome { +namespace si4713 { + +class RDSStationText : public text::Text, public Parented { + public: + RDSStationText() = default; + + protected: + void control(const std::string &value) override; +}; + +} // namespace si4713 +} // namespace esphome diff --git a/esphome/components/si4713_i2c/text/rds_text_text.cpp b/esphome/components/si4713_i2c/text/rds_text_text.cpp new file mode 100644 index 0000000000..b4e94102f6 --- /dev/null +++ b/esphome/components/si4713_i2c/text/rds_text_text.cpp @@ -0,0 +1,12 @@ +#include "rds_text_text.h" + +namespace esphome { +namespace si4713 { + +void RDSTextText::control(const std::string &value) { + this->publish_state(value); + this->parent_->set_rds_text(value); +} + +} // namespace si4713 +} // namespace esphome diff --git a/esphome/components/si4713_i2c/text/rds_text_text.h b/esphome/components/si4713_i2c/text/rds_text_text.h new file mode 100644 index 0000000000..fd9ba83e8b --- /dev/null +++ b/esphome/components/si4713_i2c/text/rds_text_text.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/text/text.h" +#include "../si4713.h" + +namespace esphome { +namespace si4713 { + +class RDSTextText : public text::Text, public Parented { + public: + RDSTextText() = default; + + protected: + void control(const std::string &value) override; +}; + +} // namespace si4713 +} // namespace esphome