From ffa5e29dabcccf4ab63ebc69110d818b90679a68 Mon Sep 17 00:00:00 2001 From: Fabian Date: Tue, 23 May 2023 21:51:12 +0200 Subject: [PATCH 01/10] [internal_temperature] ESP32-S3 needs ESP IDF V4.4.3 or higher (#4873) Co-authored-by: Your Name --- .../internal_temperature.cpp | 4 +++ .../components/internal_temperature/sensor.py | 28 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/esphome/components/internal_temperature/internal_temperature.cpp b/esphome/components/internal_temperature/internal_temperature.cpp index 9a22a77f633e..a38770826366 100644 --- a/esphome/components/internal_temperature/internal_temperature.cpp +++ b/esphome/components/internal_temperature/internal_temperature.cpp @@ -33,6 +33,10 @@ void InternalTemperatureSensor::update() { temp_sensor_config_t tsens = TSENS_CONFIG_DEFAULT(); temp_sensor_set_config(tsens); temp_sensor_start(); +#if defined(USE_ESP32_VARIANT_ESP32S3) && (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 3)) +#error \ + "ESP32-S3 internal temperature sensor requires ESP IDF V4.4.3 or higher. See https://github.com/esphome/issues/issues/4271" +#endif esp_err_t result = temp_sensor_read_celsius(&temperature); temp_sensor_stop(); success = (result == ESP_OK); diff --git a/esphome/components/internal_temperature/sensor.py b/esphome/components/internal_temperature/sensor.py index 2655711bb5ea..8d462bd80175 100644 --- a/esphome/components/internal_temperature/sensor.py +++ b/esphome/components/internal_temperature/sensor.py @@ -1,18 +1,45 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor +from esphome.components.esp32 import get_esp32_variant +from esphome.components.esp32.const import ( + VARIANT_ESP32S3, +) from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, DEVICE_CLASS_TEMPERATURE, ENTITY_CATEGORY_DIAGNOSTIC, + KEY_CORE, + KEY_FRAMEWORK_VERSION, ) +from esphome.core import CORE internal_temperature_ns = cg.esphome_ns.namespace("internal_temperature") InternalTemperatureSensor = internal_temperature_ns.class_( "InternalTemperatureSensor", sensor.Sensor, cg.PollingComponent ) + +def validate_config(config): + if CORE.is_esp32: + variant = get_esp32_variant() + if variant == VARIANT_ESP32S3: + if CORE.using_arduino and CORE.data[KEY_CORE][ + KEY_FRAMEWORK_VERSION + ] < cv.Version(2, 0, 6): + raise cv.Invalid( + "ESP32-S3 Internal Temperature Sensor requires framework version 2.0.6 or higher. See ." + ) + if CORE.using_esp_idf and CORE.data[KEY_CORE][ + KEY_FRAMEWORK_VERSION + ] < cv.Version(4, 4, 3): + raise cv.Invalid( + "ESP32-S3 Internal Temperature Sensor requires framework version 4.4.3 or higher. See ." + ) + return config + + CONFIG_SCHEMA = cv.All( sensor.sensor_schema( InternalTemperatureSensor, @@ -23,6 +50,7 @@ entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ).extend(cv.polling_component_schema("60s")), cv.only_on(["esp32", "rp2040"]), + validate_config, ) From 35ef4aad6086869e0e74d1f6b4d25d34b812ab9c Mon Sep 17 00:00:00 2001 From: Davrosx <75336029+Davrosx@users.noreply.github.com> Date: Tue, 23 May 2023 20:52:34 +0100 Subject: [PATCH 02/10] Update cover.h for compile errors with stop() (#4879) --- esphome/components/cover/cover.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/cover/cover.h b/esphome/components/cover/cover.h index d21fbe02be3f..89598a9636e6 100644 --- a/esphome/components/cover/cover.h +++ b/esphome/components/cover/cover.h @@ -140,8 +140,9 @@ class Cover : public EntityBase, public EntityBase_DeviceClass { /** Stop the cover. * * This is a legacy method and may be removed later, please use `.make_call()` instead. + * As per solution from issue #2885 the call should include perform() */ - ESPDEPRECATED("stop() is deprecated, use make_call().set_command_stop() instead.", "2021.9") + ESPDEPRECATED("stop() is deprecated, use make_call().set_command_stop().perform() instead.", "2021.9") void stop(); void add_on_state_callback(std::function &&f); From baa08160bbec62ec517c2e4ed929c7265d23485a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 24 May 2023 09:56:15 +1200 Subject: [PATCH 03/10] Print ESPHome version when running commands (#4883) --- esphome/__main__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/__main__.py b/esphome/__main__.py index f82a48e33b33..603a06bdd239 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -980,6 +980,8 @@ def run_esphome(argv): _LOGGER.error(e, exc_info=args.verbose) return 1 + safe_print(f"ESPHome {const.__version__}") + for conf_path in args.configuration: if any(os.path.basename(conf_path) == x for x in SECRETS_FILES): _LOGGER.warning("Skipping secrets file %s", conf_path) From 4141100b1c7568c64f82c04a57c110c45d1c3027 Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Tue, 23 May 2023 15:00:33 -0700 Subject: [PATCH 04/10] fix modbus sending FP32_R values (#4882) --- esphome/components/modbus_controller/modbus_controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index 57f714f23327..79c13e3f6843 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -506,12 +506,12 @@ void number_to_payload(std::vector &data, int64_t value, SensorValueTy case SensorValueType::U_DWORD: case SensorValueType::S_DWORD: case SensorValueType::FP32: - case SensorValueType::FP32_R: data.push_back((value & 0xFFFF0000) >> 16); data.push_back(value & 0xFFFF); break; case SensorValueType::U_DWORD_R: case SensorValueType::S_DWORD_R: + case SensorValueType::FP32_R: data.push_back(value & 0xFFFF); data.push_back((value & 0xFFFF0000) >> 16); break; From 2e8b4fbdc8429e6c8e8e0aa33d750aeb50d0bde3 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 24 May 2023 19:19:49 +1200 Subject: [PATCH 05/10] Fix rp2040_pio_led_strip color modes (#4887) --- esphome/components/rp2040_pio_led_strip/led_strip.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/rp2040_pio_led_strip/led_strip.h b/esphome/components/rp2040_pio_led_strip/led_strip.h index b3a6b87d7dd0..25ef9ca55f2b 100644 --- a/esphome/components/rp2040_pio_led_strip/led_strip.h +++ b/esphome/components/rp2040_pio_led_strip/led_strip.h @@ -55,7 +55,7 @@ class RP2040PIOLEDStripLightOutput : public light::AddressableLight { int32_t size() const override { return this->num_leds_; } light::LightTraits get_traits() override { auto traits = light::LightTraits(); - this->is_rgbw_ ? traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::RGB_WHITE}) + this->is_rgbw_ ? traits.set_supported_color_modes({light::ColorMode::RGB_WHITE, light::ColorMode::WHITE}) : traits.set_supported_color_modes({light::ColorMode::RGB}); return traits; } From 2153cfc7499886f666411eedd5821ddf62f33c58 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 24 May 2023 19:20:06 +1200 Subject: [PATCH 06/10] Fix esp32_rmt_led_strip color modes (#4886) --- esphome/components/esp32_rmt_led_strip/led_strip.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/esp32_rmt_led_strip/led_strip.h b/esphome/components/esp32_rmt_led_strip/led_strip.h index 508f784ec874..11d61b07e157 100644 --- a/esphome/components/esp32_rmt_led_strip/led_strip.h +++ b/esphome/components/esp32_rmt_led_strip/led_strip.h @@ -34,7 +34,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { light::LightTraits get_traits() override { auto traits = light::LightTraits(); if (this->is_rgbw_) { - traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::RGB_WHITE}); + traits.set_supported_color_modes({light::ColorMode::RGB_WHITE, light::ColorMode::WHITE}); } else { traits.set_supported_color_modes({light::ColorMode::RGB}); } From bb044a789c636ac787bdb6e53c5b20d851dd117c Mon Sep 17 00:00:00 2001 From: Rajan Patel Date: Wed, 24 May 2023 03:28:08 -0400 Subject: [PATCH 07/10] Add i2s mclk (#4885) --- esphome/components/i2s_audio/__init__.py | 4 ++++ esphome/components/i2s_audio/i2s_audio.h | 4 +++- tests/test4.yaml | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/esphome/components/i2s_audio/__init__.py b/esphome/components/i2s_audio/__init__.py index 39d81ef1a1e4..d72e13630f22 100644 --- a/esphome/components/i2s_audio/__init__.py +++ b/esphome/components/i2s_audio/__init__.py @@ -18,6 +18,7 @@ CONF_I2S_DOUT_PIN = "i2s_dout_pin" CONF_I2S_DIN_PIN = "i2s_din_pin" +CONF_I2S_MCLK_PIN = "i2s_mclk_pin" CONF_I2S_BCLK_PIN = "i2s_bclk_pin" CONF_I2S_LRCLK_PIN = "i2s_lrclk_pin" @@ -44,6 +45,7 @@ cv.GenerateID(): cv.declare_id(I2SAudioComponent), cv.Required(CONF_I2S_LRCLK_PIN): pins.internal_gpio_output_pin_number, cv.Optional(CONF_I2S_BCLK_PIN): pins.internal_gpio_output_pin_number, + cv.Optional(CONF_I2S_MCLK_PIN): pins.internal_gpio_output_pin_number, } ) @@ -69,3 +71,5 @@ async def to_code(config): cg.add(var.set_lrclk_pin(config[CONF_I2S_LRCLK_PIN])) if CONF_I2S_BCLK_PIN in config: cg.add(var.set_bclk_pin(config[CONF_I2S_BCLK_PIN])) + if CONF_I2S_MCLK_PIN in config: + cg.add(var.set_mclk_pin(config[CONF_I2S_MCLK_PIN])) diff --git a/esphome/components/i2s_audio/i2s_audio.h b/esphome/components/i2s_audio/i2s_audio.h index f030ed4e757e..d8d4a23dde9b 100644 --- a/esphome/components/i2s_audio/i2s_audio.h +++ b/esphome/components/i2s_audio/i2s_audio.h @@ -21,7 +21,7 @@ class I2SAudioComponent : public Component { i2s_pin_config_t get_pin_config() const { return { - .mck_io_num = I2S_PIN_NO_CHANGE, + .mck_io_num = this->mclk_pin_, .bck_io_num = this->bclk_pin_, .ws_io_num = this->lrclk_pin_, .data_out_num = I2S_PIN_NO_CHANGE, @@ -29,6 +29,7 @@ class I2SAudioComponent : public Component { }; } + void set_mclk_pin(int pin) { this->mclk_pin_ = pin; } void set_bclk_pin(int pin) { this->bclk_pin_ = pin; } void set_lrclk_pin(int pin) { this->lrclk_pin_ = pin; } @@ -44,6 +45,7 @@ class I2SAudioComponent : public Component { I2SAudioIn *audio_in_{nullptr}; I2SAudioOut *audio_out_{nullptr}; + int mclk_pin_{I2S_PIN_NO_CHANGE}; int bclk_pin_{I2S_PIN_NO_CHANGE}; int lrclk_pin_; i2s_port_t port_{}; diff --git a/tests/test4.yaml b/tests/test4.yaml index c1d49a4349e5..8e76a5fd6603 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -666,6 +666,7 @@ touchscreen: i2s_audio: i2s_lrclk_pin: GPIO26 i2s_bclk_pin: GPIO27 + i2s_mclk_pin: GPIO25 media_player: - platform: i2s_audio From 9cd173ef83b4a0acfc42a5406725d81d064fcb6a Mon Sep 17 00:00:00 2001 From: guillempages Date: Thu, 25 May 2023 23:49:52 +0200 Subject: [PATCH 08/10] Allow partially looping animations (#4693) Add the possibility of specifying a "loop" in an animation; where the requested frames (start - end) will be repeateadly shown for "count" times. --- esphome/components/animation/__init__.py | 25 ++++++++++++++++++- esphome/components/display/display_buffer.cpp | 23 +++++++++++++++-- esphome/components/display/display_buffer.h | 10 ++++++-- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/esphome/components/animation/__init__.py b/esphome/components/animation/__init__.py index 1b804bd5273c..f51d115d9ef4 100644 --- a/esphome/components/animation/__init__.py +++ b/esphome/components/animation/__init__.py @@ -6,7 +6,14 @@ from esphome.components.image import CONF_USE_TRANSPARENCY import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_FILE, CONF_ID, CONF_RAW_DATA_ID, CONF_RESIZE, CONF_TYPE +from esphome.const import ( + CONF_FILE, + CONF_ID, + CONF_RAW_DATA_ID, + CONF_REPEAT, + CONF_RESIZE, + CONF_TYPE, +) from esphome.core import CORE, HexInt _LOGGER = logging.getLogger(__name__) @@ -14,6 +21,10 @@ DEPENDENCIES = ["display"] MULTI_CONF = True +CONF_LOOP = "loop" +CONF_START_FRAME = "start_frame" +CONF_END_FRAME = "end_frame" + Animation_ = display.display_ns.class_("Animation", espImage.Image_) @@ -48,6 +59,13 @@ def validate_cross_dependencies(config): # Not setting default here on purpose; the default depends on the image type, # and thus will be set in the "validate_cross_dependencies" validator. cv.Optional(CONF_USE_TRANSPARENCY): cv.boolean, + cv.Optional(CONF_LOOP): cv.All( + { + cv.Optional(CONF_START_FRAME, default=0): cv.positive_int, + cv.Optional(CONF_END_FRAME): cv.positive_int, + cv.Optional(CONF_REPEAT): cv.positive_int, + } + ), cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), }, validate_cross_dependencies, @@ -227,3 +245,8 @@ async def to_code(config): espImage.IMAGE_TYPE[config[CONF_TYPE]], ) cg.add(var.set_transparency(transparent)) + if CONF_LOOP in config: + start = config[CONF_LOOP][CONF_START_FRAME] + end = config[CONF_LOOP].get(CONF_END_FRAME, frames) + count = config[CONF_LOOP].get(CONF_REPEAT, -1) + cg.add(var.set_loop(start, end, count)) diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index 35e55bc1ba99..0d76fa09ec6f 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -773,12 +773,31 @@ Color Animation::get_grayscale_pixel(int x, int y) const { return Color(gray, gray, gray, alpha); } Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type) - : Image(data_start, width, height, type), current_frame_(0), animation_frame_count_(animation_frame_count) {} -int Animation::get_animation_frame_count() const { return this->animation_frame_count_; } + : Image(data_start, width, height, type), + current_frame_(0), + animation_frame_count_(animation_frame_count), + loop_start_frame_(0), + loop_end_frame_(animation_frame_count_), + loop_count_(0), + loop_current_iteration_(1) {} +void Animation::set_loop(uint32_t start_frame, uint32_t end_frame, int count) { + loop_start_frame_ = std::min(start_frame, animation_frame_count_); + loop_end_frame_ = std::min(end_frame, animation_frame_count_); + loop_count_ = count; + loop_current_iteration_ = 1; +} + +uint32_t Animation::get_animation_frame_count() const { return this->animation_frame_count_; } int Animation::get_current_frame() const { return this->current_frame_; } void Animation::next_frame() { this->current_frame_++; + if (loop_count_ && this->current_frame_ == loop_end_frame_ && + (this->loop_current_iteration_ < loop_count_ || loop_count_ < 0)) { + this->current_frame_ = loop_start_frame_; + this->loop_current_iteration_++; + } if (this->current_frame_ >= animation_frame_count_) { + this->loop_current_iteration_ = 1; this->current_frame_ = 0; } } diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index a8ec0e588fbe..2474d6f5a0a4 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -569,7 +569,7 @@ class Animation : public Image { Color get_rgb565_pixel(int x, int y) const override; Color get_grayscale_pixel(int x, int y) const override; - int get_animation_frame_count() const; + uint32_t get_animation_frame_count() const; int get_current_frame() const override; void next_frame(); void prev_frame(); @@ -580,9 +580,15 @@ class Animation : public Image { */ void set_frame(int frame); + void set_loop(uint32_t start_frame, uint32_t end_frame, int count); + protected: int current_frame_; - int animation_frame_count_; + uint32_t animation_frame_count_; + uint32_t loop_start_frame_; + uint32_t loop_end_frame_; + int loop_count_; + int loop_current_iteration_; }; template class DisplayPageShowAction : public Action { From 79abd773a2c402b65f0637b3d18267f383e51074 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 26 May 2023 15:50:44 +1200 Subject: [PATCH 09/10] Allow i2s microphone bits per sample to be configured (#4884) --- .../i2s_audio/microphone/__init__.py | 14 +++++++- .../microphone/i2s_audio_microphone.cpp | 35 ++++++++++++++++--- .../microphone/i2s_audio_microphone.h | 4 ++- esphome/components/microphone/__init__.py | 2 +- esphome/components/microphone/automation.h | 4 +-- esphome/components/microphone/microphone.h | 4 +-- .../voice_assistant/voice_assistant.cpp | 5 +-- 7 files changed, 54 insertions(+), 14 deletions(-) diff --git a/esphome/components/i2s_audio/microphone/__init__.py b/esphome/components/i2s_audio/microphone/__init__.py index 089e796ae0ed..07f51581887a 100644 --- a/esphome/components/i2s_audio/microphone/__init__.py +++ b/esphome/components/i2s_audio/microphone/__init__.py @@ -20,6 +20,7 @@ CONF_ADC_PIN = "adc_pin" CONF_ADC_TYPE = "adc_type" CONF_PDM = "pdm" +CONF_BITS_PER_SAMPLE = "bits_per_sample" I2SAudioMicrophone = i2s_audio_ns.class_( "I2SAudioMicrophone", I2SAudioIn, microphone.Microphone, cg.Component @@ -30,10 +31,17 @@ "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() @@ -54,6 +62,9 @@ def validate_esp32_variant(config): 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_BITS_PER_SAMPLE, default="16bit"): cv.All( + _validate_bits, cv.enum(BITS_PER_SAMPLE) + ), } ).extend(cv.COMPONENT_SCHEMA) @@ -93,6 +104,7 @@ async def to_code(config): cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN])) cg.add(var.set_pdm(config[CONF_PDM])) - cg.add(var.set_channel(CHANNELS[config[CONF_CHANNEL]])) + cg.add(var.set_channel(config[CONF_CHANNEL])) + cg.add(var.set_bits_per_sample(config[CONF_BITS_PER_SAMPLE])) 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 9452762e94cf..9c661c3ac2cb 100644 --- a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +++ b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp @@ -16,7 +16,13 @@ static const char *const TAG = "i2s_audio.microphone"; void I2SAudioMicrophone::setup() { ESP_LOGCONFIG(TAG, "Setting up I2S Audio Microphone..."); - this->buffer_.resize(BUFFER_SIZE); + ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + this->buffer_ = allocator.allocate(BUFFER_SIZE); + if (this->buffer_ == nullptr) { + ESP_LOGE(TAG, "Failed to allocate buffer!"); + this->mark_failed(); + return; + } #if SOC_I2S_SUPPORTS_ADC if (this->adc_) { @@ -48,7 +54,7 @@ void I2SAudioMicrophone::start_() { i2s_driver_config_t config = { .mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_RX), .sample_rate = 16000, - .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, + .bits_per_sample = this->bits_per_sample_, .channel_format = this->channel_, .communication_format = I2S_COMM_FORMAT_STAND_I2S, .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, @@ -107,16 +113,35 @@ void I2SAudioMicrophone::stop_() { void I2SAudioMicrophone::read_() { size_t bytes_read = 0; esp_err_t err = - i2s_read(this->parent_->get_port(), this->buffer_.data(), BUFFER_SIZE, &bytes_read, (100 / portTICK_PERIOD_MS)); + i2s_read(this->parent_->get_port(), this->buffer_, BUFFER_SIZE, &bytes_read, (100 / portTICK_PERIOD_MS)); if (err != ESP_OK) { ESP_LOGW(TAG, "Error reading from I2S microphone: %s", esp_err_to_name(err)); this->status_set_warning(); return; } - this->status_clear_warning(); - this->data_callbacks_.call(this->buffer_); + std::vector samples; + size_t samples_read = 0; + if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_16BIT) { + samples_read = bytes_read / sizeof(int16_t); + } else if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_32BIT) { + samples_read = bytes_read / sizeof(int32_t); + } else { + ESP_LOGE(TAG, "Unsupported bits per sample: %d", this->bits_per_sample_); + return; + } + samples.resize(samples_read); + if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_16BIT) { + memcpy(samples.data(), this->buffer_, bytes_read); + } else if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_32BIT) { + for (size_t i = 0; i < samples_read; i++) { + int32_t temp = reinterpret_cast(this->buffer_)[i] >> 14; + samples[i] = clamp(temp, INT16_MIN, INT16_MAX); + } + } + + this->data_callbacks_.call(samples); } void I2SAudioMicrophone::loop() { diff --git a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h index acc7d2b45aa9..0cb87d42fd6a 100644 --- a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h +++ b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h @@ -29,6 +29,7 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub #endif void set_channel(i2s_channel_fmt_t channel) { this->channel_ = channel; } + void set_bits_per_sample(i2s_bits_per_sample_t bits_per_sample) { this->bits_per_sample_ = bits_per_sample; } protected: void start_(); @@ -41,8 +42,9 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub bool adc_{false}; #endif bool pdm_{false}; - std::vector buffer_; + uint8_t *buffer_; i2s_channel_fmt_t channel_; + i2s_bits_per_sample_t bits_per_sample_; HighFrequencyLoopRequester high_freq_; }; diff --git a/esphome/components/microphone/__init__.py b/esphome/components/microphone/__init__.py index ff1f7aa9633d..d99500bbed78 100644 --- a/esphome/components/microphone/__init__.py +++ b/esphome/components/microphone/__init__.py @@ -41,7 +41,7 @@ async def setup_microphone_core_(var, config): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation( trigger, - [(cg.std_vector.template(cg.uint8).operator("ref").operator("const"), "x")], + [(cg.std_vector.template(cg.int16).operator("ref").operator("const"), "x")], conf, ) diff --git a/esphome/components/microphone/automation.h b/esphome/components/microphone/automation.h index 5f404b8d74fa..5313f07f727c 100644 --- a/esphome/components/microphone/automation.h +++ b/esphome/components/microphone/automation.h @@ -16,10 +16,10 @@ template class StopCaptureAction : public Action, public void play(Ts... x) override { this->parent_->stop(); } }; -class DataTrigger : public Trigger &> { +class DataTrigger : public Trigger &> { public: explicit DataTrigger(Microphone *mic) { - mic->add_data_callback([this](const std::vector &data) { this->trigger(data); }); + mic->add_data_callback([this](const std::vector &data) { this->trigger(data); }); } }; diff --git a/esphome/components/microphone/microphone.h b/esphome/components/microphone/microphone.h index b725f66ad764..5b16a67c0063 100644 --- a/esphome/components/microphone/microphone.h +++ b/esphome/components/microphone/microphone.h @@ -17,7 +17,7 @@ class Microphone { public: virtual void start() = 0; virtual void stop() = 0; - void add_data_callback(std::function &)> &&data_callback) { + void add_data_callback(std::function &)> &&data_callback) { this->data_callbacks_.add(std::move(data_callback)); } @@ -26,7 +26,7 @@ class Microphone { protected: State state_{STATE_STOPPED}; - CallbackManager &)> data_callbacks_{}; + CallbackManager &)> data_callbacks_{}; }; } // namespace microphone diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index fb96d484d485..42455787111f 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -58,11 +58,12 @@ void VoiceAssistant::setup() { } #endif - this->mic_->add_data_callback([this](const std::vector &data) { + this->mic_->add_data_callback([this](const std::vector &data) { if (!this->running_) { return; } - this->socket_->sendto(data.data(), data.size(), 0, (struct sockaddr *) &this->dest_addr_, sizeof(this->dest_addr_)); + this->socket_->sendto(data.data(), data.size() * sizeof(int16_t), 0, (struct sockaddr *) &this->dest_addr_, + sizeof(this->dest_addr_)); }); } From 97c1c347082f471d345311a8d31361ebef5130ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Fri, 26 May 2023 07:01:21 +0200 Subject: [PATCH 10/10] Add support for TMP1075 temperature sensor (#4776) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add support for TMP1075 temperature sensor TMP1075 is a temperature sensor with I2C interface in industry standard LM75 form factor and pinout. https://www.ti.com/product/TMP1075 Example YAML: ```yaml sensor: - platform: tmp1075 name: TMP1075 Temperature id: radiator_temp update_interval: 10s i2c_id: i2c_bus_1 conversion_rate: 27.5ms alert: limit_low: 50 limit_high: 75 fault_count: 1 polarity: active_high ``` * Add myself as codeowner of the TMP1075 component * Include '°C' unit when logging low/high limit setting * Reformat No functional changes. * Fix logging: use %.4f for temperatures, not %d * Fix config initialisation * Use relative include for `tmp1075.h` * Apply formatting changes suggested by script/clang-tidy for ESP32 * Add YAML to test1.yaml * Fix test1.yaml by giving TMP1075 a name * Less verbose logging (debug -> verbose level) * Schema: reduce accuracy_decimals to 2 * I2C address as hexadecimal * Proper name for enum in Python The enum on the C++ side was renamed (clang-tidy) but I forgot to take that into account in the Python code. * Expose 'alert function' to the code generator/YAML params and remove 'shutdown' Shutdown mode doesn't work the way I expect it, so remove it until someone actually asks for it. Also 'alert mode' was renamed to 'alert function' for clarity. * Move simple setters to header file * Remove `load_config_();` function --- CODEOWNERS | 1 + esphome/components/tmp1075/__init__.py | 1 + esphome/components/tmp1075/sensor.py | 92 ++++++++++++++++++ esphome/components/tmp1075/tmp1075.cpp | 129 +++++++++++++++++++++++++ esphome/components/tmp1075/tmp1075.h | 92 ++++++++++++++++++ tests/test1.yaml | 11 +++ 6 files changed, 326 insertions(+) create mode 100644 esphome/components/tmp1075/__init__.py create mode 100644 esphome/components/tmp1075/sensor.py create mode 100644 esphome/components/tmp1075/tmp1075.cpp create mode 100644 esphome/components/tmp1075/tmp1075.h diff --git a/CODEOWNERS b/CODEOWNERS index ded5501c62a3..1b752036548f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -285,6 +285,7 @@ esphome/components/tm1637/* @glmnet esphome/components/tm1638/* @skykingjwc esphome/components/tm1651/* @freekode esphome/components/tmp102/* @timsavage +esphome/components/tmp1075/* @sybrenstuvel esphome/components/tmp117/* @Azimath esphome/components/tof10120/* @wstrzalka esphome/components/toshiba/* @kbx81 diff --git a/esphome/components/tmp1075/__init__.py b/esphome/components/tmp1075/__init__.py new file mode 100644 index 000000000000..ddd04ad11a46 --- /dev/null +++ b/esphome/components/tmp1075/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@sybrenstuvel"] diff --git a/esphome/components/tmp1075/sensor.py b/esphome/components/tmp1075/sensor.py new file mode 100644 index 000000000000..25ec350b7ac9 --- /dev/null +++ b/esphome/components/tmp1075/sensor.py @@ -0,0 +1,92 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + ICON_THERMOMETER, +) + +DEPENDENCIES = ["i2c"] + +tmp1075_ns = cg.esphome_ns.namespace("tmp1075") + +TMP1075Sensor = tmp1075_ns.class_( + "TMP1075Sensor", cg.PollingComponent, sensor.Sensor, i2c.I2CDevice +) + +EConversionRate = tmp1075_ns.enum("EConversionRate") +CONVERSION_RATES = { + "27.5ms": EConversionRate.CONV_RATE_27_5_MS, + "55ms": EConversionRate.CONV_RATE_55_MS, + "110ms": EConversionRate.CONV_RATE_110_MS, + "220ms": EConversionRate.CONV_RATE_220_MS, +} + +POLARITY = { + "ACTIVE_LOW": 0, + "ACTIVE_HIGH": 1, +} + +EAlertFunction = tmp1075_ns.enum("EAlertFunction") +ALERT_FUNCTION = { + "COMPARATOR": EAlertFunction.ALERT_COMPARATOR, + "INTERRUPT": EAlertFunction.ALERT_INTERRUPT, +} + +CONF_ALERT = "alert" +CONF_LIMIT_LOW = "limit_low" +CONF_LIMIT_HIGH = "limit_high" +CONF_FAULT_COUNT = "fault_count" +CONF_POLARITY = "polarity" +CONF_CONVERSION_RATE = "conversion_rate" +CONF_FUNCTION = "function" + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + TMP1075Sensor, + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend( + { + cv.Optional(CONF_CONVERSION_RATE): cv.enum(CONVERSION_RATES, lower=True), + cv.Optional(CONF_ALERT, default={}): cv.Schema( + { + cv.Optional(CONF_LIMIT_LOW): cv.temperature, + cv.Optional(CONF_LIMIT_HIGH): cv.temperature, + cv.Optional(CONF_FAULT_COUNT): cv.int_range(min=1, max=4), + cv.Optional(CONF_POLARITY): cv.enum(POLARITY, upper=True), + cv.Optional(CONF_FUNCTION): cv.enum(ALERT_FUNCTION, upper=True), + } + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x48)) +) + + +async def to_code(config): + var = await sensor.new_sensor(config) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if CONF_CONVERSION_RATE in config: + cg.add(var.set_conversion_rate(config[CONF_CONVERSION_RATE])) + + alert = config[CONF_ALERT] + if CONF_LIMIT_LOW in alert: + cg.add(var.set_alert_limit_low(alert[CONF_LIMIT_LOW])) + if CONF_LIMIT_HIGH in alert: + cg.add(var.set_alert_limit_high(alert[CONF_LIMIT_HIGH])) + if CONF_FAULT_COUNT in alert: + cg.add(var.set_fault_count(alert[CONF_FAULT_COUNT])) + if CONF_POLARITY in alert: + cg.add(var.set_alert_polarity(alert[CONF_POLARITY])) + if CONF_FUNCTION in alert: + cg.add(var.set_alert_function(alert[CONF_FUNCTION])) diff --git a/esphome/components/tmp1075/tmp1075.cpp b/esphome/components/tmp1075/tmp1075.cpp new file mode 100644 index 000000000000..38ed2bea31f7 --- /dev/null +++ b/esphome/components/tmp1075/tmp1075.cpp @@ -0,0 +1,129 @@ +#include "esphome/core/log.h" +#include "tmp1075.h" + +namespace esphome { +namespace tmp1075 { + +static const char *const TAG = "tmp1075"; + +constexpr uint8_t REG_TEMP = 0x0; // Temperature result +constexpr uint8_t REG_CFGR = 0x1; // Configuration +constexpr uint8_t REG_LLIM = 0x2; // Low limit +constexpr uint8_t REG_HLIM = 0x3; // High limit +constexpr uint8_t REG_DIEID = 0xF; // Device ID + +constexpr uint16_t EXPECT_DIEID = 0x0075; // Expected Device ID. + +static uint16_t temp2regvalue(float temp); +static float regvalue2temp(uint16_t regvalue); + +void TMP1075Sensor::setup() { + uint8_t die_id; + if (!this->read_byte(REG_DIEID, &die_id)) { + ESP_LOGW(TAG, "'%s' - unable to read ID", this->name_.c_str()); + this->mark_failed(); + return; + } + if (die_id != EXPECT_DIEID) { + ESP_LOGW(TAG, "'%s' - unexpected ID 0x%x found, expected 0x%x", this->name_.c_str(), die_id, EXPECT_DIEID); + this->mark_failed(); + return; + } + + this->write_config(); +} + +void TMP1075Sensor::update() { + uint16_t regvalue; + if (!read_byte_16(REG_TEMP, ®value)) { + ESP_LOGW(TAG, "'%s' - unable to read temperature register", this->name_.c_str()); + this->status_set_warning(); + return; + } + + const float temp = regvalue2temp(regvalue); + this->publish_state(temp); +} + +void TMP1075Sensor::dump_config() { + LOG_SENSOR("", "TMP1075 Sensor", this); + if (this->is_failed()) { + ESP_LOGE(TAG, " Communication with TMP1075 failed!"); + return; + } + ESP_LOGCONFIG(TAG, " limit low : %.4f °C", alert_limit_low_); + ESP_LOGCONFIG(TAG, " limit high : %.4f °C", alert_limit_high_); + ESP_LOGCONFIG(TAG, " oneshot : %d", config_.fields.oneshot); + ESP_LOGCONFIG(TAG, " rate : %d", config_.fields.rate); + ESP_LOGCONFIG(TAG, " fault_count: %d", config_.fields.faults); + ESP_LOGCONFIG(TAG, " polarity : %d", config_.fields.polarity); + ESP_LOGCONFIG(TAG, " alert_mode : %d", config_.fields.alert_mode); + ESP_LOGCONFIG(TAG, " shutdown : %d", config_.fields.shutdown); +} + +void TMP1075Sensor::set_fault_count(const int faults) { + if (faults < 1) { + ESP_LOGE(TAG, "'%s' - fault_count too low: %d", this->name_.c_str(), faults); + return; + } + if (faults > 4) { + ESP_LOGE(TAG, "'%s' - fault_count too high: %d", this->name_.c_str(), faults); + return; + } + config_.fields.faults = faults - 1; +} + +void TMP1075Sensor::log_config_() { + ESP_LOGV(TAG, " oneshot : %d", config_.fields.oneshot); + ESP_LOGV(TAG, " rate : %d", config_.fields.rate); + ESP_LOGV(TAG, " faults : %d", config_.fields.faults); + ESP_LOGV(TAG, " polarity : %d", config_.fields.polarity); + ESP_LOGV(TAG, " alert_mode: %d", config_.fields.alert_mode); + ESP_LOGV(TAG, " shutdown : %d", config_.fields.shutdown); +} + +void TMP1075Sensor::write_config() { + send_alert_limit_low_(); + send_alert_limit_high_(); + send_config_(); +} + +void TMP1075Sensor::send_config_() { + ESP_LOGV(TAG, "'%s' - sending configuration %04x", this->name_.c_str(), config_.regvalue); + log_config_(); + if (!this->write_byte_16(REG_CFGR, config_.regvalue)) { + ESP_LOGW(TAG, "'%s' - unable to write configuration register", this->name_.c_str()); + return; + } +} + +void TMP1075Sensor::send_alert_limit_low_() { + ESP_LOGV(TAG, "'%s' - sending alert limit low %.3f °C", this->name_.c_str(), alert_limit_low_); + const uint16_t regvalue = temp2regvalue(alert_limit_low_); + if (!this->write_byte_16(REG_LLIM, regvalue)) { + ESP_LOGW(TAG, "'%s' - unable to write low limit register", this->name_.c_str()); + return; + } +} + +void TMP1075Sensor::send_alert_limit_high_() { + ESP_LOGV(TAG, "'%s' - sending alert limit high %.3f °C", this->name_.c_str(), alert_limit_high_); + const uint16_t regvalue = temp2regvalue(alert_limit_high_); + if (!this->write_byte_16(REG_HLIM, regvalue)) { + ESP_LOGW(TAG, "'%s' - unable to write high limit register", this->name_.c_str()); + return; + } +} + +static uint16_t temp2regvalue(const float temp) { + const uint16_t regvalue = temp / 0.0625f; + return regvalue << 4; +} + +static float regvalue2temp(const uint16_t regvalue) { + const int16_t signed_value = regvalue; + return (signed_value >> 4) * 0.0625f; +} + +} // namespace tmp1075 +} // namespace esphome diff --git a/esphome/components/tmp1075/tmp1075.h b/esphome/components/tmp1075/tmp1075.h new file mode 100644 index 000000000000..db2bac517a65 --- /dev/null +++ b/esphome/components/tmp1075/tmp1075.h @@ -0,0 +1,92 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace tmp1075 { + +struct TMP1075Config { + union { + struct { + uint8_t oneshot : 1; // One-shot conversion mode. Writing 1, starts a single temperature + // conversion. Read returns 0. + + uint8_t rate : 2; // Conversion rate setting when device is in continuous conversion mode. + // 00: 27.5 ms conversion rate + // 01: 55 ms conversion rate + // 10: 110 ms conversion rate + // 11: 220 ms conversion rate (35 ms TMP1075N) + + uint8_t faults : 2; // Consecutive fault measurements to trigger the alert function. + // 00: 1 fault + // 01: 2 faults + // 10: 3 faults (4 faults TMP1075N) + // 11: 4 faults (6 faults TMP1075N) + + uint8_t polarity : 1; // Polarity of the output pin. + // 0: Active low ALERT pin + // 1: Active high ALERT pin + + uint8_t alert_mode : 1; // Selects the function of the ALERT pin. + // 0: ALERT pin functions in comparator mode + // 1: ALERT pin functions in interrupt mode + + uint8_t shutdown : 1; // Sets the device in shutdown mode to conserve power. + // 0: Device is in continuous conversion + // 1: Device is in shutdown mode + uint8_t unused : 8; + } fields; + uint16_t regvalue; + }; +}; + +enum EConversionRate { + CONV_RATE_27_5_MS, + CONV_RATE_55_MS, + CONV_RATE_110_MS, + CONV_RATE_220_MS, +}; + +enum EAlertFunction { + ALERT_COMPARATOR = 0, + ALERT_INTERRUPT = 1, +}; + +class TMP1075Sensor : public PollingComponent, public sensor::Sensor, public i2c::I2CDevice { + public: + void setup() override; + void update() override; + + float get_setup_priority() const override { return setup_priority::DATA; } + + void dump_config() override; + + // Call write_config() after calling any of these to send the new config to + // the IC. The setup() function also does this. + void set_alert_limit_low(const float temp) { this->alert_limit_low_ = temp; } + void set_alert_limit_high(const float temp) { this->alert_limit_high_ = temp; } + void set_oneshot(const bool oneshot) { config_.fields.oneshot = oneshot; } + void set_conversion_rate(const enum EConversionRate rate) { config_.fields.rate = rate; } + void set_alert_polarity(const bool polarity) { config_.fields.polarity = polarity; } + void set_alert_function(const enum EAlertFunction function) { config_.fields.alert_mode = function; } + void set_fault_count(int faults); + + void write_config(); + + protected: + TMP1075Config config_ = {}; + + // Disable the alert pin by default. + float alert_limit_low_ = -128.0f; + float alert_limit_high_ = 127.9375f; + + void send_alert_limit_low_(); + void send_alert_limit_high_(); + void send_config_(); + void log_config_(); +}; + +} // namespace tmp1075 +} // namespace esphome diff --git a/tests/test1.yaml b/tests/test1.yaml index 0058c08c7457..bee0d93faf6f 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1299,6 +1299,17 @@ sensor: id: temp_etuve humidity: name: "Humidity hyt271" + - platform: tmp1075 + name: "Temperature TMP1075" + update_interval: 10s + i2c_id: i2c_bus + conversion_rate: 27.5ms + alert: + limit_low: 50 + limit_high: 75 + fault_count: 1 + polarity: active_high + function: comparator esp32_touch: setup_mode: false