1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-02 16:11:53 +00:00

Compare commits

..

43 Commits

Author SHA1 Message Date
Jesse Hills
e8f3eefd49 Discard changes to .github/workflows/ci.yml 2024-09-11 07:59:47 +12:00
Jesse Hills
679f8140c6 Merge branch 'dev' into jesserockz-2023-284 2024-09-11 07:59:26 +12:00
Jesse Hills
5f1a08c922 Fix key 2024-06-26 13:31:38 +12:00
Jesse Hills
9567b45b30 Add more components 2024-06-26 13:29:59 +12:00
Jesse Hills
6d8afce3cd Disable all other CI flows 2024-06-26 13:20:56 +12:00
Jesse Hills
e0485894ca Try without changing component folder name 2024-06-26 13:20:46 +12:00
Keith Burzinski
d5c008c027 Add audio_sal 2024-06-24 23:06:39 -05:00
Keith Burzinski
f72151ac9b Be more specific in idf_component.yml 2024-06-24 22:35:46 -05:00
Keith Burzinski
aa72db9db6 Be more specific in idf_component.yml 2024-06-24 21:22:27 -05:00
Keith Burzinski
3773aa6f41 Add esp-adf to idf_component.yml 2024-06-24 20:38:01 -05:00
Keith Burzinski
abb812b9e1 Merge branch 'dev' into jesserockz-2023-284 2024-06-24 20:34:42 -05:00
Jesse Hills
85a3789fb9 Fix i2s channel 2024-06-05 13:41:18 +12:00
Jesse Hills
b6f69cef56 Add min version requirement 2024-01-17 11:03:48 +09:00
Jesse Hills
219d704ce4 Use unique_ptr 2024-01-17 11:02:44 +09:00
Jesse Hills
14bc9b64bd Allocate speaker buffer in psram 2024-01-16 14:16:17 +09:00
Jesse Hills
dd7f56e502 Use RingBuffer class 2024-01-16 14:14:30 +09:00
Jesse Hills
c018ea0848 Fail if memory not allocated 2024-01-15 13:31:51 +09:00
Jesse Hills
317ee53188 Fix microphone read empty check 2024-01-11 09:51:06 +09:00
Jesse Hills
6e09f4b4d7 Send actual buffered byte count 2024-01-11 09:50:34 +09:00
Jesse Hills
709a92649c Remove unused algorithm stream code 2023-12-07 13:03:55 +09:00
Jesse Hills
78a8271797 Update esphome/components/esp_adf/__init__.py
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2023-11-29 12:00:01 +13:00
Jesse Hills
970680b1b2 Add has_buffered_data() function to ESPADFSpeaker class 2023-11-24 21:19:36 +13:00
Jesse Hills
f500bd5e6f Extend timeout to 500ms 2023-11-24 21:19:36 +13:00
Jesse Hills
e2bb81e233 Update esphome/components/esp_adf/microphone/esp_adf_microphone.cpp
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2023-11-24 12:01:28 +13:00
Jesse Hills
26a1d14ee0 Add s3-box-3 board 2023-11-16 15:04:49 +13:00
Jesse Hills
97f07f8d13 Merge branch 'dev' into jesserockz-2023-284 2023-11-16 13:10:34 +13:00
Jesse Hills
ff9bffc363 Dont include when not using a supported board 2023-10-11 17:33:16 +13:00
Jesse Hills
89b3af8be4 Merge remote-tracking branch 'origin/dev' into jesserockz-2023-284 2023-10-11 16:32:26 +13:00
Jesse Hills
c9b2e54c1a Fix esp-adf init 2023-10-10 11:43:17 +13:00
Jesse Hills
6dd92053b5 Fix adf version 2023-10-05 12:18:46 +13:00
Jesse Hills
33346c0b6a Add back esp-dsp component.
Without this, esp-idf will auto download a version and may be incompatible
2023-10-04 13:37:21 +13:00
Jesse Hills
161fbecfe1 Allow esp-adf libraries to be used by any board 2023-09-29 09:57:05 +13:00
Jesse Hills
fce2eafda0 Allow manual overriding of esp-adf board 2023-09-13 11:49:27 +12:00
Jesse Hills
c19f0cf6bc Remove unused esp-dsp component 2023-09-11 16:07:19 +12:00
Jesse Hills
b05e7bfe0a Update flags 2023-09-11 16:07:09 +12:00
Jesse Hills
3e58ee2130 Allow patching esp-idf with esp-adf files 2023-09-01 10:13:00 +12:00
Jesse Hills
bab9c7c70e Remove afe and try to use algorithm stream 2023-08-31 17:38:49 +12:00
Jesse Hills
0b60a1d9eb Move microphone to use rtos task to read
Use afe_fr for noise suppression (not working)
2023-08-29 11:19:09 +12:00
Jesse Hills
f7455ad76a Fix ifdef 2023-08-24 17:18:52 +12:00
Jesse Hills
3190e86ba8 Dont warn when buffer is just empty 2023-08-24 17:02:50 +12:00
Jesse Hills
a34569d314 Merge branch 'dev' into jesserockz-2023-284 2023-08-23 14:31:19 +12:00
Jesse Hills
6c1c200cf9 Codeowners 2023-08-23 14:31:04 +12:00
Jesse Hills
3635179564 ESP-ADF microphone and speaker board support for s3-box 2023-08-11 07:53:34 +12:00
56 changed files with 1246 additions and 380 deletions

View File

