mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-03 16:41:50 +00:00 
			
		
		
		
	Compare commits
	
		
			22 Commits
		
	
	
		
			jesserockz
			...
			jesserockz
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					6c132be24c | ||
| 
						 | 
					323c641ecd | ||
| 
						 | 
					6207510279 | ||
| 
						 | 
					95a340d7a3 | ||
| 
						 | 
					6f9e725a2c | ||
| 
						 | 
					43f6793ad9 | ||
| 
						 | 
					cb7b4d9365 | ||
| 
						 | 
					af1a909c27 | ||
| 
						 | 
					9ed2ed8177 | ||
| 
						 | 
					664b219387 | ||
| 
						 | 
					63cda412f9 | ||
| 
						 | 
					625726c650 | ||
| 
						 | 
					955a909846 | ||
| 
						 | 
					e3ae8cd31e | ||
| 
						 | 
					04248b6840 | ||
| 
						 | 
					dbecade122 | ||
| 
						 | 
					ffc2b58714 | ||
| 
						 | 
					39ad358b51 | ||
| 
						 | 
					08480d24f0 | ||
| 
						 | 
					41eb178f5a | ||
| 
						 | 
					d7b0060265 | ||
| 
						 | 
					e07f9a5b9c | 
							
								
								
									
										91
									
								
								.github/workflows/codeql.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								.github/workflows/codeql.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,91 @@
 | 
				
			|||||||
 | 
					# For most projects, this workflow file will not need changing; you simply need
 | 
				
			||||||
 | 
					# to commit it to your repository.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# You may wish to alter this file to override the set of languages analyzed,
 | 
				
			||||||
 | 
					# or to provide custom queries or build logic.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# ******** NOTE ********
 | 
				
			||||||
 | 
					# We have attempted to detect the languages in your repository. Please check
 | 
				
			||||||
 | 
					# the `language` matrix defined below to confirm you have the correct set of
 | 
				
			||||||
 | 
					# supported CodeQL languages.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					name: "CodeQL Advanced"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					on:
 | 
				
			||||||
 | 
					  workflow_dispatch:
 | 
				
			||||||
 | 
					  schedule:
 | 
				
			||||||
 | 
					    - cron: "30 18 * * 4"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					  analyze:
 | 
				
			||||||
 | 
					    name: Analyze (${{ matrix.language }})
 | 
				
			||||||
 | 
					    # Runner size impacts CodeQL analysis time. To learn more, please see:
 | 
				
			||||||
 | 
					    #   - https://gh.io/recommended-hardware-resources-for-running-codeql
 | 
				
			||||||
 | 
					    #   - https://gh.io/supported-runners-and-hardware-resources
 | 
				
			||||||
 | 
					    #   - https://gh.io/using-larger-runners (GitHub.com only)
 | 
				
			||||||
 | 
					    # Consider using larger runners or machines with greater resources for possible analysis time improvements.
 | 
				
			||||||
 | 
					    runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
 | 
				
			||||||
 | 
					    permissions:
 | 
				
			||||||
 | 
					      # required for all workflows
 | 
				
			||||||
 | 
					      security-events: write
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # required to fetch internal or private CodeQL packs
 | 
				
			||||||
 | 
					      packages: read
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # only required for workflows in private repositories
 | 
				
			||||||
 | 
					      actions: read
 | 
				
			||||||
 | 
					      contents: read
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    strategy:
 | 
				
			||||||
 | 
					      fail-fast: false
 | 
				
			||||||
 | 
					      matrix:
 | 
				
			||||||
 | 
					        include:
 | 
				
			||||||
 | 
					          # - language: c-cpp
 | 
				
			||||||
 | 
					          #   build-mode: autobuild
 | 
				
			||||||
 | 
					          - language: python
 | 
				
			||||||
 | 
					            build-mode: none
 | 
				
			||||||
 | 
					            # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
 | 
				
			||||||
 | 
					            # Use `c-cpp` to analyze code written in C, C++ or both
 | 
				
			||||||
 | 
					            # Use 'java-kotlin' to analyze code written in Java, Kotlin or both
 | 
				
			||||||
 | 
					            # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
 | 
				
			||||||
 | 
					            # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
 | 
				
			||||||
 | 
					            # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
 | 
				
			||||||
 | 
					            # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
 | 
				
			||||||
 | 
					            # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - name: Checkout repository
 | 
				
			||||||
 | 
					        uses: actions/checkout@v4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # Initializes the CodeQL tools for scanning.
 | 
				
			||||||
 | 
					      - name: Initialize CodeQL
 | 
				
			||||||
 | 
					        uses: github/codeql-action/init@v3
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          languages: ${{ matrix.language }}
 | 
				
			||||||
 | 
					          build-mode: ${{ matrix.build-mode }}
 | 
				
			||||||
 | 
					          # If you wish to specify custom queries, you can do so here or in a config file.
 | 
				
			||||||
 | 
					          # By default, queries listed here will override any specified in a config file.
 | 
				
			||||||
 | 
					          # Prefix the list here with "+" to use these queries and those in the config file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
 | 
				
			||||||
 | 
					          # queries: security-extended,security-and-quality
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # If the analyze step fails for one of the languages you are analyzing with
 | 
				
			||||||
 | 
					      # "We were unable to automatically build your code", modify the matrix above
 | 
				
			||||||
 | 
					      # to set the build mode to "manual" for that language. Then modify this step
 | 
				
			||||||
 | 
					      # to build your code.
 | 
				
			||||||
 | 
					      # ℹ️ Command-line programs to run using the OS shell.
 | 
				
			||||||
 | 
					      # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
 | 
				
			||||||
 | 
					      - if: matrix.build-mode == 'manual'
 | 
				
			||||||
 | 
					        shell: bash
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          echo 'If you are using a "manual" build mode for one or more of the' \
 | 
				
			||||||
 | 
					            'languages you are analyzing, replace this with the commands to build' \
 | 
				
			||||||
 | 
					            'your code, for example:'
 | 
				
			||||||
 | 
					          echo '  make bootstrap'
 | 
				
			||||||
 | 
					          echo '  make release'
 | 
				
			||||||
 | 
					          exit 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Perform CodeQL Analysis
 | 
				
			||||||
 | 
					        uses: github/codeql-action/analyze@v3
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          category: "/language:${{matrix.language}}"
 | 
				
			||||||
@@ -136,7 +136,6 @@ esphome/components/esp32_improv/* @jesserockz
 | 
				
			|||||||
esphome/components/esp32_rmt/* @jesserockz
 | 
					esphome/components/esp32_rmt/* @jesserockz
 | 
				
			||||||
esphome/components/esp32_rmt_led_strip/* @jesserockz
 | 
					esphome/components/esp32_rmt_led_strip/* @jesserockz
 | 
				
			||||||
esphome/components/esp8266/* @esphome/core
 | 
					esphome/components/esp8266/* @esphome/core
 | 
				
			||||||
esphome/components/esp_adf/* @jesserockz
 | 
					 | 
				
			||||||
esphome/components/ethernet_info/* @gtjadsonsantos
 | 
					esphome/components/ethernet_info/* @gtjadsonsantos
 | 
				
			||||||
esphome/components/event/* @nohat
 | 
					esphome/components/event/* @nohat
 | 
				
			||||||
esphome/components/exposure_notifications/* @OttoWinter
 | 
					esphome/components/exposure_notifications/* @OttoWinter
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -140,6 +140,8 @@ CONF_TEST_PATTERN = "test_pattern"
 | 
				
			|||||||
# framerates
 | 
					# framerates
 | 
				
			||||||
CONF_MAX_FRAMERATE = "max_framerate"
 | 
					CONF_MAX_FRAMERATE = "max_framerate"
 | 
				
			||||||
CONF_IDLE_FRAMERATE = "idle_framerate"
 | 
					CONF_IDLE_FRAMERATE = "idle_framerate"
 | 
				
			||||||
 | 
					# frame buffer
 | 
				
			||||||
 | 
					CONF_FRAME_BUFFER_COUNT = "frame_buffer_count"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# stream trigger
 | 
					# stream trigger
 | 
				
			||||||
CONF_ON_STREAM_START = "on_stream_start"
 | 
					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.Optional(CONF_IDLE_FRAMERATE, default="0.1 fps"): cv.All(
 | 
				
			||||||
            cv.framerate, cv.Range(min=0, max=1)
 | 
					            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.Optional(CONF_ON_STREAM_START): automation.validate_automation(
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
					                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))
 | 
					        cg.add(var.set_idle_update_interval(0))
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        cg.add(var.set_idle_update_interval(1000 / config[CONF_IDLE_FRAMERATE]))
 | 
					        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(var.set_frame_size(config[CONF_RESOLUTION]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    cg.add_define("USE_ESP32_CAMERA")
 | 
					    cg.add_define("USE_ESP32_CAMERA")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -127,7 +127,7 @@ void ESP32Camera::dump_config() {
 | 
				
			|||||||
  sensor_t *s = esp_camera_sensor_get();
 | 
					  sensor_t *s = esp_camera_sensor_get();
 | 
				
			||||||
  auto st = s->status;
 | 
					  auto st = s->status;
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "  JPEG Quality: %u", st.quality);
 | 
					  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, "  Contrast: %d", st.contrast);
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "  Brightness: %d", st.brightness);
 | 
					  ESP_LOGCONFIG(TAG, "  Brightness: %d", st.brightness);
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "  Saturation: %d", st.saturation);
 | 
					  ESP_LOGCONFIG(TAG, "  Saturation: %d", st.saturation);
 | 
				
			||||||
@@ -212,6 +212,8 @@ ESP32Camera::ESP32Camera() {
 | 
				
			|||||||
  this->config_.frame_size = FRAMESIZE_VGA;  // 640x480
 | 
					  this->config_.frame_size = FRAMESIZE_VGA;  // 640x480
 | 
				
			||||||
  this->config_.jpeg_quality = 10;
 | 
					  this->config_.jpeg_quality = 10;
 | 
				
			||||||
  this->config_.fb_count = 1;
 | 
					  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;
 | 
					  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) {
 | 
					void ESP32Camera::set_idle_update_interval(uint32_t idle_update_interval) {
 | 
				
			||||||
  this->idle_update_interval_ = 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) ---------------- */
 | 
					/* ---------------- public API (specific) ---------------- */
 | 
				
			||||||
