si4713 commands, properties

This commit is contained in:
Gábor Poczkodi 2024-10-15 03:38:55 +02:00
parent 0c7c84011a
commit 8033ef48a8
20 changed files with 2415 additions and 0 deletions

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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<Si4713Component> {
public:
FrequencyNumber() = default;
protected:
void control(float value) override;
};
} // namespace si4713
} // namespace esphome

View file

@ -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)

View file

@ -0,0 +1,400 @@
#include "si4713.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <cstdio>
#include <cmath>
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

View file

@ -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 <string>
#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<typename CMD> bool send_cmd(const CMD &cmd) {
return this->send_cmd((const void *) &cmd, sizeof(cmd), nullptr, 0);
}
template<typename CMD, typename RES> bool send_cmd(CMD cmd, RES &res) {
return this->send_cmd((const void *) &cmd, sizeof(cmd), (void *) &res, sizeof(res));
}
template<typename P> bool set_prop(P p) {
CmdSetProperty cmd = CmdSetProperty(p.PROP, p.PROPD);
return this->send_cmd(cmd);
}
template<typename P> 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

File diff suppressed because it is too large Load diff

View file

@ -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)

View file

@ -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

View file

@ -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<Si4713Component> {
public:
MonoSwitch() = default;
protected:
void write_state(bool value) override;
};
} // namespace si4713
} // namespace esphome

View file

@ -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

View file

@ -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<Si4713Component> {
public:
MuteSwitch() = default;
protected:
void write_state(bool value) override;
};
} // namespace si4713
} // namespace esphome

View file

@ -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

View file

@ -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<Si4713Component> {
public:
RDSEnableSwitch() = default;
protected:
void write_state(bool value) override;
};
} // namespace si4713
} // namespace esphome

View file

@ -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
)

View file

@ -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

View file

@ -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<Si4713Component> {
public:
RDSStationText() = default;
protected:
void control(const std::string &value) override;
};
} // namespace si4713
} // namespace esphome

View file

@ -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

View file

@ -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<Si4713Component> {
public:
RDSTextText() = default;
protected:
void control(const std::string &value) override;
};
} // namespace si4713
} // namespace esphome