@@ -136,6 +136,7 @@ esphome/components/esp32_improv/* @jesserockz
esphome/components/esp32_rmt/* @jesserockz
esphome/components/esp32_rmt_led_strip/* @jesserockz
esphome/components/esp8266/* @esphome/core
esphome/components/esp_adf/* @jesserockz
esphome/components/ethernet_info/* @gtjadsonsantos
esphome/components/event/* @nohat
esphome/components/exposure_notifications/* @OttoWinter

View File

@@ -140,8 +140,6 @@ 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"
@@ -215,7 +213,6 @@ 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(
@@ -288,7 +285,6 @@ 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")

View File

@@ -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,8 +212,6 @@ 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;
}
@@ -335,12 +333,6 @@ 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) {

View File

@@ -145,9 +145,6 @@ 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;

View File

@@ -0,0 +1,103 @@
import os
import esphome.config_validation as cv
import esphome.codegen as cg
import esphome.final_validate as fv
from esphome.components import esp32
from esphome.const import CONF_ID, CONF_BOARD
CODEOWNERS = ["@jesserockz"]
DEPENDENCIES = ["esp32"]
CONF_ESP_ADF_ID = "esp_adf_id"
CONF_ESP_ADF = "esp_adf"
esp_adf_ns = cg.esphome_ns.namespace("esp_adf")
ESPADF = esp_adf_ns.class_("ESPADF", cg.Component)
ESPADFPipeline = esp_adf_ns.class_("ESPADFPipeline", cg.Parented.template(ESPADF))
SUPPORTED_BOARDS = {
"esp32s3box": "CONFIG_ESP32_S3_BOX_BOARD",
"esp32s3boxlite": "CONFIG_ESP32_S3_BOX_LITE_BOARD",
"esp32s3box3": "CONFIG_ESP32_S3_BOX_3_BOARD",
"esp32s3korvo1": "CONFIG_ESP32_S3_KORVO1_BOARD",
}
def _default_board(config):
config = config.copy()
if board := config.get(CONF_BOARD) is None:
board = esp32.get_board()
if board in SUPPORTED_BOARDS:
config[CONF_BOARD] = board
return config
def final_validate_usable_board(platform: str):
def _validate(adf_config):
board = adf_config.get(CONF_BOARD)
if board not in SUPPORTED_BOARDS:
raise cv.Invalid(f"Board {board} is not supported by esp-adf {platform}")
return adf_config
return cv.Schema(
{cv.Required(CONF_ESP_ADF_ID): fv.id_declaration_match_schema(_validate)},
extra=cv.ALLOW_EXTRA,
)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(ESPADF),
cv.Optional(CONF_BOARD): cv.string_strict,
}
),
_default_board,
cv.only_with_esp_idf,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
cg.add_define("USE_ESP_ADF")
cg.add_platformio_option("build_unflags", "-Wl,--end-group")
esp32.add_idf_component(
name="esp-adf",
repo="https://github.com/espressif/esp-adf",
path="components",
ref="v2.5",
components=["*"],
submodules=["components/esp-sr", "components/esp-adf-libs"],
)
esp32.add_idf_component(
name="esp-dsp",
repo="https://github.com/espressif/esp-dsp",
ref="v1.2.0",
)
cg.add_platformio_option(
"board_build.embed_txtfiles", "components/dueros_service/duer_profile"
)
if board := config.get(CONF_BOARD):
cg.add_define("USE_ESP_ADF_BOARD")
esp32.add_idf_sdkconfig_option(SUPPORTED_BOARDS[board], True)
esp32.add_extra_script(
"pre",
"apply_adf_patches.py",
os.path.join(os.path.dirname(__file__), "apply_adf_patches.py.script"),
)
esp32.add_extra_build_file(
"esp_adf_patches/idf_v4.4_freertos.patch",
"https://github.com/espressif/esp-adf/raw/v2.5/idf_patches/idf_v4.4_freertos.patch",
)

View File

@@ -0,0 +1,23 @@
from os.path import join, isfile
Import("env")
FRAMEWORK_DIR = env.PioPlatform().get_package_dir("framework-espidf")
patchflag_path = join(FRAMEWORK_DIR, ".adf-patching-done")
PROJECT_DIR = env.get('PROJECT_DIR')
PATCH_FILE = join(PROJECT_DIR, "esp_adf_patches", "idf_v4.4_freertos.patch")
# patch file only if we didn't do it before
if not isfile(patchflag_path):
print(PATCH_FILE)
assert isfile(PATCH_FILE)
env.Execute("patch -p1 -d %s -i %s" % (FRAMEWORK_DIR, PATCH_FILE))
def _touch(path):
with open(path, "w") as fp:
fp.write("")
env.Execute(lambda *args, **kwargs: _touch(patchflag_path))

View File

@@ -0,0 +1,30 @@
#include "esp_adf.h"
#include "esphome/core/defines.h"
#ifdef USE_ESP_IDF
#ifdef USE_ESP_ADF_BOARD
#include <board.h>
#endif
#include "esphome/core/log.h"
namespace esphome {
namespace esp_adf {
static const char *const TAG = "esp_adf";
void ESPADF::setup() {
#ifdef USE_ESP_ADF_BOARD
ESP_LOGI(TAG, "Start codec chip");
audio_board_handle_t board_handle = audio_board_init();
audio_hal_ctrl_codec(board_handle->audio_hal, AUDIO_HAL_CODEC_MODE_BOTH, AUDIO_HAL_CTRL_START);
#endif
}
float ESPADF::get_setup_priority() const { return setup_priority::HARDWARE; }
} // namespace esp_adf
} // namespace esphome
#endif // USE_ESP_IDF

View File

@@ -0,0 +1,58 @@
#pragma once
#ifdef USE_ESP_IDF
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace esp_adf {
static const size_t BUFFER_SIZE = 1024;
enum class TaskEventType : uint8_t {
STARTING = 0,
STARTED,
RUNNING,
STOPPING,
STOPPED,
WARNING = 255,
};
struct TaskEvent {
TaskEventType type;
esp_err_t err;
};
struct CommandEvent {
bool stop;
};
struct DataEvent {
bool stop;
size_t len;
uint8_t data[BUFFER_SIZE];
};
class ESPADF;
class ESPADFPipeline : public Parented<ESPADF> {};
class ESPADF : public Component {
public:
void setup() override;
float get_setup_priority() const override;
void lock() { this->lock_.lock(); }
bool try_lock() { return this->lock_.try_lock(); }
void unlock() { this->lock_.unlock(); }
protected:
Mutex lock_;
};
} // namespace esp_adf
} // namespace esphome
#endif // USE_ESP_IDF

View File

@@ -0,0 +1,42 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import microphone
from esphome.const import CONF_ID
from .. import (
CONF_ESP_ADF_ID,
ESPADF,
ESPADFPipeline,
esp_adf_ns,
final_validate_usable_board,
)
AUTO_LOAD = ["esp_adf"]
CONFLICTS_WITH = ["i2s_audio"]
DEPENDENCIES = ["esp32"]
ESPADFMicrophone = esp_adf_ns.class_(
"ESPADFMicrophone", ESPADFPipeline, microphone.Microphone, cg.Component
)
CONFIG_SCHEMA = cv.All(
microphone.MICROPHONE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(ESPADFMicrophone),
cv.GenerateID(CONF_ESP_ADF_ID): cv.use_id(ESPADF),
}
).extend(cv.COMPONENT_SCHEMA),
cv.only_with_esp_idf,
cv.require_esphome_version(2023, 12, 7),
)
FINAL_VALIDATE_SCHEMA = final_validate_usable_board("microphone")
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_ESP_ADF_ID])
await microphone.register_microphone(var, config)

View File

@@ -0,0 +1,315 @@
#include "esp_adf_microphone.h"
#ifdef USE_ESP_IDF
#include <driver/i2s.h>
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include <algorithm_stream.h>
#include <audio_element.h>
#include <audio_hal.h>
#include <audio_pipeline.h>
#include <filter_resample.h>
#include <i2s_stream.h>
#include <raw_stream.h>
#include <recorder_sr.h>
#include <board.h>
namespace esphome {
namespace esp_adf {
static const char *const TAG = "esp_adf.microphone";
void ESPADFMicrophone::setup() {
ESP_LOGCONFIG(TAG, "Setting up ESP ADF Microphone...");
this->ring_buffer_ = RingBuffer::create(8000 * sizeof(int16_t));
if (this->ring_buffer_ == nullptr) {
ESP_LOGE(TAG, "Could not allocate ring buffer");
this->mark_failed();
return;
}
this->read_event_queue_ = xQueueCreate(20, sizeof(TaskEvent));
if (this->read_event_queue_ == nullptr) {
ESP_LOGW(TAG, "Could not allocate event queue");
this->mark_failed();
return;
}
this->read_command_queue_ = xQueueCreate(20, sizeof(CommandEvent));
if (this->read_command_queue_ == nullptr) {
ESP_LOGW(TAG, "Could not allocate command queue");
this->mark_failed();
return;
}
ESP_LOGCONFIG(TAG, "Successfully set up ESP ADF Microphone");
}
void ESPADFMicrophone::start() {
if (this->is_failed())
return;
if (this->state_ == microphone::STATE_STOPPING) {
ESP_LOGW(TAG, "Microphone is stopping, cannot start.");
return;
}
this->state_ = microphone::STATE_STARTING;
}
void ESPADFMicrophone::start_() {
if (!this->parent_->try_lock()) {
return;
}
xTaskCreate(ESPADFMicrophone::read_task, "read_task", 8192, (void *) this, 0, &this->read_task_handle_);
}
void ESPADFMicrophone::read_task(void *params) {
ESPADFMicrophone *this_mic = (ESPADFMicrophone *) params;
TaskEvent event;
ExternalRAMAllocator<int16_t> allocator(ExternalRAMAllocator<int16_t>::ALLOW_FAILURE);
int16_t *buffer = allocator.allocate(BUFFER_SIZE / sizeof(int16_t));
if (buffer == nullptr) {
event.type = TaskEventType::WARNING;
event.err = ESP_ERR_NO_MEM;
xQueueSend(this_mic->read_event_queue_, &event, portMAX_DELAY);
event.type = TaskEventType::STOPPED;
event.err = ESP_OK;
xQueueSend(this_mic->read_event_queue_, &event, portMAX_DELAY);
while (true) {
delay(10);
}
return;
}
event.type = TaskEventType::STARTING;
xQueueSend(this_mic->read_event_queue_, &event, portMAX_DELAY);
audio_pipeline_cfg_t pipeline_cfg = {
.rb_size = 8 * 1024,
};
audio_pipeline_handle_t pipeline = audio_pipeline_init(&pipeline_cfg);
i2s_driver_config_t i2s_config = {
.mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_RX),
.sample_rate = 16000,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL2 | ESP_INTR_FLAG_IRAM,
.dma_buf_count = 8,
.dma_buf_len = 128,
.use_apll = false,
.tx_desc_auto_clear = true,
.fixed_mclk = 0,
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
.bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
};
i2s_stream_cfg_t i2s_cfg = {
.type = AUDIO_STREAM_READER,
.i2s_config = i2s_config,
.i2s_port = static_cast<i2s_port_t>(CODEC_ADC_I2S_PORT),
.use_alc = false,
.volume = 0,
.out_rb_size = I2S_STREAM_RINGBUFFER_SIZE,
.task_stack = I2S_STREAM_TASK_STACK,
.task_core = I2S_STREAM_TASK_CORE,
.task_prio = I2S_STREAM_TASK_PRIO,
.stack_in_ext = false,
.multi_out_num = 0,
.uninstall_drv = true,
.need_expand = false,
.expand_src_bits = I2S_BITS_PER_SAMPLE_16BIT,
};
audio_element_handle_t i2s_stream_reader = i2s_stream_init(&i2s_cfg);
rsp_filter_cfg_t rsp_cfg = {
.src_rate = 16000,
.src_ch = 2,
.dest_rate = 16000,
.dest_bits = 16,
.dest_ch = 1,
.src_bits = I2S_BITS_PER_SAMPLE_16BIT,
.mode = RESAMPLE_DECODE_MODE,
.max_indata_bytes = RSP_FILTER_BUFFER_BYTE,
.out_len_bytes = RSP_FILTER_BUFFER_BYTE,
.type = ESP_RESAMPLE_TYPE_AUTO,
.complexity = 2,
.down_ch_idx = 0,
.prefer_flag = ESP_RSP_PREFER_TYPE_SPEED,
.out_rb_size = RSP_FILTER_RINGBUFFER_SIZE,
.task_stack = RSP_FILTER_TASK_STACK,
.task_core = RSP_FILTER_TASK_CORE,
.task_prio = RSP_FILTER_TASK_PRIO,
.stack_in_ext = true,
};
audio_element_handle_t filter = rsp_filter_init(&rsp_cfg);
raw_stream_cfg_t raw_cfg = {
.type = AUDIO_STREAM_READER,
.out_rb_size = 8 * 1024,
};
audio_element_handle_t raw_read = raw_stream_init(&raw_cfg);
audio_pipeline_register(pipeline, i2s_stream_reader, "i2s");
audio_pipeline_register(pipeline, filter, "filter");
audio_pipeline_register(pipeline, raw_read, "raw");
const char *link_tag[3] = {"i2s", "filter", "raw"};
audio_pipeline_link(pipeline, &link_tag[0], 3);
audio_pipeline_run(pipeline);
event.type = TaskEventType::STARTED;
xQueueSend(this_mic->read_event_queue_, &event, portMAX_DELAY);
CommandEvent command_event;
while (true) {
if (xQueueReceive(this_mic->read_command_queue_, &command_event, 0) == pdTRUE) {
if (command_event.stop) {
// Stop signal from main thread
break;
}
}
int bytes_read = raw_stream_read(raw_read, (char *) buffer, BUFFER_SIZE);
if (bytes_read == -2 || bytes_read == 0) {
// No data in buffers to read.
continue;
} else if (bytes_read < 0) {
event.type = TaskEventType::WARNING;
event.err = bytes_read;
xQueueSend(this_mic->read_event_queue_, &event, 0);
continue;
}
size_t written = this_mic->ring_buffer_->write((void *) buffer, bytes_read);
event.type = TaskEventType::RUNNING;
event.err = written;
xQueueSend(this_mic->read_event_queue_, &event, 0);
}
allocator.deallocate(buffer, BUFFER_SIZE / sizeof(int16_t));
audio_pipeline_stop(pipeline);
audio_pipeline_wait_for_stop(pipeline);
audio_pipeline_terminate(pipeline);
event.type = TaskEventType::STOPPING;
xQueueSend(this_mic->read_event_queue_, &event, portMAX_DELAY);
audio_pipeline_unregister(pipeline, i2s_stream_reader);
audio_pipeline_unregister(pipeline, filter);
// audio_pipeline_unregister(pipeline, algo_stream);
audio_pipeline_unregister(pipeline, raw_read);
audio_pipeline_deinit(pipeline);
audio_element_deinit(i2s_stream_reader);
audio_element_deinit(filter);
// audio_element_deinit(algo_stream);
audio_element_deinit(raw_read);
event.type = TaskEventType::STOPPED;
xQueueSend(this_mic->read_event_queue_, &event, portMAX_DELAY);
while (true) {
delay(10);
}
}
void ESPADFMicrophone::stop() {
if (this->state_ == microphone::STATE_STOPPED || this->state_ == microphone::STATE_STOPPING || this->is_failed())
return;
this->state_ = microphone::STATE_STOPPING;
CommandEvent command_event;
command_event.stop = true;
xQueueSendToFront(this->read_command_queue_, &command_event, portMAX_DELAY);
ESP_LOGD(TAG, "Stopping microphone");
}
size_t ESPADFMicrophone::read(int16_t *buf, size_t len) {
if (this->is_failed()) {
ESP_LOGE(TAG, "Microphone is failed, cannot read");
return 0;
}
if (this->ring_buffer_->available() == 0) {
return 0; // No data
}
size_t bytes_read = this->ring_buffer_->read((void *) buf, len);
if (bytes_read == 0) {
// No data in buffers to read.
this->status_set_warning();
return 0;
}
this->status_clear_warning();
return bytes_read;
}
void ESPADFMicrophone::read_() {
std::vector<int16_t> samples;
samples.resize(BUFFER_SIZE);
this->read(samples.data(), samples.size());
this->data_callbacks_.call(samples);
}
void ESPADFMicrophone::watch_() {
TaskEvent event;
if (xQueueReceive(this->read_event_queue_, &event, 0) == pdTRUE) {
switch (event.type) {
case TaskEventType::STARTING:
case TaskEventType::STOPPING:
break;
case TaskEventType::STARTED:
ESP_LOGD(TAG, "Microphone started");
this->state_ = microphone::STATE_RUNNING;
break;
case TaskEventType::RUNNING:
this->status_clear_warning();
// ESP_LOGD(TAG, "Putting %d bytes into ring buffer", event.err);
break;
case TaskEventType::STOPPED:
this->parent_->unlock();
this->state_ = microphone::STATE_STOPPED;
vTaskDelete(this->read_task_handle_);
this->read_task_handle_ = nullptr;
ESP_LOGD(TAG, "Microphone stopped");
break;
case TaskEventType::WARNING:
ESP_LOGW(TAG, "Error writing to pipeline: %s", esp_err_to_name(event.err));
this->status_set_warning();
break;
}
}
}
void ESPADFMicrophone::loop() {
this->watch_();
switch (this->state_) {
case microphone::STATE_STOPPED:
case microphone::STATE_STOPPING:
break;
case microphone::STATE_STARTING:
this->start_();
break;
case microphone::STATE_RUNNING:
if (this->data_callbacks_.size() > 0) {
this->read_();
}
break;
}
}
} // namespace esp_adf
} // namespace esphome
#endif // USE_ESP_IDF

View File

@@ -0,0 +1,42 @@
#pragma once
#ifdef USE_ESP_IDF
#include "../esp_adf.h"
#include "esphome/core/component.h"
#include "esphome/core/ring_buffer.h"
#include "esphome/components/microphone/microphone.h"
namespace esphome {
namespace esp_adf {
class ESPADFMicrophone : public ESPADFPipeline, public microphone::Microphone, public Component {
public:
void setup() override;
void start() override;
void stop() override;
void loop() override;
size_t read(int16_t *buf, size_t len) override;
protected:
void start_();
void read_();
void watch_();
static void read_task(void *params);
std::unique_ptr<RingBuffer> ring_buffer_;
TaskHandle_t read_task_handle_{nullptr};
QueueHandle_t read_event_queue_;
QueueHandle_t read_command_queue_;
};
} // namespace esp_adf
} // namespace esphome
#endif // USE_ESP_IDF

View File

@@ -0,0 +1,41 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import speaker
from esphome.const import CONF_ID
from .. import (
CONF_ESP_ADF_ID,
ESPADF,
ESPADFPipeline,
esp_adf_ns,
final_validate_usable_board,
)
AUTO_LOAD = ["esp_adf"]
CONFLICTS_WITH = ["i2s_audio"]
DEPENDENCIES = ["esp32"]
ESPADFSpeaker = esp_adf_ns.class_(
"ESPADFSpeaker", ESPADFPipeline, speaker.Speaker, cg.Component
)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(ESPADFSpeaker),
cv.GenerateID(CONF_ESP_ADF_ID): cv.use_id(ESPADF),
}
).extend(cv.COMPONENT_SCHEMA),
cv.only_with_esp_idf,
)
FINAL_VALIDATE_SCHEMA = final_validate_usable_board("speaker")
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_ESP_ADF_ID])
await speaker.register_speaker(var, config)

View File

@@ -0,0 +1,295 @@
#include "esp_adf_speaker.h"
#ifdef USE_ESP_IDF
#include <driver/i2s.h>
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include <audio_hal.h>
#include <filter_resample.h>
#include <i2s_stream.h>
#include <raw_stream.h>
namespace esphome {
namespace esp_adf {
static const size_t BUFFER_COUNT = 50;
static const char *const TAG = "esp_adf.speaker";
void ESPADFSpeaker::setup() {
ESP_LOGCONFIG(TAG, "Setting up ESP ADF Speaker...");
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
this->buffer_queue_.storage = allocator.allocate(sizeof(StaticQueue_t) + (BUFFER_COUNT * sizeof(DataEvent)));
if (this->buffer_queue_.storage == nullptr) {
ESP_LOGE(TAG, "Failed to allocate buffer queue!");
this->mark_failed();
return;
}
this->buffer_queue_.handle =
xQueueCreateStatic(BUFFER_COUNT, sizeof(DataEvent), this->buffer_queue_.storage + sizeof(StaticQueue_t),
(StaticQueue_t *) (this->buffer_queue_.storage));
this->event_queue_ = xQueueCreate(20, sizeof(TaskEvent));
if (this->event_queue_ == nullptr) {
ESP_LOGW(TAG, "Could not allocate event queue.");
this->mark_failed();
return;
}
}
void ESPADFSpeaker::start() { this->state_ = speaker::STATE_STARTING; }
void ESPADFSpeaker::start_() {
if (!this->parent_->try_lock()) {
return; // Waiting for another i2s component to return lock
}
xTaskCreate(ESPADFSpeaker::player_task, "speaker_task", 8192, (void *) this, 0, &this->player_task_handle_);
}
void ESPADFSpeaker::player_task(void *params) {
ESPADFSpeaker *this_speaker = (ESPADFSpeaker *) params;
TaskEvent event;
event.type = TaskEventType::STARTING;
xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
i2s_driver_config_t i2s_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_ONLY_LEFT,
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL2 | ESP_INTR_FLAG_IRAM,
.dma_buf_count = 8,
.dma_buf_len = 1024,
.use_apll = false,
.tx_desc_auto_clear = true,
.fixed_mclk = 0,
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
.bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
};
audio_pipeline_cfg_t pipeline_cfg = {
.rb_size = 8 * 1024,
};
audio_pipeline_handle_t pipeline = audio_pipeline_init(&pipeline_cfg);
i2s_stream_cfg_t i2s_cfg = {
.type = AUDIO_STREAM_WRITER,
.i2s_config = i2s_config,
.i2s_port = I2S_NUM_0,
.use_alc = false,
.volume = 0,
.out_rb_size = I2S_STREAM_RINGBUFFER_SIZE,
.task_stack = I2S_STREAM_TASK_STACK,
.task_core = I2S_STREAM_TASK_CORE,
.task_prio = I2S_STREAM_TASK_PRIO,
.stack_in_ext = false,
.multi_out_num = 0,
.uninstall_drv = true,
.need_expand = false,
.expand_src_bits = I2S_BITS_PER_SAMPLE_16BIT,
};
audio_element_handle_t i2s_stream_writer = i2s_stream_init(&i2s_cfg);
rsp_filter_cfg_t rsp_cfg = {
.src_rate = 16000,
.src_ch = 1,
.dest_rate = 16000,
.dest_bits = 16,
.dest_ch = 2,
.src_bits = 16,
.mode = RESAMPLE_DECODE_MODE,
.max_indata_bytes = RSP_FILTER_BUFFER_BYTE,
.out_len_bytes = RSP_FILTER_BUFFER_BYTE,
.type = ESP_RESAMPLE_TYPE_AUTO,
.complexity = 2,
.down_ch_idx = 0,
.prefer_flag = ESP_RSP_PREFER_TYPE_SPEED,
.out_rb_size = RSP_FILTER_RINGBUFFER_SIZE,
.task_stack = RSP_FILTER_TASK_STACK,
.task_core = RSP_FILTER_TASK_CORE,
.task_prio = RSP_FILTER_TASK_PRIO,
.stack_in_ext = true,
};
audio_element_handle_t filter = rsp_filter_init(&rsp_cfg);
raw_stream_cfg_t raw_cfg = {
.type = AUDIO_STREAM_WRITER,
.out_rb_size = 8 * 1024,
};
audio_element_handle_t raw_write = raw_stream_init(&raw_cfg);
audio_pipeline_register(pipeline, raw_write, "raw");
audio_pipeline_register(pipeline, filter, "filter");
audio_pipeline_register(pipeline, i2s_stream_writer, "i2s");
const char *link_tag[3] = {
"raw",
// "filter",
"i2s",
};
audio_pipeline_link(pipeline, &link_tag[0], 2);
audio_pipeline_run(pipeline);
DataEvent data_event;
event.type = TaskEventType::STARTED;
xQueueSend(this_speaker->event_queue_, &event, 0);
uint32_t last_received = millis();
while (true) {
if (xQueueReceive(this_speaker->buffer_queue_.handle, &data_event, 0) != pdTRUE) {
if (millis() - last_received > 500) {
// No audio for 500ms, stop
break;
} else {
continue;
}
}
if (data_event.stop) {
// Stop signal from main thread
while (xQueueReceive(this_speaker->buffer_queue_.handle, &data_event, 0) == pdTRUE) {
// Flush queue
}
break;
}
size_t remaining = data_event.len;
size_t current = 0;
if (remaining > 0)
last_received = millis();
while (remaining > 0) {
int bytes_written = raw_stream_write(raw_write, (char *) data_event.data + current, remaining);
if (bytes_written == ESP_FAIL) {
event = {.type = TaskEventType::WARNING, .err = ESP_FAIL};
xQueueSend(this_speaker->event_queue_, &event, 0);
continue;
}
remaining -= bytes_written;
current += bytes_written;
}
event.type = TaskEventType::RUNNING;
xQueueSend(this_speaker->event_queue_, &event, 0);
}
audio_pipeline_stop(pipeline);
audio_pipeline_wait_for_stop(pipeline);
audio_pipeline_terminate(pipeline);
event.type = TaskEventType::STOPPING;
xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
audio_pipeline_unregister(pipeline, i2s_stream_writer);
audio_pipeline_unregister(pipeline, filter);
audio_pipeline_unregister(pipeline, raw_write);
audio_pipeline_deinit(pipeline);
audio_element_deinit(i2s_stream_writer);
audio_element_deinit(filter);
audio_element_deinit(raw_write);
event.type = TaskEventType::STOPPED;
xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
while (true) {
delay(10);
}
}
void ESPADFSpeaker::stop() {
if (this->state_ == speaker::STATE_STOPPED)
return;
if (this->state_ == speaker::STATE_STARTING) {
this->state_ = speaker::STATE_STOPPED;
return;
}
this->state_ = speaker::STATE_STOPPING;
DataEvent data;
data.stop = true;
xQueueSendToFront(this->buffer_queue_.handle, &data, portMAX_DELAY);
}
void ESPADFSpeaker::watch_() {
TaskEvent event;
if (xQueueReceive(this->event_queue_, &event, 0) == pdTRUE) {
switch (event.type) {
case TaskEventType::STARTING:
case TaskEventType::STOPPING:
break;
case TaskEventType::STARTED:
this->state_ = speaker::STATE_RUNNING;
break;
case TaskEventType::RUNNING:
this->status_clear_warning();
break;
case TaskEventType::STOPPED:
this->parent_->unlock();
this->state_ = speaker::STATE_STOPPED;
vTaskDelete(this->player_task_handle_);
this->player_task_handle_ = nullptr;
break;
case TaskEventType::WARNING:
ESP_LOGW(TAG, "Error writing to pipeline: %s", esp_err_to_name(event.err));
this->status_set_warning();
break;
}
}
}
void ESPADFSpeaker::loop() {
this->watch_();
switch (this->state_) {
case speaker::STATE_STARTING:
this->start_();
break;
case speaker::STATE_RUNNING:
case speaker::STATE_STOPPING:
case speaker::STATE_STOPPED:
break;
}
}
size_t ESPADFSpeaker::play(const uint8_t *data, size_t length) {
if (this->is_failed()) {
ESP_LOGE(TAG, "Failed to play audio, speaker is in failed state.");
return 0;
}
if (this->state_ != speaker::STATE_RUNNING && this->state_ != speaker::STATE_STARTING) {
this->start();
}
size_t remaining = length;
size_t index = 0;
while (remaining > 0) {
DataEvent event;
event.stop = false;
size_t to_send_length = std::min(remaining, BUFFER_SIZE);
event.len = to_send_length;
memcpy(event.data, data + index, to_send_length);
if (xQueueSend(this->buffer_queue_.handle, &event, 0) != pdTRUE) {
return index; // Queue full
}
remaining -= to_send_length;
index += to_send_length;
}
return index;
}
bool ESPADFSpeaker::has_buffered_data() const { return uxQueueMessagesWaiting(this->buffer_queue_.handle) > 0; }
} // namespace esp_adf
} // namespace esphome
#endif // USE_ESP_IDF

View File

@@ -0,0 +1,51 @@
#pragma once
#ifdef USE_ESP_IDF
#include "../esp_adf.h"
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
#include "esphome/components/speaker/speaker.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include <audio_element.h>
#include <audio_pipeline.h>
namespace esphome {
namespace esp_adf {
class ESPADFSpeaker : public ESPADFPipeline, public speaker::Speaker, public Component {
public:
float get_setup_priority() const override { return esphome::setup_priority::LATE; }
void setup() override;
void loop() override;
void start() override;
void stop() override;
size_t play(const uint8_t *data, size_t length) override;
bool has_buffered_data() const override;
protected:
void start_();
void watch_();
static void player_task(void *params);
TaskHandle_t player_task_handle_{nullptr};
struct {
QueueHandle_t handle;
uint8_t *storage;
} buffer_queue_;
QueueHandle_t event_queue_;
};
} // namespace esp_adf
} // namespace esphome
#endif // USE_ESP_IDF

View File

@@ -1,16 +1,16 @@
from esphome import pins
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
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,26 +25,16 @@ 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)
I2SAudioBase = i2s_audio_ns.class_(
"I2SAudioBase", cg.Parented.template(I2SAudioComponent)
I2SAudioIn = i2s_audio_ns.class_("I2SAudioIn", cg.Parented.template(I2SAudioComponent))
I2SAudioOut = i2s_audio_ns.class_(
"I2SAudioOut", 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 = {
@@ -60,75 +50,6 @@ 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),

View File

@@ -11,27 +11,9 @@ namespace i2s_audio {
class 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 I2SAudioIn : 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 I2SAudioOut : public Parented<I2SAudioComponent> {};
class I2SAudioComponent : public Component {
public:

View File

@@ -12,10 +12,6 @@ from .. import (
I2SAudioOut,
CONF_I2S_AUDIO_ID,
CONF_I2S_DOUT_PIN,
CONF_LEFT,
CONF_RIGHT,
CONF_MONO,
CONF_STEREO,
)
CODEOWNERS = ["@jesserockz"]
@@ -34,12 +30,12 @@ CONF_DAC_TYPE = "dac_type"
CONF_I2S_COMM_FMT = "i2s_comm_fmt"
INTERNAL_DAC_OPTIONS = {
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,
"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,
}
EXTERNAL_DAC_OPTIONS = [CONF_MONO, CONF_STEREO]
EXTERNAL_DAC_OPTIONS = ["mono", "stereo"]
NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2]

View File

@@ -23,7 +23,7 @@ enum I2SState : uint8_t {
I2S_STATE_STOPPING,
};
class I2SAudioMediaPlayer : public Component, public Parented<I2SAudioComponent>, public media_player::MediaPlayer {
class I2SAudioMediaPlayer : public Component, public media_player::MediaPlayer, public I2SAudioOut {
public:
void setup() override;
float get_setup_priority() const override { return esphome::setup_priority::LATE; }

View File

@@ -1,17 +1,20 @@
from esphome import pins
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
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
from esphome.components.adc import ESP32_VARIANT_ADC1_PIN_TO_CHANNEL, validate_adc_pin
from .. import (
CONF_I2S_DIN_PIN,
CONF_RIGHT,
I2SAudioIn,
i2s_audio_component_schema,
CONF_I2S_MODE,
CONF_PRIMARY,
I2S_MODE_OPTIONS,
i2s_audio_ns,
register_i2s_audio_component,
I2SAudioComponent,
I2SAudioIn,
CONF_I2S_AUDIO_ID,
CONF_I2S_DIN_PIN,
)
CODEOWNERS = ["@jesserockz"]
@@ -20,14 +23,29 @@ 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()
@@ -44,15 +62,21 @@ def validate_esp32_variant(config):
BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend(
i2s_audio_component_schema(
I2SAudioMicrophone,
default_sample_rate=16000,
default_channel=CONF_RIGHT,
default_bits_per_sample="32bit",
)
{
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
),
}
).extend(cv.COMPONENT_SCHEMA)
CONFIG_SCHEMA = cv.All(
cv.typed_schema(
{
@@ -64,7 +88,7 @@ CONFIG_SCHEMA = cv.All(
"external": BASE_SCHEMA.extend(
{
cv.Required(CONF_I2S_DIN_PIN): pins.internal_gpio_input_pin_number,
cv.Optional(CONF_PDM, default=False): cv.boolean,
cv.Required(CONF_PDM): cv.boolean,
}
),
},
@@ -77,8 +101,8 @@ 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 microphone.register_microphone(var, config)
await cg.register_parented(var, config[CONF_I2S_AUDIO_ID])
if config[CONF_ADC_TYPE] == "internal":
variant = esp32.get_esp32_variant()
@@ -88,3 +112,11 @@ 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)

View File

@@ -58,7 +58,7 @@ void I2SAudioMicrophone::start_() {
.tx_desc_auto_clear = false,
.fixed_mclk = 0,
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
.bits_per_chan = this->bits_per_channel_,
.bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
};
esp_err_t err;
@@ -167,24 +167,21 @@ size_t I2SAudioMicrophone::read(int16_t *buf, size_t len) {
return 0;
}
this->status_clear_warning();
// 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);
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);
}
default:
ESP_LOGE(TAG, "Unsupported bits per sample: %d", this->bits_per_sample_);
return 0;
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;
}
}

View File

@@ -30,6 +30,13 @@ 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_();
@@ -41,6 +48,11 @@ 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_;
};

View File

@@ -1,19 +1,15 @@
from esphome import pins
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 esphome import pins
from esphome.const import CONF_ID, CONF_MODE
from esphome.components import esp32, speaker
from .. import (
CONF_I2S_AUDIO_ID,
CONF_I2S_DOUT_PIN,
CONF_LEFT,
CONF_MONO,
CONF_RIGHT,
CONF_STEREO,
I2SAudioComponent,
I2SAudioOut,
i2s_audio_component_schema,
i2s_audio_ns,
register_i2s_audio_component,
)
CODEOWNERS = ["@jesserockz"]
@@ -23,16 +19,19 @@ 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 = {
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,
"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,
}
EXTERNAL_DAC_OPTIONS = ["mono", "stereo"]
NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2]
@@ -45,40 +44,28 @@ 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": BASE_SCHEMA.extend(
"internal": speaker.SPEAKER_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),
}
),
"external": BASE_SCHEMA.extend(
).extend(cv.COMPONENT_SCHEMA),
"external": speaker.SPEAKER_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,
),
@@ -89,11 +76,12 @@ 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_CHANNEL]))
cg.add(var.set_internal_dac_mode(config[CONF_MODE]))
else:
cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN]))
cg.add(var.set_timeout(config[CONF_TIMEOUT]))
cg.add(var.set_external_dac_channels(2 if config[CONF_MODE] == "stereo" else 1))

View File

@@ -56,21 +56,6 @@ 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;
@@ -79,19 +64,19 @@ void I2SAudioSpeaker::player_task(void *params) {
xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
i2s_driver_config_t config = {
.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_,
.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,
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 8,
.dma_buf_len = 256,
.use_apll = this_speaker->use_apll_,
.dma_buf_len = 128,
.use_apll = false,
.tx_desc_auto_clear = true,
.fixed_mclk = 0,
.fixed_mclk = I2S_PIN_NO_CHANGE,
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
.bits_per_chan = this_speaker->bits_per_channel_,
.bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
};
#if SOC_I2S_SUPPORTS_DAC
if (this_speaker->internal_dac_mode_ != I2S_DAC_CHANNEL_DISABLE) {
@@ -129,11 +114,10 @@ void I2SAudioSpeaker::player_task(void *params) {
event.type = TaskEventType::STARTED;
xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
int32_t buffer[BUFFER_SIZE];
int16_t buffer[BUFFER_SIZE / 2];
while (true) {
if (xQueueReceive(this_speaker->buffer_queue_, &data_event, this_speaker->timeout_ / portTICK_PERIOD_MS) !=
pdTRUE) {
if (xQueueReceive(this_speaker->buffer_queue_, &data_event, 100 / portTICK_PERIOD_MS) != pdTRUE) {
break; // End of audio from main thread
}
if (data_event.stop) {
@@ -141,28 +125,17 @@ void I2SAudioSpeaker::player_task(void *params) {
xQueueReset(this_speaker->buffer_queue_); // Flush queue
break;
}
size_t bytes_written;
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;
}
}
memmove(buffer, data_event.data, data_event.len);
size_t remaining = data_event.len / 2;
size_t current = 0;
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));
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));
if (err != ESP_OK) {
event = {.type = TaskEventType::WARNING, .err = err};
if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
@@ -170,8 +143,21 @@ void I2SAudioSpeaker::player_task(void *params) {
}
continue;
}
data += bytes_written;
remaining -= bytes_written;
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");
}
}
@@ -227,11 +213,13 @@ 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_);

View File

@@ -21,6 +21,7 @@ static const size_t BUFFER_SIZE = 1024;
enum class TaskEventType : uint8_t {
STARTING = 0,
STARTED,
PLAYING,
STOPPING,
STOPPED,
WARNING = 255,
@@ -37,18 +38,18 @@ struct DataEvent {
uint8_t data[BUFFER_SIZE];
};
class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Component {
class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAudioOut {
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;
@@ -69,13 +70,13 @@ class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Comp
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

View File

@@ -32,12 +32,6 @@ 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);
}
@@ -135,12 +129,7 @@ void MAX31856Sensor::clear_fault_() {
}
void MAX31856Sensor::set_thermocouple_type_() {
MAX31856ThermocoupleType type;
if (this->thermocouple_type_ < 0 || this->thermocouple_type_ > 7) {
type = MAX31856_TCTYPE_K;
} else {
type = this->thermocouple_type_;
}
MAX31856ThermocoupleType type = MAX31856_TCTYPE_K;
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

View File

@@ -50,6 +50,7 @@ enum MAX31856Registers {
/**
* Multiple types of thermocouples supported by the chip.
* Currently only K type implemented here.
*/
enum MAX31856ThermocoupleType {
MAX31856_TCTYPE_B = 0b0000, // 0x00
@@ -77,15 +78,11 @@ class MAX31856Sensor : public sensor::Sensor,
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void set_filter(MAX31856ConfigFilter filter) { this->filter_ = filter; }
void set_thermocouple_type(MAX31856ThermocoupleType thermocouple_type) {
this->thermocouple_type_ = thermocouple_type;
}
void set_filter(MAX31856ConfigFilter filter) { filter_ = filter; }
void update() override;
protected:
MAX31856ConfigFilter filter_;
MAX31856ThermocoupleType thermocouple_type_;
uint8_t read_register_(uint8_t reg);
uint32_t read_register24_(uint8_t reg);

View File

@@ -3,7 +3,6 @@ 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,
@@ -19,17 +18,6 @@ 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(
@@ -46,13 +34,6 @@ 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())
)
@@ -63,4 +44,3 @@ 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]))

