mirror of
https://github.com/esphome/esphome.git
synced 2024-12-25 06:54:52 +01:00
Merge branch 'dev' into hbridge-switch
This commit is contained in:
commit
a60e069d3d
43 changed files with 379 additions and 208 deletions
|
@ -140,6 +140,8 @@ CONF_TEST_PATTERN = "test_pattern"
|
|||
# framerates
|
||||
CONF_MAX_FRAMERATE = "max_framerate"
|
||||
CONF_IDLE_FRAMERATE = "idle_framerate"
|
||||
# frame buffer
|
||||
CONF_FRAME_BUFFER_COUNT = "frame_buffer_count"
|
||||
|
||||
# stream trigger
|
||||
CONF_ON_STREAM_START = "on_stream_start"
|
||||
|
@ -213,6 +215,7 @@ CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
|
|||
cv.Optional(CONF_IDLE_FRAMERATE, default="0.1 fps"): cv.All(
|
||||
cv.framerate, cv.Range(min=0, max=1)
|
||||
),
|
||||
cv.Optional(CONF_FRAME_BUFFER_COUNT, default=1): cv.int_range(min=1, max=2),
|
||||
cv.Optional(CONF_ON_STREAM_START): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
|
@ -285,6 +288,7 @@ async def to_code(config):
|
|||
cg.add(var.set_idle_update_interval(0))
|
||||
else:
|
||||
cg.add(var.set_idle_update_interval(1000 / config[CONF_IDLE_FRAMERATE]))
|
||||
cg.add(var.set_frame_buffer_count(config[CONF_FRAME_BUFFER_COUNT]))
|
||||
cg.add(var.set_frame_size(config[CONF_RESOLUTION]))
|
||||
|
||||
cg.add_define("USE_ESP32_CAMERA")
|
||||
|
|
|
@ -127,7 +127,7 @@ void ESP32Camera::dump_config() {
|
|||
sensor_t *s = esp_camera_sensor_get();
|
||||
auto st = s->status;
|
||||
ESP_LOGCONFIG(TAG, " JPEG Quality: %u", st.quality);
|
||||
// ESP_LOGCONFIG(TAG, " Framebuffer Count: %u", conf.fb_count);
|
||||
ESP_LOGCONFIG(TAG, " Framebuffer Count: %u", conf.fb_count);
|
||||
ESP_LOGCONFIG(TAG, " Contrast: %d", st.contrast);
|
||||
ESP_LOGCONFIG(TAG, " Brightness: %d", st.brightness);
|
||||
ESP_LOGCONFIG(TAG, " Saturation: %d", st.saturation);
|
||||
|
@ -212,6 +212,8 @@ ESP32Camera::ESP32Camera() {
|
|||
this->config_.frame_size = FRAMESIZE_VGA; // 640x480
|
||||
this->config_.jpeg_quality = 10;
|
||||
this->config_.fb_count = 1;
|
||||
this->config_.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
|
||||
this->config_.fb_location = CAMERA_FB_IN_PSRAM;
|
||||
|
||||
global_esp32_camera = this;
|
||||
}
|
||||
|
@ -333,6 +335,12 @@ void ESP32Camera::set_max_update_interval(uint32_t max_update_interval) {
|
|||
void ESP32Camera::set_idle_update_interval(uint32_t idle_update_interval) {
|
||||
this->idle_update_interval_ = idle_update_interval;
|
||||
}
|
||||
/* set frame buffer parameters */
|
||||
void ESP32Camera::set_frame_buffer_mode(camera_grab_mode_t mode) { this->config_.grab_mode = mode; }
|
||||
void ESP32Camera::set_frame_buffer_count(uint8_t fb_count) {
|
||||
this->config_.fb_count = fb_count;
|
||||
this->set_frame_buffer_mode(fb_count > 1 ? CAMERA_GRAB_LATEST : CAMERA_GRAB_WHEN_EMPTY);
|
||||
}
|
||||
|
||||
/* ---------------- public API (specific) ---------------- */
|
||||
void ESP32Camera::add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&callback) {
|
||||
|
|
|
@ -145,6 +145,9 @@ class ESP32Camera : public Component, public EntityBase {
|
|||
/* -- framerates */
|
||||
void set_max_update_interval(uint32_t max_update_interval);
|
||||
void set_idle_update_interval(uint32_t idle_update_interval);
|
||||
/* -- frame buffer */
|
||||
void set_frame_buffer_mode(camera_grab_mode_t mode);
|
||||
void set_frame_buffer_count(uint8_t fb_count);
|
||||
|
||||
/* public API (derivated) */
|
||||
void setup() override;
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import esphome.config_validation as cv
|
||||
import esphome.final_validate as fv
|
||||
import esphome.codegen as cg
|
||||
|
||||
from esphome import pins
|
||||
from esphome.const import CONF_ID
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.esp32 import get_esp32_variant
|
||||
from esphome.components.esp32.const import (
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
VARIANT_ESP32C3,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CHANNEL, CONF_ID, CONF_SAMPLE_RATE
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
import esphome.final_validate as fv
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
|
@ -25,16 +25,26 @@ CONF_I2S_LRCLK_PIN = "i2s_lrclk_pin"
|
|||
CONF_I2S_AUDIO = "i2s_audio"
|
||||
CONF_I2S_AUDIO_ID = "i2s_audio_id"
|
||||
|
||||
CONF_BITS_PER_SAMPLE = "bits_per_sample"
|
||||
CONF_I2S_MODE = "i2s_mode"
|
||||
CONF_PRIMARY = "primary"
|
||||
CONF_SECONDARY = "secondary"
|
||||
|
||||
CONF_USE_APLL = "use_apll"
|
||||
CONF_BITS_PER_SAMPLE = "bits_per_sample"
|
||||
CONF_BITS_PER_CHANNEL = "bits_per_channel"
|
||||
CONF_MONO = "mono"
|
||||
CONF_LEFT = "left"
|
||||
CONF_RIGHT = "right"
|
||||
CONF_STEREO = "stereo"
|
||||
|
||||
i2s_audio_ns = cg.esphome_ns.namespace("i2s_audio")
|
||||
I2SAudioComponent = i2s_audio_ns.class_("I2SAudioComponent", cg.Component)
|
||||
I2SAudioIn = i2s_audio_ns.class_("I2SAudioIn", cg.Parented.template(I2SAudioComponent))
|
||||
I2SAudioOut = i2s_audio_ns.class_(
|
||||
"I2SAudioOut", cg.Parented.template(I2SAudioComponent)
|
||||
I2SAudioBase = i2s_audio_ns.class_(
|
||||
"I2SAudioBase", cg.Parented.template(I2SAudioComponent)
|
||||
)
|
||||
I2SAudioIn = i2s_audio_ns.class_("I2SAudioIn", I2SAudioBase)
|
||||
I2SAudioOut = i2s_audio_ns.class_("I2SAudioOut", I2SAudioBase)
|
||||
|
||||
i2s_mode_t = cg.global_ns.enum("i2s_mode_t")
|
||||
I2S_MODE_OPTIONS = {
|
||||
|
@ -50,6 +60,75 @@ I2S_PORTS = {
|
|||
VARIANT_ESP32C3: 1,
|
||||
}
|
||||
|
||||
i2s_channel_fmt_t = cg.global_ns.enum("i2s_channel_fmt_t")
|
||||
I2S_CHANNELS = {
|
||||
CONF_MONO: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ALL_LEFT,
|
||||
CONF_LEFT: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_LEFT,
|
||||
CONF_RIGHT: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_RIGHT,
|
||||
CONF_STEREO: i2s_channel_fmt_t.I2S_CHANNEL_FMT_RIGHT_LEFT,
|
||||
}
|
||||
|
||||
i2s_bits_per_sample_t = cg.global_ns.enum("i2s_bits_per_sample_t")
|
||||
I2S_BITS_PER_SAMPLE = {
|
||||
8: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_8BIT,
|
||||
16: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_16BIT,
|
||||
24: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_24BIT,
|
||||
32: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_32BIT,
|
||||
}
|
||||
|
||||
i2s_bits_per_chan_t = cg.global_ns.enum("i2s_bits_per_chan_t")
|
||||
I2S_BITS_PER_CHANNEL = {
|
||||
"default": i2s_bits_per_chan_t.I2S_BITS_PER_CHAN_DEFAULT,
|
||||
8: i2s_bits_per_chan_t.I2S_BITS_PER_CHAN_8BIT,
|
||||
16: i2s_bits_per_chan_t.I2S_BITS_PER_CHAN_16BIT,
|
||||
24: i2s_bits_per_chan_t.I2S_BITS_PER_CHAN_24BIT,
|
||||
32: i2s_bits_per_chan_t.I2S_BITS_PER_CHAN_32BIT,
|
||||
}
|
||||
|
||||
_validate_bits = cv.float_with_unit("bits", "bit")
|
||||
|
||||
|
||||
def i2s_audio_component_schema(
|
||||
class_: MockObjClass,
|
||||
*,
|
||||
default_sample_rate: int,
|
||||
default_channel: str,
|
||||
default_bits_per_sample: str,
|
||||
):
|
||||
return cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(class_),
|
||||
cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent),
|
||||
cv.Optional(CONF_CHANNEL, default=default_channel): cv.enum(I2S_CHANNELS),
|
||||
cv.Optional(CONF_SAMPLE_RATE, default=default_sample_rate): cv.int_range(
|
||||
min=1
|
||||
),
|
||||
cv.Optional(CONF_BITS_PER_SAMPLE, default=default_bits_per_sample): cv.All(
|
||||
_validate_bits, cv.enum(I2S_BITS_PER_SAMPLE)
|
||||
),
|
||||
cv.Optional(CONF_I2S_MODE, default=CONF_PRIMARY): cv.enum(
|
||||
I2S_MODE_OPTIONS, lower=True
|
||||
),
|
||||
cv.Optional(CONF_USE_APLL, default=False): cv.boolean,
|
||||
cv.Optional(CONF_BITS_PER_CHANNEL, default="default"): cv.All(
|
||||
cv.Any(cv.float_with_unit("bits", "bit"), "default"),
|
||||
cv.enum(I2S_BITS_PER_CHANNEL),
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def register_i2s_audio_component(var, config):
|
||||
await cg.register_parented(var, config[CONF_I2S_AUDIO_ID])
|
||||
|
||||
cg.add(var.set_i2s_mode(config[CONF_I2S_MODE]))
|
||||
cg.add(var.set_channel(config[CONF_CHANNEL]))
|
||||
cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE]))
|
||||
cg.add(var.set_bits_per_sample(config[CONF_BITS_PER_SAMPLE]))
|
||||
cg.add(var.set_bits_per_channel(config[CONF_BITS_PER_CHANNEL]))
|
||||
cg.add(var.set_use_apll(config[CONF_USE_APLL]))
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(I2SAudioComponent),
|
||||
|
|
|
@ -11,9 +11,27 @@ namespace i2s_audio {
|
|||
|
||||
class I2SAudioComponent;
|
||||
|
||||
class I2SAudioIn : public Parented<I2SAudioComponent> {};
|
||||
class I2SAudioBase : public Parented<I2SAudioComponent> {
|
||||
public:
|
||||
void set_i2s_mode(i2s_mode_t mode) { this->i2s_mode_ = mode; }
|
||||
void set_channel(i2s_channel_fmt_t channel) { this->channel_ = channel; }
|
||||
void set_sample_rate(uint32_t sample_rate) { this->sample_rate_ = sample_rate; }
|
||||
void set_bits_per_sample(i2s_bits_per_sample_t bits_per_sample) { this->bits_per_sample_ = bits_per_sample; }
|
||||
void set_bits_per_channel(i2s_bits_per_chan_t bits_per_channel) { this->bits_per_channel_ = bits_per_channel; }
|
||||
void set_use_apll(uint32_t use_apll) { this->use_apll_ = use_apll; }
|
||||
|
||||
class I2SAudioOut : public Parented<I2SAudioComponent> {};
|
||||
protected:
|
||||
i2s_mode_t i2s_mode_{};
|
||||
i2s_channel_fmt_t channel_;
|
||||
uint32_t sample_rate_;
|
||||
i2s_bits_per_sample_t bits_per_sample_;
|
||||
i2s_bits_per_chan_t bits_per_channel_;
|
||||
bool use_apll_;
|
||||
};
|
||||
|
||||
class I2SAudioIn : public I2SAudioBase {};
|
||||
|
||||
class I2SAudioOut : public I2SAudioBase {};
|
||||
|
||||
class I2SAudioComponent : public Component {
|
||||
public:
|
||||
|
|
|
@ -12,6 +12,10 @@ from .. import (
|
|||
I2SAudioOut,
|
||||
CONF_I2S_AUDIO_ID,
|
||||
CONF_I2S_DOUT_PIN,
|
||||
CONF_LEFT,
|
||||
CONF_RIGHT,
|
||||
CONF_MONO,
|
||||
CONF_STEREO,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
|
@ -30,12 +34,12 @@ CONF_DAC_TYPE = "dac_type"
|
|||
CONF_I2S_COMM_FMT = "i2s_comm_fmt"
|
||||
|
||||
INTERNAL_DAC_OPTIONS = {
|
||||
"left": i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN,
|
||||
"right": i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN,
|
||||
"stereo": i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN,
|
||||
CONF_LEFT: i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN,
|
||||
CONF_RIGHT: i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN,
|
||||
CONF_STEREO: i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN,
|
||||
}
|
||||
|
||||
EXTERNAL_DAC_OPTIONS = ["mono", "stereo"]
|
||||
EXTERNAL_DAC_OPTIONS = [CONF_MONO, CONF_STEREO]
|
||||
|
||||
NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2]
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ enum I2SState : uint8_t {
|
|||
I2S_STATE_STOPPING,
|
||||
};
|
||||
|
||||
class I2SAudioMediaPlayer : public Component, public media_player::MediaPlayer, public I2SAudioOut {
|
||||
class I2SAudioMediaPlayer : public Component, public Parented<I2SAudioComponent>, public media_player::MediaPlayer {
|
||||
public:
|
||||
void setup() override;
|
||||
float get_setup_priority() const override { return esphome::setup_priority::LATE; }
|
||||
|
|
|
@ -1,20 +1,17 @@
|
|||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
|
||||
from esphome import pins
|
||||
from esphome.const import CONF_CHANNEL, CONF_ID, CONF_NUMBER, CONF_SAMPLE_RATE
|
||||
from esphome.components import microphone, esp32
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import esp32, microphone
|
||||
from esphome.components.adc import ESP32_VARIANT_ADC1_PIN_TO_CHANNEL, validate_adc_pin
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_NUMBER
|
||||
|
||||
from .. import (
|
||||
CONF_I2S_MODE,
|
||||
CONF_PRIMARY,
|
||||
I2S_MODE_OPTIONS,
|
||||
i2s_audio_ns,
|
||||
I2SAudioComponent,
|
||||
I2SAudioIn,
|
||||
CONF_I2S_AUDIO_ID,
|
||||
CONF_I2S_DIN_PIN,
|
||||
CONF_RIGHT,
|
||||
I2SAudioIn,
|
||||
i2s_audio_component_schema,
|
||||
i2s_audio_ns,
|
||||
register_i2s_audio_component,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
|
@ -23,29 +20,14 @@ DEPENDENCIES = ["i2s_audio"]
|
|||
CONF_ADC_PIN = "adc_pin"
|
||||
CONF_ADC_TYPE = "adc_type"
|
||||
CONF_PDM = "pdm"
|
||||
CONF_BITS_PER_SAMPLE = "bits_per_sample"
|
||||
CONF_USE_APLL = "use_apll"
|
||||
|
||||
I2SAudioMicrophone = i2s_audio_ns.class_(
|
||||
"I2SAudioMicrophone", I2SAudioIn, microphone.Microphone, cg.Component
|
||||
)
|
||||
|
||||
i2s_channel_fmt_t = cg.global_ns.enum("i2s_channel_fmt_t")
|
||||
CHANNELS = {
|
||||
"left": i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_LEFT,
|
||||
"right": i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_RIGHT,
|
||||
}
|
||||
i2s_bits_per_sample_t = cg.global_ns.enum("i2s_bits_per_sample_t")
|
||||
BITS_PER_SAMPLE = {
|
||||
16: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_16BIT,
|
||||
32: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_32BIT,
|
||||
}
|
||||
|
||||
INTERNAL_ADC_VARIANTS = [esp32.const.VARIANT_ESP32]
|
||||
PDM_VARIANTS = [esp32.const.VARIANT_ESP32, esp32.const.VARIANT_ESP32S3]
|
||||
|
||||
_validate_bits = cv.float_with_unit("bits", "bit")
|
||||
|
||||
|
||||
def validate_esp32_variant(config):
|
||||
variant = esp32.get_esp32_variant()
|
||||
|
@ -62,21 +44,15 @@ def validate_esp32_variant(config):
|
|||
|
||||
|
||||
BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(I2SAudioMicrophone),
|
||||
cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent),
|
||||
cv.Optional(CONF_CHANNEL, default="right"): cv.enum(CHANNELS),
|
||||
cv.Optional(CONF_SAMPLE_RATE, default=16000): cv.int_range(min=1),
|
||||
cv.Optional(CONF_BITS_PER_SAMPLE, default="32bit"): cv.All(
|
||||
_validate_bits, cv.enum(BITS_PER_SAMPLE)
|
||||
),
|
||||
cv.Optional(CONF_USE_APLL, default=False): cv.boolean,
|
||||
cv.Optional(CONF_I2S_MODE, default=CONF_PRIMARY): cv.enum(
|
||||
I2S_MODE_OPTIONS, lower=True
|
||||
),
|
||||
}
|
||||
i2s_audio_component_schema(
|
||||
I2SAudioMicrophone,
|
||||
default_sample_rate=16000,
|
||||
default_channel=CONF_RIGHT,
|
||||
default_bits_per_sample="32bit",
|
||||
)
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.typed_schema(
|
||||
{
|
||||
|
@ -88,7 +64,7 @@ CONFIG_SCHEMA = cv.All(
|
|||
"external": BASE_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_I2S_DIN_PIN): pins.internal_gpio_input_pin_number,
|
||||
cv.Required(CONF_PDM): cv.boolean,
|
||||
cv.Optional(CONF_PDM, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
},
|
||||
|
@ -101,8 +77,8 @@ CONFIG_SCHEMA = cv.All(
|
|||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
await cg.register_parented(var, config[CONF_I2S_AUDIO_ID])
|
||||
await register_i2s_audio_component(var, config)
|
||||
await microphone.register_microphone(var, config)
|
||||
|
||||
if config[CONF_ADC_TYPE] == "internal":
|
||||
variant = esp32.get_esp32_variant()
|
||||
|
@ -112,11 +88,3 @@ async def to_code(config):
|
|||
else:
|
||||
cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN]))
|
||||
cg.add(var.set_pdm(config[CONF_PDM]))
|
||||
|
||||
cg.add(var.set_i2s_mode(config[CONF_I2S_MODE]))
|
||||
cg.add(var.set_channel(config[CONF_CHANNEL]))
|
||||
cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE]))
|
||||
cg.add(var.set_bits_per_sample(config[CONF_BITS_PER_SAMPLE]))
|
||||
cg.add(var.set_use_apll(config[CONF_USE_APLL]))
|
||||
|
||||
await microphone.register_microphone(var, config)
|
||||
|
|
|
@ -58,7 +58,7 @@ void I2SAudioMicrophone::start_() {
|
|||
.tx_desc_auto_clear = false,
|
||||
.fixed_mclk = 0,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
.bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
|
||||
.bits_per_chan = this->bits_per_channel_,
|
||||
};
|
||||
|
||||
esp_err_t err;
|
||||
|
@ -167,21 +167,24 @@ size_t I2SAudioMicrophone::read(int16_t *buf, size_t len) {
|
|||
return 0;
|
||||
}
|
||||
this->status_clear_warning();
|
||||
if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_16BIT) {
|
||||
return bytes_read;
|
||||
} else if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_32BIT) {
|
||||
std::vector<int16_t> samples;
|
||||
size_t samples_read = bytes_read / sizeof(int32_t);
|
||||
samples.resize(samples_read);
|
||||
for (size_t i = 0; i < samples_read; i++) {
|
||||
int32_t temp = reinterpret_cast<int32_t *>(buf)[i] >> 14;
|
||||
samples[i] = clamp<int16_t>(temp, INT16_MIN, INT16_MAX);
|
||||
// ESP-IDF I2S implementation right-extends 8-bit data to 16 bits,
|
||||
// and 24-bit data to 32 bits.
|
||||
switch (this->bits_per_sample_) {
|
||||
case I2S_BITS_PER_SAMPLE_8BIT:
|
||||
case I2S_BITS_PER_SAMPLE_16BIT:
|
||||
return bytes_read;
|
||||
case I2S_BITS_PER_SAMPLE_24BIT:
|
||||
case I2S_BITS_PER_SAMPLE_32BIT: {
|
||||
size_t samples_read = bytes_read / sizeof(int32_t);
|
||||
for (size_t i = 0; i < samples_read; i++) {
|
||||
int32_t temp = reinterpret_cast<int32_t *>(buf)[i] >> 14;
|
||||
buf[i] = clamp<int16_t>(temp, INT16_MIN, INT16_MAX);
|
||||
}
|
||||
return samples_read * sizeof(int16_t);
|
||||
}
|
||||
memcpy(buf, samples.data(), samples_read * sizeof(int16_t));
|
||||
return samples_read * sizeof(int16_t);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Unsupported bits per sample: %d", this->bits_per_sample_);
|
||||
return 0;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Unsupported bits per sample: %d", this->bits_per_sample_);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,13 +30,6 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
|
|||
}
|
||||
#endif
|
||||
|
||||
void set_i2s_mode(i2s_mode_t mode) { this->i2s_mode_ = mode; }
|
||||
|
||||
void set_channel(i2s_channel_fmt_t channel) { this->channel_ = channel; }
|
||||
void set_sample_rate(uint32_t sample_rate) { this->sample_rate_ = sample_rate; }
|
||||
void set_bits_per_sample(i2s_bits_per_sample_t bits_per_sample) { this->bits_per_sample_ = bits_per_sample; }
|
||||
void set_use_apll(uint32_t use_apll) { this->use_apll_ = use_apll; }
|
||||
|
||||
protected:
|
||||
void start_();
|
||||
void stop_();
|
||||
|
@ -48,11 +41,6 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
|
|||
bool adc_{false};
|
||||
#endif
|
||||
bool pdm_{false};
|
||||
i2s_mode_t i2s_mode_{};
|
||||
i2s_channel_fmt_t channel_;
|
||||
uint32_t sample_rate_;
|
||||
i2s_bits_per_sample_t bits_per_sample_;
|
||||
bool use_apll_;
|
||||
|
||||
HighFrequencyLoopRequester high_freq_;
|
||||
};
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.const import CONF_ID, CONF_MODE
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import esp32, speaker
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CHANNEL, CONF_ID, CONF_MODE, CONF_TIMEOUT
|
||||
|
||||
from .. import (
|
||||
CONF_I2S_AUDIO_ID,
|
||||
CONF_I2S_DOUT_PIN,
|
||||
I2SAudioComponent,
|
||||
CONF_LEFT,
|
||||
CONF_MONO,
|
||||
CONF_RIGHT,
|
||||
CONF_STEREO,
|
||||
I2SAudioOut,
|
||||
i2s_audio_component_schema,
|
||||
i2s_audio_ns,
|
||||
register_i2s_audio_component,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
|
@ -19,19 +23,16 @@ I2SAudioSpeaker = i2s_audio_ns.class_(
|
|||
"I2SAudioSpeaker", cg.Component, speaker.Speaker, I2SAudioOut
|
||||
)
|
||||
|
||||
i2s_dac_mode_t = cg.global_ns.enum("i2s_dac_mode_t")
|
||||
|
||||
CONF_MUTE_PIN = "mute_pin"
|
||||
CONF_DAC_TYPE = "dac_type"
|
||||
|
||||
i2s_dac_mode_t = cg.global_ns.enum("i2s_dac_mode_t")
|
||||
INTERNAL_DAC_OPTIONS = {
|
||||
"left": i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN,
|
||||
"right": i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN,
|
||||
"stereo": i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN,
|
||||
CONF_LEFT: i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN,
|
||||
CONF_RIGHT: i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN,
|
||||
CONF_STEREO: i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN,
|
||||
}
|
||||
|
||||
EXTERNAL_DAC_OPTIONS = ["mono", "stereo"]
|
||||
|
||||
NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2]
|
||||
|
||||
|
||||
|
@ -44,28 +45,40 @@ def validate_esp32_variant(config):
|
|||
return config
|
||||
|
||||
|
||||
BASE_SCHEMA = (
|
||||
speaker.SPEAKER_SCHEMA.extend(
|
||||
i2s_audio_component_schema(
|
||||
I2SAudioSpeaker,
|
||||
default_sample_rate=16000,
|
||||
default_channel=CONF_MONO,
|
||||
default_bits_per_sample="16bit",
|
||||
)
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.Optional(
|
||||
CONF_TIMEOUT, default="100ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.typed_schema(
|
||||
{
|
||||
"internal": speaker.SPEAKER_SCHEMA.extend(
|
||||
"internal": BASE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(I2SAudioSpeaker),
|
||||
cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent),
|
||||
cv.Required(CONF_MODE): cv.enum(INTERNAL_DAC_OPTIONS, lower=True),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
"external": speaker.SPEAKER_SCHEMA.extend(
|
||||
),
|
||||
"external": BASE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(I2SAudioSpeaker),
|
||||
cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent),
|
||||
cv.Required(
|
||||
CONF_I2S_DOUT_PIN
|
||||
): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_MODE, default="mono"): cv.one_of(
|
||||
*EXTERNAL_DAC_OPTIONS, lower=True
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
),
|
||||
},
|
||||
key=CONF_DAC_TYPE,
|
||||
),
|
||||
|
@ -76,12 +89,11 @@ CONFIG_SCHEMA = cv.All(
|
|||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await register_i2s_audio_component(var, config)
|
||||
await speaker.register_speaker(var, config)
|
||||
|
||||
await cg.register_parented(var, config[CONF_I2S_AUDIO_ID])
|
||||
|
||||
if config[CONF_DAC_TYPE] == "internal":
|
||||
cg.add(var.set_internal_dac_mode(config[CONF_MODE]))
|
||||
cg.add(var.set_internal_dac_mode(config[CONF_CHANNEL]))
|
||||
else:
|
||||
cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN]))
|
||||
cg.add(var.set_external_dac_channels(2 if config[CONF_MODE] == "stereo" else 1))
|
||||
cg.add(var.set_timeout(config[CONF_TIMEOUT]))
|
||||
|
|
|
@ -56,6 +56,21 @@ void I2SAudioSpeaker::start_() {
|
|||
this->task_created_ = true;
|
||||
}
|
||||
|
||||
template<typename a, typename b> const uint8_t *convert_data_format(const a *from, b *to, size_t &bytes, bool repeat) {
|
||||
if (sizeof(a) == sizeof(b) && !repeat) {
|
||||
return reinterpret_cast<const uint8_t *>(from);
|
||||
}
|
||||
const b *result = to;
|
||||
for (size_t i = 0; i < bytes; i += sizeof(a)) {
|
||||
b value = static_cast<b>(*from++) << (sizeof(b) - sizeof(a)) * 8;
|
||||
*to++ = value;
|
||||
if (repeat)
|
||||
*to++ = value;
|
||||
}
|
||||
bytes *= (sizeof(b) / sizeof(a)) * (repeat ? 2 : 1); // NOLINT
|
||||
return reinterpret_cast<const uint8_t *>(result);
|
||||
}
|
||||
|
||||
void I2SAudioSpeaker::player_task(void *params) {
|
||||
I2SAudioSpeaker *this_speaker = (I2SAudioSpeaker *) params;
|
||||
|
||||
|
@ -64,19 +79,19 @@ void I2SAudioSpeaker::player_task(void *params) {
|
|||
xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
|
||||
|
||||
i2s_driver_config_t config = {
|
||||
.mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_TX),
|
||||
.sample_rate = 16000,
|
||||
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
|
||||
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
|
||||
.mode = (i2s_mode_t) (this_speaker->i2s_mode_ | I2S_MODE_TX),
|
||||
.sample_rate = this_speaker->sample_rate_,
|
||||
.bits_per_sample = this_speaker->bits_per_sample_,
|
||||
.channel_format = this_speaker->channel_,
|
||||
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
|
||||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
|
||||
.dma_buf_count = 8,
|
||||
.dma_buf_len = 128,
|
||||
.use_apll = false,
|
||||
.dma_buf_len = 256,
|
||||
.use_apll = this_speaker->use_apll_,
|
||||
.tx_desc_auto_clear = true,
|
||||
.fixed_mclk = I2S_PIN_NO_CHANGE,
|
||||
.fixed_mclk = 0,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||
.bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
|
||||
.bits_per_chan = this_speaker->bits_per_channel_,
|
||||
};
|
||||
#if SOC_I2S_SUPPORTS_DAC
|
||||
if (this_speaker->internal_dac_mode_ != I2S_DAC_CHANNEL_DISABLE) {
|
||||
|
@ -114,10 +129,11 @@ void I2SAudioSpeaker::player_task(void *params) {
|
|||
event.type = TaskEventType::STARTED;
|
||||
xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
|
||||
|
||||
int16_t buffer[BUFFER_SIZE / 2];
|
||||
int32_t buffer[BUFFER_SIZE];
|
||||
|
||||
while (true) {
|
||||
if (xQueueReceive(this_speaker->buffer_queue_, &data_event, 100 / portTICK_PERIOD_MS) != pdTRUE) {
|
||||
if (xQueueReceive(this_speaker->buffer_queue_, &data_event, this_speaker->timeout_ / portTICK_PERIOD_MS) !=
|
||||
pdTRUE) {
|
||||
break; // End of audio from main thread
|
||||
}
|
||||
if (data_event.stop) {
|
||||
|
@ -125,17 +141,28 @@ void I2SAudioSpeaker::player_task(void *params) {
|
|||
xQueueReset(this_speaker->buffer_queue_); // Flush queue
|
||||
break;
|
||||
}
|
||||
size_t bytes_written;
|
||||
|
||||
memmove(buffer, data_event.data, data_event.len);
|
||||
size_t remaining = data_event.len / 2;
|
||||
size_t current = 0;
|
||||
const uint8_t *data = data_event.data;
|
||||
size_t remaining = data_event.len;
|
||||
switch (this_speaker->bits_per_sample_) {
|
||||
case I2S_BITS_PER_SAMPLE_8BIT:
|
||||
case I2S_BITS_PER_SAMPLE_16BIT: {
|
||||
data = convert_data_format(reinterpret_cast<const int16_t *>(data), reinterpret_cast<int16_t *>(buffer),
|
||||
remaining, this_speaker->channel_ == I2S_CHANNEL_FMT_ALL_LEFT);
|
||||
break;
|
||||
}
|
||||
case I2S_BITS_PER_SAMPLE_24BIT:
|
||||
case I2S_BITS_PER_SAMPLE_32BIT: {
|
||||
data = convert_data_format(reinterpret_cast<const int16_t *>(data), reinterpret_cast<int32_t *>(buffer),
|
||||
remaining, this_speaker->channel_ == I2S_CHANNEL_FMT_ALL_LEFT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (remaining > 0) {
|
||||
uint32_t sample = (buffer[current] << 16) | (buffer[current] & 0xFFFF);
|
||||
|
||||
esp_err_t err = i2s_write(this_speaker->parent_->get_port(), &sample, sizeof(sample), &bytes_written,
|
||||
(10 / portTICK_PERIOD_MS));
|
||||
while (remaining != 0) {
|
||||
size_t bytes_written;
|
||||
esp_err_t err =
|
||||
i2s_write(this_speaker->parent_->get_port(), data, remaining, &bytes_written, (32 / portTICK_PERIOD_MS));
|
||||
if (err != ESP_OK) {
|
||||
event = {.type = TaskEventType::WARNING, .err = err};
|
||||
if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
|
||||
|
@ -143,21 +170,8 @@ void I2SAudioSpeaker::player_task(void *params) {
|
|||
}
|
||||
continue;
|
||||
}
|
||||
if (bytes_written != sizeof(sample)) {
|
||||
event = {.type = TaskEventType::WARNING, .err = ESP_FAIL};
|
||||
if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
|
||||
ESP_LOGW(TAG, "Failed to send WARNING event");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
remaining--;
|
||||
current++;
|
||||
}
|
||||
|
||||
event.type = TaskEventType::PLAYING;
|
||||
event.err = current;
|
||||
if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
|
||||
ESP_LOGW(TAG, "Failed to send PLAYING event");
|
||||
data += bytes_written;
|
||||
remaining -= bytes_written;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -213,13 +227,11 @@ void I2SAudioSpeaker::watch_() {
|
|||
case TaskEventType::STARTED:
|
||||
ESP_LOGD(TAG, "Started I2S Audio Speaker");
|
||||
this->state_ = speaker::STATE_RUNNING;
|
||||
this->status_clear_warning();
|
||||
break;
|
||||
case TaskEventType::STOPPING:
|
||||
ESP_LOGD(TAG, "Stopping I2S Audio Speaker");
|
||||
break;
|
||||
case TaskEventType::PLAYING:
|
||||
this->status_clear_warning();
|
||||
break;
|
||||
case TaskEventType::STOPPED:
|
||||
this->state_ = speaker::STATE_STOPPED;
|
||||
vTaskDelete(this->player_task_handle_);
|
||||
|
|
|
@ -21,7 +21,6 @@ static const size_t BUFFER_SIZE = 1024;
|
|||
enum class TaskEventType : uint8_t {
|
||||
STARTING = 0,
|
||||
STARTED,
|
||||
PLAYING,
|
||||
STOPPING,
|
||||
STOPPED,
|
||||
WARNING = 255,
|
||||
|
@ -38,18 +37,18 @@ struct DataEvent {
|
|||
uint8_t data[BUFFER_SIZE];
|
||||
};
|
||||
|
||||
class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAudioOut {
|
||||
class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Component {
|
||||
public:
|
||||
float get_setup_priority() const override { return esphome::setup_priority::LATE; }
|
||||
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
|
||||
void set_timeout(uint32_t ms) { this->timeout_ = ms; }
|
||||
void set_dout_pin(uint8_t pin) { this->dout_pin_ = pin; }
|
||||
#if SOC_I2S_SUPPORTS_DAC
|
||||
void set_internal_dac_mode(i2s_dac_mode_t mode) { this->internal_dac_mode_ = mode; }
|
||||
#endif
|
||||
void set_external_dac_channels(uint8_t channels) { this->external_dac_channels_ = channels; }
|
||||
|
||||
void start() override;
|
||||
void stop() override;
|
||||
|
@ -70,13 +69,13 @@ class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAud
|
|||
QueueHandle_t buffer_queue_;
|
||||
QueueHandle_t event_queue_;
|
||||
|
||||
uint32_t timeout_{0};
|
||||
uint8_t dout_pin_{0};
|
||||
bool task_created_{false};
|
||||
|
||||
#if SOC_I2S_SUPPORTS_DAC
|
||||
i2s_dac_mode_t internal_dac_mode_{I2S_DAC_CHANNEL_DISABLE};
|
||||
#endif
|
||||
uint8_t external_dac_channels_;
|
||||
};
|
||||
|
||||
} // namespace i2s_audio
|
||||
|
|
|
@ -32,6 +32,12 @@ void MAX31856Sensor::dump_config() {
|
|||
LOG_PIN(" CS Pin: ", this->cs_);
|
||||
ESP_LOGCONFIG(TAG, " Mains Filter: %s",
|
||||
(filter_ == FILTER_60HZ ? "60 Hz" : (filter_ == FILTER_50HZ ? "50 Hz" : "Unknown!")));
|
||||
if (this->thermocouple_type_ < 0 || this->thermocouple_type_ > 7) {
|
||||
ESP_LOGCONFIG(TAG, " Thermocouple Type: Unknown");
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Thermocouple Type: %c", "BEJKNRST"[this->thermocouple_type_]);
|
||||
}
|
||||
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
|
@ -129,7 +135,12 @@ void MAX31856Sensor::clear_fault_() {
|
|||
}
|
||||
|
||||
void MAX31856Sensor::set_thermocouple_type_() {
|
||||
MAX31856ThermocoupleType type = MAX31856_TCTYPE_K;
|
||||
MAX31856ThermocoupleType type;
|
||||
if (this->thermocouple_type_ < 0 || this->thermocouple_type_ > 7) {
|
||||
type = MAX31856_TCTYPE_K;
|
||||
} else {
|
||||
type = this->thermocouple_type_;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, "set_thermocouple_type_: 0x%02X", type);
|
||||
uint8_t t = this->read_register_(MAX31856_CR1_REG);
|
||||
t &= 0xF0; // mask off bottom 4 bits
|
||||
|
|
|
@ -50,7 +50,6 @@ enum MAX31856Registers {
|
|||
|
||||
/**
|
||||
* Multiple types of thermocouples supported by the chip.
|
||||
* Currently only K type implemented here.
|
||||
*/
|
||||
enum MAX31856ThermocoupleType {
|
||||
MAX31856_TCTYPE_B = 0b0000, // 0x00
|
||||
|
@ -78,11 +77,15 @@ class MAX31856Sensor : public sensor::Sensor,
|
|||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void set_filter(MAX31856ConfigFilter filter) { filter_ = filter; }
|
||||
void set_filter(MAX31856ConfigFilter filter) { this->filter_ = filter; }
|
||||
void set_thermocouple_type(MAX31856ThermocoupleType thermocouple_type) {
|
||||
this->thermocouple_type_ = thermocouple_type;
|
||||
}
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
MAX31856ConfigFilter filter_;
|
||||
MAX31856ThermocoupleType thermocouple_type_;
|
||||
|
||||
uint8_t read_register_(uint8_t reg);
|
||||
uint32_t read_register24_(uint8_t reg);
|
||||
|
|
|
@ -3,6 +3,7 @@ from esphome.components import sensor, spi
|
|||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_MAINS_FILTER,
|
||||
CONF_THERMOCOUPLE_TYPE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
|
@ -18,6 +19,17 @@ FILTER = {
|
|||
50: MAX31865ConfigFilter.FILTER_50HZ,
|
||||
60: MAX31865ConfigFilter.FILTER_60HZ,
|
||||
}
|
||||
MAX31856ThermocoupleType = max31856_ns.enum("MAX31856ThermocoupleType")
|
||||
THERMOCOUPLE_TYPE = {
|
||||
"B": MAX31856ThermocoupleType.MAX31856_TCTYPE_B,
|
||||
"E": MAX31856ThermocoupleType.MAX31856_TCTYPE_E,
|
||||
"J": MAX31856ThermocoupleType.MAX31856_TCTYPE_J,
|
||||
"K": MAX31856ThermocoupleType.MAX31856_TCTYPE_K,
|
||||
"N": MAX31856ThermocoupleType.MAX31856_TCTYPE_N,
|
||||
"R": MAX31856ThermocoupleType.MAX31856_TCTYPE_R,
|
||||
"S": MAX31856ThermocoupleType.MAX31856_TCTYPE_S,
|
||||
"T": MAX31856ThermocoupleType.MAX31856_TCTYPE_T,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(
|
||||
|
@ -34,6 +46,13 @@ CONFIG_SCHEMA = (
|
|||
),
|
||||
}
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.Optional(CONF_THERMOCOUPLE_TYPE, default="K"): cv.enum(
|
||||
THERMOCOUPLE_TYPE, upper=True, space=""
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(spi.spi_device_schema())
|
||||
)
|
||||
|
@ -44,3 +63,4 @@ async def to_code(config):
|
|||
await cg.register_component(var, config)
|
||||
await spi.register_spi_device(var, config)
|
||||
cg.add(var.set_filter(config[CONF_MAINS_FILTER]))
|
||||
cg.add(var.set_thermocouple_type(config[CONF_THERMOCOUPLE_TYPE]))
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_THERMOCOUPLE_TYPE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
)
|
||||
|
||||
CONF_THERMOCOUPLE_TYPE = "thermocouple_type"
|
||||
CONF_HOT_JUNCTION = "hot_junction"
|
||||
CONF_COLD_JUNCTION = "cold_junction"
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ from .const import (
|
|||
CONF_CUSTOM_COMMAND,
|
||||
CONF_FORCE_NEW_RANGE,
|
||||
CONF_MODBUS_CONTROLLER_ID,
|
||||
CONF_MAX_CMD_RETRIES,
|
||||
CONF_ON_COMMAND_SENT,
|
||||
CONF_REGISTER_COUNT,
|
||||
CONF_REGISTER_TYPE,
|
||||
|
@ -131,6 +132,7 @@ CONFIG_SCHEMA = cv.All(
|
|||
cv.Optional(
|
||||
CONF_COMMAND_THROTTLE, default="0ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_MAX_CMD_RETRIES, default=4): cv.positive_int,
|
||||
cv.Optional(CONF_OFFLINE_SKIP_UPDATES, default=0): cv.positive_int,
|
||||
cv.Optional(
|
||||
CONF_SERVER_REGISTERS,
|
||||
|
@ -257,6 +259,7 @@ async def to_code(config):
|
|||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
cg.add(var.set_allow_duplicate_commands(config[CONF_ALLOW_DUPLICATE_COMMANDS]))
|
||||
cg.add(var.set_command_throttle(config[CONF_COMMAND_THROTTLE]))
|
||||
cg.add(var.set_max_cmd_retries(config[CONF_MAX_CMD_RETRIES]))
|
||||
cg.add(var.set_offline_skip_updates(config[CONF_OFFLINE_SKIP_UPDATES]))
|
||||
if CONF_SERVER_REGISTERS in config:
|
||||
for server_register in config[CONF_SERVER_REGISTERS]:
|
||||
|
|
|
@ -5,6 +5,7 @@ CONF_COMMAND_THROTTLE = "command_throttle"
|
|||
CONF_OFFLINE_SKIP_UPDATES = "offline_skip_updates"
|
||||
CONF_CUSTOM_COMMAND = "custom_command"
|
||||
CONF_FORCE_NEW_RANGE = "force_new_range"
|
||||
CONF_MAX_CMD_RETRIES = "max_cmd_retries"
|
||||
CONF_MODBUS_CONTROLLER_ID = "modbus_controller_id"
|
||||
CONF_MODBUS_FUNCTIONCODE = "modbus_functioncode"
|
||||
CONF_ON_COMMAND_SENT = "on_command_sent"
|
||||
|
|
|
@ -18,11 +18,11 @@ void ModbusController::setup() { this->create_register_ranges_(); }
|
|||
bool ModbusController::send_next_command_() {
|
||||
uint32_t last_send = millis() - this->last_command_timestamp_;
|
||||
|
||||
if ((last_send > this->command_throttle_) && !waiting_for_response() && !command_queue_.empty()) {
|
||||
auto &command = command_queue_.front();
|
||||
if ((last_send > this->command_throttle_) && !waiting_for_response() && !this->command_queue_.empty()) {
|
||||
auto &command = this->command_queue_.front();
|
||||
|
||||
// remove from queue if command was sent too often
|
||||
if (command->send_countdown < 1) {
|
||||
if (!command->should_retry(this->max_cmd_retries_)) {
|
||||
if (!this->module_offline_) {
|
||||
ESP_LOGW(TAG, "Modbus device=%d set offline", this->address_);
|
||||
|
||||
|
@ -34,11 +34,9 @@ bool ModbusController::send_next_command_() {
|
|||
}
|
||||
}
|
||||
this->module_offline_ = true;
|
||||
ESP_LOGD(
|
||||
TAG,
|
||||
"Modbus command to device=%d register=0x%02X countdown=%d no response received - removed from send queue",
|
||||
this->address_, command->register_address, command->send_countdown);
|
||||
command_queue_.pop_front();
|
||||
ESP_LOGD(TAG, "Modbus command to device=%d register=0x%02X no response received - removed from send queue",
|
||||
this->address_, command->register_address);
|
||||
this->command_queue_.pop_front();
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Sending next modbus command to device %d register 0x%02X count %d", this->address_,
|
||||
command->register_address, command->register_count);
|
||||
|
@ -50,11 +48,11 @@ bool ModbusController::send_next_command_() {
|
|||
|
||||
// remove from queue if no handler is defined
|
||||
if (!command->on_data_func) {
|
||||
command_queue_.pop_front();
|
||||
this->command_queue_.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
return (!command_queue_.empty());
|
||||
return (!this->command_queue_.empty());
|
||||
}
|
||||
|
||||
// Queue incoming response
|
||||
|
@ -77,7 +75,7 @@ void ModbusController::on_modbus_data(const std::vector<uint8_t> &data) {
|
|||
current_command->payload = data;
|
||||
this->incoming_queue_.push(std::move(current_command));
|
||||
ESP_LOGV(TAG, "Modbus response queued");
|
||||
command_queue_.pop_front();
|
||||
this->command_queue_.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -99,7 +97,7 @@ void ModbusController::on_modbus_error(uint8_t function_code, uint8_t exception_
|
|||
"payload size=%zu",
|
||||
function_code, current_command->register_address, current_command->register_count,
|
||||
current_command->payload.size());
|
||||
command_queue_.pop_front();
|
||||
this->command_queue_.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -178,7 +176,7 @@ void ModbusController::queue_command(const ModbusCommandItem &command) {
|
|||
if (!this->allow_duplicate_commands_) {
|
||||
// check if this command is already qeued.
|
||||
// not very effective but the queue is never really large
|
||||
for (auto &item : command_queue_) {
|
||||
for (auto &item : this->command_queue_) {
|
||||
if (item->is_equal(command)) {
|
||||
ESP_LOGW(TAG, "Duplicate modbus command found: type=0x%x address=%u count=%u",
|
||||
static_cast<uint8_t>(command.register_type), command.register_address, command.register_count);
|
||||
|
@ -189,7 +187,7 @@ void ModbusController::queue_command(const ModbusCommandItem &command) {
|
|||
}
|
||||
}
|
||||
}
|
||||
command_queue_.push_back(make_unique<ModbusCommandItem>(command));
|
||||
this->command_queue_.push_back(make_unique<ModbusCommandItem>(command));
|
||||
}
|
||||
|
||||
void ModbusController::update_range_(RegisterRange &r) {
|
||||
|
@ -224,8 +222,8 @@ void ModbusController::update_range_(RegisterRange &r) {
|
|||
// Once we get a response to the command it is removed from the queue and the next command is send
|
||||
//
|
||||
void ModbusController::update() {
|
||||
if (!command_queue_.empty()) {
|
||||
ESP_LOGV(TAG, "%zu modbus commands already in queue", command_queue_.size());
|
||||
if (!this->command_queue_.empty()) {
|
||||
ESP_LOGV(TAG, "%zu modbus commands already in queue", this->command_queue_.size());
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Updating modbus component");
|
||||
}
|
||||
|
@ -346,6 +344,8 @@ size_t ModbusController::create_register_ranges_() {
|
|||
void ModbusController::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ModbusController:");
|
||||
ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_);
|
||||
ESP_LOGCONFIG(TAG, " Max Command Retries: %d", this->max_cmd_retries_);
|
||||
ESP_LOGCONFIG(TAG, " Offline Skip Updates: %d", this->offline_skip_updates_);
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
ESP_LOGCONFIG(TAG, "sensormap");
|
||||
for (auto &it : sensorset_) {
|
||||
|
@ -560,8 +560,9 @@ bool ModbusCommandItem::send() {
|
|||
} else {
|
||||
modbusdevice->send_raw(this->payload);
|
||||
}
|
||||
ESP_LOGV(TAG, "Command sent %d 0x%X %d", uint8_t(this->function_code), this->register_address, this->register_count);
|
||||
send_countdown--;
|
||||
this->send_count_++;
|
||||
ESP_LOGV(TAG, "Command sent %d 0x%X %d send_count: %d", uint8_t(this->function_code), this->register_address,
|
||||
this->register_count, this->send_count_);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -312,7 +312,6 @@ struct RegisterRange {
|
|||
class ModbusCommandItem {
|
||||
public:
|
||||
static const size_t MAX_PAYLOAD_BYTES = 240;
|
||||
static const uint8_t MAX_SEND_REPEATS = 5;
|
||||
ModbusController *modbusdevice;
|
||||
uint16_t register_address;
|
||||
uint16_t register_count;
|
||||
|
@ -322,9 +321,9 @@ class ModbusCommandItem {
|
|||
on_data_func;
|
||||
std::vector<uint8_t> payload = {};
|
||||
bool send();
|
||||
// wrong commands (esp. custom commands) can block the send queue
|
||||
// limit the number of repeats
|
||||
uint8_t send_countdown{MAX_SEND_REPEATS};
|
||||
/// Check if the command should be retried based on the max_retries parameter
|
||||
bool should_retry(uint8_t max_retries) { return this->send_count_ <= max_retries; };
|
||||
|
||||
/// factory methods
|
||||
/** Create modbus read command
|
||||
* Function code 02-04
|
||||
|
@ -413,6 +412,11 @@ class ModbusCommandItem {
|
|||
&&handler = nullptr);
|
||||
|
||||
bool is_equal(const ModbusCommandItem &other);
|
||||
|
||||
protected:
|
||||
// wrong commands (esp. custom commands) can block the send queue, limit the number of repeats.
|
||||
/// How many times this command has been sent
|
||||
uint8_t send_count_{0};
|
||||
};
|
||||
|
||||
/** Modbus controller class.
|
||||
|
@ -464,6 +468,10 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
|
|||
bool get_module_offline() { return module_offline_; }
|
||||
/// Set callback for commands
|
||||
void add_on_command_sent_callback(std::function<void(int, int)> &&callback);
|
||||
/// called by esphome generated code to set the max_cmd_retries.
|
||||
void set_max_cmd_retries(uint8_t max_cmd_retries) { this->max_cmd_retries_ = max_cmd_retries; }
|
||||
/// get how many times a command will be (re)sent if no response is received
|
||||
uint8_t get_max_cmd_retries() { return this->max_cmd_retries_; }
|
||||
|
||||
protected:
|
||||
/// parse sensormap_ and create range of sequential addresses
|
||||
|
@ -498,6 +506,8 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
|
|||
bool module_offline_;
|
||||
/// how many updates to skip if module is offline
|
||||
uint16_t offline_skip_updates_;
|
||||
/// How many times we will retry a command if we get no response
|
||||
uint8_t max_cmd_retries_{4};
|
||||
CallbackManager<void(int, int)> command_sent_callback_{};
|
||||
};
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ CONFIG_SCHEMA = cv.Schema(
|
|||
esp8266=False,
|
||||
esp32=False,
|
||||
rp2040=False,
|
||||
bk72xx=False,
|
||||
): cv.All(
|
||||
cv.boolean,
|
||||
cv.Any(
|
||||
|
@ -25,6 +26,7 @@ CONFIG_SCHEMA = cv.Schema(
|
|||
esp32_arduino=cv.Version(0, 0, 0),
|
||||
esp8266_arduino=cv.Version(0, 0, 0),
|
||||
rp2040_arduino=cv.Version(0, 0, 0),
|
||||
bk72xx_libretiny=cv.Version(1, 7, 0),
|
||||
),
|
||||
cv.boolean_false,
|
||||
),
|
||||
|
@ -52,3 +54,5 @@ async def to_code(config):
|
|||
cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_ENABLE_IPV6")
|
||||
if CORE.is_esp8266:
|
||||
cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_LWIP2_IPV6_LOW_MEMORY")
|
||||
if CORE.is_bk72xx:
|
||||
cg.add_build_flag("-DCONFIG_IPV6")
|
||||
|
|
|
@ -45,11 +45,8 @@ void UponorSmatrixComponent::loop() {
|
|||
|
||||
// Read incoming data
|
||||
while (this->available()) {
|
||||
// The controller polls devices every 10 seconds, with around 200 ms between devices.
|
||||
// Remember timestamps so we can send our own packets when the bus is expected to be silent.
|
||||
if (now - this->last_rx_ > 500) {
|
||||
this->last_poll_start_ = now;
|
||||
}
|
||||
// The controller polls devices every 10 seconds in some units or continuously in others with around 200 ms between
|
||||
// devices. Remember timestamps so we can send our own packets when the bus is expected to be silent.
|
||||
this->last_rx_ = now;
|
||||
|
||||
uint8_t byte;
|
||||
|
@ -60,7 +57,8 @@ void UponorSmatrixComponent::loop() {
|
|||
}
|
||||
|
||||
// Send packets during bus silence
|
||||
if ((now - this->last_rx_ > 300) && (now - this->last_poll_start_ < 9500) && (now - this->last_tx_ > 200)) {
|
||||
if (this->rx_buffer_.empty() && (now - this->last_rx_ > 50) && (now - this->last_rx_ < 100) &&
|
||||
(now - this->last_tx_ > 200)) {
|
||||
#ifdef USE_TIME
|
||||
// Only build time packet when bus is silent and queue is empty to make sure we can send it right away
|
||||
if (this->send_time_requested_ && this->tx_queue_.empty() && this->do_send_time_())
|
||||
|
|
|
@ -93,7 +93,6 @@ class UponorSmatrixComponent : public uart::UARTDevice, public Component {
|
|||
std::queue<std::vector<uint8_t>> tx_queue_;
|
||||
uint32_t last_rx_;
|
||||
uint32_t last_tx_;
|
||||
uint32_t last_poll_start_;
|
||||
|
||||
#ifdef USE_TIME
|
||||
time::RealTimeClock *time_id_{nullptr};
|
||||
|
|
|
@ -85,7 +85,16 @@ bool WiFiComponent::wifi_sta_ip_config_(optional<ManualIP> manual_ip) {
|
|||
network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() {
|
||||
if (!this->has_sta())
|
||||
return {};
|
||||
return {WiFi.localIP()};
|
||||
network::IPAddresses addresses;
|
||||
addresses[0] = WiFi.localIP();
|
||||
#if USE_NETWORK_IPV6
|
||||
int i = 1;
|
||||
auto v6_addresses = WiFi.allLocalIPv6();
|
||||
for (auto address : v6_addresses) {
|
||||
addresses[i++] = network::IPAddress(address.toString().c_str());
|
||||
}
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
return addresses;
|
||||
}
|
||||
|
||||
bool WiFiComponent::wifi_apply_hostname_() {
|
||||
|
@ -321,6 +330,11 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
|||
s_sta_connecting = false;
|
||||
break;
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6: {
|
||||
// auto it = info.got_ip.ip_info;
|
||||
ESP_LOGV(TAG, "Event: Got IPv6");
|
||||
break;
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_STA_LOST_IP: {
|
||||
ESP_LOGV(TAG, "Event: Lost IP");
|
||||
break;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"""Constants used by esphome."""
|
||||
|
||||
__version__ = "2024.9.0-dev"
|
||||
__version__ = "2024.10.0-dev"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||
|
@ -852,6 +852,7 @@ CONF_TEMPERATURE_STEP = "temperature_step"
|
|||
CONF_TEXT = "text"
|
||||
CONF_TEXT_SENSORS = "text_sensors"
|
||||
CONF_THEN = "then"
|
||||
CONF_THERMOCOUPLE_TYPE = "thermocouple_type"
|
||||
CONF_THRESHOLD = "threshold"
|
||||
CONF_THROTTLE = "throttle"
|
||||
CONF_TILT = "tilt"
|
||||
|
|
|
@ -10,3 +10,4 @@ sensor:
|
|||
cs_pin: 12
|
||||
update_interval: 15s
|
||||
mains_filter: 50Hz
|
||||
thermocouple_type: N
|
||||
|
|
|
@ -10,3 +10,5 @@ sensor:
|
|||
cs_pin: 8
|
||||
update_interval: 15s
|
||||
mains_filter: 50Hz
|
||||
thermocouple_type: N
|
||||
|
||||
|
|
|
@ -10,3 +10,5 @@ sensor:
|
|||
cs_pin: 8
|
||||
update_interval: 15s
|
||||
mains_filter: 50Hz
|
||||
thermocouple_type: N
|
||||
|
||||
|
|
|
@ -10,3 +10,4 @@ sensor:
|
|||
cs_pin: 12
|
||||
update_interval: 15s
|
||||
mains_filter: 50Hz
|
||||
thermocouple_type: N
|
||||
|
|
|
@ -10,3 +10,5 @@ sensor:
|
|||
cs_pin: 15
|
||||
update_interval: 15s
|
||||
mains_filter: 50Hz
|
||||
thermocouple_type: N
|
||||
|
||||
|
|
|
@ -10,3 +10,5 @@ sensor:
|
|||
cs_pin: 6
|
||||
update_interval: 15s
|
||||
mains_filter: 50Hz
|
||||
thermocouple_type: N
|
||||
|
||||
|
|
|
@ -29,3 +29,4 @@ modbus_controller:
|
|||
value_type: S_DWORD_R
|
||||
read_lambda: |-
|
||||
return 42.3;
|
||||
max_cmd_retries: 0
|
||||
|
|
|
@ -13,3 +13,4 @@ modbus_controller:
|
|||
address: 0x2
|
||||
modbus_id: mod_bus1
|
||||
allow_duplicate_commands: true
|
||||
max_cmd_retries: 10
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
substitutions:
|
||||
network_enable_ipv6: "false"
|
||||
network_enable_ipv6: "true"
|
||||
|
||||
bk72xx:
|
||||
framework:
|
||||
version: 1.7.0
|
||||
|
||||
<<: !include common.yaml
|
||||
|
|
|
@ -21,4 +21,3 @@ speaker:
|
|||
id: speaker_id
|
||||
dac_type: external
|
||||
i2s_dout_pin: 13
|
||||
mode: mono
|
||||
|
|
|
@ -21,4 +21,3 @@ speaker:
|
|||
id: speaker_id
|
||||
dac_type: external
|
||||
i2s_dout_pin: 3
|
||||
mode: mono
|
||||
|
|
|
@ -21,4 +21,3 @@ speaker:
|
|||
id: speaker_id
|
||||
dac_type: external
|
||||
i2s_dout_pin: 3
|
||||
mode: mono
|
||||
|
|
|
@ -21,4 +21,3 @@ speaker:
|
|||
id: speaker_id
|
||||
dac_type: external
|
||||
i2s_dout_pin: 13
|
||||
mode: mono
|
||||
|
|
|
@ -28,7 +28,6 @@ speaker:
|
|||
id: speaker_id
|
||||
dac_type: external
|
||||
i2s_dout_pin: 12
|
||||
mode: mono
|
||||
|
||||
voice_assistant:
|
||||
microphone: mic_id_external
|
||||
|
|
|
@ -28,7 +28,6 @@ speaker:
|
|||
id: speaker_id
|
||||
dac_type: external
|
||||
i2s_dout_pin: 2
|
||||
mode: mono
|
||||
|
||||
voice_assistant:
|
||||
microphone: mic_id_external
|
||||
|
|
|
@ -28,7 +28,6 @@ speaker:
|
|||
id: speaker_id
|
||||
dac_type: external
|
||||
i2s_dout_pin: 2
|
||||
mode: mono
|
||||
|
||||
voice_assistant:
|
||||
microphone: mic_id_external
|
||||
|
|
|
@ -28,7 +28,6 @@ speaker:
|
|||
id: speaker_id
|
||||
dac_type: external
|
||||
i2s_dout_pin: 12
|
||||
mode: mono
|
||||
|
||||
voice_assistant:
|
||||
microphone: mic_id_external
|
||||
|
|
Loading…
Reference in a new issue