void ESP32Camera::add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&callback) {
 | 
					void ESP32Camera::add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&callback) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -145,6 +145,9 @@ class ESP32Camera : public Component, public EntityBase {
 | 
				
			|||||||
  /* -- framerates */
 | 
					  /* -- framerates */
 | 
				
			||||||
  void set_max_update_interval(uint32_t max_update_interval);
 | 
					  void set_max_update_interval(uint32_t max_update_interval);
 | 
				
			||||||
  void set_idle_update_interval(uint32_t idle_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) */
 | 
					  /* public API (derivated) */
 | 
				
			||||||
  void setup() override;
 | 
					  void setup() override;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,103 +0,0 @@
 | 
				
			|||||||
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",
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
@@ -1,23 +0,0 @@
 | 
				
			|||||||
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))
 | 
					 | 
				
			||||||
@@ -1,30 +0,0 @@
 | 
				
			|||||||
#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
 | 
					 | 
				
			||||||
@@ -1,58 +0,0 @@
 | 
				
			|||||||
#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
 | 
					 | 
				
			||||||
@@ -1,42 +0,0 @@
 | 
				
			|||||||
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)
 | 
					 | 
				
			||||||
@@ -1,315 +0,0 @@
 | 
				
			|||||||
#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
 | 
					 | 
				
			||||||
@@ -1,42 +0,0 @@
 | 
				
			|||||||
#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
 | 
					 | 
				
			||||||
@@ -1,41 +0,0 @@
 | 
				
			|||||||
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)
 | 
					 | 
				
			||||||
@@ -1,295 +0,0 @@
 | 
				
			|||||||
#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
 | 
					 | 
				
			||||||
@@ -1,51 +0,0 @@
 | 
				
			|||||||
#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
 | 
					 | 
				
			||||||
@@ -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 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 import get_esp32_variant
 | 
				
			||||||