View File

@@ -1,14 +1,14 @@
import esphome.codegen as cg
from esphome.components import i2c, sensor
import esphome.config_validation as cv
from esphome.components import i2c, sensor
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"

View File

@@ -21,7 +21,6 @@ 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,
@@ -132,7 +131,6 @@ 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,
@@ -259,7 +257,6 @@ 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]:

View File

@@ -5,7 +5,6 @@ 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"

View File

@@ -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() && !this->command_queue_.empty()) {
auto &command = this->command_queue_.front();
if ((last_send > this->command_throttle_) && !waiting_for_response() && !command_queue_.empty()) {
auto &command = command_queue_.front();
// remove from queue if command was sent too often
if (!command->should_retry(this->max_cmd_retries_)) {
if (command->send_countdown < 1) {
if (!this->module_offline_) {
ESP_LOGW(TAG, "Modbus device=%d set offline", this->address_);
@@ -34,9 +34,11 @@ bool ModbusController::send_next_command_() {
}
}
this->module_offline_ = true;
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();
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();
} else {
ESP_LOGV(TAG, "Sending next modbus command to device %d register 0x%02X count %d", this->address_,
command->register_address, command->register_count);
@@ -48,11 +50,11 @@ bool ModbusController::send_next_command_() {
// remove from queue if no handler is defined
if (!command->on_data_func) {
this->command_queue_.pop_front();
command_queue_.pop_front();
}
}
}
return (!this->command_queue_.empty());
return (!command_queue_.empty());
}
// Queue incoming response
@@ -75,7 +77,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");
this->command_queue_.pop_front();
command_queue_.pop_front();
}
}
@@ -97,7 +99,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());
this->command_queue_.pop_front();
command_queue_.pop_front();
}
}
@@ -176,7 +178,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 : this->command_queue_) {
for (auto &item : 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);
@@ -187,7 +189,7 @@ void ModbusController::queue_command(const ModbusCommandItem &command) {
}
}
}
this->command_queue_.push_back(make_unique<ModbusCommandItem>(command));
command_queue_.push_back(make_unique<ModbusCommandItem>(command));
}
void ModbusController::update_range_(RegisterRange &r) {
@@ -222,8 +224,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 (!this->command_queue_.empty()) {
ESP_LOGV(TAG, "%zu modbus commands already in queue", this->command_queue_.size());
if (!command_queue_.empty()) {
ESP_LOGV(TAG, "%zu modbus commands already in queue", command_queue_.size());
} else {
ESP_LOGV(TAG, "Updating modbus component");
}
@@ -344,8 +346,6 @@ 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,9 +560,8 @@ bool ModbusCommandItem::send() {
} else {
modbusdevice->send_raw(this->payload);
}
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_);
ESP_LOGV(TAG, "Command sent %d 0x%X %d", uint8_t(this->function_code), this->register_address, this->register_count);
send_countdown--;
return true;
}

