diff --git a/.github/actions/build-image/action.yaml b/.github/actions/build-image/action.yaml index 56be20bd87..d277ec06c7 100644 --- a/.github/actions/build-image/action.yaml +++ b/.github/actions/build-image/action.yaml @@ -47,6 +47,9 @@ runs: - name: Build and push to ghcr by digest id: build-ghcr uses: docker/build-push-action@v6.7.0 + env: + DOCKER_BUILD_SUMMARY: false + DOCKER_BUILD_RECORD_UPLOAD: false with: context: . file: ./docker/Dockerfile @@ -70,6 +73,9 @@ runs: - name: Build and push to dockerhub by digest id: build-dockerhub uses: docker/build-push-action@v6.7.0 + env: + DOCKER_BUILD_SUMMARY: false + DOCKER_BUILD_RECORD_UPLOAD: false with: context: . file: ./docker/Dockerfile diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000000..ddeb0a99d2 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -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}}" diff --git a/docker/Dockerfile b/docker/Dockerfile index 4393d5a447..e255f4e2fc 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -49,7 +49,7 @@ RUN \ zlib1g-dev=1:1.2.13.dfsg-1 \ libjpeg-dev=1:2.1.5-2 \ libfreetype-dev=2.12.1+dfsg-5+deb12u3 \ - libssl-dev=3.0.14-1~deb12u1 \ + libssl-dev=3.0.14-1~deb12u2 \ libffi-dev=3.4.4-1 \ libopenjp2-7=2.5.0-2 \ libtiff6=4.5.0-6+deb12u1 \ @@ -96,14 +96,19 @@ RUN \ # First install requirements to leverage caching when requirements don't change # tmpfs is for https://github.com/rust-lang/cargo/issues/8719 -COPY requirements.txt requirements_optional.txt script/platformio_install_deps.py platformio.ini / +COPY requirements.txt requirements_optional.txt / RUN --mount=type=tmpfs,target=/root/.cargo if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \ - export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \ + curl -L https://www.piwheels.org/cp311/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl -o /tmp/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl \ + && pip3 install --break-system-packages --no-cache-dir /tmp/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl \ + && rm /tmp/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl \ + && export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \ fi; \ CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse CARGO_HOME=/root/.cargo \ pip3 install \ - --break-system-packages --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ - && /platformio_install_deps.py /platformio.ini --libraries + --break-system-packages --no-cache-dir -r /requirements.txt -r /requirements_optional.txt + +COPY script/platformio_install_deps.py platformio.ini / +RUN /platformio_install_deps.py /platformio.ini --libraries # Avoid unsafe git error when container user and file config volume permissions don't match RUN git config --system --add safe.directory '*' diff --git a/esphome/components/async_tcp/__init__.py b/esphome/components/async_tcp/__init__.py index eae8c0e2df..99e250b6fc 100644 --- a/esphome/components/async_tcp/__init__.py +++ b/esphome/components/async_tcp/__init__.py @@ -1,13 +1,13 @@ # Dummy integration to allow relying on AsyncTCP import esphome.codegen as cg import esphome.config_validation as cv -from esphome.core import CORE, coroutine_with_priority from esphome.const import ( + PLATFORM_BK72XX, PLATFORM_ESP32, PLATFORM_ESP8266, - PLATFORM_BK72XX, PLATFORM_RTL87XX, ) +from esphome.core import CORE, coroutine_with_priority CODEOWNERS = ["@OttoWinter"] @@ -22,7 +22,7 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): if CORE.is_esp32 or CORE.is_libretiny: # https://github.com/esphome/AsyncTCP/blob/master/library.json - cg.add_library("esphome/AsyncTCP-esphome", "2.1.3") + cg.add_library("esphome/AsyncTCP-esphome", "2.1.4") elif CORE.is_esp8266: # https://github.com/esphome/ESPAsyncTCP cg.add_library("esphome/ESPAsyncTCP-esphome", "2.0.0") diff --git a/esphome/components/bl0942/bl0942.cpp b/esphome/components/bl0942/bl0942.cpp index af56e77de6..e6f96c1b19 100644 --- a/esphome/components/bl0942/bl0942.cpp +++ b/esphome/components/bl0942/bl0942.cpp @@ -137,7 +137,8 @@ void BL0942::setup() { } this->write_reg_(BL0942_REG_USR_WRPROT, BL0942_REG_USR_WRPROT_MAGIC); - this->write_reg_(BL0942_REG_SOFT_RESET, BL0942_REG_SOFT_RESET_MAGIC); + if (this->reset_) + this->write_reg_(BL0942_REG_SOFT_RESET, BL0942_REG_SOFT_RESET_MAGIC); uint32_t mode = BL0942_REG_MODE_DEFAULT; mode |= BL0942_REG_MODE_RMS_UPDATE_SEL; /* 800ms refresh time */ @@ -196,6 +197,7 @@ void BL0942::received_package_(DataPacket *data) { void BL0942::dump_config() { // NOLINT(readability-function-cognitive-complexity) ESP_LOGCONFIG(TAG, "BL0942:"); + ESP_LOGCONFIG(TAG, " Reset: %s", TRUEFALSE(this->reset_)); ESP_LOGCONFIG(TAG, " Address: %d", this->address_); ESP_LOGCONFIG(TAG, " Nominal line frequency: %d Hz", this->line_freq_); ESP_LOGCONFIG(TAG, " Current reference: %f", this->current_reference_); diff --git a/esphome/components/bl0942/bl0942.h b/esphome/components/bl0942/bl0942.h index 1dc930183f..37b884e6ca 100644 --- a/esphome/components/bl0942/bl0942.h +++ b/esphome/components/bl0942/bl0942.h @@ -93,6 +93,7 @@ class BL0942 : public PollingComponent, public uart::UARTDevice { void set_frequency_sensor(sensor::Sensor *frequency_sensor) { frequency_sensor_ = frequency_sensor; } void set_line_freq(LineFrequency freq) { this->line_freq_ = freq; } void set_address(uint8_t address) { this->address_ = address; } + void set_reset(bool reset) { this->reset_ = reset; } void set_current_reference(float current_ref) { this->current_reference_ = current_ref; this->current_reference_set_ = true; @@ -137,6 +138,7 @@ class BL0942 : public PollingComponent, public uart::UARTDevice { float energy_reference_ = BL0942_EREF; bool energy_reference_set_ = false; uint8_t address_ = 0; + bool reset_ = false; LineFrequency line_freq_ = LINE_FREQUENCY_50HZ; uint32_t rx_start_ = 0; uint32_t prev_cf_cnt_ = 0; diff --git a/esphome/components/bl0942/sensor.py b/esphome/components/bl0942/sensor.py index 3574443636..550f534b74 100644 --- a/esphome/components/bl0942/sensor.py +++ b/esphome/components/bl0942/sensor.py @@ -27,6 +27,7 @@ from esphome.const import ( CONF_CURRENT_REFERENCE = "current_reference" CONF_ENERGY_REFERENCE = "energy_reference" CONF_POWER_REFERENCE = "power_reference" +CONF_RESET = "reset" CONF_VOLTAGE_REFERENCE = "voltage_reference" DEPENDENCIES = ["uart"] @@ -58,19 +59,19 @@ CONFIG_SCHEMA = ( ), cv.Optional(CONF_POWER): sensor.sensor_schema( unit_of_measurement=UNIT_WATT, - accuracy_decimals=0, + accuracy_decimals=1, device_class=DEVICE_CLASS_POWER, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ENERGY): sensor.sensor_schema( unit_of_measurement=UNIT_KILOWATT_HOURS, - accuracy_decimals=0, + accuracy_decimals=3, device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( unit_of_measurement=UNIT_HERTZ, - accuracy_decimals=0, + accuracy_decimals=2, device_class=DEVICE_CLASS_FREQUENCY, state_class=STATE_CLASS_MEASUREMENT, ), @@ -82,6 +83,7 @@ CONFIG_SCHEMA = ( ), ), cv.Optional(CONF_ADDRESS, default=0): cv.int_range(min=0, max=3), + cv.Optional(CONF_RESET, default=True): cv.boolean, cv.Optional(CONF_CURRENT_REFERENCE): cv.float_, cv.Optional(CONF_ENERGY_REFERENCE): cv.float_, cv.Optional(CONF_POWER_REFERENCE): cv.float_, @@ -115,6 +117,7 @@ async def to_code(config): cg.add(var.set_frequency_sensor(sens)) cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY])) cg.add(var.set_address(config[CONF_ADDRESS])) + cg.add(var.set_reset(config[CONF_RESET])) if (current_reference := config.get(CONF_CURRENT_REFERENCE, None)) is not None: cg.add(var.set_current_reference(current_reference)) if (voltage_reference := config.get(CONF_VOLTAGE_REFERENCE, None)) is not None: diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp index f382730912..193ea1d4e5 100644 --- a/esphome/components/dsmr/dsmr.cpp +++ b/esphome/components/dsmr/dsmr.cpp @@ -256,6 +256,7 @@ bool Dsmr::parse_telegram() { MyData data; ESP_LOGV(TAG, "Trying to parse telegram"); this->stop_requesting_data_(); + ::dsmr::ParseResult res = ::dsmr::P1Parser::parse(&data, this->telegram_, this->bytes_read_, false, this->crc_check_); // Parse telegram according to data definition. Ignore unknown values. @@ -267,6 +268,11 @@ bool Dsmr::parse_telegram() { } else { this->status_clear_warning(); this->publish_sensors(data); + + // publish the telegram, after publishing the sensors so it can also trigger action based on latest values + if (this->s_telegram_ != nullptr) { + this->s_telegram_->publish_state(std::string(this->telegram_, this->bytes_read_)); + } return true; } } diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h index 6621d02cae..7304737b50 100644 --- a/esphome/components/dsmr/dsmr.h +++ b/esphome/components/dsmr/dsmr.h @@ -85,6 +85,9 @@ class Dsmr : public Component, public uart::UARTDevice { void set_##s(text_sensor::TextSensor *sensor) { s_##s##_ = sensor; } DSMR_TEXT_SENSOR_LIST(DSMR_SET_TEXT_SENSOR, ) + // handled outside dsmr + void set_telegram(text_sensor::TextSensor *sensor) { s_telegram_ = sensor; } + protected: void receive_telegram_(); void receive_encrypted_telegram_(); @@ -124,6 +127,9 @@ class Dsmr : public Component, public uart::UARTDevice { bool header_found_{false}; bool footer_found_{false}; + // handled outside dsmr + text_sensor::TextSensor *s_telegram_{nullptr}; + // Sensor member pointers #define DSMR_DECLARE_SENSOR(s) sensor::Sensor *s_##s##_{nullptr}; DSMR_SENSOR_LIST(DSMR_DECLARE_SENSOR, ) diff --git a/esphome/components/dsmr/text_sensor.py b/esphome/components/dsmr/text_sensor.py index 202cc07020..7c13fe7d58 100644 --- a/esphome/components/dsmr/text_sensor.py +++ b/esphome/components/dsmr/text_sensor.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import text_sensor - +from esphome.const import CONF_INTERNAL from . import Dsmr, CONF_DSMR_ID AUTO_LOAD = ["dsmr"] @@ -22,6 +22,9 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional("water_equipment_id"): text_sensor.text_sensor_schema(), cv.Optional("sub_equipment_id"): text_sensor.text_sensor_schema(), cv.Optional("gas_delivered_text"): text_sensor.text_sensor_schema(), + cv.Optional("telegram"): text_sensor.text_sensor_schema().extend( + {cv.Optional(CONF_INTERNAL, default=True): cv.boolean} + ), } ).extend(cv.COMPONENT_SCHEMA) @@ -37,7 +40,9 @@ async def to_code(config): if id and id.type == text_sensor.TextSensor: var = await text_sensor.new_text_sensor(conf) cg.add(getattr(hub, f"set_{key}")(var)) - text_sensors.append(f"F({key})") + if key != "telegram": + # telegram is not handled by dsmr + text_sensors.append(f"F({key})") if text_sensors: cg.add_define( diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index 4187429412..2f1f9b90bb 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -140,6 +140,8 @@ CONF_TEST_PATTERN = "test_pattern" # framerates CONF_MAX_FRAMERATE = "max_framerate" CONF_IDLE_FRAMERATE = "idle_framerate" +# frame buffer +CONF_FRAME_BUFFER_COUNT = "frame_buffer_count" # stream trigger CONF_ON_STREAM_START = "on_stream_start" @@ -213,6 +215,7 @@ CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( cv.Optional(CONF_IDLE_FRAMERATE, default="0.1 fps"): cv.All( cv.framerate, cv.Range(min=0, max=1) ), + cv.Optional(CONF_FRAME_BUFFER_COUNT, default=1): cv.int_range(min=1, max=2), cv.Optional(CONF_ON_STREAM_START): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( @@ -285,6 +288,7 @@ async def to_code(config): cg.add(var.set_idle_update_interval(0)) else: cg.add(var.set_idle_update_interval(1000 / config[CONF_IDLE_FRAMERATE])) + cg.add(var.set_frame_buffer_count(config[CONF_FRAME_BUFFER_COUNT])) cg.add(var.set_frame_size(config[CONF_RESOLUTION])) cg.add_define("USE_ESP32_CAMERA") diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index 555f6ca5f1..e9e9d3cffb 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -127,7 +127,7 @@ void ESP32Camera::dump_config() { sensor_t *s = esp_camera_sensor_get(); auto st = s->status; ESP_LOGCONFIG(TAG, " JPEG Quality: %u", st.quality); - // ESP_LOGCONFIG(TAG, " Framebuffer Count: %u", conf.fb_count); + ESP_LOGCONFIG(TAG, " Framebuffer Count: %u", conf.fb_count); ESP_LOGCONFIG(TAG, " Contrast: %d", st.contrast); ESP_LOGCONFIG(TAG, " Brightness: %d", st.brightness); ESP_LOGCONFIG(TAG, " Saturation: %d", st.saturation); @@ -212,6 +212,8 @@ ESP32Camera::ESP32Camera() { this->config_.frame_size = FRAMESIZE_VGA; // 640x480 this->config_.jpeg_quality = 10; this->config_.fb_count = 1; + this->config_.grab_mode = CAMERA_GRAB_WHEN_EMPTY; + this->config_.fb_location = CAMERA_FB_IN_PSRAM; global_esp32_camera = this; } @@ -333,6 +335,12 @@ void ESP32Camera::set_max_update_interval(uint32_t max_update_interval) { void ESP32Camera::set_idle_update_interval(uint32_t idle_update_interval) { this->idle_update_interval_ = idle_update_interval; } +/* set frame buffer parameters */ +void ESP32Camera::set_frame_buffer_mode(camera_grab_mode_t mode) { this->config_.grab_mode = mode; } +void ESP32Camera::set_frame_buffer_count(uint8_t fb_count) { + this->config_.fb_count = fb_count; + this->set_frame_buffer_mode(fb_count > 1 ? CAMERA_GRAB_LATEST : CAMERA_GRAB_WHEN_EMPTY); +} /* ---------------- public API (specific) ---------------- */ void ESP32Camera::add_image_callback(std::function)> &&callback) { diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h index 0c25381039..71f47d3c06 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -145,6 +145,9 @@ class ESP32Camera : public Component, public EntityBase { /* -- framerates */ void set_max_update_interval(uint32_t max_update_interval); void set_idle_update_interval(uint32_t idle_update_interval); + /* -- frame buffer */ + void set_frame_buffer_mode(camera_grab_mode_t mode); + void set_frame_buffer_count(uint8_t fb_count); /* public API (derivated) */ void setup() override; diff --git a/esphome/components/i2s_audio/__init__.py b/esphome/components/i2s_audio/__init__.py index 05e44696d8..d376907925 100644 --- a/esphome/components/i2s_audio/__init__.py +++ b/esphome/components/i2s_audio/__init__.py @@ -1,16 +1,16 @@ -import esphome.config_validation as cv -import esphome.final_validate as fv -import esphome.codegen as cg - from esphome import pins -from esphome.const import CONF_ID +import esphome.codegen as cg from esphome.components.esp32 import get_esp32_variant from esphome.components.esp32.const import ( VARIANT_ESP32, + VARIANT_ESP32C3, VARIANT_ESP32S2, VARIANT_ESP32S3, - VARIANT_ESP32C3, ) +import esphome.config_validation as cv +from esphome.const import CONF_CHANNEL, CONF_ID, CONF_SAMPLE_RATE +from esphome.cpp_generator import MockObjClass +import esphome.final_validate as fv CODEOWNERS = ["@jesserockz"] DEPENDENCIES = ["esp32"] @@ -25,16 +25,26 @@ CONF_I2S_LRCLK_PIN = "i2s_lrclk_pin" CONF_I2S_AUDIO = "i2s_audio" CONF_I2S_AUDIO_ID = "i2s_audio_id" +CONF_BITS_PER_SAMPLE = "bits_per_sample" CONF_I2S_MODE = "i2s_mode" CONF_PRIMARY = "primary" CONF_SECONDARY = "secondary" +CONF_USE_APLL = "use_apll" +CONF_BITS_PER_SAMPLE = "bits_per_sample" +CONF_BITS_PER_CHANNEL = "bits_per_channel" +CONF_MONO = "mono" +CONF_LEFT = "left" +CONF_RIGHT = "right" +CONF_STEREO = "stereo" + i2s_audio_ns = cg.esphome_ns.namespace("i2s_audio") I2SAudioComponent = i2s_audio_ns.class_("I2SAudioComponent", cg.Component) -I2SAudioIn = i2s_audio_ns.class_("I2SAudioIn", cg.Parented.template(I2SAudioComponent)) -I2SAudioOut = i2s_audio_ns.class_( - "I2SAudioOut", cg.Parented.template(I2SAudioComponent) +I2SAudioBase = i2s_audio_ns.class_( + "I2SAudioBase", cg.Parented.template(I2SAudioComponent) ) +I2SAudioIn = i2s_audio_ns.class_("I2SAudioIn", I2SAudioBase) +I2SAudioOut = i2s_audio_ns.class_("I2SAudioOut", I2SAudioBase) i2s_mode_t = cg.global_ns.enum("i2s_mode_t") I2S_MODE_OPTIONS = { @@ -50,6 +60,75 @@ I2S_PORTS = { VARIANT_ESP32C3: 1, } +i2s_channel_fmt_t = cg.global_ns.enum("i2s_channel_fmt_t") +I2S_CHANNELS = { + CONF_MONO: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ALL_LEFT, + CONF_LEFT: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_LEFT, + CONF_RIGHT: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_RIGHT, + CONF_STEREO: i2s_channel_fmt_t.I2S_CHANNEL_FMT_RIGHT_LEFT, +} + +i2s_bits_per_sample_t = cg.global_ns.enum("i2s_bits_per_sample_t") +I2S_BITS_PER_SAMPLE = { + 8: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_8BIT, + 16: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_16BIT, + 24: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_24BIT, + 32: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_32BIT, +} + +i2s_bits_per_chan_t = cg.global_ns.enum("i2s_bits_per_chan_t") +I2S_BITS_PER_CHANNEL = { + "default": i2s_bits_per_chan_t.I2S_BITS_PER_CHAN_DEFAULT, + 8: i2s_bits_per_chan_t.I2S_BITS_PER_CHAN_8BIT, + 16: i2s_bits_per_chan_t.I2S_BITS_PER_CHAN_16BIT, + 24: i2s_bits_per_chan_t.I2S_BITS_PER_CHAN_24BIT, + 32: i2s_bits_per_chan_t.I2S_BITS_PER_CHAN_32BIT, +} + +_validate_bits = cv.float_with_unit("bits", "bit") + + +def i2s_audio_component_schema( + class_: MockObjClass, + *, + default_sample_rate: int, + default_channel: str, + default_bits_per_sample: str, +): + return cv.Schema( + { + cv.GenerateID(): cv.declare_id(class_), + cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), + cv.Optional(CONF_CHANNEL, default=default_channel): cv.enum(I2S_CHANNELS), + cv.Optional(CONF_SAMPLE_RATE, default=default_sample_rate): cv.int_range( + min=1 + ), + cv.Optional(CONF_BITS_PER_SAMPLE, default=default_bits_per_sample): cv.All( + _validate_bits, cv.enum(I2S_BITS_PER_SAMPLE) + ), + cv.Optional(CONF_I2S_MODE, default=CONF_PRIMARY): cv.enum( + I2S_MODE_OPTIONS, lower=True + ), + cv.Optional(CONF_USE_APLL, default=False): cv.boolean, + cv.Optional(CONF_BITS_PER_CHANNEL, default="default"): cv.All( + cv.Any(cv.float_with_unit("bits", "bit"), "default"), + cv.enum(I2S_BITS_PER_CHANNEL), + ), + } + ) + + +async def register_i2s_audio_component(var, config): + await cg.register_parented(var, config[CONF_I2S_AUDIO_ID]) + + cg.add(var.set_i2s_mode(config[CONF_I2S_MODE])) + cg.add(var.set_channel(config[CONF_CHANNEL])) + cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE])) + cg.add(var.set_bits_per_sample(config[CONF_BITS_PER_SAMPLE])) + cg.add(var.set_bits_per_channel(config[CONF_BITS_PER_CHANNEL])) + cg.add(var.set_use_apll(config[CONF_USE_APLL])) + + CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(I2SAudioComponent), diff --git a/esphome/components/i2s_audio/i2s_audio.h b/esphome/components/i2s_audio/i2s_audio.h index d8d4a23dde..7e2798c33d 100644 --- a/esphome/components/i2s_audio/i2s_audio.h +++ b/esphome/components/i2s_audio/i2s_audio.h @@ -11,9 +11,27 @@ namespace i2s_audio { class I2SAudioComponent; -class I2SAudioIn : public Parented {}; +class I2SAudioBase : public Parented { + 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 {}; + protected: + i2s_mode_t i2s_mode_{}; + i2s_channel_fmt_t channel_; + uint32_t sample_rate_; + i2s_bits_per_sample_t bits_per_sample_; + i2s_bits_per_chan_t bits_per_channel_; + bool use_apll_; +}; + +class I2SAudioIn : public I2SAudioBase {}; + +class I2SAudioOut : public I2SAudioBase {}; class I2SAudioComponent : public Component { public: diff --git a/esphome/components/i2s_audio/media_player/__init__.py b/esphome/components/i2s_audio/media_player/__init__.py index 600a308e6c..dfa69ecadd 100644 --- a/esphome/components/i2s_audio/media_player/__init__.py +++ b/esphome/components/i2s_audio/media_player/__init__.py @@ -12,6 +12,10 @@ from .. import ( I2SAudioOut, CONF_I2S_AUDIO_ID, CONF_I2S_DOUT_PIN, + CONF_LEFT, + CONF_RIGHT, + CONF_MONO, + CONF_STEREO, ) CODEOWNERS = ["@jesserockz"] @@ -30,12 +34,12 @@ CONF_DAC_TYPE = "dac_type" CONF_I2S_COMM_FMT = "i2s_comm_fmt" INTERNAL_DAC_OPTIONS = { - "left": i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN, - "right": i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN, - "stereo": i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN, + CONF_LEFT: i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN, + CONF_RIGHT: i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN, + CONF_STEREO: i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN, } -EXTERNAL_DAC_OPTIONS = ["mono", "stereo"] +EXTERNAL_DAC_OPTIONS = [CONF_MONO, CONF_STEREO] NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2] diff --git a/esphome/components/i2s_audio/media_player/i2s_audio_media_player.h b/esphome/components/i2s_audio/media_player/i2s_audio_media_player.h index 5afe778122..4672f94d7e 100644 --- a/esphome/components/i2s_audio/media_player/i2s_audio_media_player.h +++ b/esphome/components/i2s_audio/media_player/i2s_audio_media_player.h @@ -23,7 +23,7 @@ enum I2SState : uint8_t { I2S_STATE_STOPPING, }; -class I2SAudioMediaPlayer : public Component, public media_player::MediaPlayer, public I2SAudioOut { +class I2SAudioMediaPlayer : public Component, public Parented, public media_player::MediaPlayer { public: void setup() override; float get_setup_priority() const override { return esphome::setup_priority::LATE; } diff --git a/esphome/components/i2s_audio/microphone/__init__.py b/esphome/components/i2s_audio/microphone/__init__.py index 844f176bea..161046e962 100644 --- a/esphome/components/i2s_audio/microphone/__init__.py +++ b/esphome/components/i2s_audio/microphone/__init__.py @@ -1,20 +1,17 @@ -import esphome.config_validation as cv -import esphome.codegen as cg - from esphome import pins -from esphome.const import CONF_CHANNEL, CONF_ID, CONF_NUMBER, CONF_SAMPLE_RATE -from esphome.components import microphone, esp32 +import esphome.codegen as cg +from esphome.components import esp32, microphone from esphome.components.adc import ESP32_VARIANT_ADC1_PIN_TO_CHANNEL, validate_adc_pin +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_NUMBER from .. import ( - CONF_I2S_MODE, - CONF_PRIMARY, - I2S_MODE_OPTIONS, - i2s_audio_ns, - I2SAudioComponent, - I2SAudioIn, - CONF_I2S_AUDIO_ID, CONF_I2S_DIN_PIN, + CONF_RIGHT, + I2SAudioIn, + i2s_audio_component_schema, + i2s_audio_ns, + register_i2s_audio_component, ) CODEOWNERS = ["@jesserockz"] @@ -23,29 +20,14 @@ DEPENDENCIES = ["i2s_audio"] CONF_ADC_PIN = "adc_pin" CONF_ADC_TYPE = "adc_type" CONF_PDM = "pdm" -CONF_BITS_PER_SAMPLE = "bits_per_sample" -CONF_USE_APLL = "use_apll" I2SAudioMicrophone = i2s_audio_ns.class_( "I2SAudioMicrophone", I2SAudioIn, microphone.Microphone, cg.Component ) -i2s_channel_fmt_t = cg.global_ns.enum("i2s_channel_fmt_t") -CHANNELS = { - "left": i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_LEFT, - "right": i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_RIGHT, -} -i2s_bits_per_sample_t = cg.global_ns.enum("i2s_bits_per_sample_t") -BITS_PER_SAMPLE = { - 16: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_16BIT, - 32: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_32BIT, -} - INTERNAL_ADC_VARIANTS = [esp32.const.VARIANT_ESP32] PDM_VARIANTS = [esp32.const.VARIANT_ESP32, esp32.const.VARIANT_ESP32S3] -_validate_bits = cv.float_with_unit("bits", "bit") - def validate_esp32_variant(config): variant = esp32.get_esp32_variant() @@ -62,21 +44,15 @@ def validate_esp32_variant(config): BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(I2SAudioMicrophone), - cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), - cv.Optional(CONF_CHANNEL, default="right"): cv.enum(CHANNELS), - cv.Optional(CONF_SAMPLE_RATE, default=16000): cv.int_range(min=1), - cv.Optional(CONF_BITS_PER_SAMPLE, default="32bit"): cv.All( - _validate_bits, cv.enum(BITS_PER_SAMPLE) - ), - cv.Optional(CONF_USE_APLL, default=False): cv.boolean, - cv.Optional(CONF_I2S_MODE, default=CONF_PRIMARY): cv.enum( - I2S_MODE_OPTIONS, lower=True - ), - } + i2s_audio_component_schema( + I2SAudioMicrophone, + default_sample_rate=16000, + default_channel=CONF_RIGHT, + default_bits_per_sample="32bit", + ) ).extend(cv.COMPONENT_SCHEMA) + CONFIG_SCHEMA = cv.All( cv.typed_schema( { @@ -88,7 +64,7 @@ CONFIG_SCHEMA = cv.All( "external": BASE_SCHEMA.extend( { cv.Required(CONF_I2S_DIN_PIN): pins.internal_gpio_input_pin_number, - cv.Required(CONF_PDM): cv.boolean, + cv.Optional(CONF_PDM, default=False): cv.boolean, } ), }, @@ -101,8 +77,8 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) - - await cg.register_parented(var, config[CONF_I2S_AUDIO_ID]) + await register_i2s_audio_component(var, config) + await microphone.register_microphone(var, config) if config[CONF_ADC_TYPE] == "internal": variant = esp32.get_esp32_variant() @@ -112,11 +88,3 @@ async def to_code(config): else: cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN])) cg.add(var.set_pdm(config[CONF_PDM])) - - cg.add(var.set_i2s_mode(config[CONF_I2S_MODE])) - cg.add(var.set_channel(config[CONF_CHANNEL])) - cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE])) - cg.add(var.set_bits_per_sample(config[CONF_BITS_PER_SAMPLE])) - cg.add(var.set_use_apll(config[CONF_USE_APLL])) - - await microphone.register_microphone(var, config) diff --git a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp index cb49a744fc..23689afb91 100644 --- a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +++ b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp @@ -58,7 +58,7 @@ void I2SAudioMicrophone::start_() { .tx_desc_auto_clear = false, .fixed_mclk = 0, .mclk_multiple = I2S_MCLK_MULTIPLE_256, - .bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT, + .bits_per_chan = this->bits_per_channel_, }; esp_err_t err; @@ -167,21 +167,24 @@ size_t I2SAudioMicrophone::read(int16_t *buf, size_t len) { return 0; } this->status_clear_warning(); - if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_16BIT) { - return bytes_read; - } else if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_32BIT) { - std::vector samples; - size_t samples_read = bytes_read / sizeof(int32_t); - samples.resize(samples_read); - for (size_t i = 0; i < samples_read; i++) { - int32_t temp = reinterpret_cast(buf)[i] >> 14; - samples[i] = clamp(temp, INT16_MIN, INT16_MAX); + // ESP-IDF I2S implementation right-extends 8-bit data to 16 bits, + // and 24-bit data to 32 bits. + switch (this->bits_per_sample_) { + case I2S_BITS_PER_SAMPLE_8BIT: + case I2S_BITS_PER_SAMPLE_16BIT: + return bytes_read; + case I2S_BITS_PER_SAMPLE_24BIT: + case I2S_BITS_PER_SAMPLE_32BIT: { + size_t samples_read = bytes_read / sizeof(int32_t); + for (size_t i = 0; i < samples_read; i++) { + int32_t temp = reinterpret_cast(buf)[i] >> 14; + buf[i] = clamp(temp, INT16_MIN, INT16_MAX); + } + return samples_read * sizeof(int16_t); } - memcpy(buf, samples.data(), samples_read * sizeof(int16_t)); - return samples_read * sizeof(int16_t); - } else { - ESP_LOGE(TAG, "Unsupported bits per sample: %d", this->bits_per_sample_); - return 0; + default: + ESP_LOGE(TAG, "Unsupported bits per sample: %d", this->bits_per_sample_); + return 0; } } diff --git a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h index 07ca0528aa..ea3f357624 100644 --- a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h +++ b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h @@ -30,13 +30,6 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub } #endif - void set_i2s_mode(i2s_mode_t mode) { this->i2s_mode_ = mode; } - - void set_channel(i2s_channel_fmt_t channel) { this->channel_ = channel; } - void set_sample_rate(uint32_t sample_rate) { this->sample_rate_ = sample_rate; } - void set_bits_per_sample(i2s_bits_per_sample_t bits_per_sample) { this->bits_per_sample_ = bits_per_sample; } - void set_use_apll(uint32_t use_apll) { this->use_apll_ = use_apll; } - protected: void start_(); void stop_(); @@ -48,11 +41,6 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub bool adc_{false}; #endif bool pdm_{false}; - i2s_mode_t i2s_mode_{}; - i2s_channel_fmt_t channel_; - uint32_t sample_rate_; - i2s_bits_per_sample_t bits_per_sample_; - bool use_apll_; HighFrequencyLoopRequester high_freq_; }; diff --git a/esphome/components/i2s_audio/speaker/__init__.py b/esphome/components/i2s_audio/speaker/__init__.py index 72455af1b7..22a5af259d 100644 --- a/esphome/components/i2s_audio/speaker/__init__.py +++ b/esphome/components/i2s_audio/speaker/__init__.py @@ -1,15 +1,19 @@ -import esphome.codegen as cg -import esphome.config_validation as cv from esphome import pins -from esphome.const import CONF_ID, CONF_MODE +import esphome.codegen as cg from esphome.components import esp32, speaker +import esphome.config_validation as cv +from esphome.const import CONF_CHANNEL, CONF_ID, CONF_MODE, CONF_TIMEOUT from .. import ( - CONF_I2S_AUDIO_ID, CONF_I2S_DOUT_PIN, - I2SAudioComponent, + CONF_LEFT, + CONF_MONO, + CONF_RIGHT, + CONF_STEREO, I2SAudioOut, + i2s_audio_component_schema, i2s_audio_ns, + register_i2s_audio_component, ) CODEOWNERS = ["@jesserockz"] @@ -19,19 +23,16 @@ I2SAudioSpeaker = i2s_audio_ns.class_( "I2SAudioSpeaker", cg.Component, speaker.Speaker, I2SAudioOut ) -i2s_dac_mode_t = cg.global_ns.enum("i2s_dac_mode_t") -CONF_MUTE_PIN = "mute_pin" CONF_DAC_TYPE = "dac_type" +i2s_dac_mode_t = cg.global_ns.enum("i2s_dac_mode_t") INTERNAL_DAC_OPTIONS = { - "left": i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN, - "right": i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN, - "stereo": i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN, + CONF_LEFT: i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN, + CONF_RIGHT: i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN, + CONF_STEREO: i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN, } -EXTERNAL_DAC_OPTIONS = ["mono", "stereo"] - NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2] @@ -44,28 +45,40 @@ def validate_esp32_variant(config): return config +BASE_SCHEMA = ( + speaker.SPEAKER_SCHEMA.extend( + i2s_audio_component_schema( + I2SAudioSpeaker, + default_sample_rate=16000, + default_channel=CONF_MONO, + default_bits_per_sample="16bit", + ) + ) + .extend( + { + cv.Optional( + CONF_TIMEOUT, default="100ms" + ): cv.positive_time_period_milliseconds, + } + ) + .extend(cv.COMPONENT_SCHEMA) +) + CONFIG_SCHEMA = cv.All( cv.typed_schema( { - "internal": speaker.SPEAKER_SCHEMA.extend( + "internal": BASE_SCHEMA.extend( { - cv.GenerateID(): cv.declare_id(I2SAudioSpeaker), - cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), cv.Required(CONF_MODE): cv.enum(INTERNAL_DAC_OPTIONS, lower=True), } - ).extend(cv.COMPONENT_SCHEMA), - "external": speaker.SPEAKER_SCHEMA.extend( + ), + "external": BASE_SCHEMA.extend( { - cv.GenerateID(): cv.declare_id(I2SAudioSpeaker), - cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), cv.Required( CONF_I2S_DOUT_PIN ): pins.internal_gpio_output_pin_number, - cv.Optional(CONF_MODE, default="mono"): cv.one_of( - *EXTERNAL_DAC_OPTIONS, lower=True - ), } - ).extend(cv.COMPONENT_SCHEMA), + ), }, key=CONF_DAC_TYPE, ), @@ -76,12 +89,11 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) + await register_i2s_audio_component(var, config) await speaker.register_speaker(var, config) - await cg.register_parented(var, config[CONF_I2S_AUDIO_ID]) - if config[CONF_DAC_TYPE] == "internal": - cg.add(var.set_internal_dac_mode(config[CONF_MODE])) + cg.add(var.set_internal_dac_mode(config[CONF_CHANNEL])) else: cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN])) - cg.add(var.set_external_dac_channels(2 if config[CONF_MODE] == "stereo" else 1)) + cg.add(var.set_timeout(config[CONF_TIMEOUT])) diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp index cf5a2c2766..4b427898a2 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp @@ -56,6 +56,21 @@ void I2SAudioSpeaker::start_() { this->task_created_ = true; } +template 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(from); + } + const b *result = to; + for (size_t i = 0; i < bytes; i += sizeof(a)) { + b value = static_cast(*from++) << (sizeof(b) - sizeof(a)) * 8; + *to++ = value; + if (repeat) + *to++ = value; + } + bytes *= (sizeof(b) / sizeof(a)) * (repeat ? 2 : 1); // NOLINT + return reinterpret_cast(result); +} + void I2SAudioSpeaker::player_task(void *params) { I2SAudioSpeaker *this_speaker = (I2SAudioSpeaker *) params; @@ -64,19 +79,19 @@ void I2SAudioSpeaker::player_task(void *params) { xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY); i2s_driver_config_t config = { - .mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_TX), - .sample_rate = 16000, - .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, - .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, + .mode = (i2s_mode_t) (this_speaker->i2s_mode_ | I2S_MODE_TX), + .sample_rate = this_speaker->sample_rate_, + .bits_per_sample = this_speaker->bits_per_sample_, + .channel_format = this_speaker->channel_, .communication_format = I2S_COMM_FORMAT_STAND_I2S, .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, .dma_buf_count = 8, - .dma_buf_len = 128, - .use_apll = false, + .dma_buf_len = 256, + .use_apll = this_speaker->use_apll_, .tx_desc_auto_clear = true, - .fixed_mclk = I2S_PIN_NO_CHANGE, + .fixed_mclk = 0, .mclk_multiple = I2S_MCLK_MULTIPLE_256, - .bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT, + .bits_per_chan = this_speaker->bits_per_channel_, }; #if SOC_I2S_SUPPORTS_DAC if (this_speaker->internal_dac_mode_ != I2S_DAC_CHANNEL_DISABLE) { @@ -114,10 +129,11 @@ void I2SAudioSpeaker::player_task(void *params) { event.type = TaskEventType::STARTED; xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY); - int16_t buffer[BUFFER_SIZE / 2]; + int32_t buffer[BUFFER_SIZE]; while (true) { - if (xQueueReceive(this_speaker->buffer_queue_, &data_event, 100 / portTICK_PERIOD_MS) != pdTRUE) { + if (xQueueReceive(this_speaker->buffer_queue_, &data_event, this_speaker->timeout_ / portTICK_PERIOD_MS) != + pdTRUE) { break; // End of audio from main thread } if (data_event.stop) { @@ -125,17 +141,28 @@ void I2SAudioSpeaker::player_task(void *params) { xQueueReset(this_speaker->buffer_queue_); // Flush queue break; } - size_t bytes_written; - memmove(buffer, data_event.data, data_event.len); - size_t remaining = data_event.len / 2; - size_t current = 0; + const uint8_t *data = data_event.data; + size_t remaining = data_event.len; + switch (this_speaker->bits_per_sample_) { + case I2S_BITS_PER_SAMPLE_8BIT: + case I2S_BITS_PER_SAMPLE_16BIT: { + data = convert_data_format(reinterpret_cast(data), reinterpret_cast(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(data), reinterpret_cast(buffer), + remaining, this_speaker->channel_ == I2S_CHANNEL_FMT_ALL_LEFT); + break; + } + } - while (remaining > 0) { - uint32_t sample = (buffer[current] << 16) | (buffer[current] & 0xFFFF); - - esp_err_t err = i2s_write(this_speaker->parent_->get_port(), &sample, sizeof(sample), &bytes_written, - (10 / portTICK_PERIOD_MS)); + while (remaining != 0) { + size_t bytes_written; + esp_err_t err = + i2s_write(this_speaker->parent_->get_port(), data, remaining, &bytes_written, (32 / portTICK_PERIOD_MS)); if (err != ESP_OK) { event = {.type = TaskEventType::WARNING, .err = err}; if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) { @@ -143,21 +170,8 @@ void I2SAudioSpeaker::player_task(void *params) { } continue; } - if (bytes_written != sizeof(sample)) { - event = {.type = TaskEventType::WARNING, .err = ESP_FAIL}; - if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) { - ESP_LOGW(TAG, "Failed to send WARNING event"); - } - continue; - } - remaining--; - current++; - } - - event.type = TaskEventType::PLAYING; - event.err = current; - if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) { - ESP_LOGW(TAG, "Failed to send PLAYING event"); + data += bytes_written; + remaining -= bytes_written; } } @@ -213,13 +227,11 @@ void I2SAudioSpeaker::watch_() { case TaskEventType::STARTED: ESP_LOGD(TAG, "Started I2S Audio Speaker"); this->state_ = speaker::STATE_RUNNING; + this->status_clear_warning(); break; case TaskEventType::STOPPING: ESP_LOGD(TAG, "Stopping I2S Audio Speaker"); break; - case TaskEventType::PLAYING: - this->status_clear_warning(); - break; case TaskEventType::STOPPED: this->state_ = speaker::STATE_STOPPED; vTaskDelete(this->player_task_handle_); diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h index 0bdb67ceba..7adc4e8a24 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h @@ -21,7 +21,6 @@ static const size_t BUFFER_SIZE = 1024; enum class TaskEventType : uint8_t { STARTING = 0, STARTED, - PLAYING, STOPPING, STOPPED, WARNING = 255, @@ -38,18 +37,18 @@ struct DataEvent { uint8_t data[BUFFER_SIZE]; }; -class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAudioOut { +class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Component { public: float get_setup_priority() const override { return esphome::setup_priority::LATE; } void setup() override; void loop() override; + void set_timeout(uint32_t ms) { this->timeout_ = ms; } void set_dout_pin(uint8_t pin) { this->dout_pin_ = pin; } #if SOC_I2S_SUPPORTS_DAC void set_internal_dac_mode(i2s_dac_mode_t mode) { this->internal_dac_mode_ = mode; } #endif - void set_external_dac_channels(uint8_t channels) { this->external_dac_channels_ = channels; } void start() override; void stop() override; @@ -70,13 +69,13 @@ class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAud QueueHandle_t buffer_queue_; QueueHandle_t event_queue_; + uint32_t timeout_{0}; uint8_t dout_pin_{0}; bool task_created_{false}; #if SOC_I2S_SUPPORTS_DAC i2s_dac_mode_t internal_dac_mode_{I2S_DAC_CHANNEL_DISABLE}; #endif - uint8_t external_dac_channels_; }; } // namespace i2s_audio diff --git a/esphome/components/libretiny/__init__.py b/esphome/components/libretiny/__init__.py index 9ba889f493..cc7fae7e70 100644 --- a/esphome/components/libretiny/__init__.py +++ b/esphome/components/libretiny/__init__.py @@ -170,13 +170,11 @@ def _notify_old_style(config): return config -# NOTE: Keep this in mind when updating the recommended version: -# * For all constants below, update platformio.ini (in this repo) # The dev and latest branches will be at *least* this version, which is what matters. ARDUINO_VERSIONS = { "dev": (cv.Version(1, 7, 0), "https://github.com/libretiny-eu/libretiny.git"), "latest": (cv.Version(1, 7, 0), "libretiny"), - "recommended": (cv.Version(1, 5, 1), None), + "recommended": (cv.Version(1, 7, 0), None), } diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index a4ca9d56f3..64f254cde8 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -22,8 +22,9 @@ from esphome.helpers import write_file_if_changed from . import defines as df, helpers, lv_validation as lvalid from .automation import disp_update, focused_widgets, update_to_code -from .defines import CONF_ADJUSTABLE, CONF_SKIP +from .defines import add_define from .encoders import ENCODERS_CONFIG, encoders_to_code, initial_focus_to_code +from .gradient import GRADIENT_SCHEMA, gradients_to_code from .lv_validation import lv_bool, lv_images_used from .lvcode import LvContext, LvglComponent from .schemas import ( @@ -128,17 +129,6 @@ for w_type in WIDGET_TYPES.values(): )(update_to_code) -lv_defines = {} # Dict of #defines to provide as build flags - - -def add_define(macro, value="1"): - if macro in lv_defines and lv_defines[macro] != value: - LOGGER.error( - "Redefinition of %s - was %s now %s", macro, lv_defines[macro], value - ) - lv_defines[macro] = value - - def as_macro(macro, value): if value is None: return f"#define {macro}" @@ -153,14 +143,14 @@ LV_CONF_H_FORMAT = """\ def generate_lv_conf_h(): - definitions = [as_macro(m, v) for m, v in lv_defines.items()] + definitions = [as_macro(m, v) for m, v in df.lv_defines.items()] definitions.sort() return LV_CONF_H_FORMAT.format("\n".join(definitions)) def final_validation(config): if pages := config.get(CONF_PAGES): - if all(p[CONF_SKIP] for p in pages): + if all(p[df.CONF_SKIP] for p in pages): raise cv.Invalid("At least one page must not be skipped") global_config = full_config.get() for display_id in config[df.CONF_DISPLAYS]: @@ -185,7 +175,7 @@ def final_validation(config): for w in focused_widgets: path = global_config.get_path_for_id(w) widget_conf = global_config.get_config_for_path(path[:-1]) - if CONF_ADJUSTABLE in widget_conf and not widget_conf[CONF_ADJUSTABLE]: + if df.CONF_ADJUSTABLE in widget_conf and not widget_conf[df.CONF_ADJUSTABLE]: raise cv.Invalid( "A non adjustable arc may not be focused", path, @@ -268,6 +258,7 @@ async def to_code(config): await encoders_to_code(lv_component, config) await theme_to_code(config) await styles_to_code(config) + await gradients_to_code(config) await set_obj_properties(lv_scr_act, config) await add_widgets(lv_scr_act, config) await add_pages(lv_component, config) @@ -351,6 +342,7 @@ CONFIG_SCHEMA = ( cv.Optional(df.CONF_THEME): cv.Schema( {cv.Optional(name): obj_schema(w) for name, w in WIDGET_TYPES.items()} ), + cv.Optional(df.CONF_GRADIENTS): GRADIENT_SCHEMA, cv.Optional(df.CONF_TOUCHSCREENS, default=None): touchscreen_schema, cv.Optional(df.CONF_ENCODERS, default=None): ENCODERS_CONFIG, cv.GenerateID(df.CONF_DEFAULT_GROUP): cv.declare_id(lv_group_t), diff --git a/esphome/components/lvgl/defines.py b/esphome/components/lvgl/defines.py index ee8472f90d..3db49d26a4 100644 --- a/esphome/components/lvgl/defines.py +++ b/esphome/components/lvgl/defines.py @@ -4,6 +4,8 @@ Constants already defined in esphome.const are not duplicated here and must be i """ +import logging + from esphome import codegen as cg, config_validation as cv from esphome.const import CONF_ITEMS from esphome.core import Lambda @@ -13,8 +15,19 @@ from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor from .helpers import requires_component +LOGGER = logging.getLogger(__name__) lvgl_ns = cg.esphome_ns.namespace("lvgl") +lv_defines = {} # Dict of #defines to provide as build flags + + +def add_define(macro, value="1"): + if macro in lv_defines and lv_defines[macro] != value: + LOGGER.error( + "Redefinition of %s - was %s now %s", macro, lv_defines[macro], value + ) + lv_defines[macro] = value + def literal(arg): if isinstance(arg, str): @@ -173,6 +186,9 @@ LV_ANIM = LvConstant( "OUT_BOTTOM", ) +LV_GRAD_DIR = LvConstant("LV_GRAD_DIR_", "NONE", "HOR", "VER") +LV_DITHER = LvConstant("LV_DITHER_", "NONE", "ORDERED", "ERR_DIFF") + LOG_LEVELS = ( "TRACE", "INFO", @@ -406,6 +422,7 @@ CONF_FLEX_ALIGN_TRACK = "flex_align_track" CONF_FLEX_GROW = "flex_grow" CONF_FREEZE = "freeze" CONF_FULL_REFRESH = "full_refresh" +CONF_GRADIENTS = "gradients" CONF_GRID_CELL_ROW_POS = "grid_cell_row_pos" CONF_GRID_CELL_COLUMN_POS = "grid_cell_column_pos" CONF_GRID_CELL_ROW_SPAN = "grid_cell_row_span" diff --git a/esphome/components/lvgl/gradient.py b/esphome/components/lvgl/gradient.py new file mode 100644 index 0000000000..bc89470d47 --- /dev/null +++ b/esphome/components/lvgl/gradient.py @@ -0,0 +1,61 @@ +from esphome import config_validation as cv +import esphome.codegen as cg +from esphome.const import ( + CONF_COLOR, + CONF_DIRECTION, + CONF_DITHER, + CONF_ID, + CONF_POSITION, +) +from esphome.cpp_generator import MockObj + +from .defines import CONF_GRADIENTS, LV_DITHER, LV_GRAD_DIR, add_define +from .lv_validation import lv_color, lv_fraction +from .lvcode import lv_assign +from .types import lv_gradient_t + +CONF_STOPS = "stops" + + +def min_stops(value): + if len(value) < 2: + raise cv.Invalid("Must have at least 2 stops") + return value + + +GRADIENT_SCHEMA = cv.ensure_list( + cv.Schema( + { + cv.GenerateID(CONF_ID): cv.declare_id(lv_gradient_t), + cv.Optional(CONF_DIRECTION, default="NONE"): LV_GRAD_DIR.one_of, + cv.Optional(CONF_DITHER, default="NONE"): LV_DITHER.one_of, + cv.Required(CONF_STOPS): cv.All( + [ + cv.Schema( + { + cv.Required(CONF_COLOR): lv_color, + cv.Required(CONF_POSITION): lv_fraction, + } + ) + ], + min_stops, + ), + } + ) +) + + +async def gradients_to_code(config): + max_stops = 2 + for gradient in config.get(CONF_GRADIENTS, ()): + var = MockObj(cg.new_Pvariable(gradient[CONF_ID]), "->") + max_stops = max(max_stops, len(gradient[CONF_STOPS])) + lv_assign(var.dir, await LV_GRAD_DIR.process(gradient[CONF_DIRECTION])) + lv_assign(var.dither, await LV_DITHER.process(gradient[CONF_DITHER])) + lv_assign(var.stops_count, len(gradient[CONF_STOPS])) + for index, stop in enumerate(gradient[CONF_STOPS]): + lv_assign(var.stops[index].color, await lv_color.process(stop[CONF_COLOR])) + lv_assign( + var.stops[index].frac, await lv_fraction.process(stop[CONF_POSITION]) + ) + add_define("LV_GRADIENT_MAX_STOPS", max_stops) diff --git a/esphome/components/lvgl/lv_validation.py b/esphome/components/lvgl/lv_validation.py index d8af9f7aa9..8593deb869 100644 --- a/esphome/components/lvgl/lv_validation.py +++ b/esphome/components/lvgl/lv_validation.py @@ -1,12 +1,19 @@ from typing import Union import esphome.codegen as cg -from esphome.components.color import ColorStruct +from esphome.components.color import CONF_HEX, ColorStruct, from_rgbw from esphome.components.font import Font from esphome.components.image import Image_ import esphome.config_validation as cv -from esphome.const import CONF_ARGS, CONF_COLOR, CONF_FORMAT, CONF_TIME, CONF_VALUE -from esphome.core import HexInt, Lambda +from esphome.const import ( + CONF_ARGS, + CONF_COLOR, + CONF_FORMAT, + CONF_ID, + CONF_TIME, + CONF_VALUE, +) +from esphome.core import CORE, ID, Lambda from esphome.cpp_generator import MockObj from esphome.cpp_types import ESPTime, uint32 from esphome.helpers import cpp_string_escape @@ -23,14 +30,9 @@ from .defines import ( call_lambda, literal, ) -from .helpers import ( - esphome_fonts_used, - lv_fonts_used, - lvgl_components_required, - requires_component, -) +from .helpers import esphome_fonts_used, lv_fonts_used, requires_component from .lvcode import lv_expr -from .types import lv_font_t, lv_img_t +from .types import lv_font_t, lv_gradient_t, lv_img_t opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER") @@ -59,11 +61,17 @@ def color_retmapper(value): if isinstance(value, cv.Lambda): return cv.returning_lambda(value) if isinstance(value, int): - hexval = HexInt(value) - return lv_expr.color_hex(hexval) - # Must be an id - lvgl_components_required.add(CONF_COLOR) - return lv_expr.color_from(MockObj(value)) + return literal( + f"lv_color_make({(value >> 16) & 0xFF}, {(value >> 8) & 0xFF}, {value & 0xFF})" + ) + if isinstance(value, ID): + cval = [x for x in CORE.config[CONF_COLOR] if x[CONF_ID] == value][0] + if CONF_HEX in cval: + r, g, b = cval[CONF_HEX] + else: + r, g, b, _ = from_rgbw(cval) + return literal(f"lv_color_make({r}, {g}, {b})") + assert False def option_string(value): @@ -132,7 +140,7 @@ radius_consts = LvConstant("LV_RADIUS_", "CIRCLE") @schema_extractor("one_of") -def radius_validator(value): +def fraction_validator(value): if value == SCHEMA_EXTRACT: return radius_consts.choices value = cv.Any(size, cv.percentage, radius_consts.one_of)(value) @@ -141,7 +149,7 @@ def radius_validator(value): return value -radius = LValidator(radius_validator, uint32, retmapper=literal) +lv_fraction = LValidator(fraction_validator, uint32, retmapper=literal) def id_name(value): @@ -242,6 +250,21 @@ lv_int = LValidator(cv.int_, cg.int_) lv_brightness = LValidator(cv.percentage, cg.float_, retmapper=lambda x: int(x * 255)) +def gradient_mapper(value): + return MockObj(value) + + +def gradient_validator(value): + return cv.use_id(lv_gradient_t)(value) + + +lv_gradient = LValidator( + validator=gradient_validator, + rtype=lv_gradient_t, + retmapper=gradient_mapper, +) + + def is_lv_font(font): return isinstance(font, str) and font.lower() in LV_FONTS diff --git a/esphome/components/lvgl/lvcode.py b/esphome/components/lvgl/lvcode.py index a3d13f7f8c..3a080d63e9 100644 --- a/esphome/components/lvgl/lvcode.py +++ b/esphome/components/lvgl/lvcode.py @@ -184,8 +184,9 @@ class LvContext(LambdaContext): self.lv_component = lv_component async def add_init_lambda(self): - cg.add(self.lv_component.add_init_lambda(await self.get_lambda())) - LvContext.added_lambda_count += 1 + if self.code_list: + cg.add(self.lv_component.add_init_lambda(await self.get_lambda())) + LvContext.added_lambda_count += 1 async def __aexit__(self, exc_type, exc_val, exc_tb): await super().__aexit__(exc_type, exc_val, exc_tb) diff --git a/esphome/components/lvgl/lvgl_esphome.h b/esphome/components/lvgl/lvgl_esphome.h index e248530971..d5cff51de2 100644 --- a/esphome/components/lvgl/lvgl_esphome.h +++ b/esphome/components/lvgl/lvgl_esphome.h @@ -42,9 +42,6 @@ extern lv_event_code_t lv_api_event; // NOLINT extern lv_event_code_t lv_update_event; // NOLINT extern std::string lv_event_code_name_for(uint8_t event_code); extern bool lv_is_pre_initialise(); -#ifdef USE_LVGL_COLOR -inline lv_color_t lv_color_from(Color color) { return lv_color_make(color.red, color.green, color.blue); } -#endif // USE_LVGL_COLOR #if LV_COLOR_DEPTH == 16 static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BITNESS_565; #elif LV_COLOR_DEPTH == 32 diff --git a/esphome/components/lvgl/schemas.py b/esphome/components/lvgl/schemas.py index 9ff0fec5bc..780057623a 100644 --- a/esphome/components/lvgl/schemas.py +++ b/esphome/components/lvgl/schemas.py @@ -17,9 +17,9 @@ from esphome.core import TimePeriod from esphome.schema_extractors import SCHEMA_EXTRACT from . import defines as df, lv_validation as lvalid -from .defines import CONF_TIME_FORMAT +from .defines import CONF_TIME_FORMAT, LV_GRAD_DIR from .helpers import add_lv_use, requires_component, validate_printf -from .lv_validation import lv_color, lv_font, lv_image +from .lv_validation import lv_color, lv_font, lv_gradient, lv_image from .lvcode import LvglComponent, lv_event_t_ptr from .types import ( LVEncoderListener, @@ -94,9 +94,10 @@ STYLE_PROPS = { "arc_width": cv.positive_int, "anim_time": lvalid.lv_milliseconds, "bg_color": lvalid.lv_color, + "bg_grad": lv_gradient, "bg_grad_color": lvalid.lv_color, "bg_dither_mode": df.LvConstant("LV_DITHER_", "NONE", "ORDERED", "ERR_DIFF").one_of, - "bg_grad_dir": df.LvConstant("LV_GRAD_DIR_", "NONE", "HOR", "VER").one_of, + "bg_grad_dir": LV_GRAD_DIR.one_of, "bg_grad_stop": lvalid.stop_value, "bg_image_opa": lvalid.opacity, "bg_image_recolor": lvalid.lv_color, @@ -160,7 +161,7 @@ STYLE_PROPS = { "max_width": lvalid.pixels_or_percent, "min_height": lvalid.pixels_or_percent, "min_width": lvalid.pixels_or_percent, - "radius": lvalid.radius, + "radius": lvalid.lv_fraction, "width": lvalid.size, "x": lvalid.pixels_or_percent, "y": lvalid.pixels_or_percent, diff --git a/esphome/components/lvgl/types.py b/esphome/components/lvgl/types.py index e4735ea58d..b452ab5fb3 100644 --- a/esphome/components/lvgl/types.py +++ b/esphome/components/lvgl/types.py @@ -59,6 +59,7 @@ LVEncoderListener = lvgl_ns.class_("LVEncoderListener") lv_obj_t = LvType("lv_obj_t") lv_page_t = LvType("LvPageType", parents=(LvCompound,)) lv_img_t = LvType("lv_img_t") +lv_gradient_t = LvType("lv_grad_dsc_t") LV_EVENT = MockObj(base="LV_EVENT_", op="") LV_STATE = MockObj(base="LV_STATE_", op="") diff --git a/esphome/components/lvgl/widgets/meter.py b/esphome/components/lvgl/widgets/meter.py index 7cf154d6f3..36f6643022 100644 --- a/esphome/components/lvgl/widgets/meter.py +++ b/esphome/components/lvgl/widgets/meter.py @@ -5,6 +5,7 @@ from esphome.const import ( CONF_COLOR, CONF_COUNT, CONF_ID, + CONF_ITEMS, CONF_LENGTH, CONF_LOCAL, CONF_RANGE_FROM, @@ -17,6 +18,7 @@ from esphome.const import ( from ..automation import action_to_code from ..defines import ( CONF_END_VALUE, + CONF_INDICATOR, CONF_MAIN, CONF_PIVOT_X, CONF_PIVOT_Y, @@ -165,7 +167,12 @@ METER_SCHEMA = {cv.Optional(CONF_SCALES): cv.ensure_list(SCALE_SCHEMA)} class MeterType(WidgetType): def __init__(self): - super().__init__(CONF_METER, lv_meter_t, (CONF_MAIN,), METER_SCHEMA) + super().__init__( + CONF_METER, + lv_meter_t, + (CONF_MAIN, CONF_INDICATOR, CONF_TICKS, CONF_ITEMS), + METER_SCHEMA, + ) async def to_code(self, w: Widget, config): """For a meter object, create and set parameters""" diff --git a/esphome/components/max31856/max31856.cpp b/esphome/components/max31856/max31856.cpp index 8ae4be6657..6a4d34b430 100644 --- a/esphome/components/max31856/max31856.cpp +++ b/esphome/components/max31856/max31856.cpp @@ -32,6 +32,12 @@ void MAX31856Sensor::dump_config() { LOG_PIN(" CS Pin: ", this->cs_); ESP_LOGCONFIG(TAG, " Mains Filter: %s", (filter_ == FILTER_60HZ ? "60 Hz" : (filter_ == FILTER_50HZ ? "50 Hz" : "Unknown!"))); + if (this->thermocouple_type_ < 0 || this->thermocouple_type_ > 7) { + ESP_LOGCONFIG(TAG, " Thermocouple Type: Unknown"); + } else { + ESP_LOGCONFIG(TAG, " Thermocouple Type: %c", "BEJKNRST"[this->thermocouple_type_]); + } + LOG_UPDATE_INTERVAL(this); } @@ -129,7 +135,12 @@ void MAX31856Sensor::clear_fault_() { } void MAX31856Sensor::set_thermocouple_type_() { - MAX31856ThermocoupleType type = MAX31856_TCTYPE_K; + MAX31856ThermocoupleType type; + if (this->thermocouple_type_ < 0 || this->thermocouple_type_ > 7) { + type = MAX31856_TCTYPE_K; + } else { + type = this->thermocouple_type_; + } ESP_LOGCONFIG(TAG, "set_thermocouple_type_: 0x%02X", type); uint8_t t = this->read_register_(MAX31856_CR1_REG); t &= 0xF0; // mask off bottom 4 bits diff --git a/esphome/components/max31856/max31856.h b/esphome/components/max31856/max31856.h index 4deb6bc855..8d64cfe8bc 100644 --- a/esphome/components/max31856/max31856.h +++ b/esphome/components/max31856/max31856.h @@ -50,7 +50,6 @@ enum MAX31856Registers { /** * Multiple types of thermocouples supported by the chip. - * Currently only K type implemented here. */ enum MAX31856ThermocoupleType { MAX31856_TCTYPE_B = 0b0000, // 0x00 @@ -78,11 +77,15 @@ class MAX31856Sensor : public sensor::Sensor, void setup() override; void dump_config() override; float get_setup_priority() const override; - void set_filter(MAX31856ConfigFilter filter) { filter_ = filter; } + void set_filter(MAX31856ConfigFilter filter) { this->filter_ = filter; } + void set_thermocouple_type(MAX31856ThermocoupleType thermocouple_type) { + this->thermocouple_type_ = thermocouple_type; + } void update() override; protected: MAX31856ConfigFilter filter_; + MAX31856ThermocoupleType thermocouple_type_; uint8_t read_register_(uint8_t reg); uint32_t read_register24_(uint8_t reg); diff --git a/esphome/components/max31856/sensor.py b/esphome/components/max31856/sensor.py index bf9741aeed..679e02b11d 100644 --- a/esphome/components/max31856/sensor.py +++ b/esphome/components/max31856/sensor.py @@ -3,6 +3,7 @@ from esphome.components import sensor, spi import esphome.config_validation as cv from esphome.const import ( CONF_MAINS_FILTER, + CONF_THERMOCOUPLE_TYPE, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, @@ -18,6 +19,17 @@ FILTER = { 50: MAX31865ConfigFilter.FILTER_50HZ, 60: MAX31865ConfigFilter.FILTER_60HZ, } +MAX31856ThermocoupleType = max31856_ns.enum("MAX31856ThermocoupleType") +THERMOCOUPLE_TYPE = { + "B": MAX31856ThermocoupleType.MAX31856_TCTYPE_B, + "E": MAX31856ThermocoupleType.MAX31856_TCTYPE_E, + "J": MAX31856ThermocoupleType.MAX31856_TCTYPE_J, + "K": MAX31856ThermocoupleType.MAX31856_TCTYPE_K, + "N": MAX31856ThermocoupleType.MAX31856_TCTYPE_N, + "R": MAX31856ThermocoupleType.MAX31856_TCTYPE_R, + "S": MAX31856ThermocoupleType.MAX31856_TCTYPE_S, + "T": MAX31856ThermocoupleType.MAX31856_TCTYPE_T, +} CONFIG_SCHEMA = ( sensor.sensor_schema( @@ -34,6 +46,13 @@ CONFIG_SCHEMA = ( ), } ) + .extend( + { + cv.Optional(CONF_THERMOCOUPLE_TYPE, default="K"): cv.enum( + THERMOCOUPLE_TYPE, upper=True, space="" + ), + } + ) .extend(cv.polling_component_schema("60s")) .extend(spi.spi_device_schema()) ) @@ -44,3 +63,4 @@ async def to_code(config): await cg.register_component(var, config) await spi.register_spi_device(var, config) cg.add(var.set_filter(config[CONF_MAINS_FILTER])) + cg.add(var.set_thermocouple_type(config[CONF_THERMOCOUPLE_TYPE])) diff --git a/esphome/components/mcp9600/sensor.py b/esphome/components/mcp9600/sensor.py index 8557c7205e..65ae5f2eec 100644 --- a/esphome/components/mcp9600/sensor.py +++ b/esphome/components/mcp9600/sensor.py @@ -1,14 +1,14 @@ import esphome.codegen as cg -import esphome.config_validation as cv from esphome.components import i2c, sensor +import esphome.config_validation as cv from esphome.const import ( CONF_ID, + CONF_THERMOCOUPLE_TYPE, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, ) -CONF_THERMOCOUPLE_TYPE = "thermocouple_type" CONF_HOT_JUNCTION = "hot_junction" CONF_COLD_JUNCTION = "cold_junction" diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index 8146124c28..6917807b07 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -21,6 +21,7 @@ from .const import ( CONF_CUSTOM_COMMAND, CONF_FORCE_NEW_RANGE, CONF_MODBUS_CONTROLLER_ID, + CONF_MAX_CMD_RETRIES, CONF_ON_COMMAND_SENT, CONF_REGISTER_COUNT, CONF_REGISTER_TYPE, @@ -131,6 +132,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional( CONF_COMMAND_THROTTLE, default="0ms" ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_MAX_CMD_RETRIES, default=4): cv.positive_int, cv.Optional(CONF_OFFLINE_SKIP_UPDATES, default=0): cv.positive_int, cv.Optional( CONF_SERVER_REGISTERS, @@ -257,6 +259,7 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) cg.add(var.set_allow_duplicate_commands(config[CONF_ALLOW_DUPLICATE_COMMANDS])) cg.add(var.set_command_throttle(config[CONF_COMMAND_THROTTLE])) + cg.add(var.set_max_cmd_retries(config[CONF_MAX_CMD_RETRIES])) cg.add(var.set_offline_skip_updates(config[CONF_OFFLINE_SKIP_UPDATES])) if CONF_SERVER_REGISTERS in config: for server_register in config[CONF_SERVER_REGISTERS]: diff --git a/esphome/components/modbus_controller/const.py b/esphome/components/modbus_controller/const.py index 5d9a61dee7..5cf7d230f1 100644 --- a/esphome/components/modbus_controller/const.py +++ b/esphome/components/modbus_controller/const.py @@ -5,6 +5,7 @@ CONF_COMMAND_THROTTLE = "command_throttle" CONF_OFFLINE_SKIP_UPDATES = "offline_skip_updates" CONF_CUSTOM_COMMAND = "custom_command" CONF_FORCE_NEW_RANGE = "force_new_range" +CONF_MAX_CMD_RETRIES = "max_cmd_retries" CONF_MODBUS_CONTROLLER_ID = "modbus_controller_id" CONF_MODBUS_FUNCTIONCODE = "modbus_functioncode" CONF_ON_COMMAND_SENT = "on_command_sent" diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index 8f48847a4f..1dcb533629 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -18,11 +18,11 @@ void ModbusController::setup() { this->create_register_ranges_(); } bool ModbusController::send_next_command_() { uint32_t last_send = millis() - this->last_command_timestamp_; - if ((last_send > this->command_throttle_) && !waiting_for_response() && !command_queue_.empty()) { - auto &command = command_queue_.front(); + if ((last_send > this->command_throttle_) && !waiting_for_response() && !this->command_queue_.empty()) { + auto &command = this->command_queue_.front(); // remove from queue if command was sent too often - if (command->send_countdown < 1) { + if (!command->should_retry(this->max_cmd_retries_)) { if (!this->module_offline_) { ESP_LOGW(TAG, "Modbus device=%d set offline", this->address_); @@ -34,11 +34,9 @@ bool ModbusController::send_next_command_() { } } this->module_offline_ = true; - ESP_LOGD( - TAG, - "Modbus command to device=%d register=0x%02X countdown=%d no response received - removed from send queue", - this->address_, command->register_address, command->send_countdown); - command_queue_.pop_front(); + ESP_LOGD(TAG, "Modbus command to device=%d register=0x%02X no response received - removed from send queue", + this->address_, command->register_address); + this->command_queue_.pop_front(); } else { ESP_LOGV(TAG, "Sending next modbus command to device %d register 0x%02X count %d", this->address_, command->register_address, command->register_count); @@ -50,11 +48,11 @@ bool ModbusController::send_next_command_() { // remove from queue if no handler is defined if (!command->on_data_func) { - command_queue_.pop_front(); + this->command_queue_.pop_front(); } } } - return (!command_queue_.empty()); + return (!this->command_queue_.empty()); } // Queue incoming response @@ -77,7 +75,7 @@ void ModbusController::on_modbus_data(const std::vector &data) { current_command->payload = data; this->incoming_queue_.push(std::move(current_command)); ESP_LOGV(TAG, "Modbus response queued"); - command_queue_.pop_front(); + this->command_queue_.pop_front(); } } @@ -99,7 +97,7 @@ void ModbusController::on_modbus_error(uint8_t function_code, uint8_t exception_ "payload size=%zu", function_code, current_command->register_address, current_command->register_count, current_command->payload.size()); - command_queue_.pop_front(); + this->command_queue_.pop_front(); } } @@ -178,7 +176,7 @@ void ModbusController::queue_command(const ModbusCommandItem &command) { if (!this->allow_duplicate_commands_) { // check if this command is already qeued. // not very effective but the queue is never really large - for (auto &item : command_queue_) { + for (auto &item : this->command_queue_) { if (item->is_equal(command)) { ESP_LOGW(TAG, "Duplicate modbus command found: type=0x%x address=%u count=%u", static_cast(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(command)); + this->command_queue_.push_back(make_unique(command)); } void ModbusController::update_range_(RegisterRange &r) { @@ -224,8 +222,8 @@ void ModbusController::update_range_(RegisterRange &r) { // Once we get a response to the command it is removed from the queue and the next command is send // void ModbusController::update() { - if (!command_queue_.empty()) { - ESP_LOGV(TAG, "%zu modbus commands already in queue", command_queue_.size()); + if (!this->command_queue_.empty()) { + ESP_LOGV(TAG, "%zu modbus commands already in queue", this->command_queue_.size()); } else { ESP_LOGV(TAG, "Updating modbus component"); } @@ -346,6 +344,8 @@ size_t ModbusController::create_register_ranges_() { void ModbusController::dump_config() { ESP_LOGCONFIG(TAG, "ModbusController:"); ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_); + ESP_LOGCONFIG(TAG, " Max Command Retries: %d", this->max_cmd_retries_); + ESP_LOGCONFIG(TAG, " Offline Skip Updates: %d", this->offline_skip_updates_); #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE ESP_LOGCONFIG(TAG, "sensormap"); for (auto &it : sensorset_) { @@ -560,8 +560,9 @@ bool ModbusCommandItem::send() { } else { modbusdevice->send_raw(this->payload); } - ESP_LOGV(TAG, "Command sent %d 0x%X %d", uint8_t(this->function_code), this->register_address, this->register_count); - send_countdown--; + this->send_count_++; + ESP_LOGV(TAG, "Command sent %d 0x%X %d send_count: %d", uint8_t(this->function_code), this->register_address, + this->register_count, this->send_count_); return true; } diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index e88f4c07f7..1fa35e1535 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -312,7 +312,6 @@ struct RegisterRange { class ModbusCommandItem { public: static const size_t MAX_PAYLOAD_BYTES = 240; - static const uint8_t MAX_SEND_REPEATS = 5; ModbusController *modbusdevice; uint16_t register_address; uint16_t register_count; @@ -322,9 +321,9 @@ class ModbusCommandItem { on_data_func; std::vector payload = {}; bool send(); - // wrong commands (esp. custom commands) can block the send queue - // limit the number of repeats - uint8_t send_countdown{MAX_SEND_REPEATS}; + /// Check if the command should be retried based on the max_retries parameter + bool should_retry(uint8_t max_retries) { return this->send_count_ <= max_retries; }; + /// factory methods /** Create modbus read command * Function code 02-04 @@ -413,6 +412,11 @@ class ModbusCommandItem { &&handler = nullptr); bool is_equal(const ModbusCommandItem &other); + + protected: + // wrong commands (esp. custom commands) can block the send queue, limit the number of repeats. + /// How many times this command has been sent + uint8_t send_count_{0}; }; /** Modbus controller class. @@ -464,6 +468,10 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { bool get_module_offline() { return module_offline_; } /// Set callback for commands void add_on_command_sent_callback(std::function &&callback); + /// called by esphome generated code to set the max_cmd_retries. + void set_max_cmd_retries(uint8_t max_cmd_retries) { this->max_cmd_retries_ = max_cmd_retries; } + /// get how many times a command will be (re)sent if no response is received + uint8_t get_max_cmd_retries() { return this->max_cmd_retries_; } protected: /// parse sensormap_ and create range of sequential addresses @@ -498,6 +506,8 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { bool module_offline_; /// how many updates to skip if module is offline uint16_t offline_skip_updates_; + /// How many times we will retry a command if we get no response + uint8_t max_cmd_retries_{4}; CallbackManager command_sent_callback_{}; }; diff --git a/esphome/components/network/__init__.py b/esphome/components/network/__init__.py index caa873a746..be4e102930 100644 --- a/esphome/components/network/__init__.py +++ b/esphome/components/network/__init__.py @@ -1,13 +1,7 @@ import esphome.codegen as cg from esphome.components.esp32 import add_idf_sdkconfig_option import esphome.config_validation as cv -from esphome.const import ( - CONF_ENABLE_IPV6, - CONF_MIN_IPV6_ADDR_COUNT, - PLATFORM_ESP32, - PLATFORM_ESP8266, - PLATFORM_RP2040, -) +from esphome.const import CONF_ENABLE_IPV6, CONF_MIN_IPV6_ADDR_COUNT from esphome.core import CORE CODEOWNERS = ["@esphome/core"] @@ -23,10 +17,17 @@ CONFIG_SCHEMA = cv.Schema( esp8266=False, esp32=False, rp2040=False, + bk72xx=False, ): cv.All( cv.boolean, cv.Any( - cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040]), + cv.require_framework_version( + esp_idf=cv.Version(0, 0, 0), + esp32_arduino=cv.Version(0, 0, 0), + esp8266_arduino=cv.Version(0, 0, 0), + rp2040_arduino=cv.Version(0, 0, 0), + bk72xx_libretiny=cv.Version(1, 7, 0), + ), cv.boolean_false, ), ), @@ -53,3 +54,5 @@ async def to_code(config): cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_ENABLE_IPV6") if CORE.is_esp8266: cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_LWIP2_IPV6_LOW_MEMORY") + if CORE.is_bk72xx: + cg.add_build_flag("-DCONFIG_IPV6") diff --git a/esphome/components/rpi_dpi_rgb/display.py b/esphome/components/rpi_dpi_rgb/display.py index 6cc8d2c27b..c26143d63e 100644 --- a/esphome/components/rpi_dpi_rgb/display.py +++ b/esphome/components/rpi_dpi_rgb/display.py @@ -1,31 +1,28 @@ -import esphome.codegen as cg -import esphome.config_validation as cv from esphome import pins +import esphome.codegen as cg from esphome.components import display +from esphome.components.esp32 import const, only_on_variant +import esphome.config_validation as cv from esphome.const import ( - CONF_ENABLE_PIN, - CONF_HSYNC_PIN, - CONF_RESET_PIN, + CONF_BLUE, + CONF_COLOR_ORDER, CONF_DATA_PINS, + CONF_DIMENSIONS, + CONF_ENABLE_PIN, + CONF_GREEN, + CONF_HEIGHT, + CONF_HSYNC_PIN, CONF_ID, CONF_IGNORE_STRAPPING_WARNING, - CONF_DIMENSIONS, - CONF_VSYNC_PIN, - CONF_WIDTH, - CONF_HEIGHT, + CONF_INVERT_COLORS, CONF_LAMBDA, - CONF_COLOR_ORDER, - CONF_RED, - CONF_GREEN, - CONF_BLUE, CONF_NUMBER, CONF_OFFSET_HEIGHT, CONF_OFFSET_WIDTH, - CONF_INVERT_COLORS, -) -from esphome.components.esp32 import ( - only_on_variant, - const, + CONF_RED, + CONF_RESET_PIN, + CONF_VSYNC_PIN, + CONF_WIDTH, ) DEPENDENCIES = ["esp32"] diff --git a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp index f173a2ec44..655b469b91 100644 --- a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp +++ b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp @@ -6,9 +6,14 @@ namespace esphome { namespace rpi_dpi_rgb { void RpiDpiRgb::setup() { - esph_log_config(TAG, "Setting up RPI_DPI_RGB"); + ESP_LOGCONFIG(TAG, "Setting up RPI_DPI_RGB"); + this->reset_display_(); esp_lcd_rgb_panel_config_t config{}; config.flags.fb_in_psram = 1; +#if ESP_IDF_VERSION_MAJOR >= 5 + config.bounce_buffer_size_px = this->width_ * 10; + config.num_fbs = 1; +#endif // ESP_IDF_VERSION_MAJOR config.timings.h_res = this->width_; config.timings.v_res = this->height_; config.timings.hsync_pulse_width = this->hsync_pulse_width_; @@ -20,7 +25,6 @@ void RpiDpiRgb::setup() { config.timings.flags.pclk_active_neg = this->pclk_inverted_; config.timings.pclk_hz = this->pclk_frequency_; config.clk_src = LCD_CLK_SRC_PLL160M; - config.sram_trans_align = 64; config.psram_trans_align = 64; size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]); for (size_t i = 0; i != data_pin_count; i++) { @@ -34,11 +38,19 @@ void RpiDpiRgb::setup() { config.pclk_gpio_num = this->pclk_pin_->get_pin(); esp_err_t err = esp_lcd_new_rgb_panel(&config, &this->handle_); if (err != ESP_OK) { - esph_log_e(TAG, "lcd_new_rgb_panel failed: %s", esp_err_to_name(err)); + ESP_LOGE(TAG, "lcd_new_rgb_panel failed: %s", esp_err_to_name(err)); + this->mark_failed(); + return; } ESP_ERROR_CHECK(esp_lcd_panel_reset(this->handle_)); ESP_ERROR_CHECK(esp_lcd_panel_init(this->handle_)); - esph_log_config(TAG, "RPI_DPI_RGB setup complete"); + ESP_LOGCONFIG(TAG, "RPI_DPI_RGB setup complete"); +} +void RpiDpiRgb::loop() { +#if ESP_IDF_VERSION_MAJOR >= 5 + if (this->handle_ != nullptr) + esp_lcd_rgb_panel_restart(this->handle_); +#endif // ESP_IDF_VERSION_MAJOR } void RpiDpiRgb::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, @@ -53,7 +65,7 @@ void RpiDpiRgb::draw_pixels_at(int x_start, int y_start, int w, int h, const uin } x_start += this->offset_x_; y_start += this->offset_y_; - esp_err_t err; + esp_err_t err = ESP_OK; // x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display. if (x_offset == 0 && x_pad == 0 && y_offset == 0) { // we could deal here with a non-zero y_offset, but if x_offset is zero, y_offset probably will be so don't bother @@ -69,7 +81,7 @@ void RpiDpiRgb::draw_pixels_at(int x_start, int y_start, int w, int h, const uin } } if (err != ESP_OK) - esph_log_e(TAG, "lcd_lcd_panel_draw_bitmap failed: %s", esp_err_to_name(err)); + ESP_LOGE(TAG, "lcd_lcd_panel_draw_bitmap failed: %s", esp_err_to_name(err)); } void RpiDpiRgb::draw_pixel_at(int x, int y, Color color) { diff --git a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h index 6d9d6d4ae9..10f77a2624 100644 --- a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h +++ b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h @@ -23,6 +23,7 @@ class RpiDpiRgb : public display::Display { public: void update() override { this->do_update_(); } void setup() override; + void loop() override; void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override; void draw_pixel_at(int x, int y, Color color) override; diff --git a/esphome/components/uponor_smatrix/uponor_smatrix.cpp b/esphome/components/uponor_smatrix/uponor_smatrix.cpp index a7014dc96c..e058de2852 100644 --- a/esphome/components/uponor_smatrix/uponor_smatrix.cpp +++ b/esphome/components/uponor_smatrix/uponor_smatrix.cpp @@ -45,11 +45,8 @@ void UponorSmatrixComponent::loop() { // Read incoming data while (this->available()) { - // The controller polls devices every 10 seconds, with around 200 ms between devices. - // Remember timestamps so we can send our own packets when the bus is expected to be silent. - if (now - this->last_rx_ > 500) { - this->last_poll_start_ = now; - } + // The controller polls devices every 10 seconds in some units or continuously in others with around 200 ms between + // devices. Remember timestamps so we can send our own packets when the bus is expected to be silent. this->last_rx_ = now; uint8_t byte; @@ -60,7 +57,8 @@ void UponorSmatrixComponent::loop() { } // Send packets during bus silence - if ((now - this->last_rx_ > 300) && (now - this->last_poll_start_ < 9500) && (now - this->last_tx_ > 200)) { + if (this->rx_buffer_.empty() && (now - this->last_rx_ > 50) && (now - this->last_rx_ < 100) && + (now - this->last_tx_ > 200)) { #ifdef USE_TIME // Only build time packet when bus is silent and queue is empty to make sure we can send it right away if (this->send_time_requested_ && this->tx_queue_.empty() && this->do_send_time_()) diff --git a/esphome/components/uponor_smatrix/uponor_smatrix.h b/esphome/components/uponor_smatrix/uponor_smatrix.h index b7667b5b87..e3e19a12fc 100644 --- a/esphome/components/uponor_smatrix/uponor_smatrix.h +++ b/esphome/components/uponor_smatrix/uponor_smatrix.h @@ -93,7 +93,6 @@ class UponorSmatrixComponent : public uart::UARTDevice, public Component { std::queue> tx_queue_; uint32_t last_rx_; uint32_t last_tx_; - uint32_t last_poll_start_; #ifdef USE_TIME time::RealTimeClock *time_id_{nullptr}; diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 577de630fb..a2210f188d 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -755,7 +755,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { 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. return; } else if (code == "wake-provider-missing" || code == "wake-engine-missing") { diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 19ade84a88..afb30c3bcf 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -85,7 +85,16 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() { if (!this->has_sta()) return {}; - return {WiFi.localIP()}; + network::IPAddresses addresses; + addresses[0] = WiFi.localIP(); +#if USE_NETWORK_IPV6 + int i = 1; + auto v6_addresses = WiFi.allLocalIPv6(); + for (auto address : v6_addresses) { + addresses[i++] = network::IPAddress(address.toString().c_str()); + } +#endif /* USE_NETWORK_IPV6 */ + return addresses; } bool WiFiComponent::wifi_apply_hostname_() { @@ -321,6 +330,11 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ s_sta_connecting = false; break; } + case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6: { + // auto it = info.got_ip.ip_info; + ESP_LOGV(TAG, "Event: Got IPv6"); + break; + } case ESPHOME_EVENT_ID_WIFI_STA_LOST_IP: { ESP_LOGV(TAG, "Event: Lost IP"); break; diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 719cc43b31..e55879e37e 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -2045,6 +2045,7 @@ def require_framework_version( esp32_arduino=None, esp8266_arduino=None, rp2040_arduino=None, + bk72xx_libretiny=None, host=None, max_version=False, extra_message=None, @@ -2059,6 +2060,13 @@ def require_framework_version( msg += f". {extra_message}" raise Invalid(msg) required = esp_idf + elif CORE.is_bk72xx and framework == "arduino": + if bk72xx_libretiny is None: + msg = "This feature is incompatible with BK72XX" + if extra_message: + msg += f". {extra_message}" + raise Invalid(msg) + required = bk72xx_libretiny elif CORE.is_esp32 and framework == "arduino": if esp32_arduino is None: msg = "This feature is incompatible with ESP32 using arduino framework" diff --git a/esphome/const.py b/esphome/const.py index 169b11a715..6e7bbdec98 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.9.0-dev" +__version__ = "2024.10.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( @@ -852,6 +852,7 @@ CONF_TEMPERATURE_STEP = "temperature_step" CONF_TEXT = "text" CONF_TEXT_SENSORS = "text_sensors" CONF_THEN = "then" +CONF_THERMOCOUPLE_TYPE = "thermocouple_type" CONF_THRESHOLD = "threshold" CONF_THROTTLE = "throttle" CONF_TILT = "tilt" diff --git a/platformio.ini b/platformio.ini index ee18068a29..7d912aaf54 100644 --- a/platformio.ini +++ b/platformio.ini @@ -119,7 +119,7 @@ lib_deps = WiFi ; wifi,web_server_base,ethernet (Arduino built-in) Update ; ota,web_server_base (Arduino built-in) ${common:arduino.lib_deps} - esphome/AsyncTCP-esphome@2.1.3 ; async_tcp + esphome/AsyncTCP-esphome@2.1.4 ; async_tcp WiFiClientSecure ; http_request,nextion (Arduino built-in) HTTPClient ; http_request,nextion (Arduino built-in) ESPmDNS ; mdns (Arduino built-in) diff --git a/tests/components/bl0942/test.bk72xx-ard.yaml b/tests/components/bl0942/test.bk72xx-ard.yaml index 12772f9375..ea61734441 100644 --- a/tests/components/bl0942/test.bk72xx-ard.yaml +++ b/tests/components/bl0942/test.bk72xx-ard.yaml @@ -10,6 +10,7 @@ sensor: - platform: bl0942 address: 0 line_frequency: 50Hz + reset: false voltage: name: BL0942 Voltage current: diff --git a/tests/components/bl0942/test.esp32-ard.yaml b/tests/components/bl0942/test.esp32-ard.yaml index 45ac85aa2a..4138543967 100644 --- a/tests/components/bl0942/test.esp32-ard.yaml +++ b/tests/components/bl0942/test.esp32-ard.yaml @@ -8,6 +8,7 @@ uart: sensor: - platform: bl0942 + reset: true voltage: name: BL0942 Voltage current: diff --git a/tests/components/lvgl/lvgl-package.yaml b/tests/components/lvgl/lvgl-package.yaml index 0db6a6a995..9d157ea5b0 100644 --- a/tests/components/lvgl/lvgl-package.yaml +++ b/tests/components/lvgl/lvgl-package.yaml @@ -1,12 +1,32 @@ lvgl: log_level: TRACE bg_color: light_blue - disp_bg_color: 0xffff00 + disp_bg_color: color_id disp_bg_image: cat_image theme: obj: border_width: 1 + gradients: + - id: color_bar + direction: hor + dither: err_diff + stops: + - color: 0xFF0000 + position: 0 + - color: 0xFFFF00 + position: 42 + - color: 0x00FF00 + position: 84 + - color: 0x00FFFF + position: 127 + - color: 0x0000FF + position: 169 + - color: 0xFF00FF + position: 212 + - color: 0xFF0000 + position: 255 + style_definitions: - id: style_test bg_color: 0x2F8CD8 @@ -31,7 +51,7 @@ lvgl: - id: date_style text_font: roboto10 align: center - text_color: 0x000000 + text_color: color_id2 bg_opa: cover radius: 4 pad_all: 2 @@ -386,6 +406,22 @@ lvgl: - id: page2 widgets: + - slider: + min_value: 0 + max_value: 255 + bg_opa: cover + bg_grad: color_bar + radius: 0 + indicator: + bg_opa: transp + knob: + radius: 1 + width: 4 + height: 10% + bg_color: 0x000000 + width: 100% + height: 10% + align: top_mid - button: styles: spin_button id: spin_up @@ -586,3 +622,13 @@ image: color: - id: light_blue hex: "3340FF" + - id: color_id + red: 0.5 + green: 0.5 + blue: 0.5 + white: 0.5 + - id: color_id2 + red_int: 0xFF + green_int: 123 + blue_int: 64 + white_int: 255 diff --git a/tests/components/max31856/test.esp32-ard.yaml b/tests/components/max31856/test.esp32-ard.yaml index 5561903207..9a4da6b2a2 100644 --- a/tests/components/max31856/test.esp32-ard.yaml +++ b/tests/components/max31856/test.esp32-ard.yaml @@ -10,3 +10,4 @@ sensor: cs_pin: 12 update_interval: 15s mains_filter: 50Hz + thermocouple_type: N diff --git a/tests/components/max31856/test.esp32-c3-ard.yaml b/tests/components/max31856/test.esp32-c3-ard.yaml index 2794866c59..71bbfffb7b 100644 --- a/tests/components/max31856/test.esp32-c3-ard.yaml +++ b/tests/components/max31856/test.esp32-c3-ard.yaml @@ -10,3 +10,5 @@ sensor: cs_pin: 8 update_interval: 15s mains_filter: 50Hz + thermocouple_type: N + diff --git a/tests/components/max31856/test.esp32-c3-idf.yaml b/tests/components/max31856/test.esp32-c3-idf.yaml index 2794866c59..71bbfffb7b 100644 --- a/tests/components/max31856/test.esp32-c3-idf.yaml +++ b/tests/components/max31856/test.esp32-c3-idf.yaml @@ -10,3 +10,5 @@ sensor: cs_pin: 8 update_interval: 15s mains_filter: 50Hz + thermocouple_type: N + diff --git a/tests/components/max31856/test.esp32-idf.yaml b/tests/components/max31856/test.esp32-idf.yaml index 5561903207..9a4da6b2a2 100644 --- a/tests/components/max31856/test.esp32-idf.yaml +++ b/tests/components/max31856/test.esp32-idf.yaml @@ -10,3 +10,4 @@ sensor: cs_pin: 12 update_interval: 15s mains_filter: 50Hz + thermocouple_type: N diff --git a/tests/components/max31856/test.esp8266-ard.yaml b/tests/components/max31856/test.esp8266-ard.yaml index dfd9572ca9..b9c42542fd 100644 --- a/tests/components/max31856/test.esp8266-ard.yaml +++ b/tests/components/max31856/test.esp8266-ard.yaml @@ -10,3 +10,5 @@ sensor: cs_pin: 15 update_interval: 15s mains_filter: 50Hz + thermocouple_type: N + diff --git a/tests/components/max31856/test.rp2040-ard.yaml b/tests/components/max31856/test.rp2040-ard.yaml index 0abc8a081b..8607eb18cf 100644 --- a/tests/components/max31856/test.rp2040-ard.yaml +++ b/tests/components/max31856/test.rp2040-ard.yaml @@ -10,3 +10,5 @@ sensor: cs_pin: 6 update_interval: 15s mains_filter: 50Hz + thermocouple_type: N + diff --git a/tests/components/modbus_controller/test.esp32-ard.yaml b/tests/components/modbus_controller/test.esp32-ard.yaml index b6e38aeb9c..cd95d149cb 100644 --- a/tests/components/modbus_controller/test.esp32-ard.yaml +++ b/tests/components/modbus_controller/test.esp32-ard.yaml @@ -29,3 +29,4 @@ modbus_controller: value_type: S_DWORD_R read_lambda: |- return 42.3; + max_cmd_retries: 0 diff --git a/tests/components/modbus_controller/test.esp32-idf.yaml b/tests/components/modbus_controller/test.esp32-idf.yaml index d5407ac406..ba28e94d73 100644 --- a/tests/components/modbus_controller/test.esp32-idf.yaml +++ b/tests/components/modbus_controller/test.esp32-idf.yaml @@ -13,3 +13,4 @@ modbus_controller: address: 0x2 modbus_id: mod_bus1 allow_duplicate_commands: true + max_cmd_retries: 10 diff --git a/tests/components/network/test-ipv6.bk72xx-ard.yaml b/tests/components/network/test-ipv6.bk72xx-ard.yaml index 361ca09977..d0c4bbfcb9 100644 --- a/tests/components/network/test-ipv6.bk72xx-ard.yaml +++ b/tests/components/network/test-ipv6.bk72xx-ard.yaml @@ -1,4 +1,8 @@ substitutions: - network_enable_ipv6: "false" + network_enable_ipv6: "true" + +bk72xx: + framework: + version: 1.7.0 <<: !include common.yaml diff --git a/tests/components/speaker/test.esp32-ard.yaml b/tests/components/speaker/test.esp32-ard.yaml index e10c3e88c1..ab20f36eb6 100644 --- a/tests/components/speaker/test.esp32-ard.yaml +++ b/tests/components/speaker/test.esp32-ard.yaml @@ -21,4 +21,3 @@ speaker: id: speaker_id dac_type: external i2s_dout_pin: 13 - mode: mono diff --git a/tests/components/speaker/test.esp32-c3-ard.yaml b/tests/components/speaker/test.esp32-c3-ard.yaml index 08699d8b22..c966f9daa7 100644 --- a/tests/components/speaker/test.esp32-c3-ard.yaml +++ b/tests/components/speaker/test.esp32-c3-ard.yaml @@ -21,4 +21,3 @@ speaker: id: speaker_id dac_type: external i2s_dout_pin: 3 - mode: mono diff --git a/tests/components/speaker/test.esp32-c3-idf.yaml b/tests/components/speaker/test.esp32-c3-idf.yaml index 08699d8b22..c966f9daa7 100644 --- a/tests/components/speaker/test.esp32-c3-idf.yaml +++ b/tests/components/speaker/test.esp32-c3-idf.yaml @@ -21,4 +21,3 @@ speaker: id: speaker_id dac_type: external i2s_dout_pin: 3 - mode: mono diff --git a/tests/components/speaker/test.esp32-idf.yaml b/tests/components/speaker/test.esp32-idf.yaml index e10c3e88c1..ab20f36eb6 100644 --- a/tests/components/speaker/test.esp32-idf.yaml +++ b/tests/components/speaker/test.esp32-idf.yaml @@ -21,4 +21,3 @@ speaker: id: speaker_id dac_type: external i2s_dout_pin: 13 - mode: mono diff --git a/tests/components/voice_assistant/test.esp32-ard.yaml b/tests/components/voice_assistant/test.esp32-ard.yaml index 7f6fd85303..cbf9460087 100644 --- a/tests/components/voice_assistant/test.esp32-ard.yaml +++ b/tests/components/voice_assistant/test.esp32-ard.yaml @@ -28,7 +28,6 @@ speaker: id: speaker_id dac_type: external i2s_dout_pin: 12 - mode: mono voice_assistant: microphone: mic_id_external diff --git a/tests/components/voice_assistant/test.esp32-c3-ard.yaml b/tests/components/voice_assistant/test.esp32-c3-ard.yaml index 248ae4d0dc..86357fad36 100644 --- a/tests/components/voice_assistant/test.esp32-c3-ard.yaml +++ b/tests/components/voice_assistant/test.esp32-c3-ard.yaml @@ -28,7 +28,6 @@ speaker: id: speaker_id dac_type: external i2s_dout_pin: 2 - mode: mono voice_assistant: microphone: mic_id_external diff --git a/tests/components/voice_assistant/test.esp32-c3-idf.yaml b/tests/components/voice_assistant/test.esp32-c3-idf.yaml index 248ae4d0dc..86357fad36 100644 --- a/tests/components/voice_assistant/test.esp32-c3-idf.yaml +++ b/tests/components/voice_assistant/test.esp32-c3-idf.yaml @@ -28,7 +28,6 @@ speaker: id: speaker_id dac_type: external i2s_dout_pin: 2 - mode: mono voice_assistant: microphone: mic_id_external diff --git a/tests/components/voice_assistant/test.esp32-idf.yaml b/tests/components/voice_assistant/test.esp32-idf.yaml index 2e0209311d..da9b50721f 100644 --- a/tests/components/voice_assistant/test.esp32-idf.yaml +++ b/tests/components/voice_assistant/test.esp32-idf.yaml @@ -28,7 +28,6 @@ speaker: id: speaker_id dac_type: external i2s_dout_pin: 12 - mode: mono voice_assistant: microphone: mic_id_external