from esphome.components.esp32.const import (
 | 
					from esphome.components.esp32.const import (
 | 
				
			||||||
    VARIANT_ESP32,
 | 
					    VARIANT_ESP32,
 | 
				
			||||||
 | 
					    VARIANT_ESP32C3,
 | 
				
			||||||
    VARIANT_ESP32S2,
 | 
					    VARIANT_ESP32S2,
 | 
				
			||||||
    VARIANT_ESP32S3,
 | 
					    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"]
 | 
					CODEOWNERS = ["@jesserockz"]
 | 
				
			||||||
DEPENDENCIES = ["esp32"]
 | 
					DEPENDENCIES = ["esp32"]
 | 
				
			||||||
@@ -25,16 +25,26 @@ CONF_I2S_LRCLK_PIN = "i2s_lrclk_pin"
 | 
				
			|||||||
CONF_I2S_AUDIO = "i2s_audio"
 | 
					CONF_I2S_AUDIO = "i2s_audio"
 | 
				
			||||||
CONF_I2S_AUDIO_ID = "i2s_audio_id"
 | 
					CONF_I2S_AUDIO_ID = "i2s_audio_id"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONF_BITS_PER_SAMPLE = "bits_per_sample"
 | 
				
			||||||
CONF_I2S_MODE = "i2s_mode"
 | 
					CONF_I2S_MODE = "i2s_mode"
 | 
				
			||||||
CONF_PRIMARY = "primary"
 | 
					CONF_PRIMARY = "primary"
 | 
				
			||||||
CONF_SECONDARY = "secondary"
 | 
					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")
 | 
					i2s_audio_ns = cg.esphome_ns.namespace("i2s_audio")
 | 
				
			||||||
I2SAudioComponent = i2s_audio_ns.class_("I2SAudioComponent", cg.Component)
 | 
					I2SAudioComponent = i2s_audio_ns.class_("I2SAudioComponent", cg.Component)
 | 
				
			||||||
I2SAudioIn = i2s_audio_ns.class_("I2SAudioIn", cg.Parented.template(I2SAudioComponent))
 | 
					I2SAudioBase = i2s_audio_ns.class_(
 | 
				
			||||||
I2SAudioOut = i2s_audio_ns.class_(
 | 
					    "I2SAudioBase", cg.Parented.template(I2SAudioComponent)
 | 
				
			||||||
    "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_t = cg.global_ns.enum("i2s_mode_t")
 | 
				
			||||||
I2S_MODE_OPTIONS = {
 | 
					I2S_MODE_OPTIONS = {
 | 
				
			||||||
@@ -50,6 +60,75 @@ I2S_PORTS = {
 | 
				
			|||||||
    VARIANT_ESP32C3: 1,
 | 
					    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(
 | 
					CONFIG_SCHEMA = cv.Schema(
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        cv.GenerateID(): cv.declare_id(I2SAudioComponent),
 | 
					        cv.GenerateID(): cv.declare_id(I2SAudioComponent),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,9 +11,27 @@ namespace i2s_audio {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class I2SAudioComponent;
 | 
					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 {
 | 
					class I2SAudioComponent : public Component {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,10 @@ from .. import (
 | 
				
			|||||||
    I2SAudioOut,
 | 
					    I2SAudioOut,
 | 
				
			||||||
    CONF_I2S_AUDIO_ID,
 | 
					    CONF_I2S_AUDIO_ID,
 | 
				
			||||||
    CONF_I2S_DOUT_PIN,
 | 
					    CONF_I2S_DOUT_PIN,
 | 
				
			||||||
 | 
					    CONF_LEFT,
 | 
				
			||||||
 | 
					    CONF_RIGHT,
 | 
				
			||||||
 | 
					    CONF_MONO,
 | 
				
			||||||
 | 
					    CONF_STEREO,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CODEOWNERS = ["@jesserockz"]
 | 
					CODEOWNERS = ["@jesserockz"]
 | 
				
			||||||
@@ -30,12 +34,12 @@ CONF_DAC_TYPE = "dac_type"
 | 
				
			|||||||
CONF_I2S_COMM_FMT = "i2s_comm_fmt"
 | 
					CONF_I2S_COMM_FMT = "i2s_comm_fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
INTERNAL_DAC_OPTIONS = {
 | 
					INTERNAL_DAC_OPTIONS = {
 | 
				
			||||||
    "left": i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN,
 | 
					    CONF_LEFT: i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN,
 | 
				
			||||||
    "right": i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN,
 | 
					    CONF_RIGHT: i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN,
 | 
				
			||||||
    "stereo": i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_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]
 | 
					NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,7 +23,7 @@ enum I2SState : uint8_t {
 | 
				
			|||||||
  I2S_STATE_STOPPING,
 | 
					  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:
 | 
					 public:
 | 
				
			||||||
  void setup() override;
 | 
					  void setup() override;
 | 
				
			||||||
  float get_setup_priority() const override { return esphome::setup_priority::LATE; }
 | 
					  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 import pins
 | 
				
			||||||
from esphome.const import CONF_CHANNEL, CONF_ID, CONF_NUMBER, CONF_SAMPLE_RATE
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import microphone, esp32
 | 
					from esphome.components import esp32, microphone
 | 
				
			||||||
from esphome.components.adc import ESP32_VARIANT_ADC1_PIN_TO_CHANNEL, validate_adc_pin
 | 
					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 (
 | 
					from .. import (
 | 
				
			||||||
    CONF_I2S_MODE,
 | 
					 | 
				
			||||||
    CONF_PRIMARY,
 | 
					 | 
				
			||||||
    I2S_MODE_OPTIONS,
 | 
					 | 
				
			||||||
    i2s_audio_ns,
 | 
					 | 
				
			||||||
    I2SAudioComponent,
 | 
					 | 
				
			||||||
    I2SAudioIn,
 | 
					 | 
				
			||||||
    CONF_I2S_AUDIO_ID,
 | 
					 | 
				
			||||||
    CONF_I2S_DIN_PIN,
 | 
					    CONF_I2S_DIN_PIN,
 | 
				
			||||||
 | 
					    CONF_RIGHT,
 | 
				
			||||||
 | 
					    I2SAudioIn,
 | 
				
			||||||
 | 
					    i2s_audio_component_schema,
 | 
				
			||||||
 | 
					    i2s_audio_ns,
 | 
				
			||||||
 | 
					    register_i2s_audio_component,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CODEOWNERS = ["@jesserockz"]
 | 
					CODEOWNERS = ["@jesserockz"]
 | 
				
			||||||
@@ -23,29 +20,14 @@ DEPENDENCIES = ["i2s_audio"]
 | 
				
			|||||||
CONF_ADC_PIN = "adc_pin"
 | 
					CONF_ADC_PIN = "adc_pin"
 | 
				
			||||||
CONF_ADC_TYPE = "adc_type"
 | 
					CONF_ADC_TYPE = "adc_type"
 | 
				
			||||||
CONF_PDM = "pdm"
 | 
					CONF_PDM = "pdm"
 | 
				
			||||||
CONF_BITS_PER_SAMPLE = "bits_per_sample"
 | 
					 | 
				
			||||||
CONF_USE_APLL = "use_apll"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
I2SAudioMicrophone = i2s_audio_ns.class_(
 | 
					I2SAudioMicrophone = i2s_audio_ns.class_(
 | 
				
			||||||
    "I2SAudioMicrophone", I2SAudioIn, microphone.Microphone, cg.Component
 | 
					    "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]
 | 
					INTERNAL_ADC_VARIANTS = [esp32.const.VARIANT_ESP32]
 | 
				
			||||||
PDM_VARIANTS = [esp32.const.VARIANT_ESP32, esp32.const.VARIANT_ESP32S3]
 | 
					PDM_VARIANTS = [esp32.const.VARIANT_ESP32, esp32.const.VARIANT_ESP32S3]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_validate_bits = cv.float_with_unit("bits", "bit")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
def validate_esp32_variant(config):
 | 
					def validate_esp32_variant(config):
 | 
				
			||||||
    variant = esp32.get_esp32_variant()
 | 
					    variant = esp32.get_esp32_variant()
 | 
				
			||||||
@@ -62,21 +44,15 @@ def validate_esp32_variant(config):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend(
 | 
					BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend(
 | 
				
			||||||
    {
 | 
					    i2s_audio_component_schema(
 | 
				
			||||||
        cv.GenerateID(): cv.declare_id(I2SAudioMicrophone),
 | 
					        I2SAudioMicrophone,
 | 
				
			||||||
        cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent),
 | 
					        default_sample_rate=16000,
 | 
				
			||||||
        cv.Optional(CONF_CHANNEL, default="right"): cv.enum(CHANNELS),
 | 
					        default_channel=CONF_RIGHT,
 | 
				
			||||||
        cv.Optional(CONF_SAMPLE_RATE, default=16000): cv.int_range(min=1),
 | 
					        default_bits_per_sample="32bit",
 | 
				
			||||||
        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)
 | 
					).extend(cv.COMPONENT_SCHEMA)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONFIG_SCHEMA = cv.All(
 | 
					CONFIG_SCHEMA = cv.All(
 | 
				
			||||||
    cv.typed_schema(
 | 
					    cv.typed_schema(
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@@ -88,7 +64,7 @@ CONFIG_SCHEMA = cv.All(
 | 
				
			|||||||
            "external": BASE_SCHEMA.extend(
 | 
					            "external": BASE_SCHEMA.extend(
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    cv.Required(CONF_I2S_DIN_PIN): pins.internal_gpio_input_pin_number,
 | 
					                    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):
 | 
					async def to_code(config):
 | 
				
			||||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
					    var = cg.new_Pvariable(config[CONF_ID])
 | 
				
			||||||
    await cg.register_component(var, config)
 | 
					    await cg.register_component(var, config)
 | 
				
			||||||
 | 
					    await register_i2s_audio_component(var, config)
 | 
				
			||||||
    await cg.register_parented(var, config[CONF_I2S_AUDIO_ID])
 | 
					    await microphone.register_microphone(var, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if config[CONF_ADC_TYPE] == "internal":
 | 
					    if config[CONF_ADC_TYPE] == "internal":
 | 
				
			||||||
        variant = esp32.get_esp32_variant()
 | 
					        variant = esp32.get_esp32_variant()
 | 
				
			||||||
@@ -112,11 +88,3 @@ async def to_code(config):
 | 
				
			|||||||
    else:
 | 
					    else:
 | 
				
			||||||
        cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN]))
 | 
					        cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN]))
 | 
				
			||||||
        cg.add(var.set_pdm(config[CONF_PDM]))
 | 
					        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,
 | 
					      .tx_desc_auto_clear = false,
 | 
				
			||||||
      .fixed_mclk = 0,
 | 
					      .fixed_mclk = 0,
 | 
				
			||||||
      .mclk_multiple = I2S_MCLK_MULTIPLE_256,
 | 
					      .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;
 | 
					  esp_err_t err;
 | 
				
			||||||
@@ -167,21 +167,24 @@ size_t I2SAudioMicrophone::read(int16_t *buf, size_t len) {
 | 
				
			|||||||
    return 0;
 | 
					    return 0;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  this->status_clear_warning();
 | 
					  this->status_clear_warning();
 | 
				
			||||||
  if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_16BIT) {
 | 
					  // ESP-IDF I2S implementation right-extends 8-bit data to 16 bits,
 | 
				
			||||||
    return bytes_read;
 | 
					  // and 24-bit data to 32 bits.
 | 
				
			||||||
  } else if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_32BIT) {
 | 
					  switch (this->bits_per_sample_) {
 | 
				
			||||||
    std::vector<int16_t> samples;
 | 
					    case I2S_BITS_PER_SAMPLE_8BIT:
 | 
				
			||||||
    size_t samples_read = bytes_read / sizeof(int32_t);
 | 
					    case I2S_BITS_PER_SAMPLE_16BIT:
 | 
				
			||||||
    samples.resize(samples_read);
 | 
					      return bytes_read;
 | 
				
			||||||
    for (size_t i = 0; i < samples_read; i++) {
 | 
					    case I2S_BITS_PER_SAMPLE_24BIT:
 | 
				
			||||||
      int32_t temp = reinterpret_cast<int32_t *>(buf)[i] >> 14;
 | 
					    case I2S_BITS_PER_SAMPLE_32BIT: {
 | 
				
			||||||
      samples[i] = clamp<int16_t>(temp, INT16_MIN, INT16_MAX);
 | 
					      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));
 | 
					    default:
 | 
				
			||||||
    return samples_read * sizeof(int16_t);
 | 
					      ESP_LOGE(TAG, "Unsupported bits per sample: %d", this->bits_per_sample_);
 | 
				
			||||||
  } else {
 | 
					      return 0;
 | 
				
			||||||
    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
 | 
					#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:
 | 
					 protected:
 | 
				
			||||||
  void start_();
 | 
					  void start_();
 | 
				
			||||||
  void stop_();
 | 
					  void stop_();
 | 
				
			||||||
@@ -48,11 +41,6 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
 | 
				
			|||||||
  bool adc_{false};
 | 
					  bool adc_{false};
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
  bool pdm_{false};
 | 
					  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_;
 | 
					  HighFrequencyLoopRequester high_freq_;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,19 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					 | 
				
			||||||
from esphome import pins
 | 
					from esphome import pins
 | 
				
			||||||
from esphome.const import CONF_ID, CONF_MODE
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.components import esp32, speaker
 | 
					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 (
 | 
					from .. import (
 | 
				
			||||||
    CONF_I2S_AUDIO_ID,
 | 
					 | 
				
			||||||
    CONF_I2S_DOUT_PIN,
 | 
					    CONF_I2S_DOUT_PIN,
 | 
				
			||||||
    I2SAudioComponent,
 | 
					    CONF_LEFT,
 | 
				
			||||||
 | 
					    CONF_MONO,
 | 
				
			||||||
 | 
					    CONF_RIGHT,
 | 
				
			||||||
 | 
					    CONF_STEREO,
 | 
				
			||||||
    I2SAudioOut,
 | 
					    I2SAudioOut,
 | 
				
			||||||
 | 
					    i2s_audio_component_schema,
 | 
				
			||||||
    i2s_audio_ns,
 | 
					    i2s_audio_ns,
 | 
				
			||||||
 | 
					    register_i2s_audio_component,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CODEOWNERS = ["@jesserockz"]
 | 
					CODEOWNERS = ["@jesserockz"]
 | 
				
			||||||
@@ -19,19 +23,16 @@ I2SAudioSpeaker = i2s_audio_ns.class_(
 | 
				
			|||||||
    "I2SAudioSpeaker", cg.Component, speaker.Speaker, I2SAudioOut
 | 
					    "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"
 | 
					CONF_DAC_TYPE = "dac_type"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					i2s_dac_mode_t = cg.global_ns.enum("i2s_dac_mode_t")
 | 
				
			||||||
INTERNAL_DAC_OPTIONS = {
 | 
					INTERNAL_DAC_OPTIONS = {
 | 
				
			||||||
    "left": i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN,
 | 
					    CONF_LEFT: i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN,
 | 
				
			||||||
    "right": i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN,
 | 
					    CONF_RIGHT: i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN,
 | 
				
			||||||
    "stereo": i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_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]
 | 
					NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -44,28 +45,40 @@ def validate_esp32_variant(config):
 | 
				
			|||||||
    return 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(
 | 
					CONFIG_SCHEMA = cv.All(
 | 
				
			||||||
    cv.typed_schema(
 | 
					    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),
 | 
					                    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(
 | 
					                    cv.Required(
 | 
				
			||||||
                        CONF_I2S_DOUT_PIN
 | 
					                        CONF_I2S_DOUT_PIN
 | 
				
			||||||
                    ): pins.internal_gpio_output_pin_number,
 | 
					                    ): 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,
 | 
					        key=CONF_DAC_TYPE,
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
@@ -76,12 +89,11 @@ CONFIG_SCHEMA = cv.All(
 | 
				
			|||||||
async def to_code(config):
 | 
					async def to_code(config):
 | 
				
			||||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
					    var = cg.new_Pvariable(config[CONF_ID])
 | 
				
			||||||
    await cg.register_component(var, config)
 | 
					    await cg.register_component(var, config)
 | 
				
			||||||
 | 
					    await register_i2s_audio_component(var, config)
 | 
				
			||||||
    await speaker.register_speaker(var, config)
 | 
					    await speaker.register_speaker(var, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await cg.register_parented(var, config[CONF_I2S_AUDIO_ID])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if config[CONF_DAC_TYPE] == "internal":
 | 
					    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:
 | 
					    else:
 | 
				
			||||||
        cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN]))
 | 
					        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;
 | 
					  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) {
 | 
					void I2SAudioSpeaker::player_task(void *params) {
 | 
				
			||||||
  I2SAudioSpeaker *this_speaker = (I2SAudioSpeaker *) params;
 | 
					  I2SAudioSpeaker *this_speaker = (I2SAudioSpeaker *) params;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -64,19 +79,19 @@ void I2SAudioSpeaker::player_task(void *params) {
 | 
				
			|||||||
  xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
 | 
					  xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  i2s_driver_config_t config = {
 | 
					  i2s_driver_config_t config = {
 | 
				
			||||||
      .mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_TX),
 | 
					      .mode = (i2s_mode_t) (this_speaker->i2s_mode_ | I2S_MODE_TX),
 | 
				
			||||||
      .sample_rate = 16000,
 | 
					      .sample_rate = this_speaker->sample_rate_,
 | 
				
			||||||
      .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
 | 
					      .bits_per_sample = this_speaker->bits_per_sample_,
 | 
				
			||||||
      .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
 | 
					      .channel_format = this_speaker->channel_,
 | 
				
			||||||
      .communication_format = I2S_COMM_FORMAT_STAND_I2S,
 | 
					      .communication_format = I2S_COMM_FORMAT_STAND_I2S,
 | 
				
			||||||
      .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
 | 
					      .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
 | 
				
			||||||
      .dma_buf_count = 8,
 | 
					      .dma_buf_count = 8,
 | 
				
			||||||
      .dma_buf_len = 128,
 | 
					      .dma_buf_len = 256,
 | 
				
			||||||
      .use_apll = false,
 | 
					      .use_apll = this_speaker->use_apll_,
 | 
				
			||||||
      .tx_desc_auto_clear = true,
 | 
					      .tx_desc_auto_clear = true,
 | 
				
			||||||
      .fixed_mclk = I2S_PIN_NO_CHANGE,
 | 
					      .fixed_mclk = 0,
 | 
				
			||||||
      .mclk_multiple = I2S_MCLK_MULTIPLE_256,
 | 
					      .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 SOC_I2S_SUPPORTS_DAC
 | 
				
			||||||
  if (this_speaker->internal_dac_mode_ != I2S_DAC_CHANNEL_DISABLE) {
 | 
					  if (this_speaker->internal_dac_mode_ != I2S_DAC_CHANNEL_DISABLE) {
 | 
				
			||||||
@@ -114,10 +129,11 @@ void I2SAudioSpeaker::player_task(void *params) {
 | 
				
			|||||||
  event.type = TaskEventType::STARTED;
 | 
					  event.type = TaskEventType::STARTED;
 | 
				
			||||||
  xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
 | 
					  xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  int16_t buffer[BUFFER_SIZE / 2];
 | 
					  int32_t buffer[BUFFER_SIZE];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  while (true) {
 | 
					  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
 | 
					      break;  // End of audio from main thread
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (data_event.stop) {
 | 
					    if (data_event.stop) {
 | 
				
			||||||
@@ -125,17 +141,28 @@ void I2SAudioSpeaker::player_task(void *params) {
 | 
				
			|||||||
      xQueueReset(this_speaker->buffer_queue_);  // Flush queue
 | 
					      xQueueReset(this_speaker->buffer_queue_);  // Flush queue
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    size_t bytes_written;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    memmove(buffer, data_event.data, data_event.len);
 | 
					    const uint8_t *data = data_event.data;
 | 
				
			||||||
    size_t remaining = data_event.len / 2;
 | 
					    size_t remaining = data_event.len;
 | 
				
			||||||
    size_t current = 0;
 | 
					    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) {
 | 
					    while (remaining != 0) {
 | 
				
			||||||
      uint32_t sample = (buffer[current] << 16) | (buffer[current] & 0xFFFF);
 | 
					      size_t bytes_written;
 | 
				
			||||||
 | 
					      esp_err_t err =
 | 
				
			||||||
      esp_err_t err = i2s_write(this_speaker->parent_->get_port(), &sample, sizeof(sample), &bytes_written,
 | 
					          i2s_write(this_speaker->parent_->get_port(), data, remaining, &bytes_written, (32 / portTICK_PERIOD_MS));
 | 
				
			||||||
                                (10 / portTICK_PERIOD_MS));
 | 
					 | 
				
			||||||
      if (err != ESP_OK) {
 | 
					      if (err != ESP_OK) {
 | 
				
			||||||
        event = {.type = TaskEventType::WARNING, .err = err};
 | 
					        event = {.type = TaskEventType::WARNING, .err = err};
 | 
				
			||||||
        if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
 | 
					        if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
 | 
				
			||||||
@@ -143,21 +170,8 @@ void I2SAudioSpeaker::player_task(void *params) {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        continue;
 | 
					        continue;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (bytes_written != sizeof(sample)) {
 | 
					      data += bytes_written;
 | 
				
			||||||
        event = {.type = TaskEventType::WARNING, .err = ESP_FAIL};
 | 
					      remaining -= bytes_written;
 | 
				
			||||||
        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");
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -213,13 +227,11 @@ void I2SAudioSpeaker::watch_() {
 | 
				
			|||||||
      case TaskEventType::STARTED:
 | 
					      case TaskEventType::STARTED:
 | 
				
			||||||
        ESP_LOGD(TAG, "Started I2S Audio Speaker");
 | 
					        ESP_LOGD(TAG, "Started I2S Audio Speaker");
 | 
				
			||||||
        this->state_ = speaker::STATE_RUNNING;
 | 
					        this->state_ = speaker::STATE_RUNNING;
 | 
				
			||||||
 | 
					        this->status_clear_warning();
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      case TaskEventType::STOPPING:
 | 
					      case TaskEventType::STOPPING:
 | 
				
			||||||
        ESP_LOGD(TAG, "Stopping I2S Audio Speaker");
 | 
					        ESP_LOGD(TAG, "Stopping I2S Audio Speaker");
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      case TaskEventType::PLAYING:
 | 
					 | 
				
			||||||
        this->status_clear_warning();
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
      case TaskEventType::STOPPED:
 | 
					      case TaskEventType::STOPPED:
 | 
				
			||||||
        this->state_ = speaker::STATE_STOPPED;
 | 
					        this->state_ = speaker::STATE_STOPPED;
 | 
				
			||||||
        vTaskDelete(this->player_task_handle_);
 | 
					        vTaskDelete(this->player_task_handle_);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,7 +21,6 @@ static const size_t BUFFER_SIZE = 1024;
 | 
				
			|||||||
enum class TaskEventType : uint8_t {
 | 
					enum class TaskEventType : uint8_t {
 | 
				
			||||||
  STARTING = 0,
 | 
					  STARTING = 0,
 | 
				
			||||||
  STARTED,
 | 
					  STARTED,
 | 
				
			||||||
  PLAYING,
 | 
					 | 
				
			||||||
  STOPPING,
 | 
					  STOPPING,
 | 
				
			||||||
  STOPPED,
 | 
					  STOPPED,
 | 
				
			||||||
  WARNING = 255,
 | 
					  WARNING = 255,
 | 
				
			||||||
@@ -38,18 +37,18 @@ struct DataEvent {
 | 
				
			|||||||
  uint8_t data[BUFFER_SIZE];
 | 
					  uint8_t data[BUFFER_SIZE];
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAudioOut {
 | 
					class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Component {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  float get_setup_priority() const override { return esphome::setup_priority::LATE; }
 | 
					  float get_setup_priority() const override { return esphome::setup_priority::LATE; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void setup() override;
 | 
					  void setup() override;
 | 
				
			||||||
  void loop() override;
 | 
					  void loop() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_timeout(uint32_t ms) { this->timeout_ = ms; }
 | 
				
			||||||
  void set_dout_pin(uint8_t pin) { this->dout_pin_ = pin; }
 | 
					  void set_dout_pin(uint8_t pin) { this->dout_pin_ = pin; }
 | 
				
			||||||
#if SOC_I2S_SUPPORTS_DAC
 | 
					#if SOC_I2S_SUPPORTS_DAC
 | 
				
			||||||
  void set_internal_dac_mode(i2s_dac_mode_t mode) { this->internal_dac_mode_ = mode; }
 | 
					  void set_internal_dac_mode(i2s_dac_mode_t mode) { this->internal_dac_mode_ = mode; }
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
  void set_external_dac_channels(uint8_t channels) { this->external_dac_channels_ = channels; }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void start() override;
 | 
					  void start() override;
 | 
				
			||||||
  void stop() override;
 | 
					  void stop() override;
 | 
				
			||||||
@@ -70,13 +69,13 @@ class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAud
 | 
				
			|||||||
  QueueHandle_t buffer_queue_;
 | 
					  QueueHandle_t buffer_queue_;
 | 
				
			||||||
  QueueHandle_t event_queue_;
 | 
					  QueueHandle_t event_queue_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint32_t timeout_{0};
 | 
				
			||||||
  uint8_t dout_pin_{0};
 | 
					  uint8_t dout_pin_{0};
 | 
				
			||||||
  bool task_created_{false};
 | 
					  bool task_created_{false};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#if SOC_I2S_SUPPORTS_DAC
 | 
					#if SOC_I2S_SUPPORTS_DAC
 | 
				
			||||||
  i2s_dac_mode_t internal_dac_mode_{I2S_DAC_CHANNEL_DISABLE};
 | 
					  i2s_dac_mode_t internal_dac_mode_{I2S_DAC_CHANNEL_DISABLE};
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
  uint8_t external_dac_channels_;
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace i2s_audio
 | 
					}  // namespace i2s_audio
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,6 +32,12 @@ void MAX31856Sensor::dump_config() {
 | 
				
			|||||||
  LOG_PIN("  CS Pin: ", this->cs_);
 | 
					  LOG_PIN("  CS Pin: ", this->cs_);
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "  Mains Filter: %s",
 | 
					  ESP_LOGCONFIG(TAG, "  Mains Filter: %s",
 | 
				
			||||||
                (filter_ == FILTER_60HZ ? "60 Hz" : (filter_ == FILTER_50HZ ? "50 Hz" : "Unknown!")));
 | 
					                (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);
 | 
					  LOG_UPDATE_INTERVAL(this);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -129,7 +135,12 @@ void MAX31856Sensor::clear_fault_() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void MAX31856Sensor::set_thermocouple_type_() {
 | 
					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);
 | 
					  ESP_LOGCONFIG(TAG, "set_thermocouple_type_: 0x%02X", type);
 | 
				
			||||||
  uint8_t t = this->read_register_(MAX31856_CR1_REG);
 | 
					  uint8_t t = this->read_register_(MAX31856_CR1_REG);
 | 
				
			||||||
  t &= 0xF0;  // mask off bottom 4 bits
 | 
					  t &= 0xF0;  // mask off bottom 4 bits
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -50,7 +50,6 @@ enum MAX31856Registers {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Multiple types of thermocouples supported by the chip.
 | 
					 * Multiple types of thermocouples supported by the chip.
 | 
				
			||||||
 * Currently only K type implemented here.
 | 
					 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
enum MAX31856ThermocoupleType {
 | 
					enum MAX31856ThermocoupleType {
 | 
				
			||||||
  MAX31856_TCTYPE_B = 0b0000,   // 0x00
 | 
					  MAX31856_TCTYPE_B = 0b0000,   // 0x00
 | 
				
			||||||
@@ -78,11 +77,15 @@ class MAX31856Sensor : public sensor::Sensor,
 | 
				
			|||||||
  void setup() override;
 | 
					  void setup() override;
 | 
				
			||||||
  void dump_config() override;
 | 
					  void dump_config() override;
 | 
				
			||||||
  float get_setup_priority() const 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;
 | 
					  void update() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  MAX31856ConfigFilter filter_;
 | 
					  MAX31856ConfigFilter filter_;
 | 
				
			||||||
 | 
					  MAX31856ThermocoupleType thermocouple_type_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  uint8_t read_register_(uint8_t reg);
 | 
					  uint8_t read_register_(uint8_t reg);
 | 
				
			||||||
  uint32_t read_register24_(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
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    CONF_MAINS_FILTER,
 | 
					    CONF_MAINS_FILTER,
 | 
				
			||||||
 | 
					    CONF_THERMOCOUPLE_TYPE,
 | 
				
			||||||
    DEVICE_CLASS_TEMPERATURE,
 | 
					    DEVICE_CLASS_TEMPERATURE,
 | 
				
			||||||
    STATE_CLASS_MEASUREMENT,
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
    UNIT_CELSIUS,
 | 
					    UNIT_CELSIUS,
 | 
				
			||||||
@@ -18,6 +19,17 @@ FILTER = {
 | 
				
			|||||||
    50: MAX31865ConfigFilter.FILTER_50HZ,
 | 
					    50: MAX31865ConfigFilter.FILTER_50HZ,
 | 
				
			||||||
    60: MAX31865ConfigFilter.FILTER_60HZ,
 | 
					    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 = (
 | 
					CONFIG_SCHEMA = (
 | 
				
			||||||
    sensor.sensor_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(cv.polling_component_schema("60s"))
 | 
				
			||||||
    .extend(spi.spi_device_schema())
 | 
					    .extend(spi.spi_device_schema())
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@@ -44,3 +63,4 @@ async def to_code(config):
 | 
				
			|||||||
    await cg.register_component(var, config)
 | 
					    await cg.register_component(var, config)
 | 
				
			||||||
    await spi.register_spi_device(var, config)
 | 
					    await spi.register_spi_device(var, config)
 | 
				
			||||||
    cg.add(var.set_filter(config[CONF_MAINS_FILTER]))
 | 
					    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.codegen as cg
 | 
				
			||||||
import esphome.config_validation as cv
 | 
					 | 
				
			||||||
from esphome.components import i2c, sensor
 | 
					from esphome.components import i2c, sensor
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    CONF_ID,
 | 
					    CONF_ID,
 | 
				
			||||||
 | 
					    CONF_THERMOCOUPLE_TYPE,
 | 
				
			||||||
    DEVICE_CLASS_TEMPERATURE,
 | 
					    DEVICE_CLASS_TEMPERATURE,
 | 
				
			||||||
    STATE_CLASS_MEASUREMENT,
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
    UNIT_CELSIUS,
 | 
					    UNIT_CELSIUS,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONF_THERMOCOUPLE_TYPE = "thermocouple_type"
 | 
					 | 
				
			||||||
CONF_HOT_JUNCTION = "hot_junction"
 | 
					CONF_HOT_JUNCTION = "hot_junction"
 | 
				
			||||||
CONF_COLD_JUNCTION = "cold_junction"
 | 
					CONF_COLD_JUNCTION = "cold_junction"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,6 +21,7 @@ from .const import (
 | 
				
			|||||||
    CONF_CUSTOM_COMMAND,
 | 
					    CONF_CUSTOM_COMMAND,
 | 
				
			||||||
    CONF_FORCE_NEW_RANGE,
 | 
					    CONF_FORCE_NEW_RANGE,
 | 
				
			||||||
    CONF_MODBUS_CONTROLLER_ID,
 | 
					    CONF_MODBUS_CONTROLLER_ID,
 | 
				
			||||||
 | 
					    CONF_MAX_CMD_RETRIES,
 | 
				
			||||||
    CONF_ON_COMMAND_SENT,
 | 
					    CONF_ON_COMMAND_SENT,
 | 
				
			||||||
    CONF_REGISTER_COUNT,
 | 
					    CONF_REGISTER_COUNT,
 | 
				
			||||||
    CONF_REGISTER_TYPE,
 | 
					    CONF_REGISTER_TYPE,
 | 
				
			||||||
@@ -131,6 +132,7 @@ CONFIG_SCHEMA = cv.All(
 | 
				
			|||||||
            cv.Optional(
 | 
					            cv.Optional(
 | 
				
			||||||
                CONF_COMMAND_THROTTLE, default="0ms"
 | 
					                CONF_COMMAND_THROTTLE, default="0ms"
 | 
				
			||||||
            ): cv.positive_time_period_milliseconds,
 | 
					            ): 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_OFFLINE_SKIP_UPDATES, default=0): cv.positive_int,
 | 
				
			||||||
            cv.Optional(
 | 
					            cv.Optional(
 | 
				
			||||||
                CONF_SERVER_REGISTERS,
 | 
					                CONF_SERVER_REGISTERS,
 | 
				
			||||||
@@ -257,6 +259,7 @@ async def to_code(config):
 | 
				
			|||||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
					    var = cg.new_Pvariable(config[CONF_ID])
 | 
				
			||||||
    cg.add(var.set_allow_duplicate_commands(config[CONF_ALLOW_DUPLICATE_COMMANDS]))
 | 
					    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_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]))
 | 
					    cg.add(var.set_offline_skip_updates(config[CONF_OFFLINE_SKIP_UPDATES]))
 | 
				
			||||||
    if CONF_SERVER_REGISTERS in config:
 | 
					    if CONF_SERVER_REGISTERS in config:
 | 
				
			||||||
        for server_register in config[CONF_SERVER_REGISTERS]:
 | 
					        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_OFFLINE_SKIP_UPDATES = "offline_skip_updates"
 | 
				
			||||||
CONF_CUSTOM_COMMAND = "custom_command"
 | 
					CONF_CUSTOM_COMMAND = "custom_command"
 | 
				
			||||||
CONF_FORCE_NEW_RANGE = "force_new_range"
 | 
					CONF_FORCE_NEW_RANGE = "force_new_range"
 | 
				
			||||||
 | 
					CONF_MAX_CMD_RETRIES = "max_cmd_retries"
 | 
				
			||||||
CONF_MODBUS_CONTROLLER_ID = "modbus_controller_id"
 | 
					CONF_MODBUS_CONTROLLER_ID = "modbus_controller_id"
 | 
				
			||||||
CONF_MODBUS_FUNCTIONCODE = "modbus_functioncode"
 | 
					CONF_MODBUS_FUNCTIONCODE = "modbus_functioncode"
 | 
				
			||||||
CONF_ON_COMMAND_SENT = "on_command_sent"
 | 
					CONF_ON_COMMAND_SENT = "on_command_sent"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,11 +18,11 @@ void ModbusController::setup() { this->create_register_ranges_(); }
 | 
				
			|||||||
bool ModbusController::send_next_command_() {
 | 
					bool ModbusController::send_next_command_() {
 | 
				
			||||||
  uint32_t last_send = millis() - this->last_command_timestamp_;
 | 
					  uint32_t last_send = millis() - this->last_command_timestamp_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if ((last_send > this->command_throttle_) && !waiting_for_response() && !command_queue_.empty()) {
 | 
					  if ((last_send > this->command_throttle_) && !waiting_for_response() && !this->command_queue_.empty()) {
 | 
				
			||||||
    auto &command = command_queue_.front();
 | 
					    auto &command = this->command_queue_.front();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // remove from queue if command was sent too often
 | 
					    // 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_) {
 | 
					      if (!this->module_offline_) {
 | 
				
			||||||
        ESP_LOGW(TAG, "Modbus device=%d set offline", this->address_);
 | 
					        ESP_LOGW(TAG, "Modbus device=%d set offline", this->address_);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -34,11 +34,9 @@ bool ModbusController::send_next_command_() {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      this->module_offline_ = true;
 | 
					      this->module_offline_ = true;
 | 
				
			||||||
      ESP_LOGD(
 | 
					      ESP_LOGD(TAG, "Modbus command to device=%d register=0x%02X no response received - removed from send queue",
 | 
				
			||||||
          TAG,
 | 
					               this->address_, command->register_address);
 | 
				
			||||||
          "Modbus command to device=%d register=0x%02X countdown=%d no response received - removed from send queue",
 | 
					      this->command_queue_.pop_front();
 | 
				
			||||||
          this->address_, command->register_address, command->send_countdown);
 | 
					 | 
				
			||||||
      command_queue_.pop_front();
 | 
					 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      ESP_LOGV(TAG, "Sending next modbus command to device %d register 0x%02X count %d", this->address_,
 | 
					      ESP_LOGV(TAG, "Sending next modbus command to device %d register 0x%02X count %d", this->address_,
 | 
				
			||||||
               command->register_address, command->register_count);
 | 
					               command->register_address, command->register_count);
 | 
				
			||||||
@@ -50,11 +48,11 @@ bool ModbusController::send_next_command_() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      // remove from queue if no handler is defined
 | 
					      // remove from queue if no handler is defined
 | 
				
			||||||
      if (!command->on_data_func) {
 | 
					      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
 | 
					// Queue incoming response
 | 
				
			||||||
@@ -77,7 +75,7 @@ void ModbusController::on_modbus_data(const std::vector<uint8_t> &data) {
 | 
				
			|||||||
    current_command->payload = data;
 | 
					    current_command->payload = data;
 | 
				
			||||||
    this->incoming_queue_.push(std::move(current_command));
 | 
					    this->incoming_queue_.push(std::move(current_command));
 | 
				
			||||||
    ESP_LOGV(TAG, "Modbus response queued");
 | 
					    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",
 | 
					             "payload size=%zu",
 | 
				
			||||||
             function_code, current_command->register_address, current_command->register_count,
 | 
					             function_code, current_command->register_address, current_command->register_count,
 | 
				
			||||||
             current_command->payload.size());
 | 
					             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_) {
 | 
					  if (!this->allow_duplicate_commands_) {
 | 
				
			||||||
    // check if this command is already qeued.
 | 
					    // check if this command is already qeued.
 | 
				
			||||||
    // not very effective but the queue is never really large
 | 
					    // 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)) {
 | 
					      if (item->is_equal(command)) {
 | 
				
			||||||
        ESP_LOGW(TAG, "Duplicate modbus command found: type=0x%x address=%u count=%u",
 | 
					        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);
 | 
					                 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) {
 | 
					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
 | 
					// Once we get a response to the command it is removed from the queue and the next command is send
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
void ModbusController::update() {
 | 
					void ModbusController::update() {
 | 
				
			||||||
  if (!command_queue_.empty()) {
 | 
					  if (!this->command_queue_.empty()) {
 | 
				
			||||||
    ESP_LOGV(TAG, "%zu modbus commands already in queue", command_queue_.size());
 | 
					    ESP_LOGV(TAG, "%zu modbus commands already in queue", this->command_queue_.size());
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    ESP_LOGV(TAG, "Updating modbus component");
 | 
					    ESP_LOGV(TAG, "Updating modbus component");
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -346,6 +344,8 @@ size_t ModbusController::create_register_ranges_() {
 | 
				
			|||||||
void ModbusController::dump_config() {
 | 
					void ModbusController::dump_config() {
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "ModbusController:");
 | 
					  ESP_LOGCONFIG(TAG, "ModbusController:");
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "  Address: 0x%02X", this->address_);
 | 
					  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
 | 
					#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "sensormap");
 | 
					  ESP_LOGCONFIG(TAG, "sensormap");
 | 
				
			||||||
  for (auto &it : sensorset_) {
 | 
					  for (auto &it : sensorset_) {
 | 
				
			||||||
@@ -560,8 +560,9 @@ bool ModbusCommandItem::send() {
 | 
				
			|||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    modbusdevice->send_raw(this->payload);
 | 
					    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);
 | 
					  this->send_count_++;
 | 
				
			||||||
  send_countdown--;
 | 
					  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;
 | 
					  return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -312,7 +312,6 @@ struct RegisterRange {
 | 
				
			|||||||
class ModbusCommandItem {
 | 
					class ModbusCommandItem {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  static const size_t MAX_PAYLOAD_BYTES = 240;
 | 
					  static const size_t MAX_PAYLOAD_BYTES = 240;
 | 
				
			||||||
  static const uint8_t MAX_SEND_REPEATS = 5;
 | 
					 | 
				
			||||||
  ModbusController *modbusdevice;
 | 
					  ModbusController *modbusdevice;
 | 
				
			||||||
  uint16_t register_address;
 | 
					  uint16_t register_address;
 | 
				
			||||||
  uint16_t register_count;
 | 
					  uint16_t register_count;
 | 
				
			||||||
@@ -322,9 +321,9 @@ class ModbusCommandItem {
 | 
				
			|||||||
      on_data_func;
 | 
					      on_data_func;
 | 
				
			||||||
  std::vector<uint8_t> payload = {};
 | 
					  std::vector<uint8_t> payload = {};
 | 
				
			||||||
  bool send();
 | 
					  bool send();
 | 
				
			||||||
  // wrong commands (esp. custom commands) can block the send queue
 | 
					  /// Check if the command should be retried based on the max_retries parameter
 | 
				
			||||||
  // limit the number of repeats
 | 
					  bool should_retry(uint8_t max_retries) { return this->send_count_ <= max_retries; };
 | 
				
			||||||
  uint8_t send_countdown{MAX_SEND_REPEATS};
 | 
					
 | 
				
			||||||
  /// factory methods
 | 
					  /// factory methods
 | 
				
			||||||
  /** Create modbus read command
 | 
					  /** Create modbus read command
 | 
				
			||||||
   *  Function code 02-04
 | 
					   *  Function code 02-04
 | 
				
			||||||
@@ -413,6 +412,11 @@ class ModbusCommandItem {
 | 
				
			|||||||
          &&handler = nullptr);
 | 
					          &&handler = nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bool is_equal(const ModbusCommandItem &other);
 | 
					  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.
 | 
					/** Modbus controller class.
 | 
				
			||||||
@@ -464,6 +468,10 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
 | 
				
			|||||||
  bool get_module_offline() { return module_offline_; }
 | 
					  bool get_module_offline() { return module_offline_; }
 | 
				
			||||||
  /// Set callback for commands
 | 
					  /// Set callback for commands
 | 
				
			||||||
  void add_on_command_sent_callback(std::function<void(int, int)> &&callback);
 | 
					  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:
 | 
					 protected:
 | 
				
			||||||
  /// parse sensormap_ and create range of sequential addresses
 | 
					  /// parse sensormap_ and create range of sequential addresses
 | 
				
			||||||
@@ -498,6 +506,8 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
 | 
				
			|||||||
  bool module_offline_;
 | 
					  bool module_offline_;
 | 
				
			||||||
  /// how many updates to skip if module is offline
 | 
					  /// how many updates to skip if module is offline
 | 
				
			||||||
  uint16_t offline_skip_updates_;
 | 
					  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_{};
 | 
					  CallbackManager<void(int, int)> command_sent_callback_{};
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,6 +17,7 @@ CONFIG_SCHEMA = cv.Schema(
 | 
				
			|||||||
            esp8266=False,
 | 
					            esp8266=False,
 | 
				
			||||||
            esp32=False,
 | 
					            esp32=False,
 | 
				
			||||||
            rp2040=False,
 | 
					            rp2040=False,
 | 
				
			||||||
 | 
					            bk72xx=False,
 | 
				
			||||||
        ): cv.All(
 | 
					        ): cv.All(
 | 
				
			||||||
            cv.boolean,
 | 
					            cv.boolean,
 | 
				
			||||||
            cv.Any(
 | 
					            cv.Any(
 | 
				
			||||||
@@ -25,6 +26,7 @@ CONFIG_SCHEMA = cv.Schema(
 | 
				
			|||||||
                    esp32_arduino=cv.Version(0, 0, 0),
 | 
					                    esp32_arduino=cv.Version(0, 0, 0),
 | 
				
			||||||
                    esp8266_arduino=cv.Version(0, 0, 0),
 | 
					                    esp8266_arduino=cv.Version(0, 0, 0),
 | 
				
			||||||
                    rp2040_arduino=cv.Version(0, 0, 0),
 | 
					                    rp2040_arduino=cv.Version(0, 0, 0),
 | 
				
			||||||
 | 
					                    bk72xx_libretiny=cv.Version(1, 7, 0),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
                cv.boolean_false,
 | 
					                cv.boolean_false,
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
@@ -52,3 +54,5 @@ async def to_code(config):
 | 
				
			|||||||
                cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_ENABLE_IPV6")
 | 
					                cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_ENABLE_IPV6")
 | 
				
			||||||
            if CORE.is_esp8266:
 | 
					            if CORE.is_esp8266:
 | 
				
			||||||
                cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_LWIP2_IPV6_LOW_MEMORY")
 | 
					                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
 | 
					  // Read incoming data
 | 
				
			||||||
  while (this->available()) {
 | 
					  while (this->available()) {
 | 
				
			||||||
    // The controller polls devices every 10 seconds, with around 200 ms between devices.
 | 
					    // The controller polls devices every 10 seconds in some units or continuously in others with around 200 ms between
 | 
				
			||||||
    // Remember timestamps so we can send our own packets when the bus is expected to be silent.
 | 
					    // 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;
 | 
					    this->last_rx_ = now;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    uint8_t byte;
 | 
					    uint8_t byte;
 | 
				
			||||||
@@ -60,7 +57,8 @@ void UponorSmatrixComponent::loop() {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Send packets during bus silence
 | 
					  // 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
 | 
					#ifdef USE_TIME
 | 
				
			||||||
    // Only build time packet when bus is silent and queue is empty to make sure we can send it right away
 | 
					    // 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_())
 | 
					    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_;
 | 
					  std::queue<std::vector<uint8_t>> tx_queue_;
 | 
				
			||||||
  uint32_t last_rx_;
 | 
					  uint32_t last_rx_;
 | 
				
			||||||
  uint32_t last_tx_;
 | 
					  uint32_t last_tx_;
 | 
				
			||||||
  uint32_t last_poll_start_;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_TIME
 | 
					#ifdef USE_TIME
 | 
				
			||||||
  time::RealTimeClock *time_id_{nullptr};
 | 
					  time::RealTimeClock *time_id_{nullptr};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -755,7 +755,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
 | 
				
			|||||||
          message = std::move(arg.value);
 | 
					          message = std::move(arg.value);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (code == "wake-word-timeout" || code == "wake_word_detection_aborted") {
 | 
					      if (code == "wake-word-timeout" || code == "wake_word_detection_aborted" || code == "no_wake_word") {
 | 
				
			||||||
        // Don't change state here since either the "tts-end" or "run-end" events will do it.
 | 
					        // Don't change state here since either the "tts-end" or "run-end" events will do it.
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      } else if (code == "wake-provider-missing" || code == "wake-engine-missing") {
 | 
					      } else if (code == "wake-provider-missing" || code == "wake-engine-missing") {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -85,7 +85,16 @@ bool WiFiComponent::wifi_sta_ip_config_(optional<ManualIP> manual_ip) {
 | 
				
			|||||||
network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() {
 | 
					network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() {
 | 
				
			||||||
  if (!this->has_sta())
 | 
					  if (!this->has_sta())
 | 
				
			||||||
    return {};
 | 
					    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_() {
 | 
					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;
 | 
					      s_sta_connecting = false;
 | 
				
			||||||
      break;
 | 
					      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: {
 | 
					    case ESPHOME_EVENT_ID_WIFI_STA_LOST_IP: {
 | 
				
			||||||
      ESP_LOGV(TAG, "Event: Lost IP");
 | 
					      ESP_LOGV(TAG, "Event: Lost IP");
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,9 @@ from esphome.const import CONF_NAME, CONF_PORT
 | 
				
			|||||||
wled_ns = cg.esphome_ns.namespace("wled")
 | 
					wled_ns = cg.esphome_ns.namespace("wled")
 | 
				
			||||||
WLEDLightEffect = wled_ns.class_("WLEDLightEffect", AddressableLightEffect)
 | 
					WLEDLightEffect = wled_ns.class_("WLEDLightEffect", AddressableLightEffect)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONFIG_SCHEMA = cv.All(cv.Schema({}), cv.only_with_arduino)
 | 
					AUTO_LOAD = ["socket"]
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = cv.Schema({})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONF_SYNC_GROUP_MASK = "sync_group_mask"
 | 
					CONF_SYNC_GROUP_MASK = "sync_group_mask"
 | 
				
			||||||
CONF_BLANK_ON_START = "blank_on_start"
 | 
					CONF_BLANK_ON_START = "blank_on_start"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,17 +1,7 @@
 | 
				
			|||||||
#ifdef USE_ARDUINO
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "wled_light_effect.h"
 | 
					#include "wled_light_effect.h"
 | 
				
			||||||
#include "esphome/core/log.h"
 | 
					
 | 
				
			||||||
#include "esphome/core/helpers.h"
 | 
					#include "esphome/core/helpers.h"
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
#ifdef USE_ESP32
 | 
					 | 
				
			||||||
#include <WiFi.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#ifdef USE_ESP8266
 | 
					 | 
				
			||||||
#include <ESP8266WiFi.h>
 | 
					 | 
				
			||||||
#include <WiFiUdp.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_BK72XX
 | 
					#ifdef USE_BK72XX
 | 
				
			||||||
#include <WiFiUdp.h>
 | 
					#include <WiFiUdp.h>
 | 
				
			||||||
@@ -43,9 +33,9 @@ void WLEDLightEffect::start() {
 | 
				
			|||||||
void WLEDLightEffect::stop() {
 | 
					void WLEDLightEffect::stop() {
 | 
				
			||||||
  AddressableLightEffect::stop();
 | 
					  AddressableLightEffect::stop();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (udp_) {
 | 
					  if (this->socket_) {
 | 
				
			||||||
    udp_->stop();
 | 
					    this->socket_->close();
 | 
				
			||||||
    udp_.reset();
 | 
					    this->socket_.reset();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -58,27 +48,48 @@ void WLEDLightEffect::blank_all_leds_(light::AddressableLight &it) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void WLEDLightEffect::apply(light::AddressableLight &it, const Color ¤t_color) {
 | 
					void WLEDLightEffect::apply(light::AddressableLight &it, const Color ¤t_color) {
 | 
				
			||||||
  // Init UDP lazily
 | 
					  // Init UDP lazily
 | 
				
			||||||
  if (!udp_) {
 | 
					  if (this->socket_ == nullptr) {
 | 
				
			||||||
    udp_ = make_unique<WiFiUDP>();
 | 
					    this->socket_ = socket::socket_ip(SOCK_DGRAM, IPPROTO_IP);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!udp_->begin(port_)) {
 | 
					    int enable = 1;
 | 
				
			||||||
      ESP_LOGW(TAG, "Cannot bind WLEDLightEffect to %d.", port_);
 | 
					    int err = this->socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
 | 
				
			||||||
 | 
					    if (err != 0) {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err);
 | 
				
			||||||
 | 
					      // we can still continue
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    err = this->socket_->setblocking(false);
 | 
				
			||||||
 | 
					    if (err != 0) {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err);
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    struct sockaddr_storage server;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_);
 | 
				
			||||||
 | 
					    if (sl == 0) {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno);
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    err = this->socket_->bind((struct sockaddr *) &server, sizeof(server));
 | 
				
			||||||
 | 
					    if (err != 0) {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno);
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  std::vector<uint8_t> payload;
 | 
					  std::vector<uint8_t> payload;
 | 
				
			||||||
  while (uint16_t packet_size = udp_->parsePacket()) {
 | 
					  uint8_t buf[1460];
 | 
				
			||||||
    payload.resize(packet_size);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!udp_->read(&payload[0], payload.size())) {
 | 
					  ssize_t len = this->socket_->read(buf, sizeof(buf));
 | 
				
			||||||
      continue;
 | 
					  if (len == -1) {
 | 
				
			||||||
    }
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  payload.resize(len);
 | 
				
			||||||
 | 
					  memmove(&payload[0], buf, len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!this->parse_frame_(it, &payload[0], payload.size())) {
 | 
					  if (!this->parse_frame_(it, &payload[0], payload.size())) {
 | 
				
			||||||
      ESP_LOGD(TAG, "Frame: Invalid (size=%zu, first=0x%02X).", payload.size(), payload[0]);
 | 
					    ESP_LOGD(TAG, "Frame: Invalid (size=%zu, first=0x%02X).", payload.size(), payload[0]);
 | 
				
			||||||
      continue;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // FIXME: Use roll-over safe arithmetic
 | 
					  // FIXME: Use roll-over safe arithmetic
 | 
				
			||||||
@@ -283,5 +294,3 @@ bool WLEDLightEffect::parse_dnrgb_frame_(light::AddressableLight &it, const uint
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
}  // namespace wled
 | 
					}  // namespace wled
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif  // USE_ARDUINO
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,12 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_ARDUINO
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "esphome/core/component.h"
 | 
					 | 
				
			||||||
#include "esphome/components/light/addressable_light_effect.h"
 | 
					#include "esphome/components/light/addressable_light_effect.h"
 | 
				
			||||||
 | 
					#include "esphome/components/socket/socket.h"
 | 
				
			||||||
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <vector>
 | 
					#include <vector>
 | 
				
			||||||
#include <memory>
 | 
					#include <memory>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class UDP;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace wled {
 | 
					namespace wled {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -34,7 +31,7 @@ class WLEDLightEffect : public light::AddressableLightEffect {
 | 
				
			|||||||
  bool parse_dnrgb_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size);
 | 
					  bool parse_dnrgb_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  uint16_t port_{0};
 | 
					  uint16_t port_{0};
 | 
				
			||||||
  std::unique_ptr<UDP> udp_;
 | 
					  std::unique_ptr<socket::Socket> socket_;
 | 
				
			||||||
  uint32_t blank_at_{0};
 | 
					  uint32_t blank_at_{0};
 | 
				
			||||||
  uint32_t dropped_{0};
 | 
					  uint32_t dropped_{0};
 | 
				
			||||||
  uint8_t sync_group_mask_{0};
 | 
					  uint8_t sync_group_mask_{0};
 | 
				
			||||||
@@ -43,5 +40,3 @@ class WLEDLightEffect : public light::AddressableLightEffect {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
}  // namespace wled
 | 
					}  // namespace wled
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif  // USE_ARDUINO
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
"""Constants used by esphome."""
 | 
					"""Constants used by esphome."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
__version__ = "2024.9.0-dev"
 | 
					__version__ = "2024.10.0-dev"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
 | 
					ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
 | 
				
			||||||
VALID_SUBSTITUTIONS_CHARACTERS = (
 | 
					VALID_SUBSTITUTIONS_CHARACTERS = (
 | 
				
			||||||
@@ -852,6 +852,7 @@ CONF_TEMPERATURE_STEP = "temperature_step"
 | 
				
			|||||||
CONF_TEXT = "text"
 | 
					CONF_TEXT = "text"
 | 
				
			||||||
CONF_TEXT_SENSORS = "text_sensors"
 | 
					CONF_TEXT_SENSORS = "text_sensors"
 | 
				
			||||||
CONF_THEN = "then"
 | 
					CONF_THEN = "then"
 | 
				
			||||||
 | 
					CONF_THERMOCOUPLE_TYPE = "thermocouple_type"
 | 
				
			||||||
CONF_THRESHOLD = "threshold"
 | 
					CONF_THRESHOLD = "threshold"
 | 
				
			||||||
CONF_THROTTLE = "throttle"
 | 
					CONF_THROTTLE = "throttle"
 | 
				
			||||||
CONF_TILT = "tilt"
 | 
					CONF_TILT = "tilt"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -90,7 +90,6 @@
 | 
				
			|||||||
// IDF-specific feature flags
 | 
					// IDF-specific feature flags
 | 
				
			||||||
#ifdef USE_ESP_IDF
 | 
					#ifdef USE_ESP_IDF
 | 
				
			||||||
#define USE_MQTT_IDF_ENQUEUE
 | 
					#define USE_MQTT_IDF_ENQUEUE
 | 
				
			||||||
#define USE_ESP_ADF
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ESP32-specific feature flags
 | 
					// ESP32-specific feature flags
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,39 +1,4 @@
 | 
				
			|||||||
dependencies:
 | 
					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:
 | 
					  esp-tflite-micro:
 | 
				
			||||||
    git: https://github.com/espressif/esp-tflite-micro.git
 | 
					    git: https://github.com/espressif/esp-tflite-micro.git
 | 
				
			||||||
    version: v1.3.1
 | 
					    version: v1.3.1
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,3 +10,4 @@ sensor:
 | 
				
			|||||||
    cs_pin: 12
 | 
					    cs_pin: 12
 | 
				
			||||||
    update_interval: 15s
 | 
					    update_interval: 15s
 | 
				
			||||||
    mains_filter: 50Hz
 | 
					    mains_filter: 50Hz
 | 
				
			||||||
 | 
					    thermocouple_type: N
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,3 +10,5 @@ sensor:
 | 
				
			|||||||
    cs_pin: 8
 | 
					    cs_pin: 8
 | 
				
			||||||
    update_interval: 15s
 | 
					    update_interval: 15s
 | 
				
			||||||
    mains_filter: 50Hz
 | 
					    mains_filter: 50Hz
 | 
				
			||||||
 | 
					    thermocouple_type: N
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,3 +10,5 @@ sensor:
 | 
				
			|||||||
    cs_pin: 8
 | 
					    cs_pin: 8
 | 
				
			||||||
    update_interval: 15s
 | 
					    update_interval: 15s
 | 
				
			||||||
    mains_filter: 50Hz
 | 
					    mains_filter: 50Hz
 | 
				
			||||||
 | 
					    thermocouple_type: N
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,3 +10,4 @@ sensor:
 | 
				
			|||||||
    cs_pin: 12
 | 
					    cs_pin: 12
 | 
				
			||||||
    update_interval: 15s
 | 
					    update_interval: 15s
 | 
				
			||||||
    mains_filter: 50Hz
 | 
					    mains_filter: 50Hz
 | 
				
			||||||
 | 
					    thermocouple_type: N
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,3 +10,5 @@ sensor:
 | 
				
			|||||||
    cs_pin: 15
 | 
					    cs_pin: 15
 | 
				
			||||||
    update_interval: 15s
 | 
					    update_interval: 15s
 | 
				
			||||||
    mains_filter: 50Hz
 | 
					    mains_filter: 50Hz
 | 
				
			||||||
 | 
					    thermocouple_type: N
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,3 +10,5 @@ sensor:
 | 
				
			|||||||
    cs_pin: 6
 | 
					    cs_pin: 6
 | 
				
			||||||
    update_interval: 15s
 | 
					    update_interval: 15s
 | 
				
			||||||
    mains_filter: 50Hz
 | 
					    mains_filter: 50Hz
 | 
				
			||||||
 | 
					    thermocouple_type: N
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,3 +29,4 @@ modbus_controller:
 | 
				
			|||||||
        value_type: S_DWORD_R
 | 
					        value_type: S_DWORD_R
 | 
				
			||||||
        read_lambda: |-
 | 
					        read_lambda: |-
 | 
				
			||||||
          return 42.3;
 | 
					          return 42.3;
 | 
				
			||||||
 | 
					    max_cmd_retries: 0
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,3 +13,4 @@ modbus_controller:
 | 
				
			|||||||
    address: 0x2
 | 
					    address: 0x2
 | 
				
			||||||
    modbus_id: mod_bus1
 | 
					    modbus_id: mod_bus1
 | 
				
			||||||
    allow_duplicate_commands: true
 | 
					    allow_duplicate_commands: true
 | 
				
			||||||
 | 
					    max_cmd_retries: 10
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,8 @@
 | 
				
			|||||||
substitutions:
 | 
					substitutions:
 | 
				
			||||||
  network_enable_ipv6: "false"
 | 
					  network_enable_ipv6: "true"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bk72xx:
 | 
				
			||||||
 | 
					  framework:
 | 
				
			||||||
 | 
					    version: 1.7.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<<: !include common.yaml
 | 
					<<: !include common.yaml
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,4 +21,3 @@ speaker:
 | 
				
			|||||||
    id: speaker_id
 | 
					    id: speaker_id
 | 
				
			||||||
    dac_type: external
 | 
					    dac_type: external
 | 
				
			||||||
    i2s_dout_pin: 13
 | 
					    i2s_dout_pin: 13
 | 
				
			||||||
    mode: mono
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,4 +21,3 @@ speaker:
 | 
				
			|||||||
    id: speaker_id
 | 
					    id: speaker_id
 | 
				
			||||||
    dac_type: external
 | 
					    dac_type: external
 | 
				
			||||||
    i2s_dout_pin: 3
 | 
					    i2s_dout_pin: 3
 | 
				
			||||||
    mode: mono
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,4 +21,3 @@ speaker:
 | 
				
			|||||||
    id: speaker_id
 | 
					    id: speaker_id
 | 
				
			||||||
    dac_type: external
 | 
					    dac_type: external
 | 
				
			||||||
    i2s_dout_pin: 3
 | 
					    i2s_dout_pin: 3
 | 
				
			||||||
    mode: mono
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,4 +21,3 @@ speaker:
 | 
				
			|||||||
    id: speaker_id
 | 
					    id: speaker_id
 | 
				
			||||||
    dac_type: external
 | 
					    dac_type: external
 | 
				
			||||||
    i2s_dout_pin: 13
 | 
					    i2s_dout_pin: 13
 | 
				
			||||||
    mode: mono
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,7 +28,6 @@ speaker:
 | 
				
			|||||||
    id: speaker_id
 | 
					    id: speaker_id
 | 
				
			||||||
    dac_type: external
 | 
					    dac_type: external
 | 
				
			||||||
    i2s_dout_pin: 12
 | 
					    i2s_dout_pin: 12
 | 
				
			||||||
    mode: mono
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
voice_assistant:
 | 
					voice_assistant:
 | 
				
			||||||
  microphone: mic_id_external
 | 
					  microphone: mic_id_external
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,7 +28,6 @@ speaker:
 | 
				
			|||||||
    id: speaker_id
 | 
					    id: speaker_id
 | 
				
			||||||
    dac_type: external
 | 
					    dac_type: external
 | 
				
			||||||
    i2s_dout_pin: 2
 | 
					    i2s_dout_pin: 2
 | 
				
			||||||
    mode: mono
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
voice_assistant:
 | 
					voice_assistant:
 | 
				
			||||||
  microphone: mic_id_external
 | 
					  microphone: mic_id_external
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,7 +28,6 @@ speaker:
 | 
				
			|||||||
    id: speaker_id
 | 
					    id: speaker_id
 | 
				
			||||||
    dac_type: external
 | 
					    dac_type: external
 | 
				
			||||||
    i2s_dout_pin: 2
 | 
					    i2s_dout_pin: 2
 | 
				
			||||||
    mode: mono
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
voice_assistant:
 | 
					voice_assistant:
 | 
				
			||||||
  microphone: mic_id_external
 | 
					  microphone: mic_id_external
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,7 +28,6 @@ speaker:
 | 
				
			|||||||
    id: speaker_id
 | 
					    id: speaker_id
 | 
				
			||||||
    dac_type: external
 | 
					    dac_type: external
 | 
				
			||||||
    i2s_dout_pin: 12
 | 
					    i2s_dout_pin: 12
 | 
				
			||||||
    mode: mono
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
voice_assistant:
 | 
					voice_assistant:
 | 
				
			||||||
  microphone: mic_id_external
 | 
					  microphone: mic_id_external
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user