View File

@@ -312,6 +312,7 @@ 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;
@@ -321,9 +322,9 @@ class ModbusCommandItem {
on_data_func;
std::vector<uint8_t> payload = {};
bool send();
/// 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; };
// wrong commands (esp. custom commands) can block the send queue
// limit the number of repeats
uint8_t send_countdown{MAX_SEND_REPEATS};
/// factory methods
/** Create modbus read command
* Function code 02-04
@@ -412,11 +413,6 @@ 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.
@@ -468,10 +464,6 @@ 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
@@ -506,8 +498,6 @@ 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_{};
};

View File

@@ -17,7 +17,6 @@ CONFIG_SCHEMA = cv.Schema(
esp8266=False,
esp32=False,
rp2040=False,
bk72xx=False,
): cv.All(
cv.boolean,
cv.Any(
@@ -26,7 +25,6 @@ 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,
),
@@ -54,5 +52,3 @@ 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")

View File

@@ -45,8 +45,11 @@ void UponorSmatrixComponent::loop() {
// Read incoming data
while (this->available()) {
// 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.
// 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;
}
this->last_rx_ = now;
uint8_t byte;
@@ -57,8 +60,7 @@ void UponorSmatrixComponent::loop() {
}
// Send packets during bus silence
if (this->rx_buffer_.empty() && (now - this->last_rx_ > 50) && (now - this->last_rx_ < 100) &&
(now - this->last_tx_ > 200)) {
if ((now - this->last_rx_ > 300) && (now - this->last_poll_start_ < 9500) && (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_())

View File

@@ -93,6 +93,7 @@ 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};

View File

@@ -85,16 +85,7 @@ bool WiFiComponent::wifi_sta_ip_config_(optional<ManualIP> manual_ip) {
network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() {
if (!this->has_sta())
return {};
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;
return {WiFi.localIP()};
}
bool WiFiComponent::wifi_apply_hostname_() {
@@ -330,11 +321,6 @@ 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;

View File

@@ -1,6 +1,6 @@
"""Constants used by esphome."""
__version__ = "2024.9.0b1"
__version__ = "2024.9.0-dev"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
VALID_SUBSTITUTIONS_CHARACTERS = (
@@ -852,7 +852,6 @@ 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"

View File

@@ -90,6 +90,7 @@
// IDF-specific feature flags
#ifdef USE_ESP_IDF
#define USE_MQTT_IDF_ENQUEUE
#define USE_ESP_ADF
#endif
// ESP32-specific feature flags

View File

@@ -1,4 +1,39 @@
dependencies:
##### ESP-ADF #####
audio_board:
git: https://github.com/espressif/esp-adf.git
version: v2.5
path: components/audio_board
audio_hal:
git: https://github.com/espressif/esp-adf.git
version: v2.5
path: components/audio_hal
audio_pipeline:
git: https://github.com/espressif/esp-adf.git
version: v2.5
path: components/audio_pipeline
audio_recorder:
git: https://github.com/espressif/esp-adf.git
version: v2.5
path: components/audio_recorder
audio_sal:
git: https://github.com/espressif/esp-adf.git
version: v2.5
path: components/audio_sal
audio_stream:
git: https://github.com/espressif/esp-adf.git
version: v2.5
path: components/audio_stream
esp-adf-libs:
git: https://github.com/espressif/esp-adf-libs.git
version: 4534b324c93cbf1d07b11ff8a236b3064d9634d0 # Submodule references in esp-adf
esp-dsp:
git: https://github.com/espressif/esp-dsp.git
version: v1.2.0
esp-sr:
git: https://github.com/espressif/esp-sr.git
version: 36ec4242d08f68b38f39cfc80b6057bc9d6c0269 # Submodule references in esp-adf
#### END ESP-ADF ####
esp-tflite-micro:
git: https://github.com/espressif/esp-tflite-micro.git
version: v1.3.1

View File

@@ -10,4 +10,3 @@ sensor:
cs_pin: 12
update_interval: 15s
mains_filter: 50Hz
thermocouple_type: N

View File

@@ -10,5 +10,3 @@ sensor:
cs_pin: 8
update_interval: 15s
mains_filter: 50Hz
thermocouple_type: N

View File

@@ -10,5 +10,3 @@ sensor:
cs_pin: 8
update_interval: 15s
mains_filter: 50Hz
thermocouple_type: N

View File

@@ -10,4 +10,3 @@ sensor:
cs_pin: 12
update_interval: 15s
mains_filter: 50Hz
thermocouple_type: N

View File

@@ -10,5 +10,3 @@ sensor:
cs_pin: 15
update_interval: 15s
mains_filter: 50Hz
thermocouple_type: N

View File

@@ -10,5 +10,3 @@ sensor:
cs_pin: 6
update_interval: 15s
mains_filter: 50Hz
thermocouple_type: N

View File

@@ -29,4 +29,3 @@ modbus_controller:
value_type: S_DWORD_R
read_lambda: |-
return 42.3;
max_cmd_retries: 0

View File

@@ -13,4 +13,3 @@ modbus_controller:
address: 0x2
modbus_id: mod_bus1
allow_duplicate_commands: true
max_cmd_retries: 10

View File

@@ -1,8 +1,4 @@
substitutions:
network_enable_ipv6: "true"
bk72xx:
framework:
version: 1.7.0
network_enable_ipv6: "false"
<<: !include common.yaml

View File

@@ -21,3 +21,4 @@ speaker:
id: speaker_id
dac_type: external
i2s_dout_pin: 13
mode: mono

View File

@@ -21,3 +21,4 @@ speaker:
id: speaker_id
dac_type: external
i2s_dout_pin: 3
mode: mono

View File

@@ -21,3 +21,4 @@ speaker:
id: speaker_id
dac_type: external
i2s_dout_pin: 3
mode: mono

View File

@@ -21,3 +21,4 @@ speaker:
id: speaker_id
dac_type: external
i2s_dout_pin: 13
mode: mono

View File

@@ -28,6 +28,7 @@ speaker:
id: speaker_id
dac_type: external
i2s_dout_pin: 12
mode: mono
voice_assistant:
microphone: mic_id_external

View File

@@ -28,6 +28,7 @@ speaker:
id: speaker_id
dac_type: external
i2s_dout_pin: 2
mode: mono
voice_assistant:
microphone: mic_id_external

View File

@@ -28,6 +28,7 @@ speaker:
id: speaker_id
dac_type: external
i2s_dout_pin: 2
mode: mono
voice_assistant:
microphone: mic_id_external

View File

@@ -28,6 +28,7 @@ speaker:
id: speaker_id
dac_type: external
i2s_dout_pin: 12
mode: mono
voice_assistant:
microphone: mic_id_external