mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Merge branch 'dev' into vornado-ir
This commit is contained in:
		
							
								
								
									
										2
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							| @@ -46,7 +46,7 @@ jobs: | |||||||
|         with: |         with: | ||||||
|           python-version: "3.9" |           python-version: "3.9" | ||||||
|       - name: Set up Docker Buildx |       - name: Set up Docker Buildx | ||||||
|         uses: docker/setup-buildx-action@v3.7.1 |         uses: docker/setup-buildx-action@v3.8.0 | ||||||
|       - name: Set up QEMU |       - name: Set up QEMU | ||||||
|         uses: docker/setup-qemu-action@v3.2.0 |         uses: docker/setup-qemu-action@v3.2.0 | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -13,6 +13,7 @@ on: | |||||||
|       - ".github/workflows/ci.yml" |       - ".github/workflows/ci.yml" | ||||||
|       - "!.yamllint" |       - "!.yamllint" | ||||||
|       - "!.github/dependabot.yml" |       - "!.github/dependabot.yml" | ||||||
|  |       - "!docker/**" | ||||||
|   merge_group: |   merge_group: | ||||||
|  |  | ||||||
| permissions: | permissions: | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -65,7 +65,7 @@ jobs: | |||||||
|           pip3 install build |           pip3 install build | ||||||
|           python3 -m build |           python3 -m build | ||||||
|       - name: Publish |       - name: Publish | ||||||
|         uses: pypa/gh-action-pypi-publish@v1.12.2 |         uses: pypa/gh-action-pypi-publish@v1.12.3 | ||||||
|  |  | ||||||
|   deploy-docker: |   deploy-docker: | ||||||
|     name: Build ESPHome ${{ matrix.platform }} |     name: Build ESPHome ${{ matrix.platform }} | ||||||
| @@ -90,7 +90,7 @@ jobs: | |||||||
|           python-version: "3.9" |           python-version: "3.9" | ||||||
|  |  | ||||||
|       - name: Set up Docker Buildx |       - name: Set up Docker Buildx | ||||||
|         uses: docker/setup-buildx-action@v3.7.1 |         uses: docker/setup-buildx-action@v3.8.0 | ||||||
|       - name: Set up QEMU |       - name: Set up QEMU | ||||||
|         if: matrix.platform != 'linux/amd64' |         if: matrix.platform != 'linux/amd64' | ||||||
|         uses: docker/setup-qemu-action@v3.2.0 |         uses: docker/setup-qemu-action@v3.2.0 | ||||||
| @@ -141,7 +141,7 @@ jobs: | |||||||
|           echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT |           echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT | ||||||
|  |  | ||||||
|       - name: Upload digests |       - name: Upload digests | ||||||
|         uses: actions/upload-artifact@v4.4.3 |         uses: actions/upload-artifact@v4.5.0 | ||||||
|         with: |         with: | ||||||
|           name: digests-${{ steps.sanitize.outputs.name }} |           name: digests-${{ steps.sanitize.outputs.name }} | ||||||
|           path: /tmp/digests |           path: /tmp/digests | ||||||
| @@ -184,7 +184,7 @@ jobs: | |||||||
|           merge-multiple: true |           merge-multiple: true | ||||||
|  |  | ||||||
|       - name: Set up Docker Buildx |       - name: Set up Docker Buildx | ||||||
|         uses: docker/setup-buildx-action@v3.7.1 |         uses: docker/setup-buildx-action@v3.8.0 | ||||||
|  |  | ||||||
|       - name: Log in to docker hub |       - name: Log in to docker hub | ||||||
|         if: matrix.registry == 'dockerhub' |         if: matrix.registry == 'dockerhub' | ||||||
|   | |||||||
| @@ -355,6 +355,7 @@ esphome/components/sdl/* @clydebarrow | |||||||
| esphome/components/sdm_meter/* @jesserockz @polyfaces | esphome/components/sdm_meter/* @jesserockz @polyfaces | ||||||
| esphome/components/sdp3x/* @Azimath | esphome/components/sdp3x/* @Azimath | ||||||
| esphome/components/seeed_mr24hpc1/* @limengdu | esphome/components/seeed_mr24hpc1/* @limengdu | ||||||
|  | esphome/components/seeed_mr60bha2/* @limengdu | ||||||
| esphome/components/seeed_mr60fda2/* @limengdu | esphome/components/seeed_mr60fda2/* @limengdu | ||||||
| esphome/components/selec_meter/* @sourabhjaiswal | esphome/components/selec_meter/* @sourabhjaiswal | ||||||
| esphome/components/select/* @esphome/core | esphome/components/select/* @esphome/core | ||||||
|   | |||||||
| @@ -1,11 +1,6 @@ | |||||||
| import esphome.codegen as cg |  | ||||||
| import esphome.config_validation as cv |  | ||||||
| from esphome import pins | from esphome import pins | ||||||
| from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER | import esphome.codegen as cg | ||||||
|  |  | ||||||
| from esphome.core import CORE |  | ||||||
| from esphome.components.esp32 import get_esp32_variant | from esphome.components.esp32 import get_esp32_variant | ||||||
| from esphome.const import PLATFORM_ESP8266 |  | ||||||
| from esphome.components.esp32.const import ( | from esphome.components.esp32.const import ( | ||||||
|     VARIANT_ESP32, |     VARIANT_ESP32, | ||||||
|     VARIANT_ESP32C2, |     VARIANT_ESP32C2, | ||||||
| @@ -15,6 +10,9 @@ from esphome.components.esp32.const import ( | |||||||
|     VARIANT_ESP32S2, |     VARIANT_ESP32S2, | ||||||
|     VARIANT_ESP32S3, |     VARIANT_ESP32S3, | ||||||
| ) | ) | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER, PLATFORM_ESP8266 | ||||||
|  | from esphome.core import CORE | ||||||
|  |  | ||||||
| CODEOWNERS = ["@esphome/core"] | CODEOWNERS = ["@esphome/core"] | ||||||
|  |  | ||||||
| @@ -102,11 +100,11 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = { | |||||||
|         6: adc1_channel_t.ADC1_CHANNEL_6, |         6: adc1_channel_t.ADC1_CHANNEL_6, | ||||||
|     }, |     }, | ||||||
|     VARIANT_ESP32H2: { |     VARIANT_ESP32H2: { | ||||||
|         0: adc1_channel_t.ADC1_CHANNEL_0, |         1: adc1_channel_t.ADC1_CHANNEL_0, | ||||||
|         1: adc1_channel_t.ADC1_CHANNEL_1, |         2: adc1_channel_t.ADC1_CHANNEL_1, | ||||||
|         2: adc1_channel_t.ADC1_CHANNEL_2, |         3: adc1_channel_t.ADC1_CHANNEL_2, | ||||||
|         3: adc1_channel_t.ADC1_CHANNEL_3, |         4: adc1_channel_t.ADC1_CHANNEL_3, | ||||||
|         4: adc1_channel_t.ADC1_CHANNEL_4, |         5: adc1_channel_t.ADC1_CHANNEL_4, | ||||||
|     }, |     }, | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,13 +3,12 @@ | |||||||
| #include "esphome/components/sensor/sensor.h" | #include "esphome/components/sensor/sensor.h" | ||||||
| #include "esphome/components/voltage_sampler/voltage_sampler.h" | #include "esphome/components/voltage_sampler/voltage_sampler.h" | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
| #include "esphome/core/defines.h" |  | ||||||
| #include "esphome/core/hal.h" | #include "esphome/core/hal.h" | ||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
| #include <esp_adc_cal.h> | #include <esp_adc_cal.h> | ||||||
| #include "driver/adc.h" | #include "driver/adc.h" | ||||||
| #endif | #endif  // USE_ESP32 | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace adc { | namespace adc { | ||||||
| @@ -43,7 +42,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage | |||||||
|     this->channel1_ = ADC1_CHANNEL_MAX; |     this->channel1_ = ADC1_CHANNEL_MAX; | ||||||
|   } |   } | ||||||
|   void set_autorange(bool autorange) { this->autorange_ = autorange; } |   void set_autorange(bool autorange) { this->autorange_ = autorange; } | ||||||
| #endif | #endif  // USE_ESP32 | ||||||
|  |  | ||||||
|   /// Update ADC values |   /// Update ADC values | ||||||
|   void update() override; |   void update() override; | ||||||
| @@ -59,11 +58,11 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage | |||||||
|  |  | ||||||
| #ifdef USE_ESP8266 | #ifdef USE_ESP8266 | ||||||
|   std::string unique_id() override; |   std::string unique_id() override; | ||||||
| #endif | #endif  // USE_ESP8266 | ||||||
|  |  | ||||||
| #ifdef USE_RP2040 | #ifdef USE_RP2040 | ||||||
|   void set_is_temperature() { this->is_temperature_ = true; } |   void set_is_temperature() { this->is_temperature_ = true; } | ||||||
| #endif | #endif  // USE_RP2040 | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   InternalGPIOPin *pin_; |   InternalGPIOPin *pin_; | ||||||
| @@ -72,7 +71,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage | |||||||
|  |  | ||||||
| #ifdef USE_RP2040 | #ifdef USE_RP2040 | ||||||
|   bool is_temperature_{false}; |   bool is_temperature_{false}; | ||||||
| #endif | #endif  // USE_RP2040 | ||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|   adc_atten_t attenuation_{ADC_ATTEN_DB_0}; |   adc_atten_t attenuation_{ADC_ATTEN_DB_0}; | ||||||
| @@ -83,8 +82,8 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage | |||||||
|   esp_adc_cal_characteristics_t cal_characteristics_[SOC_ADC_ATTEN_NUM] = {}; |   esp_adc_cal_characteristics_t cal_characteristics_[SOC_ADC_ATTEN_NUM] = {}; | ||||||
| #else | #else | ||||||
|   esp_adc_cal_characteristics_t cal_characteristics_[ADC_ATTEN_MAX] = {}; |   esp_adc_cal_characteristics_t cal_characteristics_[ADC_ATTEN_MAX] = {}; | ||||||
| #endif | #endif  // ESP_IDF_VERSION_MAJOR | ||||||
| #endif | #endif  // USE_ESP32 | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace adc | }  // namespace adc | ||||||
|   | |||||||
							
								
								
									
										24
									
								
								esphome/components/adc/adc_sensor_common.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								esphome/components/adc/adc_sensor_common.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | #include "adc_sensor.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace adc { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "adc.common"; | ||||||
|  |  | ||||||
|  | void ADCSensor::update() { | ||||||
|  |   float value_v = this->sample(); | ||||||
|  |   ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v); | ||||||
|  |   this->publish_state(value_v); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ADCSensor::set_sample_count(uint8_t sample_count) { | ||||||
|  |   if (sample_count != 0) { | ||||||
|  |     this->sample_count_ = sample_count; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float ADCSensor::get_setup_priority() const { return setup_priority::DATA; } | ||||||
|  |  | ||||||
|  | }  // namespace adc | ||||||
|  | }  // namespace esphome | ||||||
| @@ -1,30 +1,13 @@ | |||||||
|  | #ifdef USE_ESP32 | ||||||
|  | 
 | ||||||
| #include "adc_sensor.h" | #include "adc_sensor.h" | ||||||
| #include "esphome/core/helpers.h" |  | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
| 
 | 
 | ||||||
| #ifdef USE_ESP8266 |  | ||||||
| #ifdef USE_ADC_SENSOR_VCC |  | ||||||
| #include <Esp.h> |  | ||||||
| ADC_MODE(ADC_VCC) |  | ||||||
| #else |  | ||||||
| #include <Arduino.h> |  | ||||||
| #endif |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| #ifdef USE_RP2040 |  | ||||||
| #ifdef CYW43_USES_VSYS_PIN |  | ||||||
| #include "pico/cyw43_arch.h" |  | ||||||
| #endif |  | ||||||
| #include <hardware/adc.h> |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace adc { | namespace adc { | ||||||
| 
 | 
 | ||||||
| static const char *const TAG = "adc"; | static const char *const TAG = "adc.esp32"; | ||||||
| 
 | 
 | ||||||
| // 13-bit for S2, 12-bit for all other ESP32 variants
 |  | ||||||
| #ifdef USE_ESP32 |  | ||||||
| static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast<adc_bits_width_t>(ADC_WIDTH_MAX - 1); | static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast<adc_bits_width_t>(ADC_WIDTH_MAX - 1); | ||||||
| 
 | 
 | ||||||
| #ifndef SOC_ADC_RTC_MAX_BITWIDTH | #ifndef SOC_ADC_RTC_MAX_BITWIDTH | ||||||
| @@ -32,24 +15,15 @@ static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast<adc_bits_widt | |||||||
| static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 13; | static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 13; | ||||||
| #else | #else | ||||||
| static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 12; | static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 12; | ||||||
| #endif | #endif  // USE_ESP32_VARIANT_ESP32S2
 | ||||||
| #endif | #endif  // SOC_ADC_RTC_MAX_BITWIDTH
 | ||||||
| 
 | 
 | ||||||
| static const int ADC_MAX = (1 << SOC_ADC_RTC_MAX_BITWIDTH) - 1;    // 4095 (12 bit) or 8191 (13 bit)
 | static const int ADC_MAX = (1 << SOC_ADC_RTC_MAX_BITWIDTH) - 1; | ||||||
| static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1;  // 2048 (12 bit) or 4096 (13 bit)
 | static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1; | ||||||
| #endif |  | ||||||
| 
 | 
 | ||||||
| #ifdef USE_RP2040 | void ADCSensor::setup() { | ||||||
| extern "C" |  | ||||||
| #endif |  | ||||||
|     void |  | ||||||
|     ADCSensor::setup() { |  | ||||||
|   ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); |   ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); | ||||||
| #if !defined(USE_ADC_SENSOR_VCC) && !defined(USE_RP2040) |  | ||||||
|   this->pin_->setup(); |  | ||||||
| #endif |  | ||||||
| 
 | 
 | ||||||
| #ifdef USE_ESP32 |  | ||||||
|   if (this->channel1_ != ADC1_CHANNEL_MAX) { |   if (this->channel1_ != ADC1_CHANNEL_MAX) { | ||||||
|     adc1_config_width(ADC_WIDTH_MAX_SOC_BITS); |     adc1_config_width(ADC_WIDTH_MAX_SOC_BITS); | ||||||
|     if (!this->autorange_) { |     if (!this->autorange_) { | ||||||
| @@ -61,7 +35,6 @@ extern "C" | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   // load characteristics for each attenuation
 |  | ||||||
|   for (int32_t i = 0; i <= ADC_ATTEN_DB_12_COMPAT; i++) { |   for (int32_t i = 0; i <= ADC_ATTEN_DB_12_COMPAT; i++) { | ||||||
|     auto adc_unit = this->channel1_ != ADC1_CHANNEL_MAX ? ADC_UNIT_1 : ADC_UNIT_2; |     auto adc_unit = this->channel1_ != ADC1_CHANNEL_MAX ? ADC_UNIT_1 : ADC_UNIT_2; | ||||||
|     auto cal_value = esp_adc_cal_characterize(adc_unit, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS, |     auto cal_value = esp_adc_cal_characterize(adc_unit, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS, | ||||||
| @@ -79,31 +52,10 @@ extern "C" | |||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 |  | ||||||
| #endif  // USE_ESP32
 |  | ||||||
| 
 |  | ||||||
| #ifdef USE_RP2040 |  | ||||||
|   static bool initialized = false; |  | ||||||
|   if (!initialized) { |  | ||||||
|     adc_init(); |  | ||||||
|     initialized = true; |  | ||||||
|   } |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
|   ESP_LOGCONFIG(TAG, "ADC '%s' setup finished!", this->get_name().c_str()); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void ADCSensor::dump_config() { | void ADCSensor::dump_config() { | ||||||
|   LOG_SENSOR("", "ADC Sensor", this); |   LOG_SENSOR("", "ADC Sensor", this); | ||||||
| #if defined(USE_ESP8266) || defined(USE_LIBRETINY) |  | ||||||
| #ifdef USE_ADC_SENSOR_VCC |  | ||||||
|   ESP_LOGCONFIG(TAG, "  Pin: VCC"); |  | ||||||
| #else |  | ||||||
|   LOG_PIN("  Pin: ", this->pin_); |  | ||||||
| #endif |  | ||||||
| #endif  // USE_ESP8266 || USE_LIBRETINY
 |  | ||||||
| 
 |  | ||||||
| #ifdef USE_ESP32 |  | ||||||
|   LOG_PIN("  Pin: ", this->pin_); |   LOG_PIN("  Pin: ", this->pin_); | ||||||
|   if (this->autorange_) { |   if (this->autorange_) { | ||||||
|     ESP_LOGCONFIG(TAG, "  Attenuation: auto"); |     ESP_LOGCONFIG(TAG, "  Attenuation: auto"); | ||||||
| @@ -125,55 +77,10 @@ void ADCSensor::dump_config() { | |||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| #endif  // USE_ESP32
 |  | ||||||
| 
 |  | ||||||
| #ifdef USE_RP2040 |  | ||||||
|   if (this->is_temperature_) { |  | ||||||
|     ESP_LOGCONFIG(TAG, "  Pin: Temperature"); |  | ||||||
|   } else { |  | ||||||
| #ifdef USE_ADC_SENSOR_VCC |  | ||||||
|     ESP_LOGCONFIG(TAG, "  Pin: VCC"); |  | ||||||
| #else |  | ||||||
|     LOG_PIN("  Pin: ", this->pin_); |  | ||||||
| #endif  // USE_ADC_SENSOR_VCC
 |  | ||||||
|   } |  | ||||||
| #endif  // USE_RP2040
 |  | ||||||
|   ESP_LOGCONFIG(TAG, "  Samples: %i", this->sample_count_); |   ESP_LOGCONFIG(TAG, "  Samples: %i", this->sample_count_); | ||||||
|   LOG_UPDATE_INTERVAL(this); |   LOG_UPDATE_INTERVAL(this); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| float ADCSensor::get_setup_priority() const { return setup_priority::DATA; } |  | ||||||
| void ADCSensor::update() { |  | ||||||
|   float value_v = this->sample(); |  | ||||||
|   ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v); |  | ||||||
|   this->publish_state(value_v); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void ADCSensor::set_sample_count(uint8_t sample_count) { |  | ||||||
|   if (sample_count != 0) { |  | ||||||
|     this->sample_count_ = sample_count; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #ifdef USE_ESP8266 |  | ||||||
| float ADCSensor::sample() { |  | ||||||
|   uint32_t raw = 0; |  | ||||||
|   for (uint8_t sample = 0; sample < this->sample_count_; sample++) { |  | ||||||
| #ifdef USE_ADC_SENSOR_VCC |  | ||||||
|     raw += ESP.getVcc();  // NOLINT(readability-static-accessed-through-instance)
 |  | ||||||
| #else |  | ||||||
|     raw += analogRead(this->pin_->get_pin());  // NOLINT
 |  | ||||||
| #endif |  | ||||||
|   } |  | ||||||
|   raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_;  // NOLINT(clang-analyzer-core.DivideZero)
 |  | ||||||
|   if (this->output_raw_) { |  | ||||||
|     return raw; |  | ||||||
|   } |  | ||||||
|   return raw / 1024.0f; |  | ||||||
| } |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| #ifdef USE_ESP32 |  | ||||||
| float ADCSensor::sample() { | float ADCSensor::sample() { | ||||||
|   if (!this->autorange_) { |   if (!this->autorange_) { | ||||||
|     uint32_t sum = 0; |     uint32_t sum = 0; | ||||||
| @@ -240,93 +147,17 @@ float ADCSensor::sample() { | |||||||
|   uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_2_5]); |   uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_2_5]); | ||||||
|   uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_0]); |   uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_0]); | ||||||
| 
 | 
 | ||||||
|   // Contribution of each value, in range 0-2048 (12 bit ADC) or 0-4096 (13 bit ADC)
 |  | ||||||
|   uint32_t c12 = std::min(raw12, ADC_HALF); |   uint32_t c12 = std::min(raw12, ADC_HALF); | ||||||
|   uint32_t c6 = ADC_HALF - std::abs(raw6 - ADC_HALF); |   uint32_t c6 = ADC_HALF - std::abs(raw6 - ADC_HALF); | ||||||
|   uint32_t c2 = ADC_HALF - std::abs(raw2 - ADC_HALF); |   uint32_t c2 = ADC_HALF - std::abs(raw2 - ADC_HALF); | ||||||
|   uint32_t c0 = std::min(ADC_MAX - raw0, ADC_HALF); |   uint32_t c0 = std::min(ADC_MAX - raw0, ADC_HALF); | ||||||
|   // max theoretical csum value is 4096*4 = 16384
 |  | ||||||
|   uint32_t csum = c12 + c6 + c2 + c0; |   uint32_t csum = c12 + c6 + c2 + c0; | ||||||
| 
 | 
 | ||||||
|   // each mv is max 3900; so max value is 3900*4096*4, fits in unsigned32
 |  | ||||||
|   uint32_t mv_scaled = (mv12 * c12) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0); |   uint32_t mv_scaled = (mv12 * c12) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0); | ||||||
|   return mv_scaled / (float) (csum * 1000U); |   return mv_scaled / (float) (csum * 1000U); | ||||||
| } | } | ||||||
| #endif  // USE_ESP32
 |  | ||||||
| 
 |  | ||||||
| #ifdef USE_RP2040 |  | ||||||
| float ADCSensor::sample() { |  | ||||||
|   if (this->is_temperature_) { |  | ||||||
|     adc_set_temp_sensor_enabled(true); |  | ||||||
|     delay(1); |  | ||||||
|     adc_select_input(4); |  | ||||||
|     uint32_t raw = 0; |  | ||||||
|     for (uint8_t sample = 0; sample < this->sample_count_; sample++) { |  | ||||||
|       raw += adc_read(); |  | ||||||
|     } |  | ||||||
|     raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_;  // NOLINT(clang-analyzer-core.DivideZero)
 |  | ||||||
|     adc_set_temp_sensor_enabled(false); |  | ||||||
|     if (this->output_raw_) { |  | ||||||
|       return raw; |  | ||||||
|     } |  | ||||||
|     return raw * 3.3f / 4096.0f; |  | ||||||
|   } else { |  | ||||||
|     uint8_t pin = this->pin_->get_pin(); |  | ||||||
| #ifdef CYW43_USES_VSYS_PIN |  | ||||||
|     if (pin == PICO_VSYS_PIN) { |  | ||||||
|       // Measuring VSYS on Raspberry Pico W needs to be wrapped with
 |  | ||||||
|       // `cyw43_thread_enter()`/`cyw43_thread_exit()` as discussed in
 |  | ||||||
|       // https://github.com/raspberrypi/pico-sdk/issues/1222, since Wifi chip and
 |  | ||||||
|       // VSYS ADC both share GPIO29
 |  | ||||||
|       cyw43_thread_enter(); |  | ||||||
|     } |  | ||||||
| #endif  // CYW43_USES_VSYS_PIN
 |  | ||||||
| 
 |  | ||||||
|     adc_gpio_init(pin); |  | ||||||
|     adc_select_input(pin - 26); |  | ||||||
| 
 |  | ||||||
|     uint32_t raw = 0; |  | ||||||
|     for (uint8_t sample = 0; sample < this->sample_count_; sample++) { |  | ||||||
|       raw += adc_read(); |  | ||||||
|     } |  | ||||||
|     raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_;  // NOLINT(clang-analyzer-core.DivideZero)
 |  | ||||||
| 
 |  | ||||||
| #ifdef CYW43_USES_VSYS_PIN |  | ||||||
|     if (pin == PICO_VSYS_PIN) { |  | ||||||
|       cyw43_thread_exit(); |  | ||||||
|     } |  | ||||||
| #endif  // CYW43_USES_VSYS_PIN
 |  | ||||||
| 
 |  | ||||||
|     if (this->output_raw_) { |  | ||||||
|       return raw; |  | ||||||
|     } |  | ||||||
|     float coeff = pin == PICO_VSYS_PIN ? 3.0 : 1.0; |  | ||||||
|     return raw * 3.3f / 4096.0f * coeff; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| #ifdef USE_LIBRETINY |  | ||||||
| float ADCSensor::sample() { |  | ||||||
|   uint32_t raw = 0; |  | ||||||
|   if (this->output_raw_) { |  | ||||||
|     for (uint8_t sample = 0; sample < this->sample_count_; sample++) { |  | ||||||
|       raw += analogRead(this->pin_->get_pin());  // NOLINT
 |  | ||||||
|     } |  | ||||||
|     raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_;  // NOLINT(clang-analyzer-core.DivideZero)
 |  | ||||||
|     return raw; |  | ||||||
|   } |  | ||||||
|   for (uint8_t sample = 0; sample < this->sample_count_; sample++) { |  | ||||||
|     raw += analogReadVoltage(this->pin_->get_pin());  // NOLINT
 |  | ||||||
|   } |  | ||||||
|   raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_;  // NOLINT(clang-analyzer-core.DivideZero)
 |  | ||||||
|   return raw / 1000.0f; |  | ||||||
| } |  | ||||||
| #endif  // USE_LIBRETINY
 |  | ||||||
| 
 |  | ||||||
| #ifdef USE_ESP8266 |  | ||||||
| std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; } |  | ||||||
| #endif |  | ||||||
| 
 | 
 | ||||||
| }  // namespace adc
 | }  // namespace adc
 | ||||||
| }  // namespace esphome
 | }  // namespace esphome
 | ||||||
|  | 
 | ||||||
|  | #endif  // USE_ESP32
 | ||||||
							
								
								
									
										58
									
								
								esphome/components/adc/adc_sensor_esp8266.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								esphome/components/adc/adc_sensor_esp8266.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | #ifdef USE_ESP8266 | ||||||
|  |  | ||||||
|  | #include "adc_sensor.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | #ifdef USE_ADC_SENSOR_VCC | ||||||
|  | #include <Esp.h> | ||||||
|  | ADC_MODE(ADC_VCC) | ||||||
|  | #else | ||||||
|  | #include <Arduino.h> | ||||||
|  | #endif  // USE_ADC_SENSOR_VCC | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace adc { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "adc.esp8266"; | ||||||
|  |  | ||||||
|  | void ADCSensor::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); | ||||||
|  | #ifndef USE_ADC_SENSOR_VCC | ||||||
|  |   this->pin_->setup(); | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ADCSensor::dump_config() { | ||||||
|  |   LOG_SENSOR("", "ADC Sensor", this); | ||||||
|  | #ifdef USE_ADC_SENSOR_VCC | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Pin: VCC"); | ||||||
|  | #else | ||||||
|  |   LOG_PIN("  Pin: ", this->pin_); | ||||||
|  | #endif  // USE_ADC_SENSOR_VCC | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Samples: %i", this->sample_count_); | ||||||
|  |   LOG_UPDATE_INTERVAL(this); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float ADCSensor::sample() { | ||||||
|  |   uint32_t raw = 0; | ||||||
|  |   for (uint8_t sample = 0; sample < this->sample_count_; sample++) { | ||||||
|  | #ifdef USE_ADC_SENSOR_VCC | ||||||
|  |     raw += ESP.getVcc();  // NOLINT(readability-static-accessed-through-instance) | ||||||
|  | #else | ||||||
|  |     raw += analogRead(this->pin_->get_pin());  // NOLINT | ||||||
|  | #endif  // USE_ADC_SENSOR_VCC | ||||||
|  |   } | ||||||
|  |   raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_;  // NOLINT(clang-analyzer-core.DivideZero) | ||||||
|  |   if (this->output_raw_) { | ||||||
|  |     return raw; | ||||||
|  |   } | ||||||
|  |   return raw / 1024.0f; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; } | ||||||
|  |  | ||||||
|  | }  // namespace adc | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif  // USE_ESP8266 | ||||||
							
								
								
									
										48
									
								
								esphome/components/adc/adc_sensor_libretiny.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								esphome/components/adc/adc_sensor_libretiny.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | #ifdef USE_LIBRETINY | ||||||
|  |  | ||||||
|  | #include "adc_sensor.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace adc { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "adc.libretiny"; | ||||||
|  |  | ||||||
|  | void ADCSensor::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); | ||||||
|  | #ifndef USE_ADC_SENSOR_VCC | ||||||
|  |   this->pin_->setup(); | ||||||
|  | #endif  // !USE_ADC_SENSOR_VCC | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ADCSensor::dump_config() { | ||||||
|  |   LOG_SENSOR("", "ADC Sensor", this); | ||||||
|  | #ifdef USE_ADC_SENSOR_VCC | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Pin: VCC"); | ||||||
|  | #else   // USE_ADC_SENSOR_VCC | ||||||
|  |   LOG_PIN("  Pin: ", this->pin_); | ||||||
|  | #endif  // USE_ADC_SENSOR_VCC | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Samples: %i", this->sample_count_); | ||||||
|  |   LOG_UPDATE_INTERVAL(this); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float ADCSensor::sample() { | ||||||
|  |   uint32_t raw = 0; | ||||||
|  |   if (this->output_raw_) { | ||||||
|  |     for (uint8_t sample = 0; sample < this->sample_count_; sample++) { | ||||||
|  |       raw += analogRead(this->pin_->get_pin());  // NOLINT | ||||||
|  |     } | ||||||
|  |     raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_;  // NOLINT(clang-analyzer-core.DivideZero) | ||||||
|  |     return raw; | ||||||
|  |   } | ||||||
|  |   for (uint8_t sample = 0; sample < this->sample_count_; sample++) { | ||||||
|  |     raw += analogReadVoltage(this->pin_->get_pin());  // NOLINT | ||||||
|  |   } | ||||||
|  |   raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_;  // NOLINT(clang-analyzer-core.DivideZero) | ||||||
|  |   return raw / 1000.0f; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace adc | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif  // USE_LIBRETINY | ||||||
							
								
								
									
										93
									
								
								esphome/components/adc/adc_sensor_rp2040.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								esphome/components/adc/adc_sensor_rp2040.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,93 @@ | |||||||
|  | #ifdef USE_RP2040 | ||||||
|  |  | ||||||
|  | #include "adc_sensor.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | #ifdef CYW43_USES_VSYS_PIN | ||||||
|  | #include "pico/cyw43_arch.h" | ||||||
|  | #endif  // CYW43_USES_VSYS_PIN | ||||||
|  | #include <hardware/adc.h> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace adc { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "adc.rp2040"; | ||||||
|  |  | ||||||
|  | void ADCSensor::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); | ||||||
|  |   static bool initialized = false; | ||||||
|  |   if (!initialized) { | ||||||
|  |     adc_init(); | ||||||
|  |     initialized = true; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ADCSensor::dump_config() { | ||||||
|  |   LOG_SENSOR("", "ADC Sensor", this); | ||||||
|  |   if (this->is_temperature_) { | ||||||
|  |     ESP_LOGCONFIG(TAG, "  Pin: Temperature"); | ||||||
|  |   } else { | ||||||
|  | #ifdef USE_ADC_SENSOR_VCC | ||||||
|  |     ESP_LOGCONFIG(TAG, "  Pin: VCC"); | ||||||
|  | #else | ||||||
|  |     LOG_PIN("  Pin: ", this->pin_); | ||||||
|  | #endif  // USE_ADC_SENSOR_VCC | ||||||
|  |   } | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Samples: %i", this->sample_count_); | ||||||
|  |   LOG_UPDATE_INTERVAL(this); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float ADCSensor::sample() { | ||||||
|  |   if (this->is_temperature_) { | ||||||
|  |     adc_set_temp_sensor_enabled(true); | ||||||
|  |     delay(1); | ||||||
|  |     adc_select_input(4); | ||||||
|  |     uint32_t raw = 0; | ||||||
|  |     for (uint8_t sample = 0; sample < this->sample_count_; sample++) { | ||||||
|  |       raw += adc_read(); | ||||||
|  |     } | ||||||
|  |     raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_;  // NOLINT(clang-analyzer-core.DivideZero) | ||||||
|  |     adc_set_temp_sensor_enabled(false); | ||||||
|  |     if (this->output_raw_) { | ||||||
|  |       return raw; | ||||||
|  |     } | ||||||
|  |     return raw * 3.3f / 4096.0f; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint8_t pin = this->pin_->get_pin(); | ||||||
|  | #ifdef CYW43_USES_VSYS_PIN | ||||||
|  |   if (pin == PICO_VSYS_PIN) { | ||||||
|  |     // Measuring VSYS on Raspberry Pico W needs to be wrapped with | ||||||
|  |     // `cyw43_thread_enter()`/`cyw43_thread_exit()` as discussed in | ||||||
|  |     // https://github.com/raspberrypi/pico-sdk/issues/1222, since Wifi chip and | ||||||
|  |     // VSYS ADC both share GPIO29 | ||||||
|  |     cyw43_thread_enter(); | ||||||
|  |   } | ||||||
|  | #endif  // CYW43_USES_VSYS_PIN | ||||||
|  |  | ||||||
|  |   adc_gpio_init(pin); | ||||||
|  |   adc_select_input(pin - 26); | ||||||
|  |  | ||||||
|  |   uint32_t raw = 0; | ||||||
|  |   for (uint8_t sample = 0; sample < this->sample_count_; sample++) { | ||||||
|  |     raw += adc_read(); | ||||||
|  |   } | ||||||
|  |   raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_;  // NOLINT(clang-analyzer-core.DivideZero) | ||||||
|  |  | ||||||
|  | #ifdef CYW43_USES_VSYS_PIN | ||||||
|  |   if (pin == PICO_VSYS_PIN) { | ||||||
|  |     cyw43_thread_exit(); | ||||||
|  |   } | ||||||
|  | #endif  // CYW43_USES_VSYS_PIN | ||||||
|  |  | ||||||
|  |   if (this->output_raw_) { | ||||||
|  |     return raw; | ||||||
|  |   } | ||||||
|  |   float coeff = pin == PICO_VSYS_PIN ? 3.0f : 1.0f; | ||||||
|  |   return raw * 3.3f / 4096.0f * coeff; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace adc | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif  // USE_RP2040 | ||||||
| @@ -25,8 +25,7 @@ void BLEClient::loop() { | |||||||
|  |  | ||||||
| void BLEClient::dump_config() { | void BLEClient::dump_config() { | ||||||
|   ESP_LOGCONFIG(TAG, "BLE Client:"); |   ESP_LOGCONFIG(TAG, "BLE Client:"); | ||||||
|   ESP_LOGCONFIG(TAG, "  Address: %s", this->address_str().c_str()); |   BLEClientBase::dump_config(); | ||||||
|   ESP_LOGCONFIG(TAG, "  Auto-Connect: %s", TRUEFALSE(this->auto_connect_)); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| bool BLEClient::parse_device(const espbt::ESPBTDevice &device) { | bool BLEClient::parse_device(const espbt::ESPBTDevice &device) { | ||||||
|   | |||||||
| @@ -13,6 +13,11 @@ namespace bluetooth_proxy { | |||||||
|  |  | ||||||
| static const char *const TAG = "bluetooth_proxy.connection"; | static const char *const TAG = "bluetooth_proxy.connection"; | ||||||
|  |  | ||||||
|  | void BluetoothConnection::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "BLE Connection:"); | ||||||
|  |   BLEClientBase::dump_config(); | ||||||
|  | } | ||||||
|  |  | ||||||
| bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||||
|                                               esp_ble_gattc_cb_param_t *param) { |                                               esp_ble_gattc_cb_param_t *param) { | ||||||
|   if (!BLEClientBase::gattc_event_handler(event, gattc_if, param)) |   if (!BLEClientBase::gattc_event_handler(event, gattc_if, param)) | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ class BluetoothProxy; | |||||||
|  |  | ||||||
| class BluetoothConnection : public esp32_ble_client::BLEClientBase { | class BluetoothConnection : public esp32_ble_client::BLEClientBase { | ||||||
|  public: |  public: | ||||||
|  |   void dump_config() override; | ||||||
|   bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, |   bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||||
|                            esp_ble_gattc_cb_param_t *param) override; |                            esp_ble_gattc_cb_param_t *param) override; | ||||||
|   void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; |   void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; | ||||||
|   | |||||||
| @@ -30,6 +30,57 @@ static const char *const TAG = "debug"; | |||||||
|  |  | ||||||
| std::string DebugComponent::get_reset_reason_() { | std::string DebugComponent::get_reset_reason_() { | ||||||
|   std::string reset_reason; |   std::string reset_reason; | ||||||
|  |   switch (esp_reset_reason()) { | ||||||
|  |     case ESP_RST_POWERON: | ||||||
|  |       reset_reason = "Reset due to power-on event"; | ||||||
|  |       break; | ||||||
|  |     case ESP_RST_EXT: | ||||||
|  |       reset_reason = "Reset by external pin"; | ||||||
|  |       break; | ||||||
|  |     case ESP_RST_SW: | ||||||
|  |       reset_reason = "Software reset via esp_restart"; | ||||||
|  |       break; | ||||||
|  |     case ESP_RST_PANIC: | ||||||
|  |       reset_reason = "Software reset due to exception/panic"; | ||||||
|  |       break; | ||||||
|  |     case ESP_RST_INT_WDT: | ||||||
|  |       reset_reason = "Reset (software or hardware) due to interrupt watchdog"; | ||||||
|  |       break; | ||||||
|  |     case ESP_RST_TASK_WDT: | ||||||
|  |       reset_reason = "Reset due to task watchdog"; | ||||||
|  |       break; | ||||||
|  |     case ESP_RST_WDT: | ||||||
|  |       reset_reason = "Reset due to other watchdogs"; | ||||||
|  |       break; | ||||||
|  |     case ESP_RST_DEEPSLEEP: | ||||||
|  |       reset_reason = "Reset after exiting deep sleep mode"; | ||||||
|  |       break; | ||||||
|  |     case ESP_RST_BROWNOUT: | ||||||
|  |       reset_reason = "Brownout reset (software or hardware)"; | ||||||
|  |       break; | ||||||
|  |     case ESP_RST_SDIO: | ||||||
|  |       reset_reason = "Reset over SDIO"; | ||||||
|  |       break; | ||||||
|  | #ifdef USE_ESP32_VARIANT_ESP32 | ||||||
|  | #if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 4)) | ||||||
|  |     case ESP_RST_USB: | ||||||
|  |       reset_reason = "Reset by USB peripheral"; | ||||||
|  |       break; | ||||||
|  |     case ESP_RST_JTAG: | ||||||
|  |       reset_reason = "Reset by JTAG"; | ||||||
|  |       break; | ||||||
|  |     case ESP_RST_EFUSE: | ||||||
|  |       reset_reason = "Reset due to efuse error"; | ||||||
|  |       break; | ||||||
|  |     case ESP_RST_PWR_GLITCH: | ||||||
|  |       reset_reason = "Reset due to power glitch detected"; | ||||||
|  |       break; | ||||||
|  |     case ESP_RST_CPU_LOCKUP: | ||||||
|  |       reset_reason = "Reset due to CPU lock up (double exception)"; | ||||||
|  |       break; | ||||||
|  | #endif        // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 4) | ||||||
|  | #endif        // USE_ESP32_VARIANT_ESP32 | ||||||
|  |     default:  // Includes ESP_RST_UNKNOWN | ||||||
|       switch (rtc_get_reset_reason(0)) { |       switch (rtc_get_reset_reason(0)) { | ||||||
|         case POWERON_RESET: |         case POWERON_RESET: | ||||||
|           reset_reason = "Power On Reset"; |           reset_reason = "Power On Reset"; | ||||||
| @@ -134,6 +185,8 @@ std::string DebugComponent::get_reset_reason_() { | |||||||
|         default: |         default: | ||||||
|           reset_reason = "Unknown Reset Reason"; |           reset_reason = "Unknown Reset Reason"; | ||||||
|       } |       } | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|   ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str()); |   ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str()); | ||||||
|   return reset_reason; |   return reset_reason; | ||||||
| } | } | ||||||
| @@ -294,4 +347,4 @@ void DebugComponent::update_platform_() { | |||||||
|  |  | ||||||
| }  // namespace debug | }  // namespace debug | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
| #endif | #endif  // USE_ESP32 | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| #include "display.h" | #include "display.h" | ||||||
| #include "display_color_utils.h" |  | ||||||
| #include <utility> | #include <utility> | ||||||
|  | #include "display_color_utils.h" | ||||||
| #include "esphome/core/hal.h" | #include "esphome/core/hal.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
| @@ -670,7 +670,7 @@ void Display::strftime(int x, int y, BaseFont *font, Color color, Color backgrou | |||||||
|     this->print(x, y, font, color, align, buffer, background); |     this->print(x, y, font, color, align, buffer, background); | ||||||
| } | } | ||||||
| void Display::strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time) { | void Display::strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time) { | ||||||
|   this->strftime(x, y, font, color, COLOR_OFF, TextAlign::TOP_LEFT, format, time); |   this->strftime(x, y, font, color, COLOR_OFF, align, format, time); | ||||||
| } | } | ||||||
| void Display::strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time) { | void Display::strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time) { | ||||||
|   this->strftime(x, y, font, color, COLOR_OFF, TextAlign::TOP_LEFT, format, time); |   this->strftime(x, y, font, color, COLOR_OFF, TextAlign::TOP_LEFT, format, time); | ||||||
|   | |||||||
| @@ -602,6 +602,9 @@ async def to_code(config): | |||||||
|         cg.add_platformio_option( |         cg.add_platformio_option( | ||||||
|             "platform_packages", ["espressif/toolchain-esp32ulp@2.35.0-20220830"] |             "platform_packages", ["espressif/toolchain-esp32ulp@2.35.0-20220830"] | ||||||
|         ) |         ) | ||||||
|  |         add_idf_sdkconfig_option( | ||||||
|  |             f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True | ||||||
|  |         ) | ||||||
|         add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False) |         add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False) | ||||||
|         add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True) |         add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True) | ||||||
|         add_idf_sdkconfig_option( |         add_idf_sdkconfig_option( | ||||||
|   | |||||||
| @@ -1,4 +1,12 @@ | |||||||
| from .const import VARIANT_ESP32, VARIANT_ESP32C3, VARIANT_ESP32S2, VARIANT_ESP32S3 | from .const import ( | ||||||
|  |     VARIANT_ESP32, | ||||||
|  |     VARIANT_ESP32C2, | ||||||
|  |     VARIANT_ESP32C3, | ||||||
|  |     VARIANT_ESP32C6, | ||||||
|  |     VARIANT_ESP32H2, | ||||||
|  |     VARIANT_ESP32S2, | ||||||
|  |     VARIANT_ESP32S3, | ||||||
|  | ) | ||||||
|  |  | ||||||
| ESP32_BASE_PINS = { | ESP32_BASE_PINS = { | ||||||
|     "TX": 1, |     "TX": 1, | ||||||
| @@ -1344,6 +1352,26 @@ done | sort | |||||||
| """ | """ | ||||||
|  |  | ||||||
| BOARDS = { | BOARDS = { | ||||||
|  |     "4d_systems_esp32s3_gen4_r8n16": { | ||||||
|  |         "name": "4D Systems GEN4-ESP32 16MB (ESP32S3-R8N16)", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|  |     "adafruit_camera_esp32s3": { | ||||||
|  |         "name": "Adafruit pyCamera S3", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|  |     "adafruit_feather_esp32c6": { | ||||||
|  |         "name": "Adafruit Feather ESP32-C6", | ||||||
|  |         "variant": VARIANT_ESP32C6, | ||||||
|  |     }, | ||||||
|  |     "adafruit_feather_esp32s2": { | ||||||
|  |         "name": "Adafruit Feather ESP32-S2", | ||||||
|  |         "variant": VARIANT_ESP32S2, | ||||||
|  |     }, | ||||||
|  |     "adafruit_feather_esp32s2_reversetft": { | ||||||
|  |         "name": "Adafruit Feather ESP32-S2 Reverse TFT", | ||||||
|  |         "variant": VARIANT_ESP32S2, | ||||||
|  |     }, | ||||||
|     "adafruit_feather_esp32s2_tft": { |     "adafruit_feather_esp32s2_tft": { | ||||||
|         "name": "Adafruit Feather ESP32-S2 TFT", |         "name": "Adafruit Feather ESP32-S2 TFT", | ||||||
|         "variant": VARIANT_ESP32S2, |         "variant": VARIANT_ESP32S2, | ||||||
| @@ -1356,6 +1384,10 @@ BOARDS = { | |||||||
|         "name": "Adafruit Feather ESP32-S3 No PSRAM", |         "name": "Adafruit Feather ESP32-S3 No PSRAM", | ||||||
|         "variant": VARIANT_ESP32S3, |         "variant": VARIANT_ESP32S3, | ||||||
|     }, |     }, | ||||||
|  |     "adafruit_feather_esp32s3_reversetft": { | ||||||
|  |         "name": "Adafruit Feather ESP32-S3 Reverse TFT", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|     "adafruit_feather_esp32s3_tft": { |     "adafruit_feather_esp32s3_tft": { | ||||||
|         "name": "Adafruit Feather ESP32-S3 TFT", |         "name": "Adafruit Feather ESP32-S3 TFT", | ||||||
|         "variant": VARIANT_ESP32S3, |         "variant": VARIANT_ESP32S3, | ||||||
| @@ -1376,10 +1408,18 @@ BOARDS = { | |||||||
|         "name": "Adafruit MagTag 2.9", |         "name": "Adafruit MagTag 2.9", | ||||||
|         "variant": VARIANT_ESP32S2, |         "variant": VARIANT_ESP32S2, | ||||||
|     }, |     }, | ||||||
|  |     "adafruit_matrixportal_esp32s3": { | ||||||
|  |         "name": "Adafruit MatrixPortal ESP32-S3", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|     "adafruit_metro_esp32s2": { |     "adafruit_metro_esp32s2": { | ||||||
|         "name": "Adafruit Metro ESP32-S2", |         "name": "Adafruit Metro ESP32-S2", | ||||||
|         "variant": VARIANT_ESP32S2, |         "variant": VARIANT_ESP32S2, | ||||||
|     }, |     }, | ||||||
|  |     "adafruit_metro_esp32s3": { | ||||||
|  |         "name": "Adafruit Metro ESP32-S3", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|     "adafruit_qtpy_esp32c3": { |     "adafruit_qtpy_esp32c3": { | ||||||
|         "name": "Adafruit QT Py ESP32-C3", |         "name": "Adafruit QT Py ESP32-C3", | ||||||
|         "variant": VARIANT_ESP32C3, |         "variant": VARIANT_ESP32C3, | ||||||
| @@ -1392,10 +1432,18 @@ BOARDS = { | |||||||
|         "name": "Adafruit QT Py ESP32-S2", |         "name": "Adafruit QT Py ESP32-S2", | ||||||
|         "variant": VARIANT_ESP32S2, |         "variant": VARIANT_ESP32S2, | ||||||
|     }, |     }, | ||||||
|  |     "adafruit_qtpy_esp32s3_n4r2": { | ||||||
|  |         "name": "Adafruit QT Py ESP32-S3 (4M Flash 2M PSRAM)", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|     "adafruit_qtpy_esp32s3_nopsram": { |     "adafruit_qtpy_esp32s3_nopsram": { | ||||||
|         "name": "Adafruit QT Py ESP32-S3 No PSRAM", |         "name": "Adafruit QT Py ESP32-S3 No PSRAM", | ||||||
|         "variant": VARIANT_ESP32S3, |         "variant": VARIANT_ESP32S3, | ||||||
|     }, |     }, | ||||||
|  |     "adafruit_qualia_s3_rgb666": { | ||||||
|  |         "name": "Adafruit Qualia ESP32-S3 RGB666", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|     "airm2m_core_esp32c3": { |     "airm2m_core_esp32c3": { | ||||||
|         "name": "AirM2M CORE ESP32C3", |         "name": "AirM2M CORE ESP32C3", | ||||||
|         "variant": VARIANT_ESP32C3, |         "variant": VARIANT_ESP32C3, | ||||||
| @@ -1404,14 +1452,30 @@ BOARDS = { | |||||||
|         "name": "ALKS ESP32", |         "name": "ALKS ESP32", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
|     }, |     }, | ||||||
|  |     "arduino_nano_esp32": { | ||||||
|  |         "name": "Arduino Nano ESP32", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|  |     "atd147_s3": { | ||||||
|  |         "name": "ArtronShop ATD1.47-S3", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|     "atmegazero_esp32s2": { |     "atmegazero_esp32s2": { | ||||||
|         "name": "EspinalLab ATMegaZero ESP32-S2", |         "name": "EspinalLab ATMegaZero ESP32-S2", | ||||||
|         "variant": VARIANT_ESP32S2, |         "variant": VARIANT_ESP32S2, | ||||||
|     }, |     }, | ||||||
|  |     "aventen_s3_sync": { | ||||||
|  |         "name": "Aventen S3 Sync", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|     "az-delivery-devkit-v4": { |     "az-delivery-devkit-v4": { | ||||||
|         "name": "AZ-Delivery ESP-32 Dev Kit C V4", |         "name": "AZ-Delivery ESP-32 Dev Kit C V4", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
|     }, |     }, | ||||||
|  |     "bee_data_logger": { | ||||||
|  |         "name": "Smart Bee Data Logger", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|     "bee_motion_mini": { |     "bee_motion_mini": { | ||||||
|         "name": "Smart Bee Motion Mini", |         "name": "Smart Bee Motion Mini", | ||||||
|         "variant": VARIANT_ESP32C3, |         "variant": VARIANT_ESP32C3, | ||||||
| @@ -1436,14 +1500,6 @@ BOARDS = { | |||||||
|         "name": "BPI-Leaf-S3", |         "name": "BPI-Leaf-S3", | ||||||
|         "variant": VARIANT_ESP32S3, |         "variant": VARIANT_ESP32S3, | ||||||
|     }, |     }, | ||||||
|     "briki_abc_esp32": { |  | ||||||
|         "name": "Briki ABC (MBC-WB) - ESP32", |  | ||||||
|         "variant": VARIANT_ESP32, |  | ||||||
|     }, |  | ||||||
|     "briki_mbc-wb_esp32": { |  | ||||||
|         "name": "Briki MBC-WB - ESP32", |  | ||||||
|         "variant": VARIANT_ESP32, |  | ||||||
|     }, |  | ||||||
|     "cnrs_aw2eth": { |     "cnrs_aw2eth": { | ||||||
|         "name": "CNRS AW2ETH", |         "name": "CNRS AW2ETH", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
| @@ -1496,18 +1552,38 @@ BOARDS = { | |||||||
|         "name": "DFRobot Beetle ESP32-C3", |         "name": "DFRobot Beetle ESP32-C3", | ||||||
|         "variant": VARIANT_ESP32C3, |         "variant": VARIANT_ESP32C3, | ||||||
|     }, |     }, | ||||||
|  |     "dfrobot_firebeetle2_esp32e": { | ||||||
|  |         "name": "DFRobot Firebeetle 2 ESP32-E", | ||||||
|  |         "variant": VARIANT_ESP32, | ||||||
|  |     }, | ||||||
|     "dfrobot_firebeetle2_esp32s3": { |     "dfrobot_firebeetle2_esp32s3": { | ||||||
|         "name": "DFRobot Firebeetle 2 ESP32-S3", |         "name": "DFRobot Firebeetle 2 ESP32-S3", | ||||||
|         "variant": VARIANT_ESP32S3, |         "variant": VARIANT_ESP32S3, | ||||||
|     }, |     }, | ||||||
|  |     "dfrobot_romeo_esp32s3": { | ||||||
|  |         "name": "DFRobot Romeo ESP32-S3", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|     "dpu_esp32": { |     "dpu_esp32": { | ||||||
|         "name": "TAMC DPU ESP32", |         "name": "TAMC DPU ESP32", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
|     }, |     }, | ||||||
|  |     "edgebox-esp-100": { | ||||||
|  |         "name": "Seeed Studio Edgebox-ESP-100", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|     "esp320": { |     "esp320": { | ||||||
|         "name": "Electronic SweetPeas ESP320", |         "name": "Electronic SweetPeas ESP320", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
|     }, |     }, | ||||||
|  |     "esp32-c2-devkitm-1": { | ||||||
|  |         "name": "Espressif ESP32-C2-DevKitM-1", | ||||||
|  |         "variant": VARIANT_ESP32C2, | ||||||
|  |     }, | ||||||
|  |     "esp32-c3-devkitc-02": { | ||||||
|  |         "name": "Espressif ESP32-C3-DevKitC-02", | ||||||
|  |         "variant": VARIANT_ESP32C3, | ||||||
|  |     }, | ||||||
|     "esp32-c3-devkitm-1": { |     "esp32-c3-devkitm-1": { | ||||||
|         "name": "Espressif ESP32-C3-DevKitM-1", |         "name": "Espressif ESP32-C3-DevKitM-1", | ||||||
|         "variant": VARIANT_ESP32C3, |         "variant": VARIANT_ESP32C3, | ||||||
| @@ -1516,6 +1592,14 @@ BOARDS = { | |||||||
|         "name": "Ai-Thinker ESP-C3-M1-I-Kit", |         "name": "Ai-Thinker ESP-C3-M1-I-Kit", | ||||||
|         "variant": VARIANT_ESP32C3, |         "variant": VARIANT_ESP32C3, | ||||||
|     }, |     }, | ||||||
|  |     "esp32-c6-devkitc-1": { | ||||||
|  |         "name": "Espressif ESP32-C6-DevKitC-1", | ||||||
|  |         "variant": VARIANT_ESP32C6, | ||||||
|  |     }, | ||||||
|  |     "esp32-c6-devkitm-1": { | ||||||
|  |         "name": "Espressif ESP32-C6-DevKitM-1", | ||||||
|  |         "variant": VARIANT_ESP32C6, | ||||||
|  |     }, | ||||||
|     "esp32cam": { |     "esp32cam": { | ||||||
|         "name": "AI Thinker ESP32-CAM", |         "name": "AI Thinker ESP32-CAM", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
| @@ -1544,6 +1628,14 @@ BOARDS = { | |||||||
|         "name": "OLIMEX ESP32-GATEWAY", |         "name": "OLIMEX ESP32-GATEWAY", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
|     }, |     }, | ||||||
|  |     "esp32-h2-devkitm-1": { | ||||||
|  |         "name": "Espressif ESP32-H2-DevKit", | ||||||
|  |         "variant": VARIANT_ESP32H2, | ||||||
|  |     }, | ||||||
|  |     "esp32-pico-devkitm-2": { | ||||||
|  |         "name": "Espressif ESP32-PICO-DevKitM-2", | ||||||
|  |         "variant": VARIANT_ESP32, | ||||||
|  |     }, | ||||||
|     "esp32-poe-iso": { |     "esp32-poe-iso": { | ||||||
|         "name": "OLIMEX ESP32-PoE-ISO", |         "name": "OLIMEX ESP32-PoE-ISO", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
| @@ -1580,10 +1672,22 @@ BOARDS = { | |||||||
|         "name": "Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)", |         "name": "Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)", | ||||||
|         "variant": VARIANT_ESP32S3, |         "variant": VARIANT_ESP32S3, | ||||||
|     }, |     }, | ||||||
|     "esp32-s3-korvo-2": { |     "esp32-s3-devkitm-1": { | ||||||
|         "name": "Espressif ESP32-S3-Korvo-2", |         "name": "Espressif ESP32-S3-DevKitM-1", | ||||||
|         "variant": VARIANT_ESP32S3, |         "variant": VARIANT_ESP32S3, | ||||||
|     }, |     }, | ||||||
|  |     "esp32s3_powerfeather": { | ||||||
|  |         "name": "ESP32-S3 PowerFeather", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|  |     "esp32s3usbotg": { | ||||||
|  |         "name": "Espressif ESP32-S3-USB-OTG", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|  |     "esp32-solo1": { | ||||||
|  |         "name": "Espressif Generic ESP32-solo1 4M Flash", | ||||||
|  |         "variant": VARIANT_ESP32, | ||||||
|  |     }, | ||||||
|     "esp32thing": { |     "esp32thing": { | ||||||
|         "name": "SparkFun ESP32 Thing", |         "name": "SparkFun ESP32 Thing", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
| @@ -1652,9 +1756,9 @@ BOARDS = { | |||||||
|         "name": "Heltec WiFi Kit 32", |         "name": "Heltec WiFi Kit 32", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
|     }, |     }, | ||||||
|     "heltec_wifi_kit_32_v2": { |     "heltec_wifi_kit_32_V3": { | ||||||
|         "name": "Heltec WiFi Kit 32 (V2)", |         "name": "Heltec WiFi Kit 32 (V3)", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32S3, | ||||||
|     }, |     }, | ||||||
|     "heltec_wifi_lora_32": { |     "heltec_wifi_lora_32": { | ||||||
|         "name": "Heltec WiFi LoRa 32", |         "name": "Heltec WiFi LoRa 32", | ||||||
| @@ -1664,6 +1768,10 @@ BOARDS = { | |||||||
|         "name": "Heltec WiFi LoRa 32 (V2)", |         "name": "Heltec WiFi LoRa 32 (V2)", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
|     }, |     }, | ||||||
|  |     "heltec_wifi_lora_32_V3": { | ||||||
|  |         "name": "Heltec WiFi LoRa 32 (V3)", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|     "heltec_wireless_stick_lite": { |     "heltec_wireless_stick_lite": { | ||||||
|         "name": "Heltec Wireless Stick Lite", |         "name": "Heltec Wireless Stick Lite", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
| @@ -1708,6 +1816,14 @@ BOARDS = { | |||||||
|         "name": "oddWires IoT-Bus Proteus", |         "name": "oddWires IoT-Bus Proteus", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
|     }, |     }, | ||||||
|  |     "ioxesp32": { | ||||||
|  |         "name": "ArtronShop IOXESP32", | ||||||
|  |         "variant": VARIANT_ESP32, | ||||||
|  |     }, | ||||||
|  |     "ioxesp32ps": { | ||||||
|  |         "name": "ArtronShop IOXESP32PS", | ||||||
|  |         "variant": VARIANT_ESP32, | ||||||
|  |     }, | ||||||
|     "kb32-ft": { |     "kb32-ft": { | ||||||
|         "name": "MakerAsia KB32-FT", |         "name": "MakerAsia KB32-FT", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
| @@ -1720,10 +1836,26 @@ BOARDS = { | |||||||
|         "name": "Labplus mPython", |         "name": "Labplus mPython", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
|     }, |     }, | ||||||
|  |     "lilka_v2": { | ||||||
|  |         "name": "Lilka v2", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|  |     "lilygo-t-display": { | ||||||
|  |         "name": "LilyGo T-Display", | ||||||
|  |         "variant": VARIANT_ESP32, | ||||||
|  |     }, | ||||||
|  |     "lilygo-t-display-s3": { | ||||||
|  |         "name": "LilyGo T-Display-S3", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|     "lionbit": { |     "lionbit": { | ||||||
|         "name": "Lion:Bit Dev Board", |         "name": "Lion:Bit Dev Board", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
|     }, |     }, | ||||||
|  |     "lionbits3": { | ||||||
|  |         "name": "Lion:Bit S3 STEM Dev Board", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|     "lolin32_lite": { |     "lolin32_lite": { | ||||||
|         "name": "WEMOS LOLIN32 Lite", |         "name": "WEMOS LOLIN32 Lite", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
| @@ -1752,10 +1884,18 @@ BOARDS = { | |||||||
|         "name": "WEMOS LOLIN S2 PICO", |         "name": "WEMOS LOLIN S2 PICO", | ||||||
|         "variant": VARIANT_ESP32S2, |         "variant": VARIANT_ESP32S2, | ||||||
|     }, |     }, | ||||||
|  |     "lolin_s3_mini": { | ||||||
|  |         "name": "WEMOS LOLIN S3 Mini", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|     "lolin_s3": { |     "lolin_s3": { | ||||||
|         "name": "WEMOS LOLIN S3", |         "name": "WEMOS LOLIN S3", | ||||||
|         "variant": VARIANT_ESP32S3, |         "variant": VARIANT_ESP32S3, | ||||||
|     }, |     }, | ||||||
|  |     "lolin_s3_pro": { | ||||||
|  |         "name": "WEMOS LOLIN S3 PRO", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|     "lopy4": { |     "lopy4": { | ||||||
|         "name": "Pycom LoPy4", |         "name": "Pycom LoPy4", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
| @@ -1768,10 +1908,18 @@ BOARDS = { | |||||||
|         "name": "M5Stack-ATOM", |         "name": "M5Stack-ATOM", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
|     }, |     }, | ||||||
|  |     "m5stack-atoms3": { | ||||||
|  |         "name": "M5Stack AtomS3", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|     "m5stack-core2": { |     "m5stack-core2": { | ||||||
|         "name": "M5Stack Core2", |         "name": "M5Stack Core2", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
|     }, |     }, | ||||||
|  |     "m5stack-core-esp32-16M": { | ||||||
|  |         "name": "M5Stack Core ESP32 16M", | ||||||
|  |         "variant": VARIANT_ESP32, | ||||||
|  |     }, | ||||||
|     "m5stack-core-esp32": { |     "m5stack-core-esp32": { | ||||||
|         "name": "M5Stack Core ESP32", |         "name": "M5Stack Core ESP32", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
| @@ -1780,6 +1928,10 @@ BOARDS = { | |||||||
|         "name": "M5Stack-Core Ink", |         "name": "M5Stack-Core Ink", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
|     }, |     }, | ||||||
|  |     "m5stack-cores3": { | ||||||
|  |         "name": "M5Stack CoreS3", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|     "m5stack-fire": { |     "m5stack-fire": { | ||||||
|         "name": "M5Stack FIRE", |         "name": "M5Stack FIRE", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
| @@ -1788,6 +1940,14 @@ BOARDS = { | |||||||
|         "name": "M5Stack GREY ESP32", |         "name": "M5Stack GREY ESP32", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
|     }, |     }, | ||||||
|  |     "m5stack_paper": { | ||||||
|  |         "name": "M5Stack Paper", | ||||||
|  |         "variant": VARIANT_ESP32, | ||||||
|  |     }, | ||||||
|  |     "m5stack-stamps3": { | ||||||
|  |         "name": "M5Stack StampS3", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|     "m5stack-station": { |     "m5stack-station": { | ||||||
|         "name": "M5Stack Station", |         "name": "M5Stack Station", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
| @@ -1796,6 +1956,10 @@ BOARDS = { | |||||||
|         "name": "M5Stack Timer CAM", |         "name": "M5Stack Timer CAM", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
|     }, |     }, | ||||||
|  |     "m5stamp-pico": { | ||||||
|  |         "name": "M5Stamp-Pico", | ||||||
|  |         "variant": VARIANT_ESP32, | ||||||
|  |     }, | ||||||
|     "m5stick-c": { |     "m5stick-c": { | ||||||
|         "name": "M5Stick-C", |         "name": "M5Stick-C", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
| @@ -1832,10 +1996,26 @@ BOARDS = { | |||||||
|         "name": "Deparment of Alchemy MiniMain ESP32-S2", |         "name": "Deparment of Alchemy MiniMain ESP32-S2", | ||||||
|         "variant": VARIANT_ESP32S2, |         "variant": VARIANT_ESP32S2, | ||||||
|     }, |     }, | ||||||
|  |     "motorgo_mini_1": { | ||||||
|  |         "name": "MotorGo Mini 1 (ESP32-S3)", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|  |     "namino_arancio": { | ||||||
|  |         "name": "Namino Arancio", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|  |     "namino_rosso": { | ||||||
|  |         "name": "Namino Rosso", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|     "nano32": { |     "nano32": { | ||||||
|         "name": "MakerAsia Nano32", |         "name": "MakerAsia Nano32", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
|     }, |     }, | ||||||
|  |     "nebulas3": { | ||||||
|  |         "name": "Kinetic Dynamics Nebula S3", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|     "nina_w10": { |     "nina_w10": { | ||||||
|         "name": "u-blox NINA-W10 series", |         "name": "u-blox NINA-W10 series", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
| @@ -1896,10 +2076,22 @@ BOARDS = { | |||||||
|         "name": "Munich Labs RedPill ESP32-S3", |         "name": "Munich Labs RedPill ESP32-S3", | ||||||
|         "variant": VARIANT_ESP32S3, |         "variant": VARIANT_ESP32S3, | ||||||
|     }, |     }, | ||||||
|  |     "roboheart_hercules": { | ||||||
|  |         "name": "RoboHeart Hercules", | ||||||
|  |         "variant": VARIANT_ESP32, | ||||||
|  |     }, | ||||||
|     "seeed_xiao_esp32c3": { |     "seeed_xiao_esp32c3": { | ||||||
|         "name": "Seeed Studio XIAO ESP32C3", |         "name": "Seeed Studio XIAO ESP32C3", | ||||||
|         "variant": VARIANT_ESP32C3, |         "variant": VARIANT_ESP32C3, | ||||||
|     }, |     }, | ||||||
|  |     "seeed_xiao_esp32s3": { | ||||||
|  |         "name": "Seeed Studio XIAO ESP32S3", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|  |     "sensebox_mcu_esp32s2": { | ||||||
|  |         "name": "senseBox MCU-S2 ESP32-S2", | ||||||
|  |         "variant": VARIANT_ESP32S2, | ||||||
|  |     }, | ||||||
|     "sensesiot_weizen": { |     "sensesiot_weizen": { | ||||||
|         "name": "LOGISENSES Senses Weizen", |         "name": "LOGISENSES Senses Weizen", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
| @@ -1912,6 +2104,10 @@ BOARDS = { | |||||||
|         "name": "S.ODI Ultra v1", |         "name": "S.ODI Ultra v1", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
|     }, |     }, | ||||||
|  |     "sparkfun_esp32c6_thing_plus": { | ||||||
|  |         "name": "Sparkfun ESP32-C6 Thing Plus", | ||||||
|  |         "variant": VARIANT_ESP32C6, | ||||||
|  |     }, | ||||||
|     "sparkfun_esp32_iot_redboard": { |     "sparkfun_esp32_iot_redboard": { | ||||||
|         "name": "SparkFun ESP32 IoT RedBoard", |         "name": "SparkFun ESP32 IoT RedBoard", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
| @@ -2004,6 +2200,10 @@ BOARDS = { | |||||||
|         "name": "Unexpected Maker FeatherS3", |         "name": "Unexpected Maker FeatherS3", | ||||||
|         "variant": VARIANT_ESP32S3, |         "variant": VARIANT_ESP32S3, | ||||||
|     }, |     }, | ||||||
|  |     "um_nanos3": { | ||||||
|  |         "name": "Unexpected Maker NanoS3", | ||||||
|  |         "variant": VARIANT_ESP32S3, | ||||||
|  |     }, | ||||||
|     "um_pros3": { |     "um_pros3": { | ||||||
|         "name": "Unexpected Maker PROS3", |         "name": "Unexpected Maker PROS3", | ||||||
|         "variant": VARIANT_ESP32S3, |         "variant": VARIANT_ESP32S3, | ||||||
| @@ -2040,6 +2240,14 @@ BOARDS = { | |||||||
|         "name": "uPesy ESP32 Wrover DevKit", |         "name": "uPesy ESP32 Wrover DevKit", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
|     }, |     }, | ||||||
|  |     "valtrack_v4_mfw_esp32_c3": { | ||||||
|  |         "name": "Valetron Systems VALTRACK-V4MVF", | ||||||
|  |         "variant": VARIANT_ESP32C3, | ||||||
|  |     }, | ||||||
|  |     "valtrack_v4_vts_esp32_c3": { | ||||||
|  |         "name": "Valetron Systems VALTRACK-V4VTS", | ||||||
|  |         "variant": VARIANT_ESP32C3, | ||||||
|  |     }, | ||||||
|     "vintlabs-devkit-v1": { |     "vintlabs-devkit-v1": { | ||||||
|         "name": "VintLabs ESP32 Devkit", |         "name": "VintLabs ESP32 Devkit", | ||||||
|         "variant": VARIANT_ESP32, |         "variant": VARIANT_ESP32, | ||||||
|   | |||||||
| @@ -27,6 +27,9 @@ namespace esp32_ble { | |||||||
|  |  | ||||||
| static const char *const TAG = "esp32_ble"; | static const char *const TAG = "esp32_ble"; | ||||||
|  |  | ||||||
|  | static RAMAllocator<BLEEvent> EVENT_ALLOCATOR(  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) | ||||||
|  |     RAMAllocator<BLEEvent>::ALLOW_FAILURE | RAMAllocator<BLEEvent>::ALLOC_INTERNAL); | ||||||
|  |  | ||||||
| void ESP32BLE::setup() { | void ESP32BLE::setup() { | ||||||
|   global_ble = this; |   global_ble = this; | ||||||
|   ESP_LOGCONFIG(TAG, "Setting up BLE..."); |   ESP_LOGCONFIG(TAG, "Setting up BLE..."); | ||||||
| @@ -322,7 +325,8 @@ void ESP32BLE::loop() { | |||||||
|       default: |       default: | ||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
|     delete ble_event;  // NOLINT(cppcoreguidelines-owning-memory) |     ble_event->~BLEEvent(); | ||||||
|  |     EVENT_ALLOCATOR.deallocate(ble_event, 1); | ||||||
|     ble_event = this->ble_events_.pop(); |     ble_event = this->ble_events_.pop(); | ||||||
|   } |   } | ||||||
|   if (this->advertising_ != nullptr) { |   if (this->advertising_ != nullptr) { | ||||||
| @@ -331,9 +335,14 @@ void ESP32BLE::loop() { | |||||||
| } | } | ||||||
|  |  | ||||||
| void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { | void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { | ||||||
|   BLEEvent *new_event = new BLEEvent(event, param);  // NOLINT(cppcoreguidelines-owning-memory) |   BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1); | ||||||
|  |   if (new_event == nullptr) { | ||||||
|  |     // Memory too fragmented to allocate new event. Can only drop it until memory comes back | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   new (new_event) BLEEvent(event, param); | ||||||
|   global_ble->ble_events_.push(new_event); |   global_ble->ble_events_.push(new_event); | ||||||
| }  // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) | }  // NOLINT(clang-analyzer-unix.Malloc) | ||||||
|  |  | ||||||
| void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { | void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { | ||||||
|   ESP_LOGV(TAG, "(BLE) gap_event_handler - %d", event); |   ESP_LOGV(TAG, "(BLE) gap_event_handler - %d", event); | ||||||
| @@ -344,9 +353,14 @@ void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap | |||||||
|  |  | ||||||
| void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, | void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, | ||||||
|                                    esp_ble_gatts_cb_param_t *param) { |                                    esp_ble_gatts_cb_param_t *param) { | ||||||
|   BLEEvent *new_event = new BLEEvent(event, gatts_if, param);  // NOLINT(cppcoreguidelines-owning-memory) |   BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1); | ||||||
|  |   if (new_event == nullptr) { | ||||||
|  |     // Memory too fragmented to allocate new event. Can only drop it until memory comes back | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   new (new_event) BLEEvent(event, gatts_if, param); | ||||||
|   global_ble->ble_events_.push(new_event); |   global_ble->ble_events_.push(new_event); | ||||||
| }  // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) | }  // NOLINT(clang-analyzer-unix.Malloc) | ||||||
|  |  | ||||||
| void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, | void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, | ||||||
|                                          esp_ble_gatts_cb_param_t *param) { |                                          esp_ble_gatts_cb_param_t *param) { | ||||||
| @@ -358,9 +372,14 @@ void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if | |||||||
|  |  | ||||||
| void ESP32BLE::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | void ESP32BLE::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||||
|                                    esp_ble_gattc_cb_param_t *param) { |                                    esp_ble_gattc_cb_param_t *param) { | ||||||
|   BLEEvent *new_event = new BLEEvent(event, gattc_if, param);  // NOLINT(cppcoreguidelines-owning-memory) |   BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1); | ||||||
|  |   if (new_event == nullptr) { | ||||||
|  |     // Memory too fragmented to allocate new event. Can only drop it until memory comes back | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   new (new_event) BLEEvent(event, gattc_if, param); | ||||||
|   global_ble->ble_events_.push(new_event); |   global_ble->ble_events_.push(new_event); | ||||||
| }  // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) | }  // NOLINT(clang-analyzer-unix.Malloc) | ||||||
|  |  | ||||||
| void ESP32BLE::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | void ESP32BLE::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||||
|                                          esp_ble_gattc_cb_param_t *param) { |                                          esp_ble_gattc_cb_param_t *param) { | ||||||
|   | |||||||
| @@ -83,7 +83,7 @@ esp_err_t BLEAdvertising::services_advertisement_() { | |||||||
|   esp_err_t err; |   esp_err_t err; | ||||||
|  |  | ||||||
|   this->advertising_data_.set_scan_rsp = false; |   this->advertising_data_.set_scan_rsp = false; | ||||||
|   this->advertising_data_.include_name = true; |   this->advertising_data_.include_name = !this->scan_response_; | ||||||
|   this->advertising_data_.include_txpower = !this->scan_response_; |   this->advertising_data_.include_txpower = !this->scan_response_; | ||||||
|   err = esp_ble_gap_config_adv_data(&this->advertising_data_); |   err = esp_ble_gap_config_adv_data(&this->advertising_data_); | ||||||
|   if (err != ESP_OK) { |   if (err != ESP_OK) { | ||||||
|   | |||||||
| @@ -26,11 +26,11 @@ template<class T> class Queue { | |||||||
|   void push(T *element) { |   void push(T *element) { | ||||||
|     if (element == nullptr) |     if (element == nullptr) | ||||||
|       return; |       return; | ||||||
|     if (xSemaphoreTake(m_, 5L / portTICK_PERIOD_MS)) { |     // It is not called from main loop. Thus it won't block main thread. | ||||||
|  |     xSemaphoreTake(m_, portMAX_DELAY); | ||||||
|     q_.push(element); |     q_.push(element); | ||||||
|     xSemaphoreGive(m_); |     xSemaphoreGive(m_); | ||||||
|   } |   } | ||||||
|   } |  | ||||||
|  |  | ||||||
|   T *pop() { |   T *pop() { | ||||||
|     T *element = nullptr; |     T *element = nullptr; | ||||||
|   | |||||||
| @@ -44,6 +44,50 @@ void BLEClientBase::loop() { | |||||||
|  |  | ||||||
| float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } | float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } | ||||||
|  |  | ||||||
|  | void BLEClientBase::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Address: %s", this->address_str().c_str()); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Auto-Connect: %s", TRUEFALSE(this->auto_connect_)); | ||||||
|  |   std::string state_name; | ||||||
|  |   switch (this->state()) { | ||||||
|  |     case espbt::ClientState::INIT: | ||||||
|  |       state_name = "INIT"; | ||||||
|  |       break; | ||||||
|  |     case espbt::ClientState::DISCONNECTING: | ||||||
|  |       state_name = "DISCONNECTING"; | ||||||
|  |       break; | ||||||
|  |     case espbt::ClientState::IDLE: | ||||||
|  |       state_name = "IDLE"; | ||||||
|  |       break; | ||||||
|  |     case espbt::ClientState::SEARCHING: | ||||||
|  |       state_name = "SEARCHING"; | ||||||
|  |       break; | ||||||
|  |     case espbt::ClientState::DISCOVERED: | ||||||
|  |       state_name = "DISCOVERED"; | ||||||
|  |       break; | ||||||
|  |     case espbt::ClientState::READY_TO_CONNECT: | ||||||
|  |       state_name = "READY_TO_CONNECT"; | ||||||
|  |       break; | ||||||
|  |     case espbt::ClientState::CONNECTING: | ||||||
|  |       state_name = "CONNECTING"; | ||||||
|  |       break; | ||||||
|  |     case espbt::ClientState::CONNECTED: | ||||||
|  |       state_name = "CONNECTED"; | ||||||
|  |       break; | ||||||
|  |     case espbt::ClientState::ESTABLISHED: | ||||||
|  |       state_name = "ESTABLISHED"; | ||||||
|  |       break; | ||||||
|  |     default: | ||||||
|  |       state_name = "UNKNOWN_STATE"; | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  |   ESP_LOGCONFIG(TAG, "  State: %s", state_name.c_str()); | ||||||
|  |   if (this->status_ == ESP_GATT_NO_RESOURCES) { | ||||||
|  |     ESP_LOGE(TAG, "  Failed due to no resources. Try to reduce number of BLE clients in config."); | ||||||
|  |   } else if (this->status_ != ESP_GATT_OK) { | ||||||
|  |     ESP_LOGW(TAG, "  Failed due to error code %d", this->status_); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) { | bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) { | ||||||
|   if (!this->auto_connect_) |   if (!this->auto_connect_) | ||||||
|     return false; |     return false; | ||||||
| @@ -129,6 +173,8 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ | |||||||
|       } else { |       } else { | ||||||
|         ESP_LOGE(TAG, "[%d] [%s] gattc app registration failed id=%d code=%d", this->connection_index_, |         ESP_LOGE(TAG, "[%d] [%s] gattc app registration failed id=%d code=%d", this->connection_index_, | ||||||
|                  this->address_str_.c_str(), param->reg.app_id, param->reg.status); |                  this->address_str_.c_str(), param->reg.app_id, param->reg.status); | ||||||
|  |         this->status_ = param->reg.status; | ||||||
|  |         this->mark_failed(); | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -26,6 +26,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { | |||||||
|   void setup() override; |   void setup() override; | ||||||
|   void loop() override; |   void loop() override; | ||||||
|   float get_setup_priority() const override; |   float get_setup_priority() const override; | ||||||
|  |   void dump_config() override; | ||||||
|  |  | ||||||
|   void run_later(std::function<void()> &&f);  // NOLINT |   void run_later(std::function<void()> &&f);  // NOLINT | ||||||
|   bool parse_device(const espbt::ESPBTDevice &device) override; |   bool parse_device(const espbt::ESPBTDevice &device) override; | ||||||
| @@ -103,6 +104,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { | |||||||
|   bool paired_{false}; |   bool paired_{false}; | ||||||
|   espbt::ConnectionType connection_type_{espbt::ConnectionType::V1}; |   espbt::ConnectionType connection_type_{espbt::ConnectionType::V1}; | ||||||
|   std::vector<BLEService *> services_; |   std::vector<BLEService *> services_; | ||||||
|  |   esp_gatt_status_t status_{ESP_GATT_OK}; | ||||||
|  |  | ||||||
|   void log_event_(const char *name); |   void log_event_(const char *name); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -58,7 +58,6 @@ void ESP32BLETracker::setup() { | |||||||
|   global_esp32_ble_tracker = this; |   global_esp32_ble_tracker = this; | ||||||
|   this->scan_result_lock_ = xSemaphoreCreateMutex(); |   this->scan_result_lock_ = xSemaphoreCreateMutex(); | ||||||
|   this->scan_end_lock_ = xSemaphoreCreateMutex(); |   this->scan_end_lock_ = xSemaphoreCreateMutex(); | ||||||
|   this->scanner_idle_ = true; |  | ||||||
|  |  | ||||||
| #ifdef USE_OTA | #ifdef USE_OTA | ||||||
|   ota::get_global_ota_callback()->add_on_state_callback( |   ota::get_global_ota_callback()->add_on_state_callback( | ||||||
| @@ -107,6 +106,15 @@ void ESP32BLETracker::loop() { | |||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |   if (connecting != connecting_ || discovered != discovered_ || searching != searching_ || | ||||||
|  |       disconnecting != disconnecting_) { | ||||||
|  |     connecting_ = connecting; | ||||||
|  |     discovered_ = discovered; | ||||||
|  |     searching_ = searching; | ||||||
|  |     disconnecting_ = disconnecting; | ||||||
|  |     ESP_LOGD(TAG, "connecting: %d, discovered: %d, searching: %d, disconnecting: %d", connecting_, discovered_, | ||||||
|  |              searching_, disconnecting_); | ||||||
|  |   } | ||||||
|   bool promote_to_connecting = discovered && !searching && !connecting; |   bool promote_to_connecting = discovered && !searching && !connecting; | ||||||
|  |  | ||||||
|   if (!this->scanner_idle_) { |   if (!this->scanner_idle_) { | ||||||
| @@ -183,8 +191,9 @@ void ESP32BLETracker::loop() { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (this->scan_start_failed_ || this->scan_set_param_failed_) { |     if (this->scan_start_failed_ || this->scan_set_param_failed_) { | ||||||
|       if (this->scan_start_fail_count_ == 255) { |       if (this->scan_start_fail_count_ == std::numeric_limits<uint8_t>::max()) { | ||||||
|         ESP_LOGE(TAG, "ESP-IDF BLE scan could not restart after 255 attempts, rebooting to restore BLE stack..."); |         ESP_LOGE(TAG, "ESP-IDF BLE scan could not restart after %d attempts, rebooting to restore BLE stack...", | ||||||
|  |                  std::numeric_limits<uint8_t>::max()); | ||||||
|         App.reboot(); |         App.reboot(); | ||||||
|       } |       } | ||||||
|       if (xSemaphoreTake(this->scan_end_lock_, 0L)) { |       if (xSemaphoreTake(this->scan_end_lock_, 0L)) { | ||||||
| @@ -282,6 +291,12 @@ void ESP32BLETracker::start_scan_(bool first) { | |||||||
|   this->scan_params_.scan_interval = this->scan_interval_; |   this->scan_params_.scan_interval = this->scan_interval_; | ||||||
|   this->scan_params_.scan_window = this->scan_window_; |   this->scan_params_.scan_window = this->scan_window_; | ||||||
|  |  | ||||||
|  |   // Start timeout before scan is started. Otherwise scan never starts if any error. | ||||||
|  |   this->set_timeout("scan", this->scan_duration_ * 2000, []() { | ||||||
|  |     ESP_LOGE(TAG, "ESP-IDF BLE scan never terminated, rebooting to restore BLE stack..."); | ||||||
|  |     App.reboot(); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|   esp_err_t err = esp_ble_gap_set_scan_params(&this->scan_params_); |   esp_err_t err = esp_ble_gap_set_scan_params(&this->scan_params_); | ||||||
|   if (err != ESP_OK) { |   if (err != ESP_OK) { | ||||||
|     ESP_LOGE(TAG, "esp_ble_gap_set_scan_params failed: %d", err); |     ESP_LOGE(TAG, "esp_ble_gap_set_scan_params failed: %d", err); | ||||||
| @@ -293,11 +308,6 @@ void ESP32BLETracker::start_scan_(bool first) { | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   this->scanner_idle_ = false; |   this->scanner_idle_ = false; | ||||||
|  |  | ||||||
|   this->set_timeout("scan", this->scan_duration_ * 2000, []() { |  | ||||||
|     ESP_LOGE(TAG, "ESP-IDF BLE scan never terminated, rebooting to restore BLE stack..."); |  | ||||||
|     App.reboot(); |  | ||||||
|   }); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void ESP32BLETracker::end_of_scan_() { | void ESP32BLETracker::end_of_scan_() { | ||||||
| @@ -371,6 +381,7 @@ void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_ga | |||||||
| } | } | ||||||
|  |  | ||||||
| void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m) { | void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m) { | ||||||
|  |   ESP_LOGV(TAG, "gap_scan_set_param_complete - status %d", param.status); | ||||||
|   if (param.status == ESP_BT_STATUS_DONE) { |   if (param.status == ESP_BT_STATUS_DONE) { | ||||||
|     this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS; |     this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS; | ||||||
|   } else { |   } else { | ||||||
| @@ -379,20 +390,25 @@ void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t: | |||||||
| } | } | ||||||
|  |  | ||||||
| void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param ¶m) { | void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param ¶m) { | ||||||
|  |   ESP_LOGV(TAG, "gap_scan_start_complete - status %d", param.status); | ||||||
|   this->scan_start_failed_ = param.status; |   this->scan_start_failed_ = param.status; | ||||||
|   if (param.status == ESP_BT_STATUS_SUCCESS) { |   if (param.status == ESP_BT_STATUS_SUCCESS) { | ||||||
|     this->scan_start_fail_count_ = 0; |     this->scan_start_fail_count_ = 0; | ||||||
|   } else { |   } else { | ||||||
|  |     if (this->scan_start_fail_count_ != std::numeric_limits<uint8_t>::max()) { | ||||||
|       this->scan_start_fail_count_++; |       this->scan_start_fail_count_++; | ||||||
|  |     } | ||||||
|     xSemaphoreGive(this->scan_end_lock_); |     xSemaphoreGive(this->scan_end_lock_); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m) { | void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m) { | ||||||
|  |   ESP_LOGV(TAG, "gap_scan_stop_complete - status %d", param.status); | ||||||
|   xSemaphoreGive(this->scan_end_lock_); |   xSemaphoreGive(this->scan_end_lock_); | ||||||
| } | } | ||||||
|  |  | ||||||
| void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) { | void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) { | ||||||
|  |   ESP_LOGV(TAG, "gap_scan_result - event %d", param.search_evt); | ||||||
|   if (param.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) { |   if (param.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) { | ||||||
|     if (xSemaphoreTake(this->scan_result_lock_, 0L)) { |     if (xSemaphoreTake(this->scan_result_lock_, 0L)) { | ||||||
|       if (this->scan_result_index_ < ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) { |       if (this->scan_result_index_ < ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) { | ||||||
| @@ -663,7 +679,14 @@ void ESP32BLETracker::dump_config() { | |||||||
|   ESP_LOGCONFIG(TAG, "  Scan Interval: %.1f ms", this->scan_interval_ * 0.625f); |   ESP_LOGCONFIG(TAG, "  Scan Interval: %.1f ms", this->scan_interval_ * 0.625f); | ||||||
|   ESP_LOGCONFIG(TAG, "  Scan Window: %.1f ms", this->scan_window_ * 0.625f); |   ESP_LOGCONFIG(TAG, "  Scan Window: %.1f ms", this->scan_window_ * 0.625f); | ||||||
|   ESP_LOGCONFIG(TAG, "  Scan Type: %s", this->scan_active_ ? "ACTIVE" : "PASSIVE"); |   ESP_LOGCONFIG(TAG, "  Scan Type: %s", this->scan_active_ ? "ACTIVE" : "PASSIVE"); | ||||||
|   ESP_LOGCONFIG(TAG, "  Continuous Scanning: %s", this->scan_continuous_ ? "True" : "False"); |   ESP_LOGCONFIG(TAG, "  Continuous Scanning: %s", YESNO(this->scan_continuous_)); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Scanner Idle: %s", YESNO(this->scanner_idle_)); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Scan End: %s", YESNO(xSemaphoreGetMutexHolder(this->scan_end_lock_) == nullptr)); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Connecting: %d, discovered: %d, searching: %d, disconnecting: %d", connecting_, discovered_, | ||||||
|  |                 searching_, disconnecting_); | ||||||
|  |   if (this->scan_start_fail_count_) { | ||||||
|  |     ESP_LOGCONFIG(TAG, "  Scan Start Fail Count: %d", this->scan_start_fail_count_); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void ESP32BLETracker::print_bt_device_info(const ESPBTDevice &device) { | void ESP32BLETracker::print_bt_device_info(const ESPBTDevice &device) { | ||||||
|   | |||||||
| @@ -178,7 +178,7 @@ class ESPBTClient : public ESPBTDeviceListener { | |||||||
|   int app_id; |   int app_id; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   ClientState state_; |   ClientState state_{ClientState::INIT}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class ESP32BLETracker : public Component, | class ESP32BLETracker : public Component, | ||||||
| @@ -229,7 +229,7 @@ class ESP32BLETracker : public Component, | |||||||
|   /// Called when a `ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT` event is received. |   /// Called when a `ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT` event is received. | ||||||
|   void gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m); |   void gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m); | ||||||
|  |  | ||||||
|   int app_id_; |   int app_id_{0}; | ||||||
|  |  | ||||||
|   /// Vector of addresses that have already been printed in print_bt_device_info |   /// Vector of addresses that have already been printed in print_bt_device_info | ||||||
|   std::vector<uint64_t> already_discovered_; |   std::vector<uint64_t> already_discovered_; | ||||||
| @@ -242,10 +242,10 @@ class ESP32BLETracker : public Component, | |||||||
|   uint32_t scan_duration_; |   uint32_t scan_duration_; | ||||||
|   uint32_t scan_interval_; |   uint32_t scan_interval_; | ||||||
|   uint32_t scan_window_; |   uint32_t scan_window_; | ||||||
|   uint8_t scan_start_fail_count_; |   uint8_t scan_start_fail_count_{0}; | ||||||
|   bool scan_continuous_; |   bool scan_continuous_; | ||||||
|   bool scan_active_; |   bool scan_active_; | ||||||
|   bool scanner_idle_; |   bool scanner_idle_{true}; | ||||||
|   bool ble_was_disabled_{true}; |   bool ble_was_disabled_{true}; | ||||||
|   bool raw_advertisements_{false}; |   bool raw_advertisements_{false}; | ||||||
|   bool parse_advertisements_{false}; |   bool parse_advertisements_{false}; | ||||||
| @@ -260,6 +260,10 @@ class ESP32BLETracker : public Component, | |||||||
|   esp_ble_gap_cb_param_t::ble_scan_result_evt_param *scan_result_buffer_; |   esp_ble_gap_cb_param_t::ble_scan_result_evt_param *scan_result_buffer_; | ||||||
|   esp_bt_status_t scan_start_failed_{ESP_BT_STATUS_SUCCESS}; |   esp_bt_status_t scan_start_failed_{ESP_BT_STATUS_SUCCESS}; | ||||||
|   esp_bt_status_t scan_set_param_failed_{ESP_BT_STATUS_SUCCESS}; |   esp_bt_status_t scan_set_param_failed_{ESP_BT_STATUS_SUCCESS}; | ||||||
|  |   int connecting_{0}; | ||||||
|  |   int discovered_{0}; | ||||||
|  |   int searching_{0}; | ||||||
|  |   int disconnecting_{0}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // NOLINTNEXTLINE | // NOLINTNEXTLINE | ||||||
|   | |||||||
| @@ -1,7 +1,8 @@ | |||||||
| import esphome.config_validation as cv |  | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
|  |  | ||||||
| from esphome.components import esp32 | from esphome.components import esp32 | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import KEY_CORE, KEY_FRAMEWORK_VERSION | ||||||
|  | from esphome.core import CORE | ||||||
|  |  | ||||||
| CODEOWNERS = ["@jesserockz"] | CODEOWNERS = ["@jesserockz"] | ||||||
|  |  | ||||||
| @@ -36,8 +37,32 @@ RMT_CHANNEL_ENUMS = { | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_rmt_channel(*, tx: bool): | def use_new_rmt_driver(): | ||||||
|  |     framework_version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] | ||||||
|  |     if CORE.using_esp_idf and framework_version >= cv.Version(5, 0, 0): | ||||||
|  |         return True | ||||||
|  |     return False | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def validate_clock_resolution(): | ||||||
|  |     def _validator(value): | ||||||
|  |         cv.only_on_esp32(value) | ||||||
|  |         value = cv.int_(value) | ||||||
|  |         variant = esp32.get_esp32_variant() | ||||||
|  |         if variant == esp32.const.VARIANT_ESP32H2 and value > 32000000: | ||||||
|  |             raise cv.Invalid( | ||||||
|  |                 f"ESP32 variant {variant} has a max clock_resolution of 32000000." | ||||||
|  |             ) | ||||||
|  |         if value > 80000000: | ||||||
|  |             raise cv.Invalid( | ||||||
|  |                 f"ESP32 variant {variant} has a max clock_resolution of 80000000." | ||||||
|  |             ) | ||||||
|  |         return value | ||||||
|  |  | ||||||
|  |     return _validator | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def validate_rmt_channel(*, tx: bool): | ||||||
|     rmt_channels = RMT_TX_CHANNELS if tx else RMT_RX_CHANNELS |     rmt_channels = RMT_TX_CHANNELS if tx else RMT_RX_CHANNELS | ||||||
|  |  | ||||||
|     def _validator(value): |     def _validator(value): | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| #include <cinttypes> |  | ||||||
| #include "led_strip.h" | #include "led_strip.h" | ||||||
|  | #include <cinttypes> | ||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
| @@ -13,9 +13,13 @@ namespace esp32_rmt_led_strip { | |||||||
|  |  | ||||||
| static const char *const TAG = "esp32_rmt_led_strip"; | static const char *const TAG = "esp32_rmt_led_strip"; | ||||||
|  |  | ||||||
|  | #ifdef USE_ESP32_VARIANT_ESP32H2 | ||||||
|  | static const uint32_t RMT_CLK_FREQ = 32000000; | ||||||
|  | static const uint8_t RMT_CLK_DIV = 1; | ||||||
|  | #else | ||||||
| static const uint32_t RMT_CLK_FREQ = 80000000; | static const uint32_t RMT_CLK_FREQ = 80000000; | ||||||
|  |  | ||||||
| static const uint8_t RMT_CLK_DIV = 2; | static const uint8_t RMT_CLK_DIV = 2; | ||||||
|  | #endif | ||||||
|  |  | ||||||
| void ESP32RMTLEDStripLightOutput::setup() { | void ESP32RMTLEDStripLightOutput::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Setting up ESP32 LED Strip..."); |   ESP_LOGCONFIG(TAG, "Setting up ESP32 LED Strip..."); | ||||||
| @@ -37,9 +41,48 @@ void ESP32RMTLEDStripLightOutput::setup() { | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | #if ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  |   RAMAllocator<rmt_symbol_word_t> rmt_allocator(this->use_psram_ ? 0 : RAMAllocator<rmt_symbol_word_t>::ALLOC_INTERNAL); | ||||||
|  |  | ||||||
|  |   // 8 bits per byte, 1 rmt_symbol_word_t per bit + 1 rmt_symbol_word_t for reset | ||||||
|  |   this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8 + 1); | ||||||
|  |  | ||||||
|  |   rmt_tx_channel_config_t channel; | ||||||
|  |   memset(&channel, 0, sizeof(channel)); | ||||||
|  |   channel.clk_src = RMT_CLK_SRC_DEFAULT; | ||||||
|  |   channel.resolution_hz = RMT_CLK_FREQ / RMT_CLK_DIV; | ||||||
|  |   channel.gpio_num = gpio_num_t(this->pin_); | ||||||
|  |   channel.mem_block_symbols = this->rmt_symbols_; | ||||||
|  |   channel.trans_queue_depth = 1; | ||||||
|  |   channel.flags.io_loop_back = 0; | ||||||
|  |   channel.flags.io_od_mode = 0; | ||||||
|  |   channel.flags.invert_out = 0; | ||||||
|  |   channel.flags.with_dma = 0; | ||||||
|  |   channel.intr_priority = 0; | ||||||
|  |   if (rmt_new_tx_channel(&channel, &this->channel_) != ESP_OK) { | ||||||
|  |     ESP_LOGE(TAG, "Channel creation failed"); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   rmt_copy_encoder_config_t encoder; | ||||||
|  |   memset(&encoder, 0, sizeof(encoder)); | ||||||
|  |   if (rmt_new_copy_encoder(&encoder, &this->encoder_) != ESP_OK) { | ||||||
|  |     ESP_LOGE(TAG, "Encoder creation failed"); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (rmt_enable(this->channel_) != ESP_OK) { | ||||||
|  |     ESP_LOGE(TAG, "Enabling channel failed"); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | #else | ||||||
|   RAMAllocator<rmt_item32_t> rmt_allocator(this->use_psram_ ? 0 : RAMAllocator<rmt_item32_t>::ALLOC_INTERNAL); |   RAMAllocator<rmt_item32_t> rmt_allocator(this->use_psram_ ? 0 : RAMAllocator<rmt_item32_t>::ALLOC_INTERNAL); | ||||||
|   this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8 + |  | ||||||
|                                           1);  // 8 bits per byte, 1 rmt_item32_t per bit + 1 rmt_item32_t for reset |   // 8 bits per byte, 1 rmt_item32_t per bit + 1 rmt_item32_t for reset | ||||||
|  |   this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8 + 1); | ||||||
|  |  | ||||||
|   rmt_config_t config; |   rmt_config_t config; | ||||||
|   memset(&config, 0, sizeof(config)); |   memset(&config, 0, sizeof(config)); | ||||||
| @@ -64,6 +107,7 @@ void ESP32RMTLEDStripLightOutput::setup() { | |||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  | #endif | ||||||
| } | } | ||||||
|  |  | ||||||
| void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high, | void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high, | ||||||
| @@ -100,7 +144,12 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) { | |||||||
|  |  | ||||||
|   ESP_LOGVV(TAG, "Writing RGB values to bus..."); |   ESP_LOGVV(TAG, "Writing RGB values to bus..."); | ||||||
|  |  | ||||||
|   if (rmt_wait_tx_done(this->channel_, pdMS_TO_TICKS(1000)) != ESP_OK) { | #if ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  |   esp_err_t error = rmt_tx_wait_all_done(this->channel_, 1000); | ||||||
|  | #else | ||||||
|  |   esp_err_t error = rmt_wait_tx_done(this->channel_, pdMS_TO_TICKS(1000)); | ||||||
|  | #endif | ||||||
|  |   if (error != ESP_OK) { | ||||||
|     ESP_LOGE(TAG, "RMT TX timeout"); |     ESP_LOGE(TAG, "RMT TX timeout"); | ||||||
|     this->status_set_warning(); |     this->status_set_warning(); | ||||||
|     return; |     return; | ||||||
| @@ -112,7 +161,11 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) { | |||||||
|   size_t size = 0; |   size_t size = 0; | ||||||
|   size_t len = 0; |   size_t len = 0; | ||||||
|   uint8_t *psrc = this->buf_; |   uint8_t *psrc = this->buf_; | ||||||
|  | #if ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  |   rmt_symbol_word_t *pdest = this->rmt_buf_; | ||||||
|  | #else | ||||||
|   rmt_item32_t *pdest = this->rmt_buf_; |   rmt_item32_t *pdest = this->rmt_buf_; | ||||||
|  | #endif | ||||||
|   while (size < buffer_size) { |   while (size < buffer_size) { | ||||||
|     uint8_t b = *psrc; |     uint8_t b = *psrc; | ||||||
|     for (int i = 0; i < 8; i++) { |     for (int i = 0; i < 8; i++) { | ||||||
| @@ -130,7 +183,16 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) { | |||||||
|     len++; |     len++; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (rmt_write_items(this->channel_, this->rmt_buf_, len, false) != ESP_OK) { | #if ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  |   rmt_transmit_config_t config; | ||||||
|  |   memset(&config, 0, sizeof(config)); | ||||||
|  |   config.loop_count = 0; | ||||||
|  |   config.flags.eot_level = 0; | ||||||
|  |   error = rmt_transmit(this->channel_, this->encoder_, this->rmt_buf_, len * sizeof(rmt_symbol_word_t), &config); | ||||||
|  | #else | ||||||
|  |   error = rmt_write_items(this->channel_, this->rmt_buf_, len, false); | ||||||
|  | #endif | ||||||
|  |   if (error != ESP_OK) { | ||||||
|     ESP_LOGE(TAG, "RMT TX error"); |     ESP_LOGE(TAG, "RMT TX error"); | ||||||
|     this->status_set_warning(); |     this->status_set_warning(); | ||||||
|     return; |     return; | ||||||
| @@ -186,7 +248,11 @@ light::ESPColorView ESP32RMTLEDStripLightOutput::get_view_internal(int32_t index | |||||||
| void ESP32RMTLEDStripLightOutput::dump_config() { | void ESP32RMTLEDStripLightOutput::dump_config() { | ||||||
|   ESP_LOGCONFIG(TAG, "ESP32 RMT LED Strip:"); |   ESP_LOGCONFIG(TAG, "ESP32 RMT LED Strip:"); | ||||||
|   ESP_LOGCONFIG(TAG, "  Pin: %u", this->pin_); |   ESP_LOGCONFIG(TAG, "  Pin: %u", this->pin_); | ||||||
|  | #if ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  |   ESP_LOGCONFIG(TAG, "  RMT Symbols: %" PRIu32, this->rmt_symbols_); | ||||||
|  | #else | ||||||
|   ESP_LOGCONFIG(TAG, "  Channel: %u", this->channel_); |   ESP_LOGCONFIG(TAG, "  Channel: %u", this->channel_); | ||||||
|  | #endif | ||||||
|   const char *rgb_order; |   const char *rgb_order; | ||||||
|   switch (this->rgb_order_) { |   switch (this->rgb_order_) { | ||||||
|     case ORDER_RGB: |     case ORDER_RGB: | ||||||
|   | |||||||
| @@ -9,8 +9,14 @@ | |||||||
| #include "esphome/core/helpers.h" | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
| #include <driver/gpio.h> | #include <driver/gpio.h> | ||||||
| #include <driver/rmt.h> |  | ||||||
| #include <esp_err.h> | #include <esp_err.h> | ||||||
|  | #include <esp_idf_version.h> | ||||||
|  |  | ||||||
|  | #if ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  | #include <driver/rmt_tx.h> | ||||||
|  | #else | ||||||
|  | #include <driver/rmt.h> | ||||||
|  | #endif | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace esp32_rmt_led_strip { | namespace esp32_rmt_led_strip { | ||||||
| @@ -54,7 +60,11 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { | |||||||
|                       uint32_t reset_time_high, uint32_t reset_time_low); |                       uint32_t reset_time_high, uint32_t reset_time_low); | ||||||
|  |  | ||||||
|   void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; } |   void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; } | ||||||
|  | #if ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  |   void set_rmt_symbols(uint32_t rmt_symbols) { this->rmt_symbols_ = rmt_symbols; } | ||||||
|  | #else | ||||||
|   void set_rmt_channel(rmt_channel_t channel) { this->channel_ = channel; } |   void set_rmt_channel(rmt_channel_t channel) { this->channel_ = channel; } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|   void clear_effect_data() override { |   void clear_effect_data() override { | ||||||
|     for (int i = 0; i < this->size(); i++) |     for (int i = 0; i < this->size(); i++) | ||||||
| @@ -70,7 +80,17 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { | |||||||
|  |  | ||||||
|   uint8_t *buf_{nullptr}; |   uint8_t *buf_{nullptr}; | ||||||
|   uint8_t *effect_data_{nullptr}; |   uint8_t *effect_data_{nullptr}; | ||||||
|  | #if ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  |   rmt_channel_handle_t channel_{nullptr}; | ||||||
|  |   rmt_encoder_handle_t encoder_{nullptr}; | ||||||
|  |   rmt_symbol_word_t *rmt_buf_{nullptr}; | ||||||
|  |   rmt_symbol_word_t bit0_, bit1_, reset_; | ||||||
|  |   uint32_t rmt_symbols_; | ||||||
|  | #else | ||||||
|   rmt_item32_t *rmt_buf_{nullptr}; |   rmt_item32_t *rmt_buf_{nullptr}; | ||||||
|  |   rmt_item32_t bit0_, bit1_, reset_; | ||||||
|  |   rmt_channel_t channel_{RMT_CHANNEL_0}; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|   uint8_t pin_; |   uint8_t pin_; | ||||||
|   uint16_t num_leds_; |   uint16_t num_leds_; | ||||||
| @@ -78,9 +98,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { | |||||||
|   bool is_wrgb_; |   bool is_wrgb_; | ||||||
|   bool use_psram_; |   bool use_psram_; | ||||||
|  |  | ||||||
|   rmt_item32_t bit0_, bit1_, reset_; |  | ||||||
|   RGBOrder rgb_order_; |   RGBOrder rgb_order_; | ||||||
|   rmt_channel_t channel_; |  | ||||||
|  |  | ||||||
|   uint32_t last_refresh_{0}; |   uint32_t last_refresh_{0}; | ||||||
|   optional<uint32_t> max_refresh_rate_{}; |   optional<uint32_t> max_refresh_rate_{}; | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ from esphome.const import ( | |||||||
|     CONF_PIN, |     CONF_PIN, | ||||||
|     CONF_RGB_ORDER, |     CONF_RGB_ORDER, | ||||||
|     CONF_RMT_CHANNEL, |     CONF_RMT_CHANNEL, | ||||||
|  |     CONF_RMT_SYMBOLS, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| CODEOWNERS = ["@jesserockz"] | CODEOWNERS = ["@jesserockz"] | ||||||
| @@ -23,8 +24,6 @@ ESP32RMTLEDStripLightOutput = esp32_rmt_led_strip_ns.class_( | |||||||
|     "ESP32RMTLEDStripLightOutput", light.AddressableLight |     "ESP32RMTLEDStripLightOutput", light.AddressableLight | ||||||
| ) | ) | ||||||
|  |  | ||||||
| rmt_channel_t = cg.global_ns.enum("rmt_channel_t") |  | ||||||
|  |  | ||||||
| RGBOrder = esp32_rmt_led_strip_ns.enum("RGBOrder") | RGBOrder = esp32_rmt_led_strip_ns.enum("RGBOrder") | ||||||
|  |  | ||||||
| RGB_ORDERS = { | RGB_ORDERS = { | ||||||
| @@ -65,6 +64,13 @@ CONF_RESET_HIGH = "reset_high" | |||||||
| CONF_RESET_LOW = "reset_low" | CONF_RESET_LOW = "reset_low" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def final_validation(config): | ||||||
|  |     if not esp32_rmt.use_new_rmt_driver() and CONF_RMT_CHANNEL not in config: | ||||||
|  |         raise cv.Invalid("rmt_channel is a required option.") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | FINAL_VALIDATE_SCHEMA = final_validation | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All( | CONFIG_SCHEMA = cv.All( | ||||||
|     light.ADDRESSABLE_LIGHT_SCHEMA.extend( |     light.ADDRESSABLE_LIGHT_SCHEMA.extend( | ||||||
|         { |         { | ||||||
| @@ -72,7 +78,18 @@ CONFIG_SCHEMA = cv.All( | |||||||
|             cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number, |             cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number, | ||||||
|             cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, |             cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, | ||||||
|             cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True), |             cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True), | ||||||
|             cv.Required(CONF_RMT_CHANNEL): esp32_rmt.validate_rmt_channel(tx=True), |             cv.Optional(CONF_RMT_CHANNEL): cv.All( | ||||||
|  |                 cv.only_with_arduino, esp32_rmt.validate_rmt_channel(tx=True) | ||||||
|  |             ), | ||||||
|  |             cv.SplitDefault( | ||||||
|  |                 CONF_RMT_SYMBOLS, | ||||||
|  |                 esp32_idf=64, | ||||||
|  |                 esp32_s2_idf=64, | ||||||
|  |                 esp32_s3_idf=48, | ||||||
|  |                 esp32_c3_idf=48, | ||||||
|  |                 esp32_c6_idf=48, | ||||||
|  |                 esp32_h2_idf=48, | ||||||
|  |             ): cv.All(cv.only_with_esp_idf, cv.int_range(min=2)), | ||||||
|             cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds, |             cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds, | ||||||
|             cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), |             cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), | ||||||
|             cv.Optional(CONF_IS_RGBW, default=False): cv.boolean, |             cv.Optional(CONF_IS_RGBW, default=False): cv.boolean, | ||||||
| @@ -148,6 +165,10 @@ async def to_code(config): | |||||||
|     cg.add(var.set_is_wrgb(config[CONF_IS_WRGB])) |     cg.add(var.set_is_wrgb(config[CONF_IS_WRGB])) | ||||||
|     cg.add(var.set_use_psram(config[CONF_USE_PSRAM])) |     cg.add(var.set_use_psram(config[CONF_USE_PSRAM])) | ||||||
|  |  | ||||||
|  |     if esp32_rmt.use_new_rmt_driver(): | ||||||
|  |         cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS])) | ||||||
|  |     else: | ||||||
|  |         rmt_channel_t = cg.global_ns.enum("rmt_channel_t") | ||||||
|         cg.add( |         cg.add( | ||||||
|             var.set_rmt_channel( |             var.set_rmt_channel( | ||||||
|                 getattr(rmt_channel_t, f"RMT_CHANNEL_{config[CONF_RMT_CHANNEL]}") |                 getattr(rmt_channel_t, f"RMT_CHANNEL_{config[CONF_RMT_CHANNEL]}") | ||||||
|   | |||||||
| @@ -51,8 +51,11 @@ CONF_IGNORE_MISSING_GLYPHS = "ignore_missing_glyphs" | |||||||
| # Cache loaded freetype fonts | # Cache loaded freetype fonts | ||||||
| class FontCache(dict): | class FontCache(dict): | ||||||
|     def __missing__(self, key): |     def __missing__(self, key): | ||||||
|  |         try: | ||||||
|             res = self[key] = freetype.Face(key) |             res = self[key] = freetype.Face(key) | ||||||
|             return res |             return res | ||||||
|  |         except freetype.FT_Exception as e: | ||||||
|  |             raise cv.Invalid(f"Could not load Font file {key}: {e}") from e | ||||||
|  |  | ||||||
|  |  | ||||||
| FONT_CACHE = FontCache() | FONT_CACHE = FontCache() | ||||||
|   | |||||||
| @@ -133,9 +133,11 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo | |||||||
|     auto diff_r = (float) color.r - (float) background.r; |     auto diff_r = (float) color.r - (float) background.r; | ||||||
|     auto diff_g = (float) color.g - (float) background.g; |     auto diff_g = (float) color.g - (float) background.g; | ||||||
|     auto diff_b = (float) color.b - (float) background.b; |     auto diff_b = (float) color.b - (float) background.b; | ||||||
|  |     auto diff_w = (float) color.w - (float) background.w; | ||||||
|     auto b_r = (float) background.r; |     auto b_r = (float) background.r; | ||||||
|     auto b_g = (float) background.g; |     auto b_g = (float) background.g; | ||||||
|     auto b_b = (float) background.g; |     auto b_b = (float) background.b; | ||||||
|  |     auto b_w = (float) background.w; | ||||||
|     for (int glyph_y = y_start + scan_y1; glyph_y != max_y; glyph_y++) { |     for (int glyph_y = y_start + scan_y1; glyph_y != max_y; glyph_y++) { | ||||||
|       for (int glyph_x = x_at + scan_x1; glyph_x != max_x; glyph_x++) { |       for (int glyph_x = x_at + scan_x1; glyph_x != max_x; glyph_x++) { | ||||||
|         uint8_t pixel = 0; |         uint8_t pixel = 0; | ||||||
| @@ -153,8 +155,8 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo | |||||||
|           display->draw_pixel_at(glyph_x, glyph_y, color); |           display->draw_pixel_at(glyph_x, glyph_y, color); | ||||||
|         } else if (pixel != 0) { |         } else if (pixel != 0) { | ||||||
|           auto on = (float) pixel / (float) bpp_max; |           auto on = (float) pixel / (float) bpp_max; | ||||||
|           auto blended = |           auto blended = Color((uint8_t) (diff_r * on + b_r), (uint8_t) (diff_g * on + b_g), | ||||||
|               Color((uint8_t) (diff_r * on + b_r), (uint8_t) (diff_g * on + b_g), (uint8_t) (diff_b * on + b_b)); |                                (uint8_t) (diff_b * on + b_b), (uint8_t) (diff_w * on + b_w)); | ||||||
|           display->draw_pixel_at(glyph_x, glyph_y, blended); |           display->draw_pixel_at(glyph_x, glyph_y, blended); | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|   | |||||||
| @@ -17,14 +17,14 @@ void IDFI2CBus::setup() { | |||||||
|   ESP_LOGCONFIG(TAG, "Setting up I2C bus..."); |   ESP_LOGCONFIG(TAG, "Setting up I2C bus..."); | ||||||
|   static i2c_port_t next_port = I2C_NUM_0; |   static i2c_port_t next_port = I2C_NUM_0; | ||||||
|   port_ = next_port; |   port_ = next_port; | ||||||
| #if I2C_NUM_MAX > 1 | #if SOC_I2C_NUM > 1 | ||||||
|   next_port = (next_port == I2C_NUM_0) ? I2C_NUM_1 : I2C_NUM_MAX; |   next_port = (next_port == I2C_NUM_0) ? I2C_NUM_1 : I2C_NUM_MAX; | ||||||
| #else | #else | ||||||
|   next_port = I2C_NUM_MAX; |   next_port = I2C_NUM_MAX; | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   if (port_ == I2C_NUM_MAX) { |   if (port_ == I2C_NUM_MAX) { | ||||||
|     ESP_LOGE(TAG, "Too many I2C buses configured"); |     ESP_LOGE(TAG, "Too many I2C buses configured. Max %u supported.", SOC_I2C_NUM); | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -247,7 +247,7 @@ void I2SAudioSpeaker::speaker_task(void *params) { | |||||||
|  |  | ||||||
|   // Ensure ring buffer is at least as large as the total size of the DMA buffers |   // Ensure ring buffer is at least as large as the total size of the DMA buffers | ||||||
|   const size_t ring_buffer_size = |   const size_t ring_buffer_size = | ||||||
|       std::min((uint32_t) dma_buffers_size, this_speaker->buffer_duration_ms_ * bytes_per_ms); |       std::max((uint32_t) dma_buffers_size, this_speaker->buffer_duration_ms_ * bytes_per_ms); | ||||||
|  |  | ||||||
|   if (this_speaker->send_esp_err_to_event_group_(this_speaker->allocate_buffers_(dma_buffers_size, ring_buffer_size))) { |   if (this_speaker->send_esp_err_to_event_group_(this_speaker->allocate_buffers_(dma_buffers_size, ring_buffer_size))) { | ||||||
|     // Failed to allocate buffers |     // Failed to allocate buffers | ||||||
|   | |||||||
| @@ -1,38 +1,20 @@ | |||||||
| #include "json_util.h" | #include "json_util.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
| #ifdef USE_ESP8266 |  | ||||||
| #include <Esp.h> |  | ||||||
| #endif |  | ||||||
| #ifdef USE_ESP32 |  | ||||||
| #include <esp_heap_caps.h> |  | ||||||
| #endif |  | ||||||
| #ifdef USE_RP2040 |  | ||||||
| #include <Arduino.h> |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace json { | namespace json { | ||||||
|  |  | ||||||
| static const char *const TAG = "json"; | static const char *const TAG = "json"; | ||||||
|  |  | ||||||
| static std::vector<char> global_json_build_buffer;  // NOLINT | static std::vector<char> global_json_build_buffer;  // NOLINT | ||||||
|  | static const auto ALLOCATOR = RAMAllocator<uint8_t>(RAMAllocator<uint8_t>::ALLOC_INTERNAL); | ||||||
|  |  | ||||||
| std::string build_json(const json_build_t &f) { | std::string build_json(const json_build_t &f) { | ||||||
|   // Here we are allocating up to 5kb of memory, |   // Here we are allocating up to 5kb of memory, | ||||||
|   // with the heap size minus 2kb to be safe if less than 5kb |   // with the heap size minus 2kb to be safe if less than 5kb | ||||||
|   // as we can not have a true dynamic sized document. |   // as we can not have a true dynamic sized document. | ||||||
|   // The excess memory is freed below with `shrinkToFit()` |   // The excess memory is freed below with `shrinkToFit()` | ||||||
| #ifdef USE_ESP8266 |   auto free_heap = ALLOCATOR.get_max_free_block_size(); | ||||||
|   const size_t free_heap = ESP.getMaxFreeBlockSize();  // NOLINT(readability-static-accessed-through-instance) |  | ||||||
| #elif defined(USE_ESP32) |  | ||||||
|   const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT); |  | ||||||
| #elif defined(USE_RP2040) |  | ||||||
|   const size_t free_heap = rp2040.getFreeHeap(); |  | ||||||
| #elif defined(USE_LIBRETINY) |  | ||||||
|   const size_t free_heap = lt_heap_get_free(); |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
|   size_t request_size = std::min(free_heap, (size_t) 512); |   size_t request_size = std::min(free_heap, (size_t) 512); | ||||||
|   while (true) { |   while (true) { | ||||||
|     ESP_LOGV(TAG, "Attempting to allocate %u bytes for JSON serialization", request_size); |     ESP_LOGV(TAG, "Attempting to allocate %u bytes for JSON serialization", request_size); | ||||||
| @@ -67,20 +49,12 @@ bool parse_json(const std::string &data, const json_parse_t &f) { | |||||||
|   // with the heap size minus 2kb to be safe if less than that |   // with the heap size minus 2kb to be safe if less than that | ||||||
|   // as we can not have a true dynamic sized document. |   // as we can not have a true dynamic sized document. | ||||||
|   // The excess memory is freed below with `shrinkToFit()` |   // The excess memory is freed below with `shrinkToFit()` | ||||||
| #ifdef USE_ESP8266 |   auto free_heap = ALLOCATOR.get_max_free_block_size(); | ||||||
|   const size_t free_heap = ESP.getMaxFreeBlockSize();  // NOLINT(readability-static-accessed-through-instance) |  | ||||||
| #elif defined(USE_ESP32) |  | ||||||
|   const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT); |  | ||||||
| #elif defined(USE_RP2040) |  | ||||||
|   const size_t free_heap = rp2040.getFreeHeap(); |  | ||||||
| #elif defined(USE_LIBRETINY) |  | ||||||
|   const size_t free_heap = lt_heap_get_free(); |  | ||||||
| #endif |  | ||||||
|   size_t request_size = std::min(free_heap, (size_t) (data.size() * 1.5)); |   size_t request_size = std::min(free_heap, (size_t) (data.size() * 1.5)); | ||||||
|   while (true) { |   while (true) { | ||||||
|     DynamicJsonDocument json_document(request_size); |     DynamicJsonDocument json_document(request_size); | ||||||
|     if (json_document.capacity() == 0) { |     if (json_document.capacity() == 0) { | ||||||
|       ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, free heap: %u", request_size, |       ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %zu bytes, free heap: %zu", request_size, | ||||||
|                free_heap); |                free_heap); | ||||||
|       return false; |       return false; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ from esphome.helpers import write_file_if_changed | |||||||
|  |  | ||||||
| from . import defines as df, helpers, lv_validation as lvalid | from . import defines as df, helpers, lv_validation as lvalid | ||||||
| from .automation import disp_update, focused_widgets, update_to_code | from .automation import disp_update, focused_widgets, update_to_code | ||||||
| from .defines import add_define | from .defines import CONF_DRAW_ROUNDING, add_define | ||||||
| from .encoders import ( | from .encoders import ( | ||||||
|     ENCODERS_CONFIG, |     ENCODERS_CONFIG, | ||||||
|     encoders_to_code, |     encoders_to_code, | ||||||
| @@ -205,6 +205,10 @@ def final_validation(configs): | |||||||
|                 raise cv.Invalid( |                 raise cv.Invalid( | ||||||
|                     "Using auto_clear_enabled: true in display config not compatible with LVGL" |                     "Using auto_clear_enabled: true in display config not compatible with LVGL" | ||||||
|                 ) |                 ) | ||||||
|  |             if draw_rounding := display.get(CONF_DRAW_ROUNDING): | ||||||
|  |                 config[CONF_DRAW_ROUNDING] = max( | ||||||
|  |                     draw_rounding, config[CONF_DRAW_ROUNDING] | ||||||
|  |                 ) | ||||||
|         buffer_frac = config[CONF_BUFFER_SIZE] |         buffer_frac = config[CONF_BUFFER_SIZE] | ||||||
|         if CORE.is_esp32 and buffer_frac > 0.5 and "psram" not in global_config: |         if CORE.is_esp32 and buffer_frac > 0.5 and "psram" not in global_config: | ||||||
|             LOGGER.warning("buffer_size: may need to be reduced without PSRAM") |             LOGGER.warning("buffer_size: may need to be reduced without PSRAM") | ||||||
|   | |||||||
| @@ -168,6 +168,7 @@ LV_EVENT_MAP = { | |||||||
|     "READY": "READY", |     "READY": "READY", | ||||||
|     "CANCEL": "CANCEL", |     "CANCEL": "CANCEL", | ||||||
|     "ALL_EVENTS": "ALL", |     "ALL_EVENTS": "ALL", | ||||||
|  |     "CHANGE": "VALUE_CHANGED", | ||||||
| } | } | ||||||
|  |  | ||||||
| LV_EVENT_TRIGGERS = tuple(f"on_{x.lower()}" for x in LV_EVENT_MAP) | LV_EVENT_TRIGGERS = tuple(f"on_{x.lower()}" for x in LV_EVENT_MAP) | ||||||
|   | |||||||
| @@ -79,7 +79,7 @@ class ImgType(WidgetType): | |||||||
|         if CONF_ANTIALIAS in config: |         if CONF_ANTIALIAS in config: | ||||||
|             lv.img_set_antialias(w.obj, config[CONF_ANTIALIAS]) |             lv.img_set_antialias(w.obj, config[CONF_ANTIALIAS]) | ||||||
|         if mode := config.get(CONF_MODE): |         if mode := config.get(CONF_MODE): | ||||||
|             lv.img_set_mode(w.obj, mode) |             await w.set_property("size_mode", mode) | ||||||
|  |  | ||||||
|  |  | ||||||
| img_spec = ImgType() | img_spec = ImgType() | ||||||
|   | |||||||
| @@ -19,10 +19,12 @@ template<typename... Ts> class MideaActionBase : public Action<Ts...> { | |||||||
|  |  | ||||||
| template<typename... Ts> class FollowMeAction : public MideaActionBase<Ts...> { | template<typename... Ts> class FollowMeAction : public MideaActionBase<Ts...> { | ||||||
|   TEMPLATABLE_VALUE(float, temperature) |   TEMPLATABLE_VALUE(float, temperature) | ||||||
|  |   TEMPLATABLE_VALUE(bool, use_fahrenheit) | ||||||
|   TEMPLATABLE_VALUE(bool, beeper) |   TEMPLATABLE_VALUE(bool, beeper) | ||||||
|  |  | ||||||
|   void play(Ts... x) override { |   void play(Ts... x) override { | ||||||
|     this->parent_->do_follow_me(this->temperature_.value(x...), this->beeper_.value(x...)); |     this->parent_->do_follow_me(this->temperature_.value(x...), this->use_fahrenheit_.value(x...), | ||||||
|  |                                 this->beeper_.value(x...)); | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| #ifdef USE_ARDUINO | #ifdef USE_ARDUINO | ||||||
|  |  | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
| #include "air_conditioner.h" | #include "air_conditioner.h" | ||||||
| #include "ac_adapter.h" | #include "ac_adapter.h" | ||||||
| #include <cmath> | #include <cmath> | ||||||
| @@ -121,7 +122,7 @@ void AirConditioner::dump_config() { | |||||||
|  |  | ||||||
| /* ACTIONS */ | /* ACTIONS */ | ||||||
|  |  | ||||||
| void AirConditioner::do_follow_me(float temperature, bool beeper) { | void AirConditioner::do_follow_me(float temperature, bool use_fahrenheit, bool beeper) { | ||||||
| #ifdef USE_REMOTE_TRANSMITTER | #ifdef USE_REMOTE_TRANSMITTER | ||||||
|   // Check if temperature is finite (not NaN or infinite) |   // Check if temperature is finite (not NaN or infinite) | ||||||
|   if (!std::isfinite(temperature)) { |   if (!std::isfinite(temperature)) { | ||||||
| @@ -131,13 +132,14 @@ void AirConditioner::do_follow_me(float temperature, bool beeper) { | |||||||
|  |  | ||||||
|   // Round and convert temperature to long, then clamp and convert it to uint8_t |   // Round and convert temperature to long, then clamp and convert it to uint8_t | ||||||
|   uint8_t temp_uint8 = |   uint8_t temp_uint8 = | ||||||
|       static_cast<uint8_t>(std::max(0L, std::min(static_cast<long>(UINT8_MAX), std::lroundf(temperature)))); |       static_cast<uint8_t>(esphome::clamp<long>(std::lroundf(temperature), 0L, static_cast<long>(UINT8_MAX))); | ||||||
|  |  | ||||||
|   ESP_LOGD(Constants::TAG, "Follow me action called with temperature: %f °C, rounded to: %u °C", temperature, |   char temp_symbol = use_fahrenheit ? 'F' : 'C'; | ||||||
|            temp_uint8); |   ESP_LOGD(Constants::TAG, "Follow me action called with temperature: %.5f °%c, rounded to: %u °%c", temperature, | ||||||
|  |            temp_symbol, temp_uint8, temp_symbol); | ||||||
|  |  | ||||||
|   // Create and transmit the data |   // Create and transmit the data | ||||||
|   IrFollowMeData data(temp_uint8, beeper); |   IrFollowMeData data(temp_uint8, use_fahrenheit, beeper); | ||||||
|   this->transmitter_.transmit(data); |   this->transmitter_.transmit(data); | ||||||
| #else | #else | ||||||
|   ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component"); |   ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component"); | ||||||
|   | |||||||
| @@ -32,7 +32,7 @@ class AirConditioner : public ApplianceBase<dudanov::midea::ac::AirConditioner>, | |||||||
|   /* ### ACTIONS ### */ |   /* ### ACTIONS ### */ | ||||||
|   /* ############### */ |   /* ############### */ | ||||||
|  |  | ||||||
|   void do_follow_me(float temperature, bool beeper = false); |   void do_follow_me(float temperature, bool use_fahrenheit, bool beeper = false); | ||||||
|   void do_display_toggle(); |   void do_display_toggle(); | ||||||
|   void do_swing_step(); |   void do_swing_step(); | ||||||
|   void do_beeper_on() { this->set_beeper_feedback(true); } |   void do_beeper_on() { this->set_beeper_feedback(true); } | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ from esphome.const import ( | |||||||
|     CONF_SUPPORTED_SWING_MODES, |     CONF_SUPPORTED_SWING_MODES, | ||||||
|     CONF_TIMEOUT, |     CONF_TIMEOUT, | ||||||
|     CONF_TEMPERATURE, |     CONF_TEMPERATURE, | ||||||
|  |     CONF_USE_FAHRENHEIT, | ||||||
|     DEVICE_CLASS_POWER, |     DEVICE_CLASS_POWER, | ||||||
|     DEVICE_CLASS_TEMPERATURE, |     DEVICE_CLASS_TEMPERATURE, | ||||||
|     DEVICE_CLASS_HUMIDITY, |     DEVICE_CLASS_HUMIDITY, | ||||||
| @@ -172,11 +173,10 @@ MIDEA_ACTION_BASE_SCHEMA = cv.Schema( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| # FollowMe action | # FollowMe action | ||||||
| MIDEA_FOLLOW_ME_MIN = 0 |  | ||||||
| MIDEA_FOLLOW_ME_MAX = 37 |  | ||||||
| MIDEA_FOLLOW_ME_SCHEMA = cv.Schema( | MIDEA_FOLLOW_ME_SCHEMA = cv.Schema( | ||||||
|     { |     { | ||||||
|         cv.Required(CONF_TEMPERATURE): cv.templatable(cv.temperature), |         cv.Required(CONF_TEMPERATURE): cv.templatable(cv.temperature), | ||||||
|  |         cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.templatable(cv.boolean), | ||||||
|         cv.Optional(CONF_BEEPER, default=False): cv.templatable(cv.boolean), |         cv.Optional(CONF_BEEPER, default=False): cv.templatable(cv.boolean), | ||||||
|     } |     } | ||||||
| ) | ) | ||||||
| @@ -186,6 +186,8 @@ MIDEA_FOLLOW_ME_SCHEMA = cv.Schema( | |||||||
| async def follow_me_to_code(var, config, args): | async def follow_me_to_code(var, config, args): | ||||||
|     template_ = await cg.templatable(config[CONF_BEEPER], args, cg.bool_) |     template_ = await cg.templatable(config[CONF_BEEPER], args, cg.bool_) | ||||||
|     cg.add(var.set_beeper(template_)) |     cg.add(var.set_beeper(template_)) | ||||||
|  |     template_ = await cg.templatable(config[CONF_USE_FAHRENHEIT], args, cg.bool_) | ||||||
|  |     cg.add(var.set_use_fahrenheit(template_)) | ||||||
|     template_ = await cg.templatable(config[CONF_TEMPERATURE], args, cg.float_) |     template_ = await cg.templatable(config[CONF_TEMPERATURE], args, cg.float_) | ||||||
|     cg.add(var.set_temperature(template_)) |     cg.add(var.set_temperature(template_)) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -16,22 +16,53 @@ class IrFollowMeData : public IrData { | |||||||
|   IrFollowMeData() : IrData({MIDEA_TYPE_FOLLOW_ME, 0x82, 0x48, 0x7F, 0x1F}) {} |   IrFollowMeData() : IrData({MIDEA_TYPE_FOLLOW_ME, 0x82, 0x48, 0x7F, 0x1F}) {} | ||||||
|   // Copy from Base |   // Copy from Base | ||||||
|   IrFollowMeData(const IrData &data) : IrData(data) {} |   IrFollowMeData(const IrData &data) : IrData(data) {} | ||||||
|   // Direct from temperature and beeper values |   // Direct from temperature in celsius and beeper values | ||||||
|   IrFollowMeData(uint8_t temp, bool beeper = false) : IrFollowMeData() { |   IrFollowMeData(uint8_t temp, bool beeper = false) : IrFollowMeData() { | ||||||
|     this->set_temp(temp); |     this->set_temp(temp, false); | ||||||
|  |     this->set_beeper(beeper); | ||||||
|  |   } | ||||||
|  |   // Direct from temperature, fahrenheit and beeper values | ||||||
|  |   IrFollowMeData(uint8_t temp, bool fahrenheit, bool beeper) : IrFollowMeData() { | ||||||
|  |     this->set_temp(temp, fahrenheit); | ||||||
|     this->set_beeper(beeper); |     this->set_beeper(beeper); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /* TEMPERATURE */ |   /* TEMPERATURE */ | ||||||
|   uint8_t temp() const { return this->get_value_(4) - 1; } |   uint8_t temp() const { | ||||||
|   void set_temp(uint8_t val) { this->set_value_(4, std::min(MAX_TEMP, val) + 1); } |     if (this->fahrenheit()) { | ||||||
|  |       return this->get_value_(4) + 31; | ||||||
|  |     } | ||||||
|  |     return this->get_value_(4) - 1; | ||||||
|  |   } | ||||||
|  |   void set_temp(uint8_t val, bool fahrenheit = false) { | ||||||
|  |     this->set_fahrenheit(fahrenheit); | ||||||
|  |     if (this->fahrenheit()) { | ||||||
|  |       // see https://github.com/esphome/feature-requests/issues/1627#issuecomment-1365639966 | ||||||
|  |       val = esphome::clamp<uint8_t>(val, MIN_TEMP_F, MAX_TEMP_F) - 31; | ||||||
|  |     } else { | ||||||
|  |       val = esphome::clamp<uint8_t>(val, MIN_TEMP_C, MAX_TEMP_C) + 1; | ||||||
|  |     } | ||||||
|  |     this->set_value_(4, val); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   /* BEEPER */ |   /* BEEPER */ | ||||||
|   bool beeper() const { return this->get_value_(3, 128); } |   bool beeper() const { return this->get_value_(3, 128); } | ||||||
|   void set_beeper(bool val) { this->set_mask_(3, val, 128); } |   void set_beeper(bool val) { this->set_mask_(3, val, 128); } | ||||||
|  |  | ||||||
|  |   /* FAHRENHEIT */ | ||||||
|  |   bool fahrenheit() const { return this->get_value_(2, 32); } | ||||||
|  |   void set_fahrenheit(bool val) { this->set_mask_(2, val, 32); } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   static const uint8_t MAX_TEMP = 37; |   static const uint8_t MIN_TEMP_C = 0; | ||||||
|  |   static const uint8_t MAX_TEMP_C = 37; | ||||||
|  |  | ||||||
|  |   // see | ||||||
|  |   // https://github.com/crankyoldgit/IRremoteESP8266/blob/9bdf8abcb465268c5409db99dc83a26df64c7445/src/ir_Midea.h#L116 | ||||||
|  |   static const uint8_t MIN_TEMP_F = 32; | ||||||
|  |   // see | ||||||
|  |   // https://github.com/crankyoldgit/IRremoteESP8266/blob/9bdf8abcb465268c5409db99dc83a26df64c7445/src/ir_Midea.h#L117 | ||||||
|  |   static const uint8_t MAX_TEMP_F = 99; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class IrSpecialData : public IrData { | class IrSpecialData : public IrData { | ||||||
|   | |||||||
| @@ -119,17 +119,17 @@ async def to_code(config): | |||||||
|             cg.add_library("ESP8266HTTPClient", None) |             cg.add_library("ESP8266HTTPClient", None) | ||||||
|  |  | ||||||
|     if CONF_TOUCH_SLEEP_TIMEOUT in config: |     if CONF_TOUCH_SLEEP_TIMEOUT in config: | ||||||
|         cg.add(var.set_touch_sleep_timeout_internal(config[CONF_TOUCH_SLEEP_TIMEOUT])) |         cg.add(var.set_touch_sleep_timeout(config[CONF_TOUCH_SLEEP_TIMEOUT])) | ||||||
|  |  | ||||||
|     if CONF_WAKE_UP_PAGE in config: |     if CONF_WAKE_UP_PAGE in config: | ||||||
|         cg.add(var.set_wake_up_page_internal(config[CONF_WAKE_UP_PAGE])) |         cg.add(var.set_wake_up_page(config[CONF_WAKE_UP_PAGE])) | ||||||
|  |  | ||||||
|     if CONF_START_UP_PAGE in config: |     if CONF_START_UP_PAGE in config: | ||||||
|         cg.add(var.set_start_up_page_internal(config[CONF_START_UP_PAGE])) |         cg.add(var.set_start_up_page(config[CONF_START_UP_PAGE])) | ||||||
|  |  | ||||||
|     cg.add(var.set_auto_wake_on_touch_internal(config[CONF_AUTO_WAKE_ON_TOUCH])) |     cg.add(var.set_auto_wake_on_touch(config[CONF_AUTO_WAKE_ON_TOUCH])) | ||||||
|  |  | ||||||
|     cg.add(var.set_exit_reparse_on_start_internal(config[CONF_EXIT_REPARSE_ON_START])) |     cg.add(var.set_exit_reparse_on_start(config[CONF_EXIT_REPARSE_ON_START])) | ||||||
|  |  | ||||||
|     cg.add(var.set_skip_connection_handshake(config[CONF_SKIP_CONNECTION_HANDSHAKE])) |     cg.add(var.set_skip_connection_handshake(config[CONF_SKIP_CONNECTION_HANDSHAKE])) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -40,7 +40,7 @@ bool Nextion::send_command_(const std::string &command) { | |||||||
| } | } | ||||||
|  |  | ||||||
| bool Nextion::check_connect_() { | bool Nextion::check_connect_() { | ||||||
|   if (this->get_is_connected_()) |   if (this->is_connected_) | ||||||
|     return true; |     return true; | ||||||
|  |  | ||||||
|   // Check if the handshake should be skipped for the Nextion connection |   // Check if the handshake should be skipped for the Nextion connection | ||||||
| @@ -280,14 +280,6 @@ void Nextion::loop() { | |||||||
|       this->goto_page(this->start_up_page_); |       this->goto_page(this->start_up_page_); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // This could probably be removed from the loop area, as those are redundant. |  | ||||||
|     this->set_auto_wake_on_touch(this->auto_wake_on_touch_); |  | ||||||
|     this->set_exit_reparse_on_start(this->exit_reparse_on_start_); |  | ||||||
|  |  | ||||||
|     if (this->touch_sleep_timeout_ != 0) { |  | ||||||
|       this->set_touch_sleep_timeout(this->touch_sleep_timeout_); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (this->wake_up_page_ != -1) { |     if (this->wake_up_page_ != -1) { | ||||||
|       this->set_wake_up_page(this->wake_up_page_); |       this->set_wake_up_page(this->wake_up_page_); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -856,76 +856,6 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe | |||||||
|    */ |    */ | ||||||
|   void set_backlight_brightness(float brightness); |   void set_backlight_brightness(float brightness); | ||||||
|  |  | ||||||
|   /** |  | ||||||
|    * Set the touch sleep timeout of the display. |  | ||||||
|    * @param timeout Timeout in seconds. |  | ||||||
|    * |  | ||||||
|    * Example: |  | ||||||
|    * ```cpp |  | ||||||
|    * it.set_touch_sleep_timeout(30); |  | ||||||
|    * ``` |  | ||||||
|    * |  | ||||||
|    * After 30 seconds the display will go to sleep. Note: the display will only wakeup by a restart or by setting up |  | ||||||
|    * `thup`. |  | ||||||
|    */ |  | ||||||
|   void set_touch_sleep_timeout(uint16_t timeout); |  | ||||||
|  |  | ||||||
|   /** |  | ||||||
|    * Sets which page Nextion loads when exiting sleep mode. Note this can be set even when Nextion is in sleep mode. |  | ||||||
|    * @param page_id The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to |  | ||||||
|    * wakes up to current page. |  | ||||||
|    * |  | ||||||
|    * Example: |  | ||||||
|    * ```cpp |  | ||||||
|    * it.set_wake_up_page(2); |  | ||||||
|    * ``` |  | ||||||
|    * |  | ||||||
|    * The display will wake up to page 2. |  | ||||||
|    */ |  | ||||||
|   void set_wake_up_page(uint8_t page_id = 255); |  | ||||||
|  |  | ||||||
|   /** |  | ||||||
|    * Sets which page Nextion loads when connecting to ESPHome. |  | ||||||
|    * @param page_id The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to |  | ||||||
|    * wakes up to current page. |  | ||||||
|    * |  | ||||||
|    * Example: |  | ||||||
|    * ```cpp |  | ||||||
|    * it.set_start_up_page(2); |  | ||||||
|    * ``` |  | ||||||
|    * |  | ||||||
|    * The display will go to page 2 when it establishes a connection to ESPHome. |  | ||||||
|    */ |  | ||||||
|   void set_start_up_page(uint8_t page_id = 255); |  | ||||||
|  |  | ||||||
|   /** |  | ||||||
|    * Sets if Nextion should auto-wake from sleep when touch press occurs. |  | ||||||
|    * @param auto_wake True or false. When auto_wake is true and Nextion is in sleep mode, |  | ||||||
|    * the first touch will only trigger the auto wake mode and not trigger a Touch Event. |  | ||||||
|    * |  | ||||||
|    * Example: |  | ||||||
|    * ```cpp |  | ||||||
|    * it.set_auto_wake_on_touch(true); |  | ||||||
|    * ``` |  | ||||||
|    * |  | ||||||
|    * The display will wake up by touch. |  | ||||||
|    */ |  | ||||||
|   void set_auto_wake_on_touch(bool auto_wake); |  | ||||||
|  |  | ||||||
|   /** |  | ||||||
|    * Sets if Nextion should exit the active reparse mode before the "connect" command is sent |  | ||||||
|    * @param exit_reparse True or false. When exit_reparse is true, the exit reparse command |  | ||||||
|    * will be sent before requesting the connection from Nextion. |  | ||||||
|    * |  | ||||||
|    * Example: |  | ||||||
|    * ```cpp |  | ||||||
|    * it.set_exit_reparse_on_start(true); |  | ||||||
|    * ``` |  | ||||||
|    * |  | ||||||
|    * The display will be requested to leave active reparse mode before setup. |  | ||||||
|    */ |  | ||||||
|   void set_exit_reparse_on_start(bool exit_reparse); |  | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * Sets whether the Nextion display should skip the connection handshake process. |    * Sets whether the Nextion display should skip the connection handshake process. | ||||||
|    * @param skip_handshake True or false. When skip_connection_handshake is true, |    * @param skip_handshake True or false. When skip_connection_handshake is true, | ||||||
| @@ -1172,15 +1102,75 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe | |||||||
|  |  | ||||||
|   void update_components_by_prefix(const std::string &prefix); |   void update_components_by_prefix(const std::string &prefix); | ||||||
|  |  | ||||||
|   void set_touch_sleep_timeout_internal(uint32_t touch_sleep_timeout) { |   /** | ||||||
|     this->touch_sleep_timeout_ = touch_sleep_timeout; |    * Set the touch sleep timeout of the display. | ||||||
|   } |    * @param timeout Timeout in seconds. | ||||||
|   void set_wake_up_page_internal(uint8_t wake_up_page) { this->wake_up_page_ = wake_up_page; } |    * | ||||||
|   void set_start_up_page_internal(uint8_t start_up_page) { this->start_up_page_ = start_up_page; } |    * Example: | ||||||
|   void set_auto_wake_on_touch_internal(bool auto_wake_on_touch) { this->auto_wake_on_touch_ = auto_wake_on_touch; } |    * ```cpp | ||||||
|   void set_exit_reparse_on_start_internal(bool exit_reparse_on_start) { |    * it.set_touch_sleep_timeout(30); | ||||||
|     this->exit_reparse_on_start_ = exit_reparse_on_start; |    * ``` | ||||||
|   } |    * | ||||||
|  |    * After 30 seconds the display will go to sleep. Note: the display will only wakeup by a restart or by setting up | ||||||
|  |    * `thup`. | ||||||
|  |    */ | ||||||
|  |   void set_touch_sleep_timeout(uint32_t touch_sleep_timeout); | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Sets which page Nextion loads when exiting sleep mode. Note this can be set even when Nextion is in sleep mode. | ||||||
|  |    * @param wake_up_page The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to | ||||||
|  |    * wakes up to current page. | ||||||
|  |    * | ||||||
|  |    * Example: | ||||||
|  |    * ```cpp | ||||||
|  |    * it.set_wake_up_page(2); | ||||||
|  |    * ``` | ||||||
|  |    * | ||||||
|  |    * The display will wake up to page 2. | ||||||
|  |    */ | ||||||
|  |   void set_wake_up_page(uint8_t wake_up_page = 255); | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Sets which page Nextion loads when connecting to ESPHome. | ||||||
|  |    * @param start_up_page The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to | ||||||
|  |    * wakes up to current page. | ||||||
|  |    * | ||||||
|  |    * Example: | ||||||
|  |    * ```cpp | ||||||
|  |    * it.set_start_up_page(2); | ||||||
|  |    * ``` | ||||||
|  |    * | ||||||
|  |    * The display will go to page 2 when it establishes a connection to ESPHome. | ||||||
|  |    */ | ||||||
|  |   void set_start_up_page(uint8_t start_up_page = 255) { this->start_up_page_ = start_up_page; } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Sets if Nextion should auto-wake from sleep when touch press occurs. | ||||||
|  |    * @param auto_wake_on_touch True or false. When auto_wake is true and Nextion is in sleep mode, | ||||||
|  |    * the first touch will only trigger the auto wake mode and not trigger a Touch Event. | ||||||
|  |    * | ||||||
|  |    * Example: | ||||||
|  |    * ```cpp | ||||||
|  |    * it.set_auto_wake_on_touch(true); | ||||||
|  |    * ``` | ||||||
|  |    * | ||||||
|  |    * The display will wake up by touch. | ||||||
|  |    */ | ||||||
|  |   void set_auto_wake_on_touch(bool auto_wake_on_touch); | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Sets if Nextion should exit the active reparse mode before the "connect" command is sent | ||||||
|  |    * @param exit_reparse_on_start True or false. When exit_reparse_on_start is true, the exit reparse command | ||||||
|  |    * will be sent before requesting the connection from Nextion. | ||||||
|  |    * | ||||||
|  |    * Example: | ||||||
|  |    * ```cpp | ||||||
|  |    * it.set_exit_reparse_on_start(true); | ||||||
|  |    * ``` | ||||||
|  |    * | ||||||
|  |    * The display will be requested to leave active reparse mode before setup. | ||||||
|  |    */ | ||||||
|  |   void set_exit_reparse_on_start(bool exit_reparse_on_start) { this->exit_reparse_on_start_ = exit_reparse_on_start; } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * @brief Retrieves the number of commands pending in the Nextion command queue. |    * @brief Retrieves the number of commands pending in the Nextion command queue. | ||||||
| @@ -1217,6 +1207,25 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe | |||||||
|    */ |    */ | ||||||
|   bool is_updating() override; |   bool is_updating() override; | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * @brief Check if the Nextion display is successfully connected. | ||||||
|  |    * | ||||||
|  |    * This method returns whether a successful connection has been established with | ||||||
|  |    * the Nextion display. A connection is considered established when: | ||||||
|  |    * | ||||||
|  |    * - The initial handshake with the display is completed successfully, or | ||||||
|  |    * - The handshake is skipped via skip_connection_handshake_ flag | ||||||
|  |    * | ||||||
|  |    * The connection status is particularly useful when: | ||||||
|  |    * - Troubleshooting communication issues | ||||||
|  |    * - Ensuring the display is ready before sending commands | ||||||
|  |    * - Implementing connection-dependent behaviors | ||||||
|  |    * | ||||||
|  |    * @return true if the Nextion display is connected and ready to receive commands | ||||||
|  |    * @return false if the display is not yet connected or connection was lost | ||||||
|  |    */ | ||||||
|  |   bool is_connected() { return this->is_connected_; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   std::deque<NextionQueue *> nextion_queue_; |   std::deque<NextionQueue *> nextion_queue_; | ||||||
|   std::deque<NextionQueue *> waveform_queue_; |   std::deque<NextionQueue *> waveform_queue_; | ||||||
| @@ -1315,8 +1324,6 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe | |||||||
|  |  | ||||||
| #endif  // USE_NEXTION_TFT_UPLOAD | #endif  // USE_NEXTION_TFT_UPLOAD | ||||||
|  |  | ||||||
|   bool get_is_connected_() { return this->is_connected_; } |  | ||||||
|  |  | ||||||
|   bool check_connect_(); |   bool check_connect_(); | ||||||
|  |  | ||||||
|   std::vector<NextionComponentBase *> touch_; |   std::vector<NextionComponentBase *> touch_; | ||||||
|   | |||||||
| @@ -10,19 +10,19 @@ static const char *const TAG = "nextion"; | |||||||
| // Sleep safe commands | // Sleep safe commands | ||||||
| void Nextion::soft_reset() { this->send_command_("rest"); } | void Nextion::soft_reset() { this->send_command_("rest"); } | ||||||
|  |  | ||||||
| void Nextion::set_wake_up_page(uint8_t page_id) { | void Nextion::set_wake_up_page(uint8_t wake_up_page) { | ||||||
|   this->add_no_result_to_queue_with_set_internal_("wake_up_page", "wup", page_id, true); |   this->wake_up_page_ = wake_up_page; | ||||||
|  |   this->add_no_result_to_queue_with_set_internal_("wake_up_page", "wup", wake_up_page, true); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Nextion::set_start_up_page(uint8_t page_id) { this->start_up_page_ = page_id; } | void Nextion::set_touch_sleep_timeout(uint32_t touch_sleep_timeout) { | ||||||
|  |   if (touch_sleep_timeout < 3) { | ||||||
| void Nextion::set_touch_sleep_timeout(uint16_t timeout) { |  | ||||||
|   if (timeout < 3) { |  | ||||||
|     ESP_LOGD(TAG, "Sleep timeout out of bounds, range 3-65535"); |     ESP_LOGD(TAG, "Sleep timeout out of bounds, range 3-65535"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   this->add_no_result_to_queue_with_set_internal_("touch_sleep_timeout", "thsp", timeout, true); |   this->touch_sleep_timeout_ = touch_sleep_timeout; | ||||||
|  |   this->add_no_result_to_queue_with_set_internal_("touch_sleep_timeout", "thsp", touch_sleep_timeout, true); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Nextion::sleep(bool sleep) { | void Nextion::sleep(bool sleep) { | ||||||
| @@ -54,7 +54,6 @@ bool Nextion::set_protocol_reparse_mode(bool active_mode) { | |||||||
|   this->ignore_is_setup_ = false; |   this->ignore_is_setup_ = false; | ||||||
|   return all_commands_sent; |   return all_commands_sent; | ||||||
| } | } | ||||||
| void Nextion::set_exit_reparse_on_start(bool exit_reparse) { this->exit_reparse_on_start_ = exit_reparse; } |  | ||||||
|  |  | ||||||
| // Set Colors - Background | // Set Colors - Background | ||||||
| void Nextion::set_component_background_color(const char *component, uint16_t color) { | void Nextion::set_component_background_color(const char *component, uint16_t color) { | ||||||
| @@ -191,8 +190,9 @@ void Nextion::set_backlight_brightness(float brightness) { | |||||||
|   this->add_no_result_to_queue_with_printf_("backlight_brightness", "dim=%d", static_cast<int>(brightness * 100)); |   this->add_no_result_to_queue_with_printf_("backlight_brightness", "dim=%d", static_cast<int>(brightness * 100)); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Nextion::set_auto_wake_on_touch(bool auto_wake) { | void Nextion::set_auto_wake_on_touch(bool auto_wake_on_touch) { | ||||||
|   this->add_no_result_to_queue_with_set("auto_wake_on_touch", "thup", auto_wake ? 1 : 0); |   this->auto_wake_on_touch_ = auto_wake_on_touch; | ||||||
|  |   this->add_no_result_to_queue_with_set("auto_wake_on_touch", "thup", auto_wake_on_touch ? 1 : 0); | ||||||
| } | } | ||||||
|  |  | ||||||
| // General Component | // General Component | ||||||
|   | |||||||
| @@ -80,15 +80,7 @@ bool OnlineImage::resize_(int width_in, int height_in) { | |||||||
|     this->width_ = width; |     this->width_ = width; | ||||||
|     ESP_LOGD(TAG, "New size: (%d, %d)", width, height); |     ESP_LOGD(TAG, "New size: (%d, %d)", width, height); | ||||||
|   } else { |   } else { | ||||||
| #if defined(USE_ESP8266) |     ESP_LOGE(TAG, "allocation failed. Biggest block in heap: %zu Bytes", this->allocator_.get_max_free_block_size()); | ||||||
|     // NOLINTNEXTLINE(readability-static-accessed-through-instance) |  | ||||||
|     int max_block = ESP.getMaxFreeBlockSize(); |  | ||||||
| #elif defined(USE_ESP32) |  | ||||||
|     int max_block = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL); |  | ||||||
| #else |  | ||||||
|     int max_block = -1; |  | ||||||
| #endif |  | ||||||
|     ESP_LOGE(TAG, "allocation failed. Biggest block in heap: %d Bytes", max_block); |  | ||||||
|     this->end_connection_(); |     this->end_connection_(); | ||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -1,10 +1,12 @@ | |||||||
| from typing import Any | from typing import Any | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  | from esphome import automation | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome import pins | from esphome import pins | ||||||
| from esphome.components import sensor | from esphome.components import sensor | ||||||
| from esphome.const import CONF_ID, PLATFORM_ESP32, PLATFORM_ESP8266 | from esphome.const import CONF_ID, PLATFORM_ESP32, PLATFORM_ESP8266, CONF_TRIGGER_ID | ||||||
| from . import const, schema, validate, generate | from . import const, schema, validate, generate | ||||||
|  |  | ||||||
| CODEOWNERS = ["@olegtarasov"] | CODEOWNERS = ["@olegtarasov"] | ||||||
| @@ -20,7 +22,21 @@ CONF_CH2_ACTIVE = "ch2_active" | |||||||
| CONF_SUMMER_MODE_ACTIVE = "summer_mode_active" | CONF_SUMMER_MODE_ACTIVE = "summer_mode_active" | ||||||
| CONF_DHW_BLOCK = "dhw_block" | CONF_DHW_BLOCK = "dhw_block" | ||||||
| CONF_SYNC_MODE = "sync_mode" | CONF_SYNC_MODE = "sync_mode" | ||||||
| CONF_OPENTHERM_VERSION = "opentherm_version" | CONF_OPENTHERM_VERSION = "opentherm_version"  # Deprecated, will be removed | ||||||
|  | CONF_BEFORE_SEND = "before_send" | ||||||
|  | CONF_BEFORE_PROCESS_RESPONSE = "before_process_response" | ||||||
|  |  | ||||||
|  | # Triggers | ||||||
|  | BeforeSendTrigger = generate.opentherm_ns.class_( | ||||||
|  |     "BeforeSendTrigger", | ||||||
|  |     automation.Trigger.template(generate.OpenthermData.operator("ref")), | ||||||
|  | ) | ||||||
|  | BeforeProcessResponseTrigger = generate.opentherm_ns.class_( | ||||||
|  |     "BeforeProcessResponseTrigger", | ||||||
|  |     automation.Trigger.template(generate.OpenthermData.operator("ref")), | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | _LOGGER = logging.getLogger(__name__) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All( | CONFIG_SCHEMA = cv.All( | ||||||
|     cv.Schema( |     cv.Schema( | ||||||
| @@ -36,7 +52,19 @@ CONFIG_SCHEMA = cv.All( | |||||||
|             cv.Optional(CONF_SUMMER_MODE_ACTIVE, False): cv.boolean, |             cv.Optional(CONF_SUMMER_MODE_ACTIVE, False): cv.boolean, | ||||||
|             cv.Optional(CONF_DHW_BLOCK, False): cv.boolean, |             cv.Optional(CONF_DHW_BLOCK, False): cv.boolean, | ||||||
|             cv.Optional(CONF_SYNC_MODE, False): cv.boolean, |             cv.Optional(CONF_SYNC_MODE, False): cv.boolean, | ||||||
|             cv.Optional(CONF_OPENTHERM_VERSION): cv.positive_float, |             cv.Optional(CONF_OPENTHERM_VERSION): cv.positive_float,  # Deprecated | ||||||
|  |             cv.Optional(CONF_BEFORE_SEND): automation.validate_automation( | ||||||
|  |                 { | ||||||
|  |                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BeforeSendTrigger), | ||||||
|  |                 } | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_BEFORE_PROCESS_RESPONSE): automation.validate_automation( | ||||||
|  |                 { | ||||||
|  |                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( | ||||||
|  |                         BeforeProcessResponseTrigger | ||||||
|  |                     ), | ||||||
|  |                 } | ||||||
|  |             ), | ||||||
|         } |         } | ||||||
|     ) |     ) | ||||||
|     .extend( |     .extend( | ||||||
| @@ -44,6 +72,11 @@ CONFIG_SCHEMA = cv.All( | |||||||
|             schema.INPUTS, (lambda _: cv.use_id(sensor.Sensor)) |             schema.INPUTS, (lambda _: cv.use_id(sensor.Sensor)) | ||||||
|         ) |         ) | ||||||
|     ) |     ) | ||||||
|  |     .extend( | ||||||
|  |         validate.create_entities_schema( | ||||||
|  |             schema.SETTINGS, (lambda s: s.validation_schema) | ||||||
|  |         ) | ||||||
|  |     ) | ||||||
|     .extend(cv.COMPONENT_SCHEMA), |     .extend(cv.COMPONENT_SCHEMA), | ||||||
|     cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266]), |     cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266]), | ||||||
| ) | ) | ||||||
| @@ -60,18 +93,33 @@ async def to_code(config: dict[str, Any]) -> None: | |||||||
|     out_pin = await cg.gpio_pin_expression(config[CONF_OUT_PIN]) |     out_pin = await cg.gpio_pin_expression(config[CONF_OUT_PIN]) | ||||||
|     cg.add(var.set_out_pin(out_pin)) |     cg.add(var.set_out_pin(out_pin)) | ||||||
|  |  | ||||||
|     non_sensors = {CONF_ID, CONF_IN_PIN, CONF_OUT_PIN} |     non_sensors = { | ||||||
|  |         CONF_ID, | ||||||
|  |         CONF_IN_PIN, | ||||||
|  |         CONF_OUT_PIN, | ||||||
|  |         CONF_BEFORE_SEND, | ||||||
|  |         CONF_BEFORE_PROCESS_RESPONSE, | ||||||
|  |     } | ||||||
|     input_sensors = [] |     input_sensors = [] | ||||||
|  |     settings = [] | ||||||
|     for key, value in config.items(): |     for key, value in config.items(): | ||||||
|         if key in non_sensors: |         if key in non_sensors: | ||||||
|             continue |             continue | ||||||
|         if key in schema.INPUTS: |         if key in schema.INPUTS: | ||||||
|             input_sensor = await cg.get_variable(value) |             input_sensor = await cg.get_variable(value) | ||||||
|             cg.add( |             cg.add(getattr(var, f"set_{key}_{const.INPUT_SENSOR}")(input_sensor)) | ||||||
|                 getattr(var, f"set_{key}_{const.INPUT_SENSOR.lower()}")(input_sensor) |  | ||||||
|             ) |  | ||||||
|             input_sensors.append(key) |             input_sensors.append(key) | ||||||
|  |         elif key in schema.SETTINGS: | ||||||
|  |             if value == schema.SETTINGS[key].default_value: | ||||||
|  |                 continue | ||||||
|  |             cg.add(getattr(var, f"set_{key}_{const.SETTING}")(value)) | ||||||
|  |             settings.append(key) | ||||||
|         else: |         else: | ||||||
|  |             if key == CONF_OPENTHERM_VERSION: | ||||||
|  |                 _LOGGER.warning( | ||||||
|  |                     "opentherm_version is deprecated and will be removed in esphome 2025.2.0\n" | ||||||
|  |                     "Please change to 'opentherm_version_controller'." | ||||||
|  |                 ) | ||||||
|             cg.add(getattr(var, f"set_{key}")(value)) |             cg.add(getattr(var, f"set_{key}")(value)) | ||||||
|  |  | ||||||
|     if len(input_sensors) > 0: |     if len(input_sensors) > 0: | ||||||
| @@ -81,3 +129,21 @@ async def to_code(config: dict[str, Any]) -> None: | |||||||
|         ) |         ) | ||||||
|         generate.define_readers(const.INPUT_SENSOR, input_sensors) |         generate.define_readers(const.INPUT_SENSOR, input_sensors) | ||||||
|         generate.add_messages(var, input_sensors, schema.INPUTS) |         generate.add_messages(var, input_sensors, schema.INPUTS) | ||||||
|  |  | ||||||
|  |     if len(settings) > 0: | ||||||
|  |         generate.define_has_settings(settings, schema.SETTINGS) | ||||||
|  |         generate.define_message_handler(const.SETTING, settings, schema.SETTINGS) | ||||||
|  |         generate.define_setting_readers(const.SETTING, settings) | ||||||
|  |         generate.add_messages(var, settings, schema.SETTINGS) | ||||||
|  |  | ||||||
|  |     for conf in config.get(CONF_BEFORE_SEND, []): | ||||||
|  |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|  |         await automation.build_automation( | ||||||
|  |             trigger, [(generate.OpenthermData.operator("ref"), "x")], conf | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     for conf in config.get(CONF_BEFORE_PROCESS_RESPONSE, []): | ||||||
|  |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|  |         await automation.build_automation( | ||||||
|  |             trigger, [(generate.OpenthermData.operator("ref"), "x")], conf | ||||||
|  |         ) | ||||||
|   | |||||||
							
								
								
									
										25
									
								
								esphome/components/opentherm/automation.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								esphome/components/opentherm/automation.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/automation.h" | ||||||
|  | #include "hub.h" | ||||||
|  | #include "opentherm.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace opentherm { | ||||||
|  |  | ||||||
|  | class BeforeSendTrigger : public Trigger<OpenthermData &> { | ||||||
|  |  public: | ||||||
|  |   BeforeSendTrigger(OpenthermHub *hub) { | ||||||
|  |     hub->add_on_before_send_callback([this](OpenthermData &x) { this->trigger(x); }); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class BeforeProcessResponseTrigger : public Trigger<OpenthermData &> { | ||||||
|  |  public: | ||||||
|  |   BeforeProcessResponseTrigger(OpenthermHub *hub) { | ||||||
|  |     hub->add_on_before_process_response_callback([this](OpenthermData &x) { this->trigger(x); }); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace opentherm | ||||||
|  | }  // namespace esphome | ||||||
| @@ -9,3 +9,4 @@ SWITCH = "switch" | |||||||
| NUMBER = "number" | NUMBER = "number" | ||||||
| OUTPUT = "output" | OUTPUT = "output" | ||||||
| INPUT_SENSOR = "input_sensor" | INPUT_SENSOR = "input_sensor" | ||||||
|  | SETTING = "setting" | ||||||
|   | |||||||
| @@ -1,13 +1,14 @@ | |||||||
| from collections.abc import Awaitable | from collections.abc import Awaitable | ||||||
| from typing import Any, Callable | from typing import Any, Callable, Optional | ||||||
|  |  | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| from esphome.const import CONF_ID | from esphome.const import CONF_ID | ||||||
| from . import const | from . import const | ||||||
| from .schema import TSchema | from .schema import TSchema, SettingSchema | ||||||
|  |  | ||||||
| opentherm_ns = cg.esphome_ns.namespace("opentherm") | opentherm_ns = cg.esphome_ns.namespace("opentherm") | ||||||
| OpenthermHub = opentherm_ns.class_("OpenthermHub", cg.Component) | OpenthermHub = opentherm_ns.class_("OpenthermHub", cg.Component) | ||||||
|  | OpenthermData = opentherm_ns.class_("OpenthermData") | ||||||
|  |  | ||||||
|  |  | ||||||
| def define_has_component(component_type: str, keys: list[str]) -> None: | def define_has_component(component_type: str, keys: list[str]) -> None: | ||||||
| @@ -21,6 +22,24 @@ def define_has_component(component_type: str, keys: list[str]) -> None: | |||||||
|         cg.add_define(f"OPENTHERM_HAS_{component_type.upper()}_{key}") |         cg.add_define(f"OPENTHERM_HAS_{component_type.upper()}_{key}") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # We need a separate set of macros for settings because there are different backing field types we need to take | ||||||
|  | # into account | ||||||
|  | def define_has_settings(keys: list[str], schemas: dict[str, SettingSchema]) -> None: | ||||||
|  |     cg.add_define( | ||||||
|  |         "OPENTHERM_SETTING_LIST(F, sep)", | ||||||
|  |         cg.RawExpression( | ||||||
|  |             " sep ".join( | ||||||
|  |                 map( | ||||||
|  |                     lambda key: f"F({schemas[key].backing_type}, {key}_setting, {schemas[key].default_value})", | ||||||
|  |                     keys, | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  |         ), | ||||||
|  |     ) | ||||||
|  |     for key in keys: | ||||||
|  |         cg.add_define(f"OPENTHERM_HAS_SETTING_{key}") | ||||||
|  |  | ||||||
|  |  | ||||||
| def define_message_handler( | def define_message_handler( | ||||||
|     component_type: str, keys: list[str], schemas: dict[str, TSchema] |     component_type: str, keys: list[str], schemas: dict[str, TSchema] | ||||||
| ) -> None: | ) -> None: | ||||||
| @@ -74,14 +93,28 @@ def define_readers(component_type: str, keys: list[str]) -> None: | |||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def add_messages(hub: cg.MockObj, keys: list[str], schemas: dict[str, TSchema]): | def define_setting_readers(component_type: str, keys: list[str]) -> None: | ||||||
|     messages: set[tuple[str, bool]] = set() |  | ||||||
|     for key in keys: |     for key in keys: | ||||||
|         messages.add((schemas[key].message, schemas[key].keep_updated)) |         cg.add_define( | ||||||
|     for msg, keep_updated in messages: |             f"OPENTHERM_READ_{key}", | ||||||
|  |             cg.RawExpression(f"this->{key}_{component_type.lower()}"), | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def add_messages(hub: cg.MockObj, keys: list[str], schemas: dict[str, TSchema]): | ||||||
|  |     messages: dict[str, tuple[bool, Optional[int]]] = {} | ||||||
|  |     for key in keys: | ||||||
|  |         messages[schemas[key].message] = ( | ||||||
|  |             schemas[key].keep_updated, | ||||||
|  |             schemas[key].order if hasattr(schemas[key], "order") else None, | ||||||
|  |         ) | ||||||
|  |     for msg, (keep_updated, order) in messages.items(): | ||||||
|         msg_expr = cg.RawExpression(f"esphome::opentherm::MessageId::{msg}") |         msg_expr = cg.RawExpression(f"esphome::opentherm::MessageId::{msg}") | ||||||
|         if keep_updated: |         if keep_updated: | ||||||
|             cg.add(hub.add_repeating_message(msg_expr)) |             cg.add(hub.add_repeating_message(msg_expr)) | ||||||
|  |         else: | ||||||
|  |             if order is not None: | ||||||
|  |                 cg.add(hub.add_initial_message(msg_expr, order)) | ||||||
|             else: |             else: | ||||||
|                 cg.add(hub.add_initial_message(msg_expr)) |                 cg.add(hub.add_initial_message(msg_expr)) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -63,7 +63,7 @@ void write_f88(const float value, OpenthermData &data) { data.f88(value); } | |||||||
| OpenthermData OpenthermHub::build_request_(MessageId request_id) const { | OpenthermData OpenthermHub::build_request_(MessageId request_id) const { | ||||||
|   OpenthermData data; |   OpenthermData data; | ||||||
|   data.type = 0; |   data.type = 0; | ||||||
|   data.id = 0; |   data.id = request_id; | ||||||
|   data.valueHB = 0; |   data.valueHB = 0; | ||||||
|   data.valueLB = 0; |   data.valueLB = 0; | ||||||
|  |  | ||||||
| @@ -82,28 +82,13 @@ OpenthermData OpenthermHub::build_request_(MessageId request_id) const { | |||||||
|     // NOLINTEND |     // NOLINTEND | ||||||
|  |  | ||||||
|     data.type = MessageType::READ_DATA; |     data.type = MessageType::READ_DATA; | ||||||
|     data.id = MessageId::STATUS; |  | ||||||
|     data.valueHB = ch_enabled | (dhw_enabled << 1) | (cooling_enabled << 2) | (otc_enabled << 3) | (ch2_enabled << 4) | |     data.valueHB = ch_enabled | (dhw_enabled << 1) | (cooling_enabled << 2) | (otc_enabled << 3) | (ch2_enabled << 4) | | ||||||
|                    (summer_mode_is_active << 5) | (dhw_blocked << 6); |                    (summer_mode_is_active << 5) | (dhw_blocked << 6); | ||||||
|  |  | ||||||
|     return data; |     return data; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Another special case is OpenTherm version number which is configured at hub level as a constant |   // Next, we start with write requests from switches and other inputs, | ||||||
|   if (request_id == MessageId::OT_VERSION_CONTROLLER) { |  | ||||||
|     data.type = MessageType::WRITE_DATA; |  | ||||||
|     data.id = MessageId::OT_VERSION_CONTROLLER; |  | ||||||
|     data.f88(this->opentherm_version_); |  | ||||||
|  |  | ||||||
|     return data; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
| // Disable incomplete switch statement warnings, because the cases in each |  | ||||||
| // switch are generated based on the configured sensors and inputs. |  | ||||||
| #pragma GCC diagnostic push |  | ||||||
| #pragma GCC diagnostic ignored "-Wswitch" |  | ||||||
|  |  | ||||||
|   // Next, we start with the write requests from switches and other inputs, |  | ||||||
|   // because we would want to write that data if it is available, rather than |   // because we would want to write that data if it is available, rather than | ||||||
|   // request a read for that type (in the case that both read and write are |   // request a read for that type (in the case that both read and write are | ||||||
|   // supported). |   // supported). | ||||||
| @@ -116,14 +101,23 @@ OpenthermData OpenthermHub::build_request_(MessageId request_id) const { | |||||||
|                                       OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, ) |                                       OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, ) | ||||||
|     OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_WRITE_MESSAGE, OPENTHERM_MESSAGE_WRITE_ENTITY, , |     OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_WRITE_MESSAGE, OPENTHERM_MESSAGE_WRITE_ENTITY, , | ||||||
|                                             OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, ) |                                             OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, ) | ||||||
|  |     OPENTHERM_SETTING_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_WRITE_MESSAGE, OPENTHERM_MESSAGE_WRITE_SETTING, , | ||||||
|  |                                        OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, ) | ||||||
|  |     default: | ||||||
|  |       break; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Finally, handle the simple read requests, which only change with the message id. |   // Finally, handle the simple read requests, which only change with the message id. | ||||||
|   switch (request_id) { OPENTHERM_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_READ_MESSAGE, OPENTHERM_IGNORE, , , ) } |   switch (request_id) { | ||||||
|  |     OPENTHERM_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_READ_MESSAGE, OPENTHERM_IGNORE, , , ) | ||||||
|  |     default: | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|   switch (request_id) { |   switch (request_id) { | ||||||
|     OPENTHERM_BINARY_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_READ_MESSAGE, OPENTHERM_IGNORE, , , ) |     OPENTHERM_BINARY_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_READ_MESSAGE, OPENTHERM_IGNORE, , , ) | ||||||
|  |     default: | ||||||
|  |       break; | ||||||
|   } |   } | ||||||
| #pragma GCC diagnostic pop |  | ||||||
|  |  | ||||||
|   // And if we get here, a message was requested which somehow wasn't handled. |   // And if we get here, a message was requested which somehow wasn't handled. | ||||||
|   // This shouldn't happen due to the way the defines are configured, so we |   // This shouldn't happen due to the way the defines are configured, so we | ||||||
| @@ -163,19 +157,37 @@ void OpenthermHub::setup() { | |||||||
|   // communicate at least once every second. Sending the status request is |   // communicate at least once every second. Sending the status request is | ||||||
|   // good practice anyway. |   // good practice anyway. | ||||||
|   this->add_repeating_message(MessageId::STATUS); |   this->add_repeating_message(MessageId::STATUS); | ||||||
|  |   this->write_initial_messages_(this->messages_); | ||||||
|   // Also ensure that we start communication with the STATUS message |   this->message_iterator_ = this->messages_.begin(); | ||||||
|   this->initial_messages_.insert(this->initial_messages_.begin(), MessageId::STATUS); |  | ||||||
|  |  | ||||||
|   if (this->opentherm_version_ > 0.0f) { |  | ||||||
|     this->initial_messages_.insert(this->initial_messages_.begin(), MessageId::OT_VERSION_CONTROLLER); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   this->current_message_iterator_ = this->initial_messages_.begin(); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void OpenthermHub::on_shutdown() { this->opentherm_->stop(); } | void OpenthermHub::on_shutdown() { this->opentherm_->stop(); } | ||||||
|  |  | ||||||
|  | // Disabling clang-tidy for this particular line since it keeps removing the trailing underscore (bug?) | ||||||
|  | void OpenthermHub::write_initial_messages_(std::vector<MessageId> &target) {  // NOLINT | ||||||
|  |   std::vector<std::pair<MessageId, uint8_t>> sorted; | ||||||
|  |   std::copy_if(this->configured_messages_.begin(), this->configured_messages_.end(), std::back_inserter(sorted), | ||||||
|  |                [](const std::pair<MessageId, uint8_t> &pair) { return pair.second < REPEATING_MESSAGE_ORDER; }); | ||||||
|  |   std::sort(sorted.begin(), sorted.end(), | ||||||
|  |             [](const std::pair<MessageId, uint8_t> &a, const std::pair<MessageId, uint8_t> &b) { | ||||||
|  |               return a.second < b.second; | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |   target.clear(); | ||||||
|  |   std::transform(sorted.begin(), sorted.end(), std::back_inserter(target), | ||||||
|  |                  [](const std::pair<MessageId, uint8_t> &pair) { return pair.first; }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Disabling clang-tidy for this particular line since it keeps removing the trailing underscore (bug?) | ||||||
|  | void OpenthermHub::write_repeating_messages_(std::vector<MessageId> &target) {  // NOLINT | ||||||
|  |   target.clear(); | ||||||
|  |   for (auto const &pair : this->configured_messages_) { | ||||||
|  |     if (pair.second == REPEATING_MESSAGE_ORDER) { | ||||||
|  |       target.push_back(pair.first); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| void OpenthermHub::loop() { | void OpenthermHub::loop() { | ||||||
|   if (this->sync_mode_) { |   if (this->sync_mode_) { | ||||||
|     this->sync_loop_(); |     this->sync_loop_(); | ||||||
| @@ -184,29 +196,18 @@ void OpenthermHub::loop() { | |||||||
|  |  | ||||||
|   auto cur_time = millis(); |   auto cur_time = millis(); | ||||||
|   auto const cur_mode = this->opentherm_->get_mode(); |   auto const cur_mode = this->opentherm_->get_mode(); | ||||||
|  |  | ||||||
|  |   if (this->handle_error_(cur_mode)) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   switch (cur_mode) { |   switch (cur_mode) { | ||||||
|     case OperationMode::WRITE: |     case OperationMode::WRITE: | ||||||
|     case OperationMode::READ: |     case OperationMode::READ: | ||||||
|     case OperationMode::LISTEN: |     case OperationMode::LISTEN: | ||||||
|       if (!this->check_timings_(cur_time)) { |  | ||||||
|         break; |  | ||||||
|       } |  | ||||||
|       this->last_mode_ = cur_mode; |  | ||||||
|       break; |  | ||||||
|     case OperationMode::ERROR_PROTOCOL: |  | ||||||
|       if (this->last_mode_ == OperationMode::WRITE) { |  | ||||||
|         this->handle_protocol_write_error_(); |  | ||||||
|       } else if (this->last_mode_ == OperationMode::READ) { |  | ||||||
|         this->handle_protocol_read_error_(); |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       this->stop_opentherm_(); |  | ||||||
|       break; |  | ||||||
|     case OperationMode::ERROR_TIMEOUT: |  | ||||||
|       this->handle_timeout_error_(); |  | ||||||
|       this->stop_opentherm_(); |  | ||||||
|       break; |       break; | ||||||
|     case OperationMode::IDLE: |     case OperationMode::IDLE: | ||||||
|  |       this->check_timings_(cur_time); | ||||||
|       if (this->should_skip_loop_(cur_time)) { |       if (this->should_skip_loop_(cur_time)) { | ||||||
|         break; |         break; | ||||||
|       } |       } | ||||||
| @@ -219,6 +220,28 @@ void OpenthermHub::loop() { | |||||||
|     case OperationMode::RECEIVED: |     case OperationMode::RECEIVED: | ||||||
|       this->read_response_(); |       this->read_response_(); | ||||||
|       break; |       break; | ||||||
|  |     default: | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  |   this->last_mode_ = cur_mode; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool OpenthermHub::handle_error_(OperationMode mode) { | ||||||
|  |   switch (mode) { | ||||||
|  |     case OperationMode::ERROR_PROTOCOL: | ||||||
|  |       // Protocol error can happen only while reading boiler response. | ||||||
|  |       this->handle_protocol_error_(); | ||||||
|  |       return true; | ||||||
|  |     case OperationMode::ERROR_TIMEOUT: | ||||||
|  |       // Timeout error might happen while we wait for device to respond. | ||||||
|  |       this->handle_timeout_error_(); | ||||||
|  |       return true; | ||||||
|  |     case OperationMode::ERROR_TIMER: | ||||||
|  |       // Timer error can happen only on ESP32. | ||||||
|  |       this->handle_timer_error_(); | ||||||
|  |       return true; | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -237,16 +260,20 @@ void OpenthermHub::sync_loop_() { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   this->start_conversation_(); |   this->start_conversation_(); | ||||||
|  |   // There may be a timer error at this point | ||||||
|  |   if (this->handle_error_(this->opentherm_->get_mode())) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Spin while message is being sent to device | ||||||
|   if (!this->spin_wait_(1150, [&] { return this->opentherm_->is_active(); })) { |   if (!this->spin_wait_(1150, [&] { return this->opentherm_->is_active(); })) { | ||||||
|     ESP_LOGE(TAG, "Hub timeout triggered during send"); |     ESP_LOGE(TAG, "Hub timeout triggered during send"); | ||||||
|     this->stop_opentherm_(); |     this->stop_opentherm_(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (this->opentherm_->is_error()) { |   // Check for errors and ensure we are in the right state (message sent successfully) | ||||||
|     this->handle_protocol_write_error_(); |   if (this->handle_error_(this->opentherm_->get_mode())) { | ||||||
|     this->stop_opentherm_(); |  | ||||||
|     return; |     return; | ||||||
|   } else if (!this->opentherm_->is_sent()) { |   } else if (!this->opentherm_->is_sent()) { | ||||||
|     ESP_LOGW(TAG, "Unexpected state after sending request: %s", |     ESP_LOGW(TAG, "Unexpected state after sending request: %s", | ||||||
| @@ -257,19 +284,20 @@ void OpenthermHub::sync_loop_() { | |||||||
|  |  | ||||||
|   // Listen for the response |   // Listen for the response | ||||||
|   this->opentherm_->listen(); |   this->opentherm_->listen(); | ||||||
|  |   // There may be a timer error at this point | ||||||
|  |   if (this->handle_error_(this->opentherm_->get_mode())) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Spin while response is being received | ||||||
|   if (!this->spin_wait_(1150, [&] { return this->opentherm_->is_active(); })) { |   if (!this->spin_wait_(1150, [&] { return this->opentherm_->is_active(); })) { | ||||||
|     ESP_LOGE(TAG, "Hub timeout triggered during receive"); |     ESP_LOGE(TAG, "Hub timeout triggered during receive"); | ||||||
|     this->stop_opentherm_(); |     this->stop_opentherm_(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (this->opentherm_->is_timeout()) { |   // Check for errors and ensure we are in the right state (message received successfully) | ||||||
|     this->handle_timeout_error_(); |   if (this->handle_error_(this->opentherm_->get_mode())) { | ||||||
|     this->stop_opentherm_(); |  | ||||||
|     return; |  | ||||||
|   } else if (this->opentherm_->is_protocol_error()) { |  | ||||||
|     this->handle_protocol_read_error_(); |  | ||||||
|     this->stop_opentherm_(); |  | ||||||
|     return; |     return; | ||||||
|   } else if (!this->opentherm_->has_message()) { |   } else if (!this->opentherm_->has_message()) { | ||||||
|     ESP_LOGW(TAG, "Unexpected state after receiving response: %s", |     ESP_LOGW(TAG, "Unexpected state after receiving response: %s", | ||||||
| @@ -281,17 +309,13 @@ void OpenthermHub::sync_loop_() { | |||||||
|   this->read_response_(); |   this->read_response_(); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool OpenthermHub::check_timings_(uint32_t cur_time) { | void OpenthermHub::check_timings_(uint32_t cur_time) { | ||||||
|   if (this->last_conversation_start_ > 0 && (cur_time - this->last_conversation_start_) > 1150) { |   if (this->last_conversation_start_ > 0 && (cur_time - this->last_conversation_start_) > 1150) { | ||||||
|     ESP_LOGW(TAG, |     ESP_LOGW(TAG, | ||||||
|              "%d ms elapsed since the start of the last convo, but 1150 ms are allowed at maximum. Look at other " |              "%d ms elapsed since the start of the last convo, but 1150 ms are allowed at maximum. Look at other " | ||||||
|              "components that might slow the loop down.", |              "components that might slow the loop down.", | ||||||
|              (int) (cur_time - this->last_conversation_start_)); |              (int) (cur_time - this->last_conversation_start_)); | ||||||
|     this->stop_opentherm_(); |  | ||||||
|     return false; |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return true; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| bool OpenthermHub::should_skip_loop_(uint32_t cur_time) const { | bool OpenthermHub::should_skip_loop_(uint32_t cur_time) const { | ||||||
| @@ -304,14 +328,17 @@ bool OpenthermHub::should_skip_loop_(uint32_t cur_time) const { | |||||||
| } | } | ||||||
|  |  | ||||||
| void OpenthermHub::start_conversation_() { | void OpenthermHub::start_conversation_() { | ||||||
|   if (this->sending_initial_ && this->current_message_iterator_ == this->initial_messages_.end()) { |   if (this->message_iterator_ == this->messages_.end()) { | ||||||
|  |     if (this->sending_initial_) { | ||||||
|       this->sending_initial_ = false; |       this->sending_initial_ = false; | ||||||
|     this->current_message_iterator_ = this->repeating_messages_.begin(); |       this->write_repeating_messages_(this->messages_); | ||||||
|   } else if (this->current_message_iterator_ == this->repeating_messages_.end()) { |     } | ||||||
|     this->current_message_iterator_ = this->repeating_messages_.begin(); |     this->message_iterator_ = this->messages_.begin(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   auto request = this->build_request_(*this->current_message_iterator_); |   auto request = this->build_request_(*this->message_iterator_); | ||||||
|  |  | ||||||
|  |   this->before_send_callback_.call(request); | ||||||
|  |  | ||||||
|   ESP_LOGD(TAG, "Sending request with id %d (%s)", request.id, |   ESP_LOGD(TAG, "Sending request with id %d (%s)", request.id, | ||||||
|            this->opentherm_->message_id_to_str((MessageId) request.id)); |            this->opentherm_->message_id_to_str((MessageId) request.id)); | ||||||
| @@ -331,37 +358,48 @@ void OpenthermHub::read_response_() { | |||||||
|  |  | ||||||
|   this->stop_opentherm_(); |   this->stop_opentherm_(); | ||||||
|  |  | ||||||
|  |   this->before_process_response_callback_.call(response); | ||||||
|   this->process_response(response); |   this->process_response(response); | ||||||
|  |  | ||||||
|   this->current_message_iterator_++; |   this->message_iterator_++; | ||||||
| } | } | ||||||
|  |  | ||||||
| void OpenthermHub::stop_opentherm_() { | void OpenthermHub::stop_opentherm_() { | ||||||
|   this->opentherm_->stop(); |   this->opentherm_->stop(); | ||||||
|   this->last_conversation_end_ = millis(); |   this->last_conversation_end_ = millis(); | ||||||
| } | } | ||||||
| void OpenthermHub::handle_protocol_write_error_() { |  | ||||||
|   ESP_LOGW(TAG, "Error while sending request: %s", | void OpenthermHub::handle_protocol_error_() { | ||||||
|            this->opentherm_->operation_mode_to_str(this->opentherm_->get_mode())); |  | ||||||
|   this->opentherm_->debug_data(this->last_request_); |  | ||||||
| } |  | ||||||
| void OpenthermHub::handle_protocol_read_error_() { |  | ||||||
|   OpenThermError error; |   OpenThermError error; | ||||||
|   this->opentherm_->get_protocol_error(error); |   this->opentherm_->get_protocol_error(error); | ||||||
|   ESP_LOGW(TAG, "Protocol error occured while receiving response: %s", |   ESP_LOGW(TAG, "Protocol error occured while receiving response: %s", | ||||||
|            this->opentherm_->protocol_error_to_to_str(error.error_type)); |            this->opentherm_->protocol_error_to_str(error.error_type)); | ||||||
|   this->opentherm_->debug_error(error); |   this->opentherm_->debug_error(error); | ||||||
| } |  | ||||||
| void OpenthermHub::handle_timeout_error_() { |  | ||||||
|   ESP_LOGW(TAG, "Receive response timed out at a protocol level"); |  | ||||||
|   this->stop_opentherm_(); |   this->stop_opentherm_(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void OpenthermHub::handle_timeout_error_() { | ||||||
|  |   ESP_LOGW(TAG, "Timeout while waiting for response from device"); | ||||||
|  |   this->stop_opentherm_(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void OpenthermHub::handle_timer_error_() { | ||||||
|  |   this->opentherm_->report_and_reset_timer_error(); | ||||||
|  |   this->stop_opentherm_(); | ||||||
|  |   // Timer error is critical, there is no point in retrying. | ||||||
|  |   this->mark_failed(); | ||||||
|  | } | ||||||
|  |  | ||||||
| void OpenthermHub::dump_config() { | void OpenthermHub::dump_config() { | ||||||
|  |   std::vector<MessageId> initial_messages; | ||||||
|  |   std::vector<MessageId> repeating_messages; | ||||||
|  |   this->write_initial_messages_(initial_messages); | ||||||
|  |   this->write_repeating_messages_(repeating_messages); | ||||||
|  |  | ||||||
|   ESP_LOGCONFIG(TAG, "OpenTherm:"); |   ESP_LOGCONFIG(TAG, "OpenTherm:"); | ||||||
|   LOG_PIN("  In: ", this->in_pin_); |   LOG_PIN("  In: ", this->in_pin_); | ||||||
|   LOG_PIN("  Out: ", this->out_pin_); |   LOG_PIN("  Out: ", this->out_pin_); | ||||||
|   ESP_LOGCONFIG(TAG, "  Sync mode: %d", this->sync_mode_); |   ESP_LOGCONFIG(TAG, "  Sync mode: %s", YESNO(this->sync_mode_)); | ||||||
|   ESP_LOGCONFIG(TAG, "  Sensors: %s", SHOW(OPENTHERM_SENSOR_LIST(ID, ))); |   ESP_LOGCONFIG(TAG, "  Sensors: %s", SHOW(OPENTHERM_SENSOR_LIST(ID, ))); | ||||||
|   ESP_LOGCONFIG(TAG, "  Binary sensors: %s", SHOW(OPENTHERM_BINARY_SENSOR_LIST(ID, ))); |   ESP_LOGCONFIG(TAG, "  Binary sensors: %s", SHOW(OPENTHERM_BINARY_SENSOR_LIST(ID, ))); | ||||||
|   ESP_LOGCONFIG(TAG, "  Switches: %s", SHOW(OPENTHERM_SWITCH_LIST(ID, ))); |   ESP_LOGCONFIG(TAG, "  Switches: %s", SHOW(OPENTHERM_SWITCH_LIST(ID, ))); | ||||||
| @@ -369,12 +407,12 @@ void OpenthermHub::dump_config() { | |||||||
|   ESP_LOGCONFIG(TAG, "  Outputs: %s", SHOW(OPENTHERM_OUTPUT_LIST(ID, ))); |   ESP_LOGCONFIG(TAG, "  Outputs: %s", SHOW(OPENTHERM_OUTPUT_LIST(ID, ))); | ||||||
|   ESP_LOGCONFIG(TAG, "  Numbers: %s", SHOW(OPENTHERM_NUMBER_LIST(ID, ))); |   ESP_LOGCONFIG(TAG, "  Numbers: %s", SHOW(OPENTHERM_NUMBER_LIST(ID, ))); | ||||||
|   ESP_LOGCONFIG(TAG, "  Initial requests:"); |   ESP_LOGCONFIG(TAG, "  Initial requests:"); | ||||||
|   for (auto type : this->initial_messages_) { |   for (auto type : initial_messages) { | ||||||
|     ESP_LOGCONFIG(TAG, "  - %d (%s)", type, this->opentherm_->message_id_to_str((type))); |     ESP_LOGCONFIG(TAG, "  - %d (%s)", type, this->opentherm_->message_id_to_str(type)); | ||||||
|   } |   } | ||||||
|   ESP_LOGCONFIG(TAG, "  Repeating requests:"); |   ESP_LOGCONFIG(TAG, "  Repeating requests:"); | ||||||
|   for (auto type : this->repeating_messages_) { |   for (auto type : repeating_messages) { | ||||||
|     ESP_LOGCONFIG(TAG, "  - %d (%s)", type, this->opentherm_->message_id_to_str((type))); |     ESP_LOGCONFIG(TAG, "  - %d (%s)", type, this->opentherm_->message_id_to_str(type)); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -38,6 +38,9 @@ | |||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace opentherm { | namespace opentherm { | ||||||
|  |  | ||||||
|  | static const uint8_t REPEATING_MESSAGE_ORDER = 255; | ||||||
|  | static const uint8_t INITIAL_UNORDERED_MESSAGE_ORDER = 254; | ||||||
|  |  | ||||||
| // OpenTherm component for ESPHome | // OpenTherm component for ESPHome | ||||||
| class OpenthermHub : public Component { | class OpenthermHub : public Component { | ||||||
|  protected: |  protected: | ||||||
| @@ -58,15 +61,12 @@ class OpenthermHub : public Component { | |||||||
|  |  | ||||||
|   OPENTHERM_INPUT_SENSOR_LIST(OPENTHERM_DECLARE_INPUT_SENSOR, ) |   OPENTHERM_INPUT_SENSOR_LIST(OPENTHERM_DECLARE_INPUT_SENSOR, ) | ||||||
|  |  | ||||||
|   // The set of initial messages to send on starting communication with the boiler |   OPENTHERM_SETTING_LIST(OPENTHERM_DECLARE_SETTING, ) | ||||||
|   std::vector<MessageId> initial_messages_; |  | ||||||
|   // and the repeating messages which are sent repeatedly to update various sensors |  | ||||||
|   // and boiler parameters (like the setpoint). |  | ||||||
|   std::vector<MessageId> repeating_messages_; |  | ||||||
|   // Indicates if we are still working on the initial requests or not |  | ||||||
|   bool sending_initial_ = true; |   bool sending_initial_ = true; | ||||||
|   // Index for the current request in one of the _requests sets. |   std::unordered_map<MessageId, uint8_t> configured_messages_; | ||||||
|   std::vector<MessageId>::const_iterator current_message_iterator_; |   std::vector<MessageId> messages_; | ||||||
|  |   std::vector<MessageId>::const_iterator message_iterator_; | ||||||
|  |  | ||||||
|   uint32_t last_conversation_start_ = 0; |   uint32_t last_conversation_start_ = 0; | ||||||
|   uint32_t last_conversation_end_ = 0; |   uint32_t last_conversation_end_ = 0; | ||||||
| @@ -78,20 +78,25 @@ class OpenthermHub : public Component { | |||||||
|   // Very likely to happen while using Dallas temperature sensors. |   // Very likely to happen while using Dallas temperature sensors. | ||||||
|   bool sync_mode_ = false; |   bool sync_mode_ = false; | ||||||
|  |  | ||||||
|   float opentherm_version_ = 0.0f; |   CallbackManager<void(OpenthermData &)> before_send_callback_; | ||||||
|  |   CallbackManager<void(OpenthermData &)> before_process_response_callback_; | ||||||
|  |  | ||||||
|   // Create OpenTherm messages based on the message id |   // Create OpenTherm messages based on the message id | ||||||
|   OpenthermData build_request_(MessageId request_id) const; |   OpenthermData build_request_(MessageId request_id) const; | ||||||
|   void handle_protocol_write_error_(); |   bool handle_error_(OperationMode mode); | ||||||
|   void handle_protocol_read_error_(); |   void handle_protocol_error_(); | ||||||
|   void handle_timeout_error_(); |   void handle_timeout_error_(); | ||||||
|  |   void handle_timer_error_(); | ||||||
|   void stop_opentherm_(); |   void stop_opentherm_(); | ||||||
|   void start_conversation_(); |   void start_conversation_(); | ||||||
|   void read_response_(); |   void read_response_(); | ||||||
|   bool check_timings_(uint32_t cur_time); |   void check_timings_(uint32_t cur_time); | ||||||
|   bool should_skip_loop_(uint32_t cur_time) const; |   bool should_skip_loop_(uint32_t cur_time) const; | ||||||
|   void sync_loop_(); |   void sync_loop_(); | ||||||
|  |  | ||||||
|  |   void write_initial_messages_(std::vector<MessageId> &target); | ||||||
|  |   void write_repeating_messages_(std::vector<MessageId> &target); | ||||||
|  |  | ||||||
|   template<typename F> bool spin_wait_(uint32_t timeout, F func) { |   template<typename F> bool spin_wait_(uint32_t timeout, F func) { | ||||||
|     auto start_time = millis(); |     auto start_time = millis(); | ||||||
|     while (func()) { |     while (func()) { | ||||||
| @@ -127,13 +132,18 @@ class OpenthermHub : public Component { | |||||||
|  |  | ||||||
|   OPENTHERM_INPUT_SENSOR_LIST(OPENTHERM_SET_INPUT_SENSOR, ) |   OPENTHERM_INPUT_SENSOR_LIST(OPENTHERM_SET_INPUT_SENSOR, ) | ||||||
|  |  | ||||||
|  |   OPENTHERM_SETTING_LIST(OPENTHERM_SET_SETTING, ) | ||||||
|  |  | ||||||
|   // Add a request to the vector of initial requests |   // Add a request to the vector of initial requests | ||||||
|   void add_initial_message(MessageId message_id) { this->initial_messages_.push_back(message_id); } |   void add_initial_message(MessageId message_id) { | ||||||
|  |     this->configured_messages_[message_id] = INITIAL_UNORDERED_MESSAGE_ORDER; | ||||||
|  |   } | ||||||
|  |   void add_initial_message(MessageId message_id, uint8_t order) { this->configured_messages_[message_id] = order; } | ||||||
|   // Add a request to the set of repeating requests. Note that a large number of repeating |   // Add a request to the set of repeating requests. Note that a large number of repeating | ||||||
|   // requests will slow down communication with the boiler. Each request may take up to 1 second, |   // requests will slow down communication with the boiler. Each request may take up to 1 second, | ||||||
|   // so with all sensors enabled, it may take about half a minute before a change in setpoint |   // so with all sensors enabled, it may take about half a minute before a change in setpoint | ||||||
|   // will be processed. |   // will be processed. | ||||||
|   void add_repeating_message(MessageId message_id) { this->repeating_messages_.push_back(message_id); } |   void add_repeating_message(MessageId message_id) { this->configured_messages_[message_id] = REPEATING_MESSAGE_ORDER; } | ||||||
|  |  | ||||||
|   // There are seven status variables, which can either be set as a simple variable, |   // There are seven status variables, which can either be set as a simple variable, | ||||||
|   // or using a switch. ch_enable and dhw_enable default to true, the others to false. |   // or using a switch. ch_enable and dhw_enable default to true, the others to false. | ||||||
| @@ -149,7 +159,13 @@ class OpenthermHub : public Component { | |||||||
|   void set_summer_mode_active(bool value) { this->summer_mode_active = value; } |   void set_summer_mode_active(bool value) { this->summer_mode_active = value; } | ||||||
|   void set_dhw_block(bool value) { this->dhw_block = value; } |   void set_dhw_block(bool value) { this->dhw_block = value; } | ||||||
|   void set_sync_mode(bool sync_mode) { this->sync_mode_ = sync_mode; } |   void set_sync_mode(bool sync_mode) { this->sync_mode_ = sync_mode; } | ||||||
|   void set_opentherm_version(float value) { this->opentherm_version_ = value; } |  | ||||||
|  |   void add_on_before_send_callback(std::function<void(OpenthermData &)> &&callback) { | ||||||
|  |     this->before_send_callback_.add(std::move(callback)); | ||||||
|  |   } | ||||||
|  |   void add_on_before_process_response_callback(std::function<void(OpenthermData &)> &&callback) { | ||||||
|  |     this->before_process_response_callback_.add(std::move(callback)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   float get_setup_priority() const override { return setup_priority::HARDWARE; } |   float get_setup_priority() const override { return setup_priority::HARDWARE; } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -52,7 +52,9 @@ bool OpenTherm::initialize() { | |||||||
|   OpenTherm::instance = this; |   OpenTherm::instance = this; | ||||||
| #endif | #endif | ||||||
|   this->in_pin_->pin_mode(gpio::FLAG_INPUT); |   this->in_pin_->pin_mode(gpio::FLAG_INPUT); | ||||||
|  |   this->in_pin_->setup(); | ||||||
|   this->out_pin_->pin_mode(gpio::FLAG_OUTPUT); |   this->out_pin_->pin_mode(gpio::FLAG_OUTPUT); | ||||||
|  |   this->out_pin_->setup(); | ||||||
|   this->out_pin_->digital_write(true); |   this->out_pin_->digital_write(true); | ||||||
|  |  | ||||||
| #if defined(ESP32) || defined(USE_ESP_IDF) | #if defined(ESP32) || defined(USE_ESP_IDF) | ||||||
| @@ -182,7 +184,7 @@ bool IRAM_ATTR OpenTherm::timer_isr(OpenTherm *arg) { | |||||||
|       } |       } | ||||||
|       arg->capture_ = 1;  // reset counter |       arg->capture_ = 1;  // reset counter | ||||||
|     } else if (arg->capture_ > 0xFF) { |     } else if (arg->capture_ > 0xFF) { | ||||||
|       // no change for too long, invalid mancheter encoding |       // no change for too long, invalid manchester encoding | ||||||
|       arg->mode_ = OperationMode::ERROR_PROTOCOL; |       arg->mode_ = OperationMode::ERROR_PROTOCOL; | ||||||
|       arg->error_type_ = ProtocolErrorType::NO_CHANGE_TOO_LONG; |       arg->error_type_ = ProtocolErrorType::NO_CHANGE_TOO_LONG; | ||||||
|       arg->stop_timer_(); |       arg->stop_timer_(); | ||||||
| @@ -312,21 +314,31 @@ bool OpenTherm::init_esp32_timer_() { | |||||||
| } | } | ||||||
|  |  | ||||||
| void IRAM_ATTR OpenTherm::start_esp32_timer_(uint64_t alarm_value) { | void IRAM_ATTR OpenTherm::start_esp32_timer_(uint64_t alarm_value) { | ||||||
|   esp_err_t result; |   // We will report timer errors outside of interrupt handler | ||||||
|  |   this->timer_error_ = ESP_OK; | ||||||
|  |   this->timer_error_type_ = TimerErrorType::NO_TIMER_ERROR; | ||||||
|  |  | ||||||
|   result = timer_set_alarm_value(this->timer_group_, this->timer_idx_, alarm_value); |   this->timer_error_ = timer_set_alarm_value(this->timer_group_, this->timer_idx_, alarm_value); | ||||||
|   if (result != ESP_OK) { |   if (this->timer_error_ != ESP_OK) { | ||||||
|     const auto *error = esp_err_to_name(result); |     this->timer_error_type_ = TimerErrorType::SET_ALARM_VALUE_ERROR; | ||||||
|     ESP_LOGE(TAG, "Failed to set alarm value. Error: %s", error); |     return; | ||||||
|  |   } | ||||||
|  |   this->timer_error_ = timer_start(this->timer_group_, this->timer_idx_); | ||||||
|  |   if (this->timer_error_ != ESP_OK) { | ||||||
|  |     this->timer_error_type_ = TimerErrorType::TIMER_START_ERROR; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void OpenTherm::report_and_reset_timer_error() { | ||||||
|  |   if (this->timer_error_ == ESP_OK) { | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   result = timer_start(this->timer_group_, this->timer_idx_); |   ESP_LOGE(TAG, "Error occured while manipulating timer (%s): %s", this->timer_error_to_str(this->timer_error_type_), | ||||||
|   if (result != ESP_OK) { |            esp_err_to_name(this->timer_error_)); | ||||||
|     const auto *error = esp_err_to_name(result); |  | ||||||
|     ESP_LOGE(TAG, "Failed to start the timer. Error: %s", error); |   this->timer_error_ = ESP_OK; | ||||||
|     return; |   this->timer_error_type_ = NO_TIMER_ERROR; | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // 5 kHz timer_ | // 5 kHz timer_ | ||||||
| @@ -343,21 +355,18 @@ void IRAM_ATTR OpenTherm::start_write_timer_() { | |||||||
|  |  | ||||||
| void IRAM_ATTR OpenTherm::stop_timer_() { | void IRAM_ATTR OpenTherm::stop_timer_() { | ||||||
|   InterruptLock const lock; |   InterruptLock const lock; | ||||||
|  |   // We will report timer errors outside of interrupt handler | ||||||
|  |   this->timer_error_ = ESP_OK; | ||||||
|  |   this->timer_error_type_ = TimerErrorType::NO_TIMER_ERROR; | ||||||
|  |  | ||||||
|   esp_err_t result; |   this->timer_error_ = timer_pause(this->timer_group_, this->timer_idx_); | ||||||
|  |   if (this->timer_error_ != ESP_OK) { | ||||||
|   result = timer_pause(this->timer_group_, this->timer_idx_); |     this->timer_error_type_ = TimerErrorType::TIMER_PAUSE_ERROR; | ||||||
|   if (result != ESP_OK) { |  | ||||||
|     const auto *error = esp_err_to_name(result); |  | ||||||
|     ESP_LOGE(TAG, "Failed to pause the timer. Error: %s", error); |  | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |   this->timer_error_ = timer_set_counter_value(this->timer_group_, this->timer_idx_, 0); | ||||||
|   result = timer_set_counter_value(this->timer_group_, this->timer_idx_, 0); |   if (this->timer_error_ != ESP_OK) { | ||||||
|   if (result != ESP_OK) { |     this->timer_error_type_ = TimerErrorType::SET_COUNTER_VALUE_ERROR; | ||||||
|     const auto *error = esp_err_to_name(result); |  | ||||||
|     ESP_LOGE(TAG, "Failed to set timer counter to 0 after pausing. Error: %s", error); |  | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -386,6 +395,9 @@ void IRAM_ATTR OpenTherm::stop_timer_() { | |||||||
|   timer1_detachInterrupt(); |   timer1_detachInterrupt(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // There is nothing to report on ESP8266 | ||||||
|  | void OpenTherm::report_and_reset_timer_error() {} | ||||||
|  |  | ||||||
| #endif  // END ESP8266 | #endif  // END ESP8266 | ||||||
|  |  | ||||||
| // https://stackoverflow.com/questions/21617970/how-to-check-if-value-has-even-parity-of-bits-or-odd | // https://stackoverflow.com/questions/21617970/how-to-check-if-value-has-even-parity-of-bits-or-odd | ||||||
| @@ -412,11 +424,12 @@ const char *OpenTherm::operation_mode_to_str(OperationMode mode) { | |||||||
|     TO_STRING_MEMBER(SENT) |     TO_STRING_MEMBER(SENT) | ||||||
|     TO_STRING_MEMBER(ERROR_PROTOCOL) |     TO_STRING_MEMBER(ERROR_PROTOCOL) | ||||||
|     TO_STRING_MEMBER(ERROR_TIMEOUT) |     TO_STRING_MEMBER(ERROR_TIMEOUT) | ||||||
|  |     TO_STRING_MEMBER(ERROR_TIMER) | ||||||
|     default: |     default: | ||||||
|       return "<INVALID>"; |       return "<INVALID>"; | ||||||
|   } |   } | ||||||
| } | } | ||||||
| const char *OpenTherm::protocol_error_to_to_str(ProtocolErrorType error_type) { | const char *OpenTherm::protocol_error_to_str(ProtocolErrorType error_type) { | ||||||
|   switch (error_type) { |   switch (error_type) { | ||||||
|     TO_STRING_MEMBER(NO_ERROR) |     TO_STRING_MEMBER(NO_ERROR) | ||||||
|     TO_STRING_MEMBER(NO_TRANSITION) |     TO_STRING_MEMBER(NO_TRANSITION) | ||||||
| @@ -427,6 +440,17 @@ const char *OpenTherm::protocol_error_to_to_str(ProtocolErrorType error_type) { | |||||||
|       return "<INVALID>"; |       return "<INVALID>"; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | const char *OpenTherm::timer_error_to_str(TimerErrorType error_type) { | ||||||
|  |   switch (error_type) { | ||||||
|  |     TO_STRING_MEMBER(NO_TIMER_ERROR) | ||||||
|  |     TO_STRING_MEMBER(SET_ALARM_VALUE_ERROR) | ||||||
|  |     TO_STRING_MEMBER(TIMER_START_ERROR) | ||||||
|  |     TO_STRING_MEMBER(TIMER_PAUSE_ERROR) | ||||||
|  |     TO_STRING_MEMBER(SET_COUNTER_VALUE_ERROR) | ||||||
|  |     default: | ||||||
|  |       return "<INVALID>"; | ||||||
|  |   } | ||||||
|  | } | ||||||
| const char *OpenTherm::message_type_to_str(MessageType message_type) { | const char *OpenTherm::message_type_to_str(MessageType message_type) { | ||||||
|   switch (message_type) { |   switch (message_type) { | ||||||
|     TO_STRING_MEMBER(READ_DATA) |     TO_STRING_MEMBER(READ_DATA) | ||||||
|   | |||||||
| @@ -36,11 +36,12 @@ enum OperationMode { | |||||||
|   READ = 2,      // reading 32-bit data frame |   READ = 2,      // reading 32-bit data frame | ||||||
|   RECEIVED = 3,  // data frame received with valid start and stop bit |   RECEIVED = 3,  // data frame received with valid start and stop bit | ||||||
|  |  | ||||||
|   WRITE = 4,  // writing data with timer_ |   WRITE = 4,  // writing data to output | ||||||
|   SENT = 5,   // all data written to output |   SENT = 5,   // all data written to output | ||||||
|  |  | ||||||
|   ERROR_PROTOCOL = 8,  // manchester protocol data transfer error |   ERROR_PROTOCOL = 8,  // protocol error, can happed only during READ | ||||||
|   ERROR_TIMEOUT = 9    // read timeout |   ERROR_TIMEOUT = 9,   // timeout while waiting for response from device, only during LISTEN | ||||||
|  |   ERROR_TIMER = 10     // error operating the ESP32 timer | ||||||
| }; | }; | ||||||
|  |  | ||||||
| enum ProtocolErrorType { | enum ProtocolErrorType { | ||||||
| @@ -51,6 +52,14 @@ enum ProtocolErrorType { | |||||||
|   NO_CHANGE_TOO_LONG = 4,  // No level change for too much timer ticks |   NO_CHANGE_TOO_LONG = 4,  // No level change for too much timer ticks | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | enum TimerErrorType { | ||||||
|  |   NO_TIMER_ERROR = 0,           // No error | ||||||
|  |   SET_ALARM_VALUE_ERROR = 1,    // No transition in the middle of the bit | ||||||
|  |   TIMER_START_ERROR = 2,        // Stop bit wasn't present when expected | ||||||
|  |   TIMER_PAUSE_ERROR = 3,        // Parity check didn't pass | ||||||
|  |   SET_COUNTER_VALUE_ERROR = 4,  // No level change for too much timer ticks | ||||||
|  | }; | ||||||
|  |  | ||||||
| enum MessageType { | enum MessageType { | ||||||
|   READ_DATA = 0, |   READ_DATA = 0, | ||||||
|   READ_ACK = 4, |   READ_ACK = 4, | ||||||
| @@ -299,7 +308,9 @@ class OpenTherm { | |||||||
|    * |    * | ||||||
|    * @return true if last listen() or send() operation ends up with an error. |    * @return true if last listen() or send() operation ends up with an error. | ||||||
|    */ |    */ | ||||||
|   bool is_error() { return mode_ == OperationMode::ERROR_TIMEOUT || mode_ == OperationMode::ERROR_PROTOCOL; } |   bool is_error() { | ||||||
|  |     return mode_ == OperationMode::ERROR_TIMEOUT || mode_ == OperationMode::ERROR_PROTOCOL || mode_ == ERROR_TIMER; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * Indicates whether last listen() or send() operation ends up with a *timeout* error |    * Indicates whether last listen() or send() operation ends up with a *timeout* error | ||||||
| @@ -313,14 +324,22 @@ class OpenTherm { | |||||||
|    */ |    */ | ||||||
|   bool is_protocol_error() { return mode_ == OperationMode::ERROR_PROTOCOL; } |   bool is_protocol_error() { return mode_ == OperationMode::ERROR_PROTOCOL; } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Indicates whether start_esp32_timer_() or stop_timer_() had an error. Only relevant when used on ESP32. | ||||||
|  |    * @return true if there was an error. | ||||||
|  |    */ | ||||||
|  |   bool is_timer_error() { return mode_ == OperationMode::ERROR_TIMER; } | ||||||
|  |  | ||||||
|   bool is_active() { return mode_ == LISTEN || mode_ == READ || mode_ == WRITE; } |   bool is_active() { return mode_ == LISTEN || mode_ == READ || mode_ == WRITE; } | ||||||
|  |  | ||||||
|   OperationMode get_mode() { return mode_; } |   OperationMode get_mode() { return mode_; } | ||||||
|  |  | ||||||
|   void debug_data(OpenthermData &data); |   void debug_data(OpenthermData &data); | ||||||
|   void debug_error(OpenThermError &error) const; |   void debug_error(OpenThermError &error) const; | ||||||
|  |   void report_and_reset_timer_error(); | ||||||
|  |  | ||||||
|   const char *protocol_error_to_to_str(ProtocolErrorType error_type); |   const char *protocol_error_to_str(ProtocolErrorType error_type); | ||||||
|  |   const char *timer_error_to_str(TimerErrorType error_type); | ||||||
|   const char *message_type_to_str(MessageType message_type); |   const char *message_type_to_str(MessageType message_type); | ||||||
|   const char *operation_mode_to_str(OperationMode mode); |   const char *operation_mode_to_str(OperationMode mode); | ||||||
|   const char *message_id_to_str(MessageId id); |   const char *message_id_to_str(MessageId id); | ||||||
| @@ -349,10 +368,12 @@ class OpenTherm { | |||||||
|   uint32_t data_; |   uint32_t data_; | ||||||
|   uint8_t bit_pos_; |   uint8_t bit_pos_; | ||||||
|   int32_t timeout_counter_;  // <0 no timeout |   int32_t timeout_counter_;  // <0 no timeout | ||||||
|  |  | ||||||
|   int32_t device_timeout_; |   int32_t device_timeout_; | ||||||
|  |  | ||||||
| #if defined(ESP32) || defined(USE_ESP_IDF) | #if defined(ESP32) || defined(USE_ESP_IDF) | ||||||
|  |   esp_err_t timer_error_ = ESP_OK; | ||||||
|  |   TimerErrorType timer_error_type_ = TimerErrorType::NO_TIMER_ERROR; | ||||||
|  |  | ||||||
|   bool init_esp32_timer_(); |   bool init_esp32_timer_(); | ||||||
|   void start_esp32_timer_(uint64_t alarm_value); |   void start_esp32_timer_(uint64_t alarm_value); | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -28,6 +28,9 @@ namespace opentherm { | |||||||
| #ifndef OPENTHERM_INPUT_SENSOR_LIST | #ifndef OPENTHERM_INPUT_SENSOR_LIST | ||||||
| #define OPENTHERM_INPUT_SENSOR_LIST(F, sep) | #define OPENTHERM_INPUT_SENSOR_LIST(F, sep) | ||||||
| #endif | #endif | ||||||
|  | #ifndef OPENTHERM_SETTING_LIST | ||||||
|  | #define OPENTHERM_SETTING_LIST(F, sep) | ||||||
|  | #endif | ||||||
|  |  | ||||||
| // Use macros to create fields for every entity specified in the ESPHome configuration | // Use macros to create fields for every entity specified in the ESPHome configuration | ||||||
| #define OPENTHERM_DECLARE_SENSOR(entity) sensor::Sensor *entity; | #define OPENTHERM_DECLARE_SENSOR(entity) sensor::Sensor *entity; | ||||||
| @@ -36,6 +39,7 @@ namespace opentherm { | |||||||
| #define OPENTHERM_DECLARE_NUMBER(entity) OpenthermNumber *entity; | #define OPENTHERM_DECLARE_NUMBER(entity) OpenthermNumber *entity; | ||||||
| #define OPENTHERM_DECLARE_OUTPUT(entity) OpenthermOutput *entity; | #define OPENTHERM_DECLARE_OUTPUT(entity) OpenthermOutput *entity; | ||||||
| #define OPENTHERM_DECLARE_INPUT_SENSOR(entity) sensor::Sensor *entity; | #define OPENTHERM_DECLARE_INPUT_SENSOR(entity) sensor::Sensor *entity; | ||||||
|  | #define OPENTHERM_DECLARE_SETTING(type, entity, def) type entity = def; | ||||||
|  |  | ||||||
| // Setter macros | // Setter macros | ||||||
| #define OPENTHERM_SET_SENSOR(entity) \ | #define OPENTHERM_SET_SENSOR(entity) \ | ||||||
| @@ -56,6 +60,9 @@ namespace opentherm { | |||||||
| #define OPENTHERM_SET_INPUT_SENSOR(entity) \ | #define OPENTHERM_SET_INPUT_SENSOR(entity) \ | ||||||
|   void set_##entity(sensor::Sensor *sensor) { this->entity = sensor; } |   void set_##entity(sensor::Sensor *sensor) { this->entity = sensor; } | ||||||
|  |  | ||||||
|  | #define OPENTHERM_SET_SETTING(type, entity, def) \ | ||||||
|  |   void set_##entity(type value) { this->entity = value; } | ||||||
|  |  | ||||||
| // ===== hub.cpp macros ===== | // ===== hub.cpp macros ===== | ||||||
|  |  | ||||||
| // *_MESSAGE_HANDLERS are generated in defines.h and look like this: | // *_MESSAGE_HANDLERS are generated in defines.h and look like this: | ||||||
| @@ -85,6 +92,9 @@ namespace opentherm { | |||||||
| #ifndef OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS | #ifndef OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS | ||||||
| #define OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS(MESSAGE, ENTITY, entity_sep, postscript, msg_sep) | #define OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS(MESSAGE, ENTITY, entity_sep, postscript, msg_sep) | ||||||
| #endif | #endif | ||||||
|  | #ifndef OPENTHERM_SETTING_MESSAGE_HANDLERS | ||||||
|  | #define OPENTHERM_SETTING_MESSAGE_HANDLERS(MESSAGE, ENTITY, entity_sep, postscript, msg_sep) | ||||||
|  | #endif | ||||||
|  |  | ||||||
| // Write data request builders | // Write data request builders | ||||||
| #define OPENTHERM_MESSAGE_WRITE_MESSAGE(msg) \ | #define OPENTHERM_MESSAGE_WRITE_MESSAGE(msg) \ | ||||||
| @@ -92,6 +102,7 @@ namespace opentherm { | |||||||
|     data.type = MessageType::WRITE_DATA; \ |     data.type = MessageType::WRITE_DATA; \ | ||||||
|     data.id = request_id; |     data.id = request_id; | ||||||
| #define OPENTHERM_MESSAGE_WRITE_ENTITY(key, msg_data) message_data::write_##msg_data(this->key->state, data); | #define OPENTHERM_MESSAGE_WRITE_ENTITY(key, msg_data) message_data::write_##msg_data(this->key->state, data); | ||||||
|  | #define OPENTHERM_MESSAGE_WRITE_SETTING(key, msg_data) message_data::write_##msg_data(this->key, data); | ||||||
| #define OPENTHERM_MESSAGE_WRITE_POSTSCRIPT \ | #define OPENTHERM_MESSAGE_WRITE_POSTSCRIPT \ | ||||||
|   return data; \ |   return data; \ | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -2,8 +2,9 @@ | |||||||
| # inputs of the OpenTherm component. | # inputs of the OpenTherm component. | ||||||
|  |  | ||||||
| from dataclasses import dataclass | from dataclasses import dataclass | ||||||
| from typing import Optional, TypeVar | from typing import Optional, TypeVar, Any | ||||||
|  |  | ||||||
|  | import esphome.config_validation as cv | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     UNIT_CELSIUS, |     UNIT_CELSIUS, | ||||||
|     UNIT_EMPTY, |     UNIT_EMPTY, | ||||||
| @@ -64,6 +65,7 @@ class SensorSchema(EntitySchema): | |||||||
|     icon: Optional[str] = None |     icon: Optional[str] = None | ||||||
|     device_class: Optional[str] = None |     device_class: Optional[str] = None | ||||||
|     disabled_by_default: bool = False |     disabled_by_default: bool = False | ||||||
|  |     order: Optional[int] = None | ||||||
|  |  | ||||||
|  |  | ||||||
| SENSORS: dict[str, SensorSchema] = { | SENSORS: dict[str, SensorSchema] = { | ||||||
| @@ -399,6 +401,7 @@ SENSORS: dict[str, SensorSchema] = { | |||||||
|         message="OT_VERSION_DEVICE", |         message="OT_VERSION_DEVICE", | ||||||
|         keep_updated=False, |         keep_updated=False, | ||||||
|         message_data="f88", |         message_data="f88", | ||||||
|  |         order=2, | ||||||
|     ), |     ), | ||||||
|     "device_type": SensorSchema( |     "device_type": SensorSchema( | ||||||
|         description="Device product type", |         description="Device product type", | ||||||
| @@ -409,6 +412,7 @@ SENSORS: dict[str, SensorSchema] = { | |||||||
|         message="VERSION_DEVICE", |         message="VERSION_DEVICE", | ||||||
|         keep_updated=False, |         keep_updated=False, | ||||||
|         message_data="u8_hb", |         message_data="u8_hb", | ||||||
|  |         order=0, | ||||||
|     ), |     ), | ||||||
|     "device_version": SensorSchema( |     "device_version": SensorSchema( | ||||||
|         description="Device product version", |         description="Device product version", | ||||||
| @@ -419,6 +423,7 @@ SENSORS: dict[str, SensorSchema] = { | |||||||
|         message="VERSION_DEVICE", |         message="VERSION_DEVICE", | ||||||
|         keep_updated=False, |         keep_updated=False, | ||||||
|         message_data="u8_lb", |         message_data="u8_lb", | ||||||
|  |         order=0, | ||||||
|     ), |     ), | ||||||
|     "device_id": SensorSchema( |     "device_id": SensorSchema( | ||||||
|         description="Device ID code", |         description="Device ID code", | ||||||
| @@ -429,6 +434,7 @@ SENSORS: dict[str, SensorSchema] = { | |||||||
|         message="DEVICE_CONFIG", |         message="DEVICE_CONFIG", | ||||||
|         keep_updated=False, |         keep_updated=False, | ||||||
|         message_data="u8_lb", |         message_data="u8_lb", | ||||||
|  |         order=4, | ||||||
|     ), |     ), | ||||||
|     "otc_hc_ratio_ub": SensorSchema( |     "otc_hc_ratio_ub": SensorSchema( | ||||||
|         description="OTC heat curve ratio upper bound", |         description="OTC heat curve ratio upper bound", | ||||||
| @@ -457,6 +463,7 @@ SENSORS: dict[str, SensorSchema] = { | |||||||
| class BinarySensorSchema(EntitySchema): | class BinarySensorSchema(EntitySchema): | ||||||
|     icon: Optional[str] = None |     icon: Optional[str] = None | ||||||
|     device_class: Optional[str] = None |     device_class: Optional[str] = None | ||||||
|  |     order: Optional[int] = None | ||||||
|  |  | ||||||
|  |  | ||||||
| BINARY_SENSORS: dict[str, BinarySensorSchema] = { | BINARY_SENSORS: dict[str, BinarySensorSchema] = { | ||||||
| @@ -525,48 +532,56 @@ BINARY_SENSORS: dict[str, BinarySensorSchema] = { | |||||||
|         message="DEVICE_CONFIG", |         message="DEVICE_CONFIG", | ||||||
|         keep_updated=False, |         keep_updated=False, | ||||||
|         message_data="flag8_hb_0", |         message_data="flag8_hb_0", | ||||||
|  |         order=4, | ||||||
|     ), |     ), | ||||||
|     "control_type_on_off": BinarySensorSchema( |     "control_type_on_off": BinarySensorSchema( | ||||||
|         description="Configuration: Control type is on/off", |         description="Configuration: Control type is on/off", | ||||||
|         message="DEVICE_CONFIG", |         message="DEVICE_CONFIG", | ||||||
|         keep_updated=False, |         keep_updated=False, | ||||||
|         message_data="flag8_hb_1", |         message_data="flag8_hb_1", | ||||||
|  |         order=4, | ||||||
|     ), |     ), | ||||||
|     "cooling_supported": BinarySensorSchema( |     "cooling_supported": BinarySensorSchema( | ||||||
|         description="Configuration: Cooling supported", |         description="Configuration: Cooling supported", | ||||||
|         message="DEVICE_CONFIG", |         message="DEVICE_CONFIG", | ||||||
|         keep_updated=False, |         keep_updated=False, | ||||||
|         message_data="flag8_hb_2", |         message_data="flag8_hb_2", | ||||||
|  |         order=4, | ||||||
|     ), |     ), | ||||||
|     "dhw_storage_tank": BinarySensorSchema( |     "dhw_storage_tank": BinarySensorSchema( | ||||||
|         description="Configuration: DHW storage tank", |         description="Configuration: DHW storage tank", | ||||||
|         message="DEVICE_CONFIG", |         message="DEVICE_CONFIG", | ||||||
|         keep_updated=False, |         keep_updated=False, | ||||||
|         message_data="flag8_hb_3", |         message_data="flag8_hb_3", | ||||||
|  |         order=4, | ||||||
|     ), |     ), | ||||||
|     "controller_pump_control_allowed": BinarySensorSchema( |     "controller_pump_control_allowed": BinarySensorSchema( | ||||||
|         description="Configuration: Controller pump control allowed", |         description="Configuration: Controller pump control allowed", | ||||||
|         message="DEVICE_CONFIG", |         message="DEVICE_CONFIG", | ||||||
|         keep_updated=False, |         keep_updated=False, | ||||||
|         message_data="flag8_hb_4", |         message_data="flag8_hb_4", | ||||||
|  |         order=4, | ||||||
|     ), |     ), | ||||||
|     "ch2_present": BinarySensorSchema( |     "ch2_present": BinarySensorSchema( | ||||||
|         description="Configuration: CH2 present", |         description="Configuration: CH2 present", | ||||||
|         message="DEVICE_CONFIG", |         message="DEVICE_CONFIG", | ||||||
|         keep_updated=False, |         keep_updated=False, | ||||||
|         message_data="flag8_hb_5", |         message_data="flag8_hb_5", | ||||||
|  |         order=4, | ||||||
|     ), |     ), | ||||||
|     "water_filling": BinarySensorSchema( |     "water_filling": BinarySensorSchema( | ||||||
|         description="Configuration: Remote water filling", |         description="Configuration: Remote water filling", | ||||||
|         message="DEVICE_CONFIG", |         message="DEVICE_CONFIG", | ||||||
|         keep_updated=False, |         keep_updated=False, | ||||||
|         message_data="flag8_hb_6", |         message_data="flag8_hb_6", | ||||||
|  |         order=4, | ||||||
|     ), |     ), | ||||||
|     "heat_mode": BinarySensorSchema( |     "heat_mode": BinarySensorSchema( | ||||||
|         description="Configuration: Heating or cooling", |         description="Configuration: Heating or cooling", | ||||||
|         message="DEVICE_CONFIG", |         message="DEVICE_CONFIG", | ||||||
|         keep_updated=False, |         keep_updated=False, | ||||||
|         message_data="flag8_hb_7", |         message_data="flag8_hb_7", | ||||||
|  |         order=4, | ||||||
|     ), |     ), | ||||||
|     "dhw_setpoint_transfer_enabled": BinarySensorSchema( |     "dhw_setpoint_transfer_enabled": BinarySensorSchema( | ||||||
|         description="Remote boiler parameters: DHW setpoint transfer enabled", |         description="Remote boiler parameters: DHW setpoint transfer enabled", | ||||||
| @@ -812,3 +827,65 @@ INPUTS: dict[str, InputSchema] = { | |||||||
|         auto_max_value=AutoConfigure(message="OTC_CURVE_BOUNDS", message_data="u8_hb"), |         auto_max_value=AutoConfigure(message="OTC_CURVE_BOUNDS", message_data="u8_hb"), | ||||||
|     ), |     ), | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @dataclass | ||||||
|  | class SettingSchema(EntitySchema): | ||||||
|  |     backing_type: str | ||||||
|  |     validation_schema: cv.Schema | ||||||
|  |     default_value: Any | ||||||
|  |     order: Optional[int] = None | ||||||
|  |  | ||||||
|  |  | ||||||
|  | SETTINGS: dict[str, SettingSchema] = { | ||||||
|  |     "controller_product_type": SettingSchema( | ||||||
|  |         description="Controller product type", | ||||||
|  |         message="VERSION_CONTROLLER", | ||||||
|  |         keep_updated=False, | ||||||
|  |         message_data="u8_hb", | ||||||
|  |         backing_type="uint8_t", | ||||||
|  |         validation_schema=cv.int_range(min=0, max=255), | ||||||
|  |         default_value=0, | ||||||
|  |         order=1, | ||||||
|  |     ), | ||||||
|  |     "controller_product_version": SettingSchema( | ||||||
|  |         description="Controller product version", | ||||||
|  |         message="VERSION_CONTROLLER", | ||||||
|  |         keep_updated=False, | ||||||
|  |         message_data="u8_lb", | ||||||
|  |         backing_type="uint8_t", | ||||||
|  |         validation_schema=cv.int_range(min=0, max=255), | ||||||
|  |         default_value=0, | ||||||
|  |         order=1, | ||||||
|  |     ), | ||||||
|  |     "opentherm_version_controller": SettingSchema( | ||||||
|  |         description="Version of OpenTherm implemented by controller", | ||||||
|  |         message="OT_VERSION_CONTROLLER", | ||||||
|  |         keep_updated=False, | ||||||
|  |         message_data="f88", | ||||||
|  |         backing_type="float", | ||||||
|  |         validation_schema=cv.positive_float, | ||||||
|  |         default_value=0, | ||||||
|  |         order=3, | ||||||
|  |     ), | ||||||
|  |     "controller_configuration": SettingSchema( | ||||||
|  |         description="Controller configuration", | ||||||
|  |         message="CONTROLLER_CONFIG", | ||||||
|  |         keep_updated=False, | ||||||
|  |         message_data="u8_hb", | ||||||
|  |         backing_type="uint8_t", | ||||||
|  |         validation_schema=cv.int_range(min=0, max=255), | ||||||
|  |         default_value=0, | ||||||
|  |         order=5, | ||||||
|  |     ), | ||||||
|  |     "controller_id": SettingSchema( | ||||||
|  |         description="Controller ID code", | ||||||
|  |         message="CONTROLLER_CONFIG", | ||||||
|  |         keep_updated=False, | ||||||
|  |         message_data="u8_lb", | ||||||
|  |         backing_type="uint8_t", | ||||||
|  |         validation_schema=cv.int_range(min=0, max=255), | ||||||
|  |         default_value=0, | ||||||
|  |         order=5, | ||||||
|  |     ), | ||||||
|  | } | ||||||
|   | |||||||
| @@ -9,12 +9,17 @@ from .schema import TSchema | |||||||
|  |  | ||||||
|  |  | ||||||
| def create_entities_schema( | def create_entities_schema( | ||||||
|     entities: dict[str, schema.EntitySchema], |     entities: dict[str, TSchema], | ||||||
|     get_entity_validation_schema: Callable[[TSchema], cv.Schema], |     get_entity_validation_schema: Callable[[TSchema], cv.Schema], | ||||||
| ) -> Schema: | ) -> Schema: | ||||||
|     entity_schema = {} |     entity_schema = {} | ||||||
|     for key, entity in entities.items(): |     for key, entity in entities.items(): | ||||||
|         entity_schema[cv.Optional(key)] = get_entity_validation_schema(entity) |         schema_key = ( | ||||||
|  |             cv.Optional(key, entity.default_value) | ||||||
|  |             if hasattr(entity, "default_value") | ||||||
|  |             else cv.Optional(key) | ||||||
|  |         ) | ||||||
|  |         entity_schema[schema_key] = get_entity_validation_schema(entity) | ||||||
|     return cv.Schema(entity_schema) |     return cv.Schema(entity_schema) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -13,9 +13,9 @@ PulseCounterStorageBase *get_storage(bool hw_pcnt) { | |||||||
|   return (hw_pcnt ? (PulseCounterStorageBase *) (new HwPulseCounterStorage) |   return (hw_pcnt ? (PulseCounterStorageBase *) (new HwPulseCounterStorage) | ||||||
|                   : (PulseCounterStorageBase *) (new BasicPulseCounterStorage)); |                   : (PulseCounterStorageBase *) (new BasicPulseCounterStorage)); | ||||||
| } | } | ||||||
| #else | #else   // HAS_PCNT | ||||||
| PulseCounterStorageBase *get_storage(bool) { return new BasicPulseCounterStorage; } | PulseCounterStorageBase *get_storage(bool) { return new BasicPulseCounterStorage; } | ||||||
| #endif | #endif  // HAS_PCNT | ||||||
|  |  | ||||||
| void IRAM_ATTR BasicPulseCounterStorage::gpio_intr(BasicPulseCounterStorage *arg) { | void IRAM_ATTR BasicPulseCounterStorage::gpio_intr(BasicPulseCounterStorage *arg) { | ||||||
|   const uint32_t now = micros(); |   const uint32_t now = micros(); | ||||||
| @@ -28,14 +28,17 @@ void IRAM_ATTR BasicPulseCounterStorage::gpio_intr(BasicPulseCounterStorage *arg | |||||||
|   switch (mode) { |   switch (mode) { | ||||||
|     case PULSE_COUNTER_DISABLE: |     case PULSE_COUNTER_DISABLE: | ||||||
|       break; |       break; | ||||||
|     case PULSE_COUNTER_INCREMENT: |     case PULSE_COUNTER_INCREMENT: { | ||||||
|       arg->counter++; |       auto x = arg->counter + 1; | ||||||
|       break; |       arg->counter = x; | ||||||
|     case PULSE_COUNTER_DECREMENT: |     } break; | ||||||
|       arg->counter--; |     case PULSE_COUNTER_DECREMENT: { | ||||||
|       break; |       auto x = arg->counter - 1; | ||||||
|  |       arg->counter = x; | ||||||
|  |     } break; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| bool BasicPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) { | bool BasicPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) { | ||||||
|   this->pin = pin; |   this->pin = pin; | ||||||
|   this->pin->setup(); |   this->pin->setup(); | ||||||
| @@ -43,6 +46,7 @@ bool BasicPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) { | |||||||
|   this->pin->attach_interrupt(BasicPulseCounterStorage::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE); |   this->pin->attach_interrupt(BasicPulseCounterStorage::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE); | ||||||
|   return true; |   return true; | ||||||
| } | } | ||||||
|  |  | ||||||
| pulse_counter_t BasicPulseCounterStorage::read_raw_value() { | pulse_counter_t BasicPulseCounterStorage::read_raw_value() { | ||||||
|   pulse_counter_t counter = this->counter; |   pulse_counter_t counter = this->counter; | ||||||
|   pulse_counter_t ret = counter - this->last_value; |   pulse_counter_t ret = counter - this->last_value; | ||||||
| @@ -141,6 +145,7 @@ bool HwPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) { | |||||||
|   } |   } | ||||||
|   return true; |   return true; | ||||||
| } | } | ||||||
|  |  | ||||||
| pulse_counter_t HwPulseCounterStorage::read_raw_value() { | pulse_counter_t HwPulseCounterStorage::read_raw_value() { | ||||||
|   pulse_counter_t counter; |   pulse_counter_t counter; | ||||||
|   pcnt_get_counter_value(this->pcnt_unit, &counter); |   pcnt_get_counter_value(this->pcnt_unit, &counter); | ||||||
| @@ -148,7 +153,7 @@ pulse_counter_t HwPulseCounterStorage::read_raw_value() { | |||||||
|   this->last_value = counter; |   this->last_value = counter; | ||||||
|   return ret; |   return ret; | ||||||
| } | } | ||||||
| #endif | #endif  // HAS_PCNT | ||||||
|  |  | ||||||
| void PulseCounterSensor::setup() { | void PulseCounterSensor::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Setting up pulse counter '%s'...", this->name_.c_str()); |   ESP_LOGCONFIG(TAG, "Setting up pulse counter '%s'...", this->name_.c_str()); | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ | |||||||
| #if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) | #if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) | ||||||
| #include <driver/pcnt.h> | #include <driver/pcnt.h> | ||||||
| #define HAS_PCNT | #define HAS_PCNT | ||||||
| #endif | #endif  // defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace pulse_counter { | namespace pulse_counter { | ||||||
| @@ -22,9 +22,9 @@ enum PulseCounterCountMode { | |||||||
|  |  | ||||||
| #ifdef HAS_PCNT | #ifdef HAS_PCNT | ||||||
| using pulse_counter_t = int16_t; | using pulse_counter_t = int16_t; | ||||||
| #else | #else   // HAS_PCNT | ||||||
| using pulse_counter_t = int32_t; | using pulse_counter_t = int32_t; | ||||||
| #endif | #endif  // HAS_PCNT | ||||||
|  |  | ||||||
| struct PulseCounterStorageBase { | struct PulseCounterStorageBase { | ||||||
|   virtual bool pulse_counter_setup(InternalGPIOPin *pin) = 0; |   virtual bool pulse_counter_setup(InternalGPIOPin *pin) = 0; | ||||||
| @@ -57,7 +57,7 @@ struct HwPulseCounterStorage : public PulseCounterStorageBase { | |||||||
|   pcnt_unit_t pcnt_unit; |   pcnt_unit_t pcnt_unit; | ||||||
|   pcnt_channel_t pcnt_channel; |   pcnt_channel_t pcnt_channel; | ||||||
| }; | }; | ||||||
| #endif | #endif  // HAS_PCNT | ||||||
|  |  | ||||||
| PulseCounterStorageBase *get_storage(bool hw_pcnt = false); | PulseCounterStorageBase *get_storage(bool hw_pcnt = false); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -81,16 +81,39 @@ void QMC5883LComponent::dump_config() { | |||||||
| } | } | ||||||
| float QMC5883LComponent::get_setup_priority() const { return setup_priority::DATA; } | float QMC5883LComponent::get_setup_priority() const { return setup_priority::DATA; } | ||||||
| void QMC5883LComponent::update() { | void QMC5883LComponent::update() { | ||||||
|  |   i2c::ErrorCode err; | ||||||
|   uint8_t status = false; |   uint8_t status = false; | ||||||
|   this->read_byte(QMC5883L_REGISTER_STATUS, &status); |   // Status byte gets cleared when data is read, so we have to read this first. | ||||||
|  |   // If status and two axes are desired, it's possible to save one byte of traffic by enabling | ||||||
|  |   // ROL_PNT in setup and reading 7 bytes starting at the status register. | ||||||
|  |   // If status and all three axes are desired, using ROL_PNT saves you 3 bytes. | ||||||
|  |   // But simply not reading status saves you 4 bytes always and is much simpler. | ||||||
|  |   if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG) { | ||||||
|  |     err = this->read_register(QMC5883L_REGISTER_STATUS, &status, 1); | ||||||
|  |     if (err != i2c::ERROR_OK) { | ||||||
|  |       this->status_set_warning(str_sprintf("status read failed (%d)", err).c_str()); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   // Always request X,Y,Z regardless if there are sensors for them |   uint16_t raw[3] = {0}; | ||||||
|   // to avoid https://github.com/esphome/issues/issues/5731 |   // Z must always be requested, otherwise the data registers will remain locked against updates. | ||||||
|   uint16_t raw_x, raw_y, raw_z; |   // Skipping the Y axis if X and Z are needed actually requires an additional byte of comms. | ||||||
|   if (!this->read_byte_16_(QMC5883L_REGISTER_DATA_X_LSB, &raw_x) || |   // Starting partway through the axes does save you traffic. | ||||||
|       !this->read_byte_16_(QMC5883L_REGISTER_DATA_Y_LSB, &raw_y) || |   uint8_t start, dest; | ||||||
|       !this->read_byte_16_(QMC5883L_REGISTER_DATA_Z_LSB, &raw_z)) { |   if (this->heading_sensor_ != nullptr || this->x_sensor_ != nullptr) { | ||||||
|     this->status_set_warning(); |     start = QMC5883L_REGISTER_DATA_X_LSB; | ||||||
|  |     dest = 0; | ||||||
|  |   } else if (this->y_sensor_ != nullptr) { | ||||||
|  |     start = QMC5883L_REGISTER_DATA_Y_LSB; | ||||||
|  |     dest = 1; | ||||||
|  |   } else { | ||||||
|  |     start = QMC5883L_REGISTER_DATA_Z_LSB; | ||||||
|  |     dest = 2; | ||||||
|  |   } | ||||||
|  |   err = this->read_bytes_16_le_(start, &raw[dest], 3 - dest); | ||||||
|  |   if (err != i2c::ERROR_OK) { | ||||||
|  |     this->status_set_warning(str_sprintf("mag read failed (%d)", err).c_str()); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -107,17 +130,18 @@ void QMC5883LComponent::update() { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   // in µT |   // in µT | ||||||
|   const float x = int16_t(raw_x) * mg_per_bit * 0.1f; |   const float x = int16_t(raw[0]) * mg_per_bit * 0.1f; | ||||||
|   const float y = int16_t(raw_y) * mg_per_bit * 0.1f; |   const float y = int16_t(raw[1]) * mg_per_bit * 0.1f; | ||||||
|   const float z = int16_t(raw_z) * mg_per_bit * 0.1f; |   const float z = int16_t(raw[2]) * mg_per_bit * 0.1f; | ||||||
|  |  | ||||||
|   float heading = atan2f(0.0f - x, y) * 180.0f / M_PI; |   float heading = atan2f(0.0f - x, y) * 180.0f / M_PI; | ||||||
|  |  | ||||||
|   float temp = NAN; |   float temp = NAN; | ||||||
|   if (this->temperature_sensor_ != nullptr) { |   if (this->temperature_sensor_ != nullptr) { | ||||||
|     uint16_t raw_temp; |     uint16_t raw_temp; | ||||||
|     if (!this->read_byte_16_(QMC5883L_REGISTER_TEMPERATURE_LSB, &raw_temp)) { |     err = this->read_bytes_16_le_(QMC5883L_REGISTER_TEMPERATURE_LSB, &raw_temp); | ||||||
|       this->status_set_warning(); |     if (err != i2c::ERROR_OK) { | ||||||
|  |       this->status_set_warning(str_sprintf("temp read failed (%d)", err).c_str()); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     temp = int16_t(raw_temp) * 0.01f; |     temp = int16_t(raw_temp) * 0.01f; | ||||||
| @@ -138,11 +162,13 @@ void QMC5883LComponent::update() { | |||||||
|     this->temperature_sensor_->publish_state(temp); |     this->temperature_sensor_->publish_state(temp); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool QMC5883LComponent::read_byte_16_(uint8_t a_register, uint16_t *data) { | i2c::ErrorCode QMC5883LComponent::read_bytes_16_le_(uint8_t a_register, uint16_t *data, uint8_t len) { | ||||||
|   if (!this->read_byte_16(a_register, data)) |   i2c::ErrorCode err = this->read_register(a_register, reinterpret_cast<uint8_t *>(data), len * 2); | ||||||
|     return false; |   if (err != i2c::ERROR_OK) | ||||||
|   *data = (*data & 0x00FF) << 8 | (*data & 0xFF00) >> 8;  // Flip Byte order, LSB first; |     return err; | ||||||
|   return true; |   for (size_t i = 0; i < len; i++) | ||||||
|  |     data[i] = convert_little_endian(data[i]); | ||||||
|  |   return err; | ||||||
| } | } | ||||||
|  |  | ||||||
| }  // namespace qmc5883l | }  // namespace qmc5883l | ||||||
|   | |||||||
| @@ -55,7 +55,7 @@ class QMC5883LComponent : public PollingComponent, public i2c::I2CDevice { | |||||||
|     NONE = 0, |     NONE = 0, | ||||||
|     COMMUNICATION_FAILED, |     COMMUNICATION_FAILED, | ||||||
|   } error_code_; |   } error_code_; | ||||||
|   bool read_byte_16_(uint8_t a_register, uint16_t *data); |   i2c::ErrorCode read_bytes_16_le_(uint8_t a_register, uint16_t *data, uint8_t len = 1); | ||||||
|   HighFrequencyLoopRequester high_freq_; |   HighFrequencyLoopRequester high_freq_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1 +1,4 @@ | |||||||
| CODEOWNERS = ["@clydebarrow"] | CODEOWNERS = ["@clydebarrow"] | ||||||
|  |  | ||||||
|  | CONF_DRAW_FROM_ORIGIN = "draw_from_origin" | ||||||
|  | CONF_DRAW_ROUNDING = "draw_rounding" | ||||||
|   | |||||||
| @@ -24,6 +24,7 @@ from esphome.const import ( | |||||||
| ) | ) | ||||||
| from esphome.core import TimePeriod | from esphome.core import TimePeriod | ||||||
|  |  | ||||||
|  | from . import CONF_DRAW_FROM_ORIGIN, CONF_DRAW_ROUNDING | ||||||
| from .models import DriverChip | from .models import DriverChip | ||||||
|  |  | ||||||
| DEPENDENCIES = ["spi"] | DEPENDENCIES = ["spi"] | ||||||
| @@ -41,7 +42,6 @@ COLOR_ORDERS = { | |||||||
| } | } | ||||||
| DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema | DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema | ||||||
|  |  | ||||||
| CONF_DRAW_FROM_ORIGIN = "draw_from_origin" |  | ||||||
| DELAY_FLAG = 0xFF | DELAY_FLAG = 0xFF | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -78,14 +78,17 @@ def _validate(config): | |||||||
|     return config |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All( | def power_of_two(value): | ||||||
|     display.FULL_DISPLAY_SCHEMA.extend( |     value = cv.int_range(1, 128)(value) | ||||||
|  |     if value & (value - 1) != 0: | ||||||
|  |         raise cv.Invalid("value must be a power of two") | ||||||
|  |     return value | ||||||
|  |  | ||||||
|  |  | ||||||
|  | BASE_SCHEMA = display.FULL_DISPLAY_SCHEMA.extend( | ||||||
|     cv.Schema( |     cv.Schema( | ||||||
|         { |         { | ||||||
|             cv.GenerateID(): cv.declare_id(QSPI_DBI), |             cv.GenerateID(): cv.declare_id(QSPI_DBI), | ||||||
|                 cv.Required(CONF_MODEL): cv.one_of( |  | ||||||
|                     *DriverChip.chips.keys(), upper=True |  | ||||||
|                 ), |  | ||||||
|             cv.Optional(CONF_INIT_SEQUENCE): cv.ensure_list(map_sequence), |             cv.Optional(CONF_INIT_SEQUENCE): cv.ensure_list(map_sequence), | ||||||
|             cv.Required(CONF_DIMENSIONS): cv.Any( |             cv.Required(CONF_DIMENSIONS): cv.Any( | ||||||
|                 cv.dimensions, |                 cv.dimensions, | ||||||
| @@ -93,32 +96,17 @@ CONFIG_SCHEMA = cv.All( | |||||||
|                     { |                     { | ||||||
|                         cv.Required(CONF_WIDTH): validate_dimension, |                         cv.Required(CONF_WIDTH): validate_dimension, | ||||||
|                         cv.Required(CONF_HEIGHT): validate_dimension, |                         cv.Required(CONF_HEIGHT): validate_dimension, | ||||||
|                             cv.Optional( |                         cv.Optional(CONF_OFFSET_HEIGHT, default=0): validate_dimension, | ||||||
|                                 CONF_OFFSET_HEIGHT, default=0 |                         cv.Optional(CONF_OFFSET_WIDTH, default=0): validate_dimension, | ||||||
|                             ): validate_dimension, |  | ||||||
|                             cv.Optional( |  | ||||||
|                                 CONF_OFFSET_WIDTH, default=0 |  | ||||||
|                             ): validate_dimension, |  | ||||||
|                     } |                     } | ||||||
|                 ), |                 ), | ||||||
|             ), |             ), | ||||||
|                 cv.Optional(CONF_TRANSFORM): cv.Schema( |             cv.Optional(CONF_DRAW_FROM_ORIGIN, default=False): cv.boolean, | ||||||
|                     { |  | ||||||
|                         cv.Optional(CONF_MIRROR_X, default=False): cv.boolean, |  | ||||||
|                         cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean, |  | ||||||
|                         cv.Optional(CONF_SWAP_XY, default=False): cv.boolean, |  | ||||||
|                     } |  | ||||||
|                 ), |  | ||||||
|                 cv.Optional(CONF_COLOR_ORDER, default="RGB"): cv.enum( |  | ||||||
|                     COLOR_ORDERS, upper=True |  | ||||||
|                 ), |  | ||||||
|                 cv.Optional(CONF_INVERT_COLORS, default=False): cv.boolean, |  | ||||||
|             cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, |             cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, | ||||||
|             cv.Optional(CONF_ENABLE_PIN): pins.gpio_output_pin_schema, |             cv.Optional(CONF_ENABLE_PIN): pins.gpio_output_pin_schema, | ||||||
|             cv.Optional(CONF_BRIGHTNESS, default=0xD0): cv.int_range( |             cv.Optional(CONF_BRIGHTNESS, default=0xD0): cv.int_range( | ||||||
|                 0, 0xFF, min_included=True, max_included=True |                 0, 0xFF, min_included=True, max_included=True | ||||||
|             ), |             ), | ||||||
|                 cv.Optional(CONF_DRAW_FROM_ORIGIN, default=False): cv.boolean, |  | ||||||
|         } |         } | ||||||
|     ).extend( |     ).extend( | ||||||
|         spi.spi_device_schema( |         spi.spi_device_schema( | ||||||
| @@ -128,6 +116,43 @@ CONFIG_SCHEMA = cv.All( | |||||||
|             quad=True, |             quad=True, | ||||||
|         ) |         ) | ||||||
|     ) |     ) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def model_property(name, defaults, fallback): | ||||||
|  |     return cv.Optional(name, default=defaults.get(name, fallback)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def model_schema(defaults): | ||||||
|  |     transform = cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.Optional(CONF_MIRROR_X, default=False): cv.boolean, | ||||||
|  |             cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean, | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     if defaults.get(CONF_SWAP_XY, True): | ||||||
|  |         transform = transform.extend( | ||||||
|  |             { | ||||||
|  |                 cv.Optional(CONF_SWAP_XY, default=False): cv.boolean, | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|  |     return BASE_SCHEMA.extend( | ||||||
|  |         { | ||||||
|  |             model_property(CONF_INVERT_COLORS, defaults, False): cv.boolean, | ||||||
|  |             model_property(CONF_COLOR_ORDER, defaults, "RGB"): cv.enum( | ||||||
|  |                 COLOR_ORDERS, upper=True | ||||||
|  |             ), | ||||||
|  |             model_property(CONF_DRAW_ROUNDING, defaults, 2): power_of_two, | ||||||
|  |             cv.Optional(CONF_TRANSFORM): transform, | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.All( | ||||||
|  |     cv.typed_schema( | ||||||
|  |         {k.upper(): model_schema(v.defaults) for k, v in DriverChip.chips.items()}, | ||||||
|  |         upper=True, | ||||||
|  |         key=CONF_MODEL, | ||||||
|     ), |     ), | ||||||
|     cv.only_with_esp_idf, |     cv.only_with_esp_idf, | ||||||
| ) | ) | ||||||
| @@ -152,6 +177,7 @@ async def to_code(config): | |||||||
|     cg.add(var.set_brightness(config[CONF_BRIGHTNESS])) |     cg.add(var.set_brightness(config[CONF_BRIGHTNESS])) | ||||||
|     cg.add(var.set_model(config[CONF_MODEL])) |     cg.add(var.set_model(config[CONF_MODEL])) | ||||||
|     cg.add(var.set_draw_from_origin(config[CONF_DRAW_FROM_ORIGIN])) |     cg.add(var.set_draw_from_origin(config[CONF_DRAW_FROM_ORIGIN])) | ||||||
|  |     cg.add(var.set_draw_rounding(config[CONF_DRAW_ROUNDING])) | ||||||
|     if enable_pin := config.get(CONF_ENABLE_PIN): |     if enable_pin := config.get(CONF_ENABLE_PIN): | ||||||
|         enable = await cg.gpio_pin_expression(enable_pin) |         enable = await cg.gpio_pin_expression(enable_pin) | ||||||
|         cg.add(var.set_enable_pin(enable)) |         cg.add(var.set_enable_pin(enable)) | ||||||
| @@ -163,7 +189,8 @@ async def to_code(config): | |||||||
|     if transform := config.get(CONF_TRANSFORM): |     if transform := config.get(CONF_TRANSFORM): | ||||||
|         cg.add(var.set_mirror_x(transform[CONF_MIRROR_X])) |         cg.add(var.set_mirror_x(transform[CONF_MIRROR_X])) | ||||||
|         cg.add(var.set_mirror_y(transform[CONF_MIRROR_Y])) |         cg.add(var.set_mirror_y(transform[CONF_MIRROR_Y])) | ||||||
|         cg.add(var.set_swap_xy(transform[CONF_SWAP_XY])) |         # swap_xy is not implemented for some chips | ||||||
|  |         cg.add(var.set_swap_xy(transform.get(CONF_SWAP_XY, False))) | ||||||
|  |  | ||||||
|     if CONF_DIMENSIONS in config: |     if CONF_DIMENSIONS in config: | ||||||
|         dimensions = config[CONF_DIMENSIONS] |         dimensions = config[CONF_DIMENSIONS] | ||||||
|   | |||||||
| @@ -1,5 +1,10 @@ | |||||||
| # Commands | # Commands | ||||||
|  | from esphome.const import CONF_INVERT_COLORS, CONF_SWAP_XY | ||||||
|  |  | ||||||
|  | from . import CONF_DRAW_ROUNDING | ||||||
|  |  | ||||||
| SW_RESET_CMD = 0x01 | SW_RESET_CMD = 0x01 | ||||||
|  | SLEEP_IN = 0x10 | ||||||
| SLEEP_OUT = 0x11 | SLEEP_OUT = 0x11 | ||||||
| NORON = 0x13 | NORON = 0x13 | ||||||
| INVERT_OFF = 0x20 | INVERT_OFF = 0x20 | ||||||
| @@ -24,11 +29,12 @@ PAGESEL = 0xFE | |||||||
| class DriverChip: | class DriverChip: | ||||||
|     chips = {} |     chips = {} | ||||||
|  |  | ||||||
|     def __init__(self, name: str): |     def __init__(self, name: str, defaults=None): | ||||||
|         name = name.upper() |         name = name.upper() | ||||||
|         self.name = name |         self.name = name | ||||||
|         self.chips[name] = self |         self.chips[name] = self | ||||||
|         self.initsequence = [] |         self.initsequence = [] | ||||||
|  |         self.defaults = defaults or {} | ||||||
|  |  | ||||||
|     def cmd(self, c, *args): |     def cmd(self, c, *args): | ||||||
|         """ |         """ | ||||||
| @@ -59,9 +65,246 @@ chip.cmd(TEON, 0x00) | |||||||
| chip.cmd(PIXFMT, 0x55) | chip.cmd(PIXFMT, 0x55) | ||||||
| chip.cmd(NORON) | chip.cmd(NORON) | ||||||
|  |  | ||||||
| chip = DriverChip("AXS15231") | chip = DriverChip("AXS15231", {CONF_DRAW_ROUNDING: 8, CONF_SWAP_XY: False}) | ||||||
| chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xA5) | chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xA5) | ||||||
| chip.cmd(0xC1, 0x33) | chip.cmd(0xC1, 0x33) | ||||||
| chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) | chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) | ||||||
|  |  | ||||||
|  | chip = DriverChip( | ||||||
|  |     "JC4832W535", | ||||||
|  |     { | ||||||
|  |         CONF_DRAW_ROUNDING: 8, | ||||||
|  |         CONF_SWAP_XY: False, | ||||||
|  |     }, | ||||||
|  | ) | ||||||
|  | chip.cmd(DISPLAY_OFF) | ||||||
|  | chip.delay(20) | ||||||
|  | chip.cmd(SLEEP_IN) | ||||||
|  | chip.delay(80) | ||||||
|  | chip.cmd(SLEEP_OUT) | ||||||
|  | chip.cmd(INVERT_OFF) | ||||||
|  | # A magic sequence to enable the windowed drawing mode | ||||||
|  | chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xA5) | ||||||
|  | chip.cmd(0xC1, 0x33) | ||||||
|  | chip.cmd(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) | ||||||
|  |  | ||||||
|  | chip = DriverChip("JC3636W518", {CONF_INVERT_COLORS: True}) | ||||||
|  | chip.cmd(0xF0, 0x08) | ||||||
|  | chip.cmd(0xF2, 0x08) | ||||||
|  | chip.cmd(0x9B, 0x51) | ||||||
|  | chip.cmd(0x86, 0x53) | ||||||
|  | chip.cmd(0xF2, 0x80) | ||||||
|  | chip.cmd(0xF0, 0x00) | ||||||
|  | chip.cmd(0xF0, 0x01) | ||||||
|  | chip.cmd(0xF1, 0x01) | ||||||
|  | chip.cmd(0xB0, 0x54) | ||||||
|  | chip.cmd(0xB1, 0x3F) | ||||||
|  | chip.cmd(0xB2, 0x2A) | ||||||
|  | chip.cmd(0xB4, 0x46) | ||||||
|  | chip.cmd(0xB5, 0x34) | ||||||
|  | chip.cmd(0xB6, 0xD5) | ||||||
|  | chip.cmd(0xB7, 0x30) | ||||||
|  | chip.cmd(0xBA, 0x00) | ||||||
|  | chip.cmd(0xBB, 0x08) | ||||||
|  | chip.cmd(0xBC, 0x08) | ||||||
|  | chip.cmd(0xBD, 0x00) | ||||||
|  | chip.cmd(0xC0, 0x80) | ||||||
|  | chip.cmd(0xC1, 0x10) | ||||||
|  | chip.cmd(0xC2, 0x37) | ||||||
|  | chip.cmd(0xC3, 0x80) | ||||||
|  | chip.cmd(0xC4, 0x10) | ||||||
|  | chip.cmd(0xC5, 0x37) | ||||||
|  | chip.cmd(0xC6, 0xA9) | ||||||
|  | chip.cmd(0xC7, 0x41) | ||||||
|  | chip.cmd(0xC8, 0x51) | ||||||
|  | chip.cmd(0xC9, 0xA9) | ||||||
|  | chip.cmd(0xCA, 0x41) | ||||||
|  | chip.cmd(0xCB, 0x51) | ||||||
|  | chip.cmd(0xD0, 0x91) | ||||||
|  | chip.cmd(0xD1, 0x68) | ||||||
|  | chip.cmd(0xD2, 0x69) | ||||||
|  | chip.cmd(0xF5, 0x00, 0xA5) | ||||||
|  | chip.cmd(0xDD, 0x3F) | ||||||
|  | chip.cmd(0xDE, 0x3F) | ||||||
|  | chip.cmd(0xF1, 0x10) | ||||||
|  | chip.cmd(0xF0, 0x00) | ||||||
|  | chip.cmd(0xF0, 0x02) | ||||||
|  | chip.cmd( | ||||||
|  |     0xE0, | ||||||
|  |     0x70, | ||||||
|  |     0x09, | ||||||
|  |     0x12, | ||||||
|  |     0x0C, | ||||||
|  |     0x0B, | ||||||
|  |     0x27, | ||||||
|  |     0x38, | ||||||
|  |     0x54, | ||||||
|  |     0x4E, | ||||||
|  |     0x19, | ||||||
|  |     0x15, | ||||||
|  |     0x15, | ||||||
|  |     0x2C, | ||||||
|  |     0x2F, | ||||||
|  | ) | ||||||
|  | chip.cmd( | ||||||
|  |     0xE1, | ||||||
|  |     0x70, | ||||||
|  |     0x08, | ||||||
|  |     0x11, | ||||||
|  |     0x0C, | ||||||
|  |     0x0B, | ||||||
|  |     0x27, | ||||||
|  |     0x38, | ||||||
|  |     0x43, | ||||||
|  |     0x4C, | ||||||
|  |     0x18, | ||||||
|  |     0x14, | ||||||
|  |     0x14, | ||||||
|  |     0x2B, | ||||||
|  |     0x2D, | ||||||
|  | ) | ||||||
|  | chip.cmd(0xF0, 0x10) | ||||||
|  | chip.cmd(0xF3, 0x10) | ||||||
|  | chip.cmd(0xE0, 0x08) | ||||||
|  | chip.cmd(0xE1, 0x00) | ||||||
|  | chip.cmd(0xE2, 0x00) | ||||||
|  | chip.cmd(0xE3, 0x00) | ||||||
|  | chip.cmd(0xE4, 0xE0) | ||||||
|  | chip.cmd(0xE5, 0x06) | ||||||
|  | chip.cmd(0xE6, 0x21) | ||||||
|  | chip.cmd(0xE7, 0x00) | ||||||
|  | chip.cmd(0xE8, 0x05) | ||||||
|  | chip.cmd(0xE9, 0x82) | ||||||
|  | chip.cmd(0xEA, 0xDF) | ||||||
|  | chip.cmd(0xEB, 0x89) | ||||||
|  | chip.cmd(0xEC, 0x20) | ||||||
|  | chip.cmd(0xED, 0x14) | ||||||
|  | chip.cmd(0xEE, 0xFF) | ||||||
|  | chip.cmd(0xEF, 0x00) | ||||||
|  | chip.cmd(0xF8, 0xFF) | ||||||
|  | chip.cmd(0xF9, 0x00) | ||||||
|  | chip.cmd(0xFA, 0x00) | ||||||
|  | chip.cmd(0xFB, 0x30) | ||||||
|  | chip.cmd(0xFC, 0x00) | ||||||
|  | chip.cmd(0xFD, 0x00) | ||||||
|  | chip.cmd(0xFE, 0x00) | ||||||
|  | chip.cmd(0xFF, 0x00) | ||||||
|  | chip.cmd(0x60, 0x42) | ||||||
|  | chip.cmd(0x61, 0xE0) | ||||||
|  | chip.cmd(0x62, 0x40) | ||||||
|  | chip.cmd(0x63, 0x40) | ||||||
|  | chip.cmd(0x64, 0x02) | ||||||
|  | chip.cmd(0x65, 0x00) | ||||||
|  | chip.cmd(0x66, 0x40) | ||||||
|  | chip.cmd(0x67, 0x03) | ||||||
|  | chip.cmd(0x68, 0x00) | ||||||
|  | chip.cmd(0x69, 0x00) | ||||||
|  | chip.cmd(0x6A, 0x00) | ||||||
|  | chip.cmd(0x6B, 0x00) | ||||||
|  | chip.cmd(0x70, 0x42) | ||||||
|  | chip.cmd(0x71, 0xE0) | ||||||
|  | chip.cmd(0x72, 0x40) | ||||||
|  | chip.cmd(0x73, 0x40) | ||||||
|  | chip.cmd(0x74, 0x02) | ||||||
|  | chip.cmd(0x75, 0x00) | ||||||
|  | chip.cmd(0x76, 0x40) | ||||||
|  | chip.cmd(0x77, 0x03) | ||||||
|  | chip.cmd(0x78, 0x00) | ||||||
|  | chip.cmd(0x79, 0x00) | ||||||
|  | chip.cmd(0x7A, 0x00) | ||||||
|  | chip.cmd(0x7B, 0x00) | ||||||
|  | chip.cmd(0x80, 0x48) | ||||||
|  | chip.cmd(0x81, 0x00) | ||||||
|  | chip.cmd(0x82, 0x05) | ||||||
|  | chip.cmd(0x83, 0x02) | ||||||
|  | chip.cmd(0x84, 0xDD) | ||||||
|  | chip.cmd(0x85, 0x00) | ||||||
|  | chip.cmd(0x86, 0x00) | ||||||
|  | chip.cmd(0x87, 0x00) | ||||||
|  | chip.cmd(0x88, 0x48) | ||||||
|  | chip.cmd(0x89, 0x00) | ||||||
|  | chip.cmd(0x8A, 0x07) | ||||||
|  | chip.cmd(0x8B, 0x02) | ||||||
|  | chip.cmd(0x8C, 0xDF) | ||||||
|  | chip.cmd(0x8D, 0x00) | ||||||
|  | chip.cmd(0x8E, 0x00) | ||||||
|  | chip.cmd(0x8F, 0x00) | ||||||
|  | chip.cmd(0x90, 0x48) | ||||||
|  | chip.cmd(0x91, 0x00) | ||||||
|  | chip.cmd(0x92, 0x09) | ||||||
|  | chip.cmd(0x93, 0x02) | ||||||
|  | chip.cmd(0x94, 0xE1) | ||||||
|  | chip.cmd(0x95, 0x00) | ||||||
|  | chip.cmd(0x96, 0x00) | ||||||
|  | chip.cmd(0x97, 0x00) | ||||||
|  | chip.cmd(0x98, 0x48) | ||||||
|  | chip.cmd(0x99, 0x00) | ||||||
|  | chip.cmd(0x9A, 0x0B) | ||||||
|  | chip.cmd(0x9B, 0x02) | ||||||
|  | chip.cmd(0x9C, 0xE3) | ||||||
|  | chip.cmd(0x9D, 0x00) | ||||||
|  | chip.cmd(0x9E, 0x00) | ||||||
|  | chip.cmd(0x9F, 0x00) | ||||||
|  | chip.cmd(0xA0, 0x48) | ||||||
|  | chip.cmd(0xA1, 0x00) | ||||||
|  | chip.cmd(0xA2, 0x04) | ||||||
|  | chip.cmd(0xA3, 0x02) | ||||||
|  | chip.cmd(0xA4, 0xDC) | ||||||
|  | chip.cmd(0xA5, 0x00) | ||||||
|  | chip.cmd(0xA6, 0x00) | ||||||
|  | chip.cmd(0xA7, 0x00) | ||||||
|  | chip.cmd(0xA8, 0x48) | ||||||
|  | chip.cmd(0xA9, 0x00) | ||||||
|  | chip.cmd(0xAA, 0x06) | ||||||
|  | chip.cmd(0xAB, 0x02) | ||||||
|  | chip.cmd(0xAC, 0xDE) | ||||||
|  | chip.cmd(0xAD, 0x00) | ||||||
|  | chip.cmd(0xAE, 0x00) | ||||||
|  | chip.cmd(0xAF, 0x00) | ||||||
|  | chip.cmd(0xB0, 0x48) | ||||||
|  | chip.cmd(0xB1, 0x00) | ||||||
|  | chip.cmd(0xB2, 0x08) | ||||||
|  | chip.cmd(0xB3, 0x02) | ||||||
|  | chip.cmd(0xB4, 0xE0) | ||||||
|  | chip.cmd(0xB5, 0x00) | ||||||
|  | chip.cmd(0xB6, 0x00) | ||||||
|  | chip.cmd(0xB7, 0x00) | ||||||
|  | chip.cmd(0xB8, 0x48) | ||||||
|  | chip.cmd(0xB9, 0x00) | ||||||
|  | chip.cmd(0xBA, 0x0A) | ||||||
|  | chip.cmd(0xBB, 0x02) | ||||||
|  | chip.cmd(0xBC, 0xE2) | ||||||
|  | chip.cmd(0xBD, 0x00) | ||||||
|  | chip.cmd(0xBE, 0x00) | ||||||
|  | chip.cmd(0xBF, 0x00) | ||||||
|  | chip.cmd(0xC0, 0x12) | ||||||
|  | chip.cmd(0xC1, 0xAA) | ||||||
|  | chip.cmd(0xC2, 0x65) | ||||||
|  | chip.cmd(0xC3, 0x74) | ||||||
|  | chip.cmd(0xC4, 0x47) | ||||||
|  | chip.cmd(0xC5, 0x56) | ||||||
|  | chip.cmd(0xC6, 0x00) | ||||||
|  | chip.cmd(0xC7, 0x88) | ||||||
|  | chip.cmd(0xC8, 0x99) | ||||||
|  | chip.cmd(0xC9, 0x33) | ||||||
|  | chip.cmd(0xD0, 0x21) | ||||||
|  | chip.cmd(0xD1, 0xAA) | ||||||
|  | chip.cmd(0xD2, 0x65) | ||||||
|  | chip.cmd(0xD3, 0x74) | ||||||
|  | chip.cmd(0xD4, 0x47) | ||||||
|  | chip.cmd(0xD5, 0x56) | ||||||
|  | chip.cmd(0xD6, 0x00) | ||||||
|  | chip.cmd(0xD7, 0x88) | ||||||
|  | chip.cmd(0xD8, 0x99) | ||||||
|  | chip.cmd(0xD9, 0x33) | ||||||
|  | chip.cmd(0xF3, 0x01) | ||||||
|  | chip.cmd(0xF0, 0x00) | ||||||
|  | chip.cmd(0xF0, 0x01) | ||||||
|  | chip.cmd(0xF1, 0x01) | ||||||
|  | chip.cmd(0xA0, 0x0B) | ||||||
|  | chip.cmd(0xA3, 0x2A) | ||||||
|  | chip.cmd(0xA5, 0xC3) | ||||||
|  | chip.cmd(PIXFMT, 0x55) | ||||||
|  |  | ||||||
|  |  | ||||||
| DriverChip("Custom") | DriverChip("Custom") | ||||||
|   | |||||||
| @@ -33,19 +33,12 @@ void QspiDbi::update() { | |||||||
|   this->do_update_(); |   this->do_update_(); | ||||||
|   if (this->buffer_ == nullptr || this->x_low_ > this->x_high_ || this->y_low_ > this->y_high_) |   if (this->buffer_ == nullptr || this->x_low_ > this->x_high_ || this->y_low_ > this->y_high_) | ||||||
|     return; |     return; | ||||||
|   // Start addresses and widths/heights must be divisible by 2 (CASET/RASET restriction in datasheet) |   // Some chips require that the drawing window be aligned on certain boundaries | ||||||
|   if (this->x_low_ % 2 == 1) { |   auto dr = this->draw_rounding_; | ||||||
|     this->x_low_--; |   this->x_low_ = this->x_low_ / dr * dr; | ||||||
|   } |   this->y_low_ = this->y_low_ / dr * dr; | ||||||
|   if (this->x_high_ % 2 == 0) { |   this->x_high_ = (this->x_high_ + dr) / dr * dr - 1; | ||||||
|     this->x_high_++; |   this->y_high_ = (this->y_high_ + dr) / dr * dr - 1; | ||||||
|   } |  | ||||||
|   if (this->y_low_ % 2 == 1) { |  | ||||||
|     this->y_low_--; |  | ||||||
|   } |  | ||||||
|   if (this->y_high_ % 2 == 0) { |  | ||||||
|     this->y_high_++; |  | ||||||
|   } |  | ||||||
|   if (this->draw_from_origin_) { |   if (this->draw_from_origin_) { | ||||||
|     this->x_low_ = 0; |     this->x_low_ = 0; | ||||||
|     this->y_low_ = 0; |     this->y_low_ = 0; | ||||||
| @@ -175,10 +168,9 @@ void QspiDbi::write_to_display_(int x_start, int y_start, int w, int h, const ui | |||||||
|     this->write_cmd_addr_data(8, 0x32, 24, 0x2C00, ptr, w * h * 2, 4); |     this->write_cmd_addr_data(8, 0x32, 24, 0x2C00, ptr, w * h * 2, 4); | ||||||
|   } else { |   } else { | ||||||
|     auto stride = x_offset + w + x_pad; |     auto stride = x_offset + w + x_pad; | ||||||
|     uint16_t cmd = 0x2C00; |     this->write_cmd_addr_data(8, 0x32, 24, 0x2C00, nullptr, 0, 4); | ||||||
|     for (int y = 0; y != h; y++) { |     for (int y = 0; y != h; y++) { | ||||||
|       this->write_cmd_addr_data(8, 0x32, 24, cmd, ptr + ((y + y_offset) * stride + x_offset) * 2, w * 2, 4); |       this->write_cmd_addr_data(0, 0, 0, 0, ptr + ((y + y_offset) * stride + x_offset) * 2, w * 2, 4); | ||||||
|       cmd = 0x3C00; |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   this->disable(); |   this->disable(); | ||||||
| @@ -220,6 +212,7 @@ void QspiDbi::dump_config() { | |||||||
|   ESP_LOGCONFIG("", "Model: %s", this->model_); |   ESP_LOGCONFIG("", "Model: %s", this->model_); | ||||||
|   ESP_LOGCONFIG(TAG, "  Height: %u", this->height_); |   ESP_LOGCONFIG(TAG, "  Height: %u", this->height_); | ||||||
|   ESP_LOGCONFIG(TAG, "  Width: %u", this->width_); |   ESP_LOGCONFIG(TAG, "  Width: %u", this->width_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Draw rounding: %u", this->draw_rounding_); | ||||||
|   LOG_PIN("  CS Pin: ", this->cs_); |   LOG_PIN("  CS Pin: ", this->cs_); | ||||||
|   LOG_PIN("  Reset Pin: ", this->reset_pin_); |   LOG_PIN("  Reset Pin: ", this->reset_pin_); | ||||||
|   ESP_LOGCONFIG(TAG, "  SPI Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000)); |   ESP_LOGCONFIG(TAG, "  SPI Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000)); | ||||||
|   | |||||||
| @@ -4,12 +4,10 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #ifdef USE_ESP_IDF | #ifdef USE_ESP_IDF | ||||||
| #include "esphome/core/component.h" |  | ||||||
| #include "esphome/components/spi/spi.h" | #include "esphome/components/spi/spi.h" | ||||||
| #include "esphome/components/display/display.h" | #include "esphome/components/display/display.h" | ||||||
| #include "esphome/components/display/display_buffer.h" | #include "esphome/components/display/display_buffer.h" | ||||||
| #include "esphome/components/display/display_color_utils.h" | #include "esphome/components/display/display_color_utils.h" | ||||||
| #include "esp_lcd_panel_ops.h" |  | ||||||
|  |  | ||||||
| #include "esp_lcd_panel_rgb.h" | #include "esp_lcd_panel_rgb.h" | ||||||
|  |  | ||||||
| @@ -105,6 +103,7 @@ class QspiDbi : public display::DisplayBuffer, | |||||||
|   int get_height_internal() override { return this->height_; } |   int get_height_internal() override { return this->height_; } | ||||||
|   bool can_proceed() override { return this->setup_complete_; } |   bool can_proceed() override { return this->setup_complete_; } | ||||||
|   void add_init_sequence(const std::vector<uint8_t> &sequence) { this->init_sequences_.push_back(sequence); } |   void add_init_sequence(const std::vector<uint8_t> &sequence) { this->init_sequences_.push_back(sequence); } | ||||||
|  |   void set_draw_rounding(unsigned rounding) { this->draw_rounding_ = rounding; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   void check_buffer_() { |   void check_buffer_() { | ||||||
| @@ -161,6 +160,7 @@ class QspiDbi : public display::DisplayBuffer, | |||||||
|   bool mirror_x_{}; |   bool mirror_x_{}; | ||||||
|   bool mirror_y_{}; |   bool mirror_y_{}; | ||||||
|   bool draw_from_origin_{false}; |   bool draw_from_origin_{false}; | ||||||
|  |   unsigned draw_rounding_{2}; | ||||||
|   uint8_t brightness_{0xD0}; |   uint8_t brightness_{0xD0}; | ||||||
|   const char *model_{"Unknown"}; |   const char *model_{"Unknown"}; | ||||||
|   std::vector<std::vector<uint8_t>> init_sequences_{}; |   std::vector<std::vector<uint8_t>> init_sequences_{}; | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ namespace remote_base { | |||||||
|  |  | ||||||
| static const char *const TAG = "remote_base"; | static const char *const TAG = "remote_base"; | ||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR < 5 | ||||||
| RemoteRMTChannel::RemoteRMTChannel(uint8_t mem_block_num) : mem_block_num_(mem_block_num) { | RemoteRMTChannel::RemoteRMTChannel(uint8_t mem_block_num) : mem_block_num_(mem_block_num) { | ||||||
|   static rmt_channel_t next_rmt_channel = RMT_CHANNEL_0; |   static rmt_channel_t next_rmt_channel = RMT_CHANNEL_0; | ||||||
|   this->channel_ = next_rmt_channel; |   this->channel_ = next_rmt_channel; | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ | |||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
| #include "esphome/core/hal.h" | #include "esphome/core/hal.h" | ||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR < 5 | ||||||
| #include <driver/rmt.h> | #include <driver/rmt.h> | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| @@ -112,25 +112,43 @@ class RemoteComponentBase { | |||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
| class RemoteRMTChannel { | class RemoteRMTChannel { | ||||||
|  public: |  public: | ||||||
|  | #if ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  |   void set_clock_resolution(uint32_t clock_resolution) { this->clock_resolution_ = clock_resolution; } | ||||||
|  |   void set_rmt_symbols(uint32_t rmt_symbols) { this->rmt_symbols_ = rmt_symbols; } | ||||||
|  | #else | ||||||
|   explicit RemoteRMTChannel(uint8_t mem_block_num = 1); |   explicit RemoteRMTChannel(uint8_t mem_block_num = 1); | ||||||
|   explicit RemoteRMTChannel(rmt_channel_t channel, uint8_t mem_block_num = 1); |   explicit RemoteRMTChannel(rmt_channel_t channel, uint8_t mem_block_num = 1); | ||||||
|  |  | ||||||
|   void config_rmt(rmt_config_t &rmt); |   void config_rmt(rmt_config_t &rmt); | ||||||
|   void set_clock_divider(uint8_t clock_divider) { this->clock_divider_ = clock_divider; } |   void set_clock_divider(uint8_t clock_divider) { this->clock_divider_ = clock_divider; } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   uint32_t from_microseconds_(uint32_t us) { |   uint32_t from_microseconds_(uint32_t us) { | ||||||
|  | #if ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  |     const uint32_t ticks_per_ten_us = this->clock_resolution_ / 100000u; | ||||||
|  | #else | ||||||
|     const uint32_t ticks_per_ten_us = 80000000u / this->clock_divider_ / 100000u; |     const uint32_t ticks_per_ten_us = 80000000u / this->clock_divider_ / 100000u; | ||||||
|  | #endif | ||||||
|     return us * ticks_per_ten_us / 10; |     return us * ticks_per_ten_us / 10; | ||||||
|   } |   } | ||||||
|   uint32_t to_microseconds_(uint32_t ticks) { |   uint32_t to_microseconds_(uint32_t ticks) { | ||||||
|  | #if ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  |     const uint32_t ticks_per_ten_us = this->clock_resolution_ / 100000u; | ||||||
|  | #else | ||||||
|     const uint32_t ticks_per_ten_us = 80000000u / this->clock_divider_ / 100000u; |     const uint32_t ticks_per_ten_us = 80000000u / this->clock_divider_ / 100000u; | ||||||
|  | #endif | ||||||
|     return (ticks * 10) / ticks_per_ten_us; |     return (ticks * 10) / ticks_per_ten_us; | ||||||
|   } |   } | ||||||
|   RemoteComponentBase *remote_base_; |   RemoteComponentBase *remote_base_; | ||||||
|  | #if ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  |   uint32_t clock_resolution_{1000000}; | ||||||
|  |   uint32_t rmt_symbols_; | ||||||
|  | #else | ||||||
|   rmt_channel_t channel_{RMT_CHANNEL_0}; |   rmt_channel_t channel_{RMT_CHANNEL_0}; | ||||||
|   uint8_t mem_block_num_; |   uint8_t mem_block_num_; | ||||||
|   uint8_t clock_divider_{80}; |   uint8_t clock_divider_{80}; | ||||||
|  | #endif | ||||||
| }; | }; | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,23 +1,28 @@ | |||||||
| import esphome.codegen as cg |  | ||||||
| import esphome.config_validation as cv |  | ||||||
| from esphome import pins | from esphome import pins | ||||||
| from esphome.components import remote_base, esp32_rmt | import esphome.codegen as cg | ||||||
|  | from esphome.components import esp32, esp32_rmt, remote_base | ||||||
|  | import esphome.config_validation as cv | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_BUFFER_SIZE, |     CONF_BUFFER_SIZE, | ||||||
|  |     CONF_CLOCK_DIVIDER, | ||||||
|  |     CONF_CLOCK_RESOLUTION, | ||||||
|     CONF_DUMP, |     CONF_DUMP, | ||||||
|     CONF_FILTER, |     CONF_FILTER, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     CONF_IDLE, |     CONF_IDLE, | ||||||
|  |     CONF_MEMORY_BLOCKS, | ||||||
|     CONF_PIN, |     CONF_PIN, | ||||||
|  |     CONF_RMT_CHANNEL, | ||||||
|  |     CONF_RMT_SYMBOLS, | ||||||
|     CONF_TOLERANCE, |     CONF_TOLERANCE, | ||||||
|     CONF_TYPE, |     CONF_TYPE, | ||||||
|     CONF_MEMORY_BLOCKS, |     CONF_USE_DMA, | ||||||
|     CONF_RMT_CHANNEL, |  | ||||||
|     CONF_VALUE, |     CONF_VALUE, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, TimePeriod | from esphome.core import CORE, TimePeriod | ||||||
|  |  | ||||||
| CONF_CLOCK_DIVIDER = "clock_divider" | CONF_FILTER_SYMBOLS = "filter_symbols" | ||||||
|  | CONF_RECEIVE_SYMBOLS = "receive_symbols" | ||||||
|  |  | ||||||
| AUTO_LOAD = ["remote_base"] | AUTO_LOAD = ["remote_base"] | ||||||
| remote_receiver_ns = cg.esphome_ns.namespace("remote_receiver") | remote_receiver_ns = cg.esphome_ns.namespace("remote_receiver") | ||||||
| @@ -98,15 +103,43 @@ CONFIG_SCHEMA = remote_base.validate_triggers( | |||||||
|                 cv.positive_time_period_microseconds, |                 cv.positive_time_period_microseconds, | ||||||
|                 cv.Range(max=TimePeriod(microseconds=4294967295)), |                 cv.Range(max=TimePeriod(microseconds=4294967295)), | ||||||
|             ), |             ), | ||||||
|             cv.SplitDefault(CONF_CLOCK_DIVIDER, esp32=80): cv.All( |             cv.SplitDefault(CONF_CLOCK_DIVIDER, esp32_arduino=80): cv.All( | ||||||
|                 cv.only_on_esp32, cv.Range(min=1, max=255) |                 cv.only_on_esp32, | ||||||
|  |                 cv.only_with_arduino, | ||||||
|  |                 cv.int_range(min=1, max=255), | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_CLOCK_RESOLUTION): cv.All( | ||||||
|  |                 cv.only_on_esp32, | ||||||
|  |                 cv.only_with_esp_idf, | ||||||
|  |                 esp32_rmt.validate_clock_resolution(), | ||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_IDLE, default="10ms"): cv.All( |             cv.Optional(CONF_IDLE, default="10ms"): cv.All( | ||||||
|                 cv.positive_time_period_microseconds, |                 cv.positive_time_period_microseconds, | ||||||
|                 cv.Range(max=TimePeriod(microseconds=4294967295)), |                 cv.Range(max=TimePeriod(microseconds=4294967295)), | ||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_MEMORY_BLOCKS, default=3): cv.Range(min=1, max=8), |             cv.SplitDefault(CONF_MEMORY_BLOCKS, esp32_arduino=3): cv.All( | ||||||
|             cv.Optional(CONF_RMT_CHANNEL): esp32_rmt.validate_rmt_channel(tx=False), |                 cv.only_with_arduino, cv.int_range(min=1, max=8) | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_RMT_CHANNEL): cv.All( | ||||||
|  |                 cv.only_with_arduino, esp32_rmt.validate_rmt_channel(tx=False) | ||||||
|  |             ), | ||||||
|  |             cv.SplitDefault( | ||||||
|  |                 CONF_RMT_SYMBOLS, | ||||||
|  |                 esp32_idf=192, | ||||||
|  |                 esp32_s2_idf=192, | ||||||
|  |                 esp32_s3_idf=192, | ||||||
|  |                 esp32_c3_idf=96, | ||||||
|  |                 esp32_c6_idf=96, | ||||||
|  |                 esp32_h2_idf=96, | ||||||
|  |             ): cv.All(cv.only_with_esp_idf, cv.int_range(min=2)), | ||||||
|  |             cv.Optional(CONF_FILTER_SYMBOLS): cv.All( | ||||||
|  |                 cv.only_with_esp_idf, cv.int_range(min=0) | ||||||
|  |             ), | ||||||
|  |             cv.SplitDefault( | ||||||
|  |                 CONF_RECEIVE_SYMBOLS, | ||||||
|  |                 esp32_idf=192, | ||||||
|  |             ): cv.All(cv.only_with_esp_idf, cv.int_range(min=2)), | ||||||
|  |             cv.Optional(CONF_USE_DMA): cv.All(cv.only_with_esp_idf, cv.boolean), | ||||||
|         } |         } | ||||||
|     ).extend(cv.COMPONENT_SCHEMA) |     ).extend(cv.COMPONENT_SCHEMA) | ||||||
| ) | ) | ||||||
| @@ -115,6 +148,20 @@ CONFIG_SCHEMA = remote_base.validate_triggers( | |||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     pin = await cg.gpio_pin_expression(config[CONF_PIN]) |     pin = await cg.gpio_pin_expression(config[CONF_PIN]) | ||||||
|     if CORE.is_esp32: |     if CORE.is_esp32: | ||||||
|  |         if esp32_rmt.use_new_rmt_driver(): | ||||||
|  |             var = cg.new_Pvariable(config[CONF_ID], pin) | ||||||
|  |             cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS])) | ||||||
|  |             cg.add(var.set_receive_symbols(config[CONF_RECEIVE_SYMBOLS])) | ||||||
|  |             if CONF_USE_DMA in config: | ||||||
|  |                 cg.add(var.set_with_dma(config[CONF_USE_DMA])) | ||||||
|  |             if CONF_CLOCK_RESOLUTION in config: | ||||||
|  |                 cg.add(var.set_clock_resolution(config[CONF_CLOCK_RESOLUTION])) | ||||||
|  |             if CONF_FILTER_SYMBOLS in config: | ||||||
|  |                 cg.add(var.set_filter_symbols(config[CONF_FILTER_SYMBOLS])) | ||||||
|  |             if CORE.using_esp_idf: | ||||||
|  |                 esp32.add_idf_sdkconfig_option("CONFIG_RMT_RECV_FUNC_IN_IRAM", True) | ||||||
|  |                 esp32.add_idf_sdkconfig_option("CONFIG_RMT_ISR_IRAM_SAFE", True) | ||||||
|  |         else: | ||||||
|             if (rmt_channel := config.get(CONF_RMT_CHANNEL, None)) is not None: |             if (rmt_channel := config.get(CONF_RMT_CHANNEL, None)) is not None: | ||||||
|                 var = cg.new_Pvariable( |                 var = cg.new_Pvariable( | ||||||
|                     config[CONF_ID], pin, rmt_channel, config[CONF_MEMORY_BLOCKS] |                     config[CONF_ID], pin, rmt_channel, config[CONF_MEMORY_BLOCKS] | ||||||
|   | |||||||
| @@ -5,6 +5,10 @@ | |||||||
|  |  | ||||||
| #include <cinttypes> | #include <cinttypes> | ||||||
|  |  | ||||||
|  | #if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  | #include <driver/rmt_rx.h> | ||||||
|  | #endif | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace remote_receiver { | namespace remote_receiver { | ||||||
|  |  | ||||||
| @@ -25,6 +29,21 @@ struct RemoteReceiverComponentStore { | |||||||
|   uint32_t filter_us{10}; |   uint32_t filter_us{10}; | ||||||
|   ISRInternalGPIOPin pin; |   ISRInternalGPIOPin pin; | ||||||
| }; | }; | ||||||
|  | #elif defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  | struct RemoteReceiverComponentStore { | ||||||
|  |   /// Stores RMT symbols and rx done event data | ||||||
|  |   volatile uint8_t *buffer{nullptr}; | ||||||
|  |   /// The position last written to | ||||||
|  |   volatile uint32_t buffer_write{0}; | ||||||
|  |   /// The position last read from | ||||||
|  |   volatile uint32_t buffer_read{0}; | ||||||
|  |   bool overflow{false}; | ||||||
|  |   uint32_t buffer_size{1000}; | ||||||
|  |   uint32_t receive_size{0}; | ||||||
|  |   uint32_t filter_symbols{0}; | ||||||
|  |   esp_err_t error{ESP_OK}; | ||||||
|  |   rmt_receive_config_t config; | ||||||
|  | }; | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| class RemoteReceiverComponent : public remote_base::RemoteReceiverBase, | class RemoteReceiverComponent : public remote_base::RemoteReceiverBase, | ||||||
| @@ -33,9 +52,10 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase, | |||||||
|     , |     , | ||||||
|                                 public remote_base::RemoteRMTChannel |                                 public remote_base::RemoteRMTChannel | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| { | { | ||||||
|  public: |  public: | ||||||
| #ifdef USE_ESP32 | #if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR < 5 | ||||||
|   RemoteReceiverComponent(InternalGPIOPin *pin, uint8_t mem_block_num = 1) |   RemoteReceiverComponent(InternalGPIOPin *pin, uint8_t mem_block_num = 1) | ||||||
|       : RemoteReceiverBase(pin), remote_base::RemoteRMTChannel(mem_block_num) {} |       : RemoteReceiverBase(pin), remote_base::RemoteRMTChannel(mem_block_num) {} | ||||||
|  |  | ||||||
| @@ -49,19 +69,32 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase, | |||||||
|   void loop() override; |   void loop() override; | ||||||
|   float get_setup_priority() const override { return setup_priority::DATA; } |   float get_setup_priority() const override { return setup_priority::DATA; } | ||||||
|  |  | ||||||
|  | #if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  |   void set_filter_symbols(uint32_t filter_symbols) { this->filter_symbols_ = filter_symbols; } | ||||||
|  |   void set_receive_symbols(uint32_t receive_symbols) { this->receive_symbols_ = receive_symbols; } | ||||||
|  |   void set_with_dma(bool with_dma) { this->with_dma_ = with_dma; } | ||||||
|  | #endif | ||||||
|   void set_buffer_size(uint32_t buffer_size) { this->buffer_size_ = buffer_size; } |   void set_buffer_size(uint32_t buffer_size) { this->buffer_size_ = buffer_size; } | ||||||
|   void set_filter_us(uint32_t filter_us) { this->filter_us_ = filter_us; } |   void set_filter_us(uint32_t filter_us) { this->filter_us_ = filter_us; } | ||||||
|   void set_idle_us(uint32_t idle_us) { this->idle_us_ = idle_us; } |   void set_idle_us(uint32_t idle_us) { this->idle_us_ = idle_us; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|   void decode_rmt_(rmt_item32_t *item, size_t len); | #if ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  |   void decode_rmt_(rmt_symbol_word_t *item, size_t item_count); | ||||||
|  |   rmt_channel_handle_t channel_{NULL}; | ||||||
|  |   uint32_t filter_symbols_{0}; | ||||||
|  |   uint32_t receive_symbols_{0}; | ||||||
|  |   bool with_dma_{false}; | ||||||
|  | #else | ||||||
|  |   void decode_rmt_(rmt_item32_t *item, size_t item_count); | ||||||
|   RingbufHandle_t ringbuf_; |   RingbufHandle_t ringbuf_; | ||||||
|  | #endif | ||||||
|   esp_err_t error_code_{ESP_OK}; |   esp_err_t error_code_{ESP_OK}; | ||||||
|   std::string error_string_{""}; |   std::string error_string_{""}; | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #if defined(USE_ESP8266) || defined(USE_LIBRETINY) | #if defined(USE_ESP8266) || defined(USE_LIBRETINY) || (defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR >= 5) | ||||||
|   RemoteReceiverComponentStore store_; |   RemoteReceiverComponentStore store_; | ||||||
|   HighFrequencyLoopRequester high_freq_; |   HighFrequencyLoopRequester high_freq_; | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -2,15 +2,104 @@ | |||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
| #include <driver/rmt.h> |  | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace remote_receiver { | namespace remote_receiver { | ||||||
|  |  | ||||||
| static const char *const TAG = "remote_receiver.esp32"; | static const char *const TAG = "remote_receiver.esp32"; | ||||||
|  | #ifdef USE_ESP32_VARIANT_ESP32H2 | ||||||
|  | static const uint32_t RMT_CLK_FREQ = 32000000; | ||||||
|  | #else | ||||||
|  | static const uint32_t RMT_CLK_FREQ = 80000000; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #if ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  | static bool IRAM_ATTR HOT rmt_callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *event, void *arg) { | ||||||
|  |   RemoteReceiverComponentStore *store = (RemoteReceiverComponentStore *) arg; | ||||||
|  |   rmt_rx_done_event_data_t *event_buffer = (rmt_rx_done_event_data_t *) (store->buffer + store->buffer_write); | ||||||
|  |   uint32_t event_size = sizeof(rmt_rx_done_event_data_t); | ||||||
|  |   uint32_t next_write = store->buffer_write + event_size + event->num_symbols * sizeof(rmt_symbol_word_t); | ||||||
|  |   if (next_write + event_size + store->receive_size > store->buffer_size) { | ||||||
|  |     next_write = 0; | ||||||
|  |   } | ||||||
|  |   if (store->buffer_read - next_write < event_size + store->receive_size) { | ||||||
|  |     next_write = store->buffer_write; | ||||||
|  |     store->overflow = true; | ||||||
|  |   } | ||||||
|  |   if (event->num_symbols <= store->filter_symbols) { | ||||||
|  |     next_write = store->buffer_write; | ||||||
|  |   } | ||||||
|  |   store->error = | ||||||
|  |       rmt_receive(channel, (uint8_t *) store->buffer + next_write + event_size, store->receive_size, &store->config); | ||||||
|  |   event_buffer->num_symbols = event->num_symbols; | ||||||
|  |   event_buffer->received_symbols = event->received_symbols; | ||||||
|  |   store->buffer_write = next_write; | ||||||
|  |   return false; | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  |  | ||||||
| void RemoteReceiverComponent::setup() { | void RemoteReceiverComponent::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Setting up Remote Receiver..."); |   ESP_LOGCONFIG(TAG, "Setting up Remote Receiver..."); | ||||||
|  | #if ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  |   rmt_rx_channel_config_t channel; | ||||||
|  |   memset(&channel, 0, sizeof(channel)); | ||||||
|  |   channel.clk_src = RMT_CLK_SRC_DEFAULT; | ||||||
|  |   channel.resolution_hz = this->clock_resolution_; | ||||||
|  |   channel.mem_block_symbols = rmt_symbols_; | ||||||
|  |   channel.gpio_num = gpio_num_t(this->pin_->get_pin()); | ||||||
|  |   channel.intr_priority = 0; | ||||||
|  |   channel.flags.invert_in = 0; | ||||||
|  |   channel.flags.with_dma = this->with_dma_; | ||||||
|  |   channel.flags.io_loop_back = 0; | ||||||
|  |   esp_err_t error = rmt_new_rx_channel(&channel, &this->channel_); | ||||||
|  |   if (error != ESP_OK) { | ||||||
|  |     this->error_code_ = error; | ||||||
|  |     if (error == ESP_ERR_NOT_FOUND) { | ||||||
|  |       this->error_string_ = "out of RMT symbol memory"; | ||||||
|  |     } else { | ||||||
|  |       this->error_string_ = "in rmt_new_rx_channel"; | ||||||
|  |     } | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   error = rmt_enable(this->channel_); | ||||||
|  |   if (error != ESP_OK) { | ||||||
|  |     this->error_code_ = error; | ||||||
|  |     this->error_string_ = "in rmt_enable"; | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   rmt_rx_event_callbacks_t callbacks; | ||||||
|  |   memset(&callbacks, 0, sizeof(callbacks)); | ||||||
|  |   callbacks.on_recv_done = rmt_callback; | ||||||
|  |   error = rmt_rx_register_event_callbacks(this->channel_, &callbacks, &this->store_); | ||||||
|  |   if (error != ESP_OK) { | ||||||
|  |     this->error_code_ = error; | ||||||
|  |     this->error_string_ = "in rmt_rx_register_event_callbacks"; | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint32_t event_size = sizeof(rmt_rx_done_event_data_t); | ||||||
|  |   uint32_t max_filter_ns = 255u * 1000 / (RMT_CLK_FREQ / 1000000); | ||||||
|  |   uint32_t max_idle_ns = 65535u * 1000; | ||||||
|  |   memset(&this->store_.config, 0, sizeof(this->store_.config)); | ||||||
|  |   this->store_.config.signal_range_min_ns = std::min(this->filter_us_ * 1000, max_filter_ns); | ||||||
|  |   this->store_.config.signal_range_max_ns = std::min(this->idle_us_ * 1000, max_idle_ns); | ||||||
|  |   this->store_.filter_symbols = this->filter_symbols_; | ||||||
|  |   this->store_.receive_size = this->receive_symbols_ * sizeof(rmt_symbol_word_t); | ||||||
|  |   this->store_.buffer_size = std::max((event_size + this->store_.receive_size) * 2, this->buffer_size_); | ||||||
|  |   this->store_.buffer = new uint8_t[this->buffer_size_]; | ||||||
|  |   error = rmt_receive(this->channel_, (uint8_t *) this->store_.buffer + event_size, this->store_.receive_size, | ||||||
|  |                       &this->store_.config); | ||||||
|  |   if (error != ESP_OK) { | ||||||
|  |     this->error_code_ = error; | ||||||
|  |     this->error_string_ = "in rmt_receive"; | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | #else | ||||||
|   this->pin_->setup(); |   this->pin_->setup(); | ||||||
|   rmt_config_t rmt{}; |   rmt_config_t rmt{}; | ||||||
|   this->config_rmt(rmt); |   this->config_rmt(rmt); | ||||||
| @@ -59,10 +148,18 @@ void RemoteReceiverComponent::setup() { | |||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  | #endif | ||||||
| } | } | ||||||
|  |  | ||||||
| void RemoteReceiverComponent::dump_config() { | void RemoteReceiverComponent::dump_config() { | ||||||
|   ESP_LOGCONFIG(TAG, "Remote Receiver:"); |   ESP_LOGCONFIG(TAG, "Remote Receiver:"); | ||||||
|   LOG_PIN("  Pin: ", this->pin_); |   LOG_PIN("  Pin: ", this->pin_); | ||||||
|  | #if ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Clock resolution: %" PRIu32 " hz", this->clock_resolution_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  RMT symbols: %" PRIu32, this->rmt_symbols_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Filter symbols: %" PRIu32, this->filter_symbols_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Receive symbols: %" PRIu32, this->receive_symbols_); | ||||||
|  | #else | ||||||
|   if (this->pin_->digital_read()) { |   if (this->pin_->digital_read()) { | ||||||
|     ESP_LOGW(TAG, "Remote Receiver Signal starts with a HIGH value. Usually this means you have to " |     ESP_LOGW(TAG, "Remote Receiver Signal starts with a HIGH value. Usually this means you have to " | ||||||
|                   "invert the signal using 'inverted: True' in the pin schema!"); |                   "invert the signal using 'inverted: True' in the pin schema!"); | ||||||
| @@ -70,6 +167,7 @@ void RemoteReceiverComponent::dump_config() { | |||||||
|   ESP_LOGCONFIG(TAG, "  Channel: %d", this->channel_); |   ESP_LOGCONFIG(TAG, "  Channel: %d", this->channel_); | ||||||
|   ESP_LOGCONFIG(TAG, "  RMT memory blocks: %d", this->mem_block_num_); |   ESP_LOGCONFIG(TAG, "  RMT memory blocks: %d", this->mem_block_num_); | ||||||
|   ESP_LOGCONFIG(TAG, "  Clock divider: %u", this->clock_divider_); |   ESP_LOGCONFIG(TAG, "  Clock divider: %u", this->clock_divider_); | ||||||
|  | #endif | ||||||
|   ESP_LOGCONFIG(TAG, "  Tolerance: %" PRIu32 "%s", this->tolerance_, |   ESP_LOGCONFIG(TAG, "  Tolerance: %" PRIu32 "%s", this->tolerance_, | ||||||
|                 (this->tolerance_mode_ == remote_base::TOLERANCE_MODE_TIME) ? " us" : "%"); |                 (this->tolerance_mode_ == remote_base::TOLERANCE_MODE_TIME) ? " us" : "%"); | ||||||
|   ESP_LOGCONFIG(TAG, "  Filter out pulses shorter than: %" PRIu32 " us", this->filter_us_); |   ESP_LOGCONFIG(TAG, "  Filter out pulses shorter than: %" PRIu32 " us", this->filter_us_); | ||||||
| @@ -81,10 +179,38 @@ void RemoteReceiverComponent::dump_config() { | |||||||
| } | } | ||||||
|  |  | ||||||
| void RemoteReceiverComponent::loop() { | void RemoteReceiverComponent::loop() { | ||||||
|  | #if ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  |   if (this->store_.error != ESP_OK) { | ||||||
|  |     ESP_LOGE(TAG, "Receive error"); | ||||||
|  |     this->error_code_ = this->store_.error; | ||||||
|  |     this->error_string_ = "in rmt_callback"; | ||||||
|  |     this->mark_failed(); | ||||||
|  |   } | ||||||
|  |   if (this->store_.overflow) { | ||||||
|  |     ESP_LOGW(TAG, "Buffer overflow"); | ||||||
|  |     this->store_.overflow = false; | ||||||
|  |   } | ||||||
|  |   uint32_t buffer_write = this->store_.buffer_write; | ||||||
|  |   while (this->store_.buffer_read != buffer_write) { | ||||||
|  |     rmt_rx_done_event_data_t *event = (rmt_rx_done_event_data_t *) (this->store_.buffer + this->store_.buffer_read); | ||||||
|  |     uint32_t event_size = sizeof(rmt_rx_done_event_data_t); | ||||||
|  |     uint32_t next_read = this->store_.buffer_read + event_size + event->num_symbols * sizeof(rmt_symbol_word_t); | ||||||
|  |     if (next_read + event_size + this->store_.receive_size > this->store_.buffer_size) { | ||||||
|  |       next_read = 0; | ||||||
|  |     } | ||||||
|  |     this->decode_rmt_(event->received_symbols, event->num_symbols); | ||||||
|  |     this->store_.buffer_read = next_read; | ||||||
|  |  | ||||||
|  |     if (!this->temp_.empty()) { | ||||||
|  |       this->temp_.push_back(-this->idle_us_); | ||||||
|  |       this->call_listeners_dumpers_(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | #else | ||||||
|   size_t len = 0; |   size_t len = 0; | ||||||
|   auto *item = (rmt_item32_t *) xRingbufferReceive(this->ringbuf_, &len, 0); |   auto *item = (rmt_item32_t *) xRingbufferReceive(this->ringbuf_, &len, 0); | ||||||
|   if (item != nullptr) { |   if (item != nullptr) { | ||||||
|     this->decode_rmt_(item, len); |     this->decode_rmt_(item, len / sizeof(rmt_item32_t)); | ||||||
|     vRingbufferReturnItem(this->ringbuf_, item); |     vRingbufferReturnItem(this->ringbuf_, item); | ||||||
|  |  | ||||||
|     if (this->temp_.empty()) |     if (this->temp_.empty()) | ||||||
| @@ -93,13 +219,18 @@ void RemoteReceiverComponent::loop() { | |||||||
|     this->temp_.push_back(-this->idle_us_); |     this->temp_.push_back(-this->idle_us_); | ||||||
|     this->call_listeners_dumpers_(); |     this->call_listeners_dumpers_(); | ||||||
|   } |   } | ||||||
|  | #endif | ||||||
| } | } | ||||||
| void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) { |  | ||||||
|  | #if ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  | void RemoteReceiverComponent::decode_rmt_(rmt_symbol_word_t *item, size_t item_count) { | ||||||
|  | #else | ||||||
|  | void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t item_count) { | ||||||
|  | #endif | ||||||
|   bool prev_level = false; |   bool prev_level = false; | ||||||
|   uint32_t prev_length = 0; |   uint32_t prev_length = 0; | ||||||
|   this->temp_.clear(); |   this->temp_.clear(); | ||||||
|   int32_t multiplier = this->pin_->is_inverted() ? -1 : 1; |   int32_t multiplier = this->pin_->is_inverted() ? -1 : 1; | ||||||
|   size_t item_count = len / sizeof(rmt_item32_t); |  | ||||||
|   uint32_t filter_ticks = this->from_microseconds_(this->filter_us_); |   uint32_t filter_ticks = this->from_microseconds_(this->filter_us_); | ||||||
|  |  | ||||||
|   ESP_LOGVV(TAG, "START:"); |   ESP_LOGVV(TAG, "START:"); | ||||||
| @@ -124,7 +255,8 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) { | |||||||
|   this->temp_.reserve(item_count * 2);  // each RMT item has 2 pulses |   this->temp_.reserve(item_count * 2);  // each RMT item has 2 pulses | ||||||
|   for (size_t i = 0; i < item_count; i++) { |   for (size_t i = 0; i < item_count; i++) { | ||||||
|     if (item[i].duration0 == 0u) { |     if (item[i].duration0 == 0u) { | ||||||
|       // Do nothing |       // EOF, sometimes garbage follows, break early | ||||||
|  |       break; | ||||||
|     } else if ((bool(item[i].level0) == prev_level) || (item[i].duration0 < filter_ticks)) { |     } else if ((bool(item[i].level0) == prev_level) || (item[i].duration0 < filter_ticks)) { | ||||||
|       prev_length += item[i].duration0; |       prev_length += item[i].duration0; | ||||||
|     } else { |     } else { | ||||||
| @@ -140,7 +272,8 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (item[i].duration1 == 0u) { |     if (item[i].duration1 == 0u) { | ||||||
|       // Do nothing |       // EOF, sometimes garbage follows, break early | ||||||
|  |       break; | ||||||
|     } else if ((bool(item[i].level1) == prev_level) || (item[i].duration1 < filter_ticks)) { |     } else if ((bool(item[i].level1) == prev_level) || (item[i].duration1 < filter_ticks)) { | ||||||
|       prev_length += item[i].duration1; |       prev_length += item[i].duration1; | ||||||
|     } else { |     } else { | ||||||
|   | |||||||
| @@ -2,12 +2,25 @@ from esphome import automation, pins | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| from esphome.components import esp32_rmt, remote_base | from esphome.components import esp32_rmt, remote_base | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.const import CONF_CARRIER_DUTY_PERCENT, CONF_ID, CONF_PIN, CONF_RMT_CHANNEL | from esphome.const import ( | ||||||
|  |     CONF_CARRIER_DUTY_PERCENT, | ||||||
|  |     CONF_CLOCK_DIVIDER, | ||||||
|  |     CONF_CLOCK_RESOLUTION, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_INVERTED, | ||||||
|  |     CONF_PIN, | ||||||
|  |     CONF_RMT_CHANNEL, | ||||||
|  |     CONF_RMT_SYMBOLS, | ||||||
|  |     CONF_USE_DMA, | ||||||
|  | ) | ||||||
|  | from esphome.core import CORE | ||||||
|  |  | ||||||
| AUTO_LOAD = ["remote_base"] | AUTO_LOAD = ["remote_base"] | ||||||
|  |  | ||||||
|  | CONF_EOT_LEVEL = "eot_level" | ||||||
| CONF_ON_TRANSMIT = "on_transmit" | CONF_ON_TRANSMIT = "on_transmit" | ||||||
| CONF_ON_COMPLETE = "on_complete" | CONF_ON_COMPLETE = "on_complete" | ||||||
|  | CONF_ONE_WIRE = "one_wire" | ||||||
|  |  | ||||||
| remote_transmitter_ns = cg.esphome_ns.namespace("remote_transmitter") | remote_transmitter_ns = cg.esphome_ns.namespace("remote_transmitter") | ||||||
| RemoteTransmitterComponent = remote_transmitter_ns.class_( | RemoteTransmitterComponent = remote_transmitter_ns.class_( | ||||||
| @@ -22,7 +35,29 @@ CONFIG_SCHEMA = cv.Schema( | |||||||
|         cv.Required(CONF_CARRIER_DUTY_PERCENT): cv.All( |         cv.Required(CONF_CARRIER_DUTY_PERCENT): cv.All( | ||||||
|             cv.percentage_int, cv.Range(min=1, max=100) |             cv.percentage_int, cv.Range(min=1, max=100) | ||||||
|         ), |         ), | ||||||
|         cv.Optional(CONF_RMT_CHANNEL): esp32_rmt.validate_rmt_channel(tx=True), |         cv.Optional(CONF_CLOCK_RESOLUTION): cv.All( | ||||||
|  |             cv.only_on_esp32, | ||||||
|  |             cv.only_with_esp_idf, | ||||||
|  |             esp32_rmt.validate_clock_resolution(), | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_CLOCK_DIVIDER): cv.All( | ||||||
|  |             cv.only_on_esp32, cv.only_with_arduino, cv.int_range(min=1, max=255) | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_EOT_LEVEL): cv.All(cv.only_with_esp_idf, cv.boolean), | ||||||
|  |         cv.Optional(CONF_ONE_WIRE): cv.All(cv.only_with_esp_idf, cv.boolean), | ||||||
|  |         cv.Optional(CONF_USE_DMA): cv.All(cv.only_with_esp_idf, cv.boolean), | ||||||
|  |         cv.SplitDefault( | ||||||
|  |             CONF_RMT_SYMBOLS, | ||||||
|  |             esp32_idf=64, | ||||||
|  |             esp32_s2_idf=64, | ||||||
|  |             esp32_s3_idf=48, | ||||||
|  |             esp32_c3_idf=48, | ||||||
|  |             esp32_c6_idf=48, | ||||||
|  |             esp32_h2_idf=48, | ||||||
|  |         ): cv.All(cv.only_with_esp_idf, cv.int_range(min=2)), | ||||||
|  |         cv.Optional(CONF_RMT_CHANNEL): cv.All( | ||||||
|  |             cv.only_with_arduino, esp32_rmt.validate_rmt_channel(tx=True) | ||||||
|  |         ), | ||||||
|         cv.Optional(CONF_ON_TRANSMIT): automation.validate_automation(single=True), |         cv.Optional(CONF_ON_TRANSMIT): automation.validate_automation(single=True), | ||||||
|         cv.Optional(CONF_ON_COMPLETE): automation.validate_automation(single=True), |         cv.Optional(CONF_ON_COMPLETE): automation.validate_automation(single=True), | ||||||
|     } |     } | ||||||
| @@ -31,8 +66,30 @@ CONFIG_SCHEMA = cv.Schema( | |||||||
|  |  | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     pin = await cg.gpio_pin_expression(config[CONF_PIN]) |     pin = await cg.gpio_pin_expression(config[CONF_PIN]) | ||||||
|  |     if CORE.is_esp32: | ||||||
|  |         if esp32_rmt.use_new_rmt_driver(): | ||||||
|  |             var = cg.new_Pvariable(config[CONF_ID], pin) | ||||||
|  |             cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS])) | ||||||
|  |             if CONF_CLOCK_RESOLUTION in config: | ||||||
|  |                 cg.add(var.set_clock_resolution(config[CONF_CLOCK_RESOLUTION])) | ||||||
|  |             if CONF_USE_DMA in config: | ||||||
|  |                 cg.add(var.set_with_dma(config[CONF_USE_DMA])) | ||||||
|  |             if CONF_ONE_WIRE in config: | ||||||
|  |                 cg.add(var.set_one_wire(config[CONF_ONE_WIRE])) | ||||||
|  |             if CONF_EOT_LEVEL in config: | ||||||
|  |                 cg.add(var.set_eot_level(config[CONF_EOT_LEVEL])) | ||||||
|  |             elif CONF_ONE_WIRE in config and config[CONF_ONE_WIRE]: | ||||||
|  |                 cg.add(var.set_eot_level(True)) | ||||||
|  |             elif CONF_INVERTED in config[CONF_PIN] and config[CONF_PIN][CONF_INVERTED]: | ||||||
|  |                 cg.add(var.set_eot_level(True)) | ||||||
|  |         else: | ||||||
|             if (rmt_channel := config.get(CONF_RMT_CHANNEL, None)) is not None: |             if (rmt_channel := config.get(CONF_RMT_CHANNEL, None)) is not None: | ||||||
|                 var = cg.new_Pvariable(config[CONF_ID], pin, rmt_channel) |                 var = cg.new_Pvariable(config[CONF_ID], pin, rmt_channel) | ||||||
|  |             else: | ||||||
|  |                 var = cg.new_Pvariable(config[CONF_ID], pin) | ||||||
|  |             if CONF_CLOCK_DIVIDER in config: | ||||||
|  |                 cg.add(var.set_clock_divider(config[CONF_CLOCK_DIVIDER])) | ||||||
|  |  | ||||||
|     else: |     else: | ||||||
|         var = cg.new_Pvariable(config[CONF_ID], pin) |         var = cg.new_Pvariable(config[CONF_ID], pin) | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|   | |||||||
| @@ -5,6 +5,10 @@ | |||||||
|  |  | ||||||
| #include <vector> | #include <vector> | ||||||
|  |  | ||||||
|  | #if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  | #include <driver/rmt_tx.h> | ||||||
|  | #endif | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace remote_transmitter { | namespace remote_transmitter { | ||||||
|  |  | ||||||
| @@ -16,7 +20,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, | |||||||
| #endif | #endif | ||||||
| { | { | ||||||
|  public: |  public: | ||||||
| #ifdef USE_ESP32 | #if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR < 5 | ||||||
|   RemoteTransmitterComponent(InternalGPIOPin *pin, uint8_t mem_block_num = 1) |   RemoteTransmitterComponent(InternalGPIOPin *pin, uint8_t mem_block_num = 1) | ||||||
|       : remote_base::RemoteTransmitterBase(pin), remote_base::RemoteRMTChannel(mem_block_num) {} |       : remote_base::RemoteTransmitterBase(pin), remote_base::RemoteRMTChannel(mem_block_num) {} | ||||||
|  |  | ||||||
| @@ -29,10 +33,18 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, | |||||||
|  |  | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|  |  | ||||||
|   float get_setup_priority() const override { return setup_priority::DATA; } |   // transmitter setup must run after receiver setup to allow the same GPIO to be used by both | ||||||
|  |   float get_setup_priority() const override { return setup_priority::DATA - 1; } | ||||||
|  |  | ||||||
|   void set_carrier_duty_percent(uint8_t carrier_duty_percent) { this->carrier_duty_percent_ = carrier_duty_percent; } |   void set_carrier_duty_percent(uint8_t carrier_duty_percent) { this->carrier_duty_percent_ = carrier_duty_percent; } | ||||||
|  |  | ||||||
|  | #if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  |   void set_with_dma(bool with_dma) { this->with_dma_ = with_dma; } | ||||||
|  |   void set_one_wire(bool one_wire) { this->one_wire_ = one_wire; } | ||||||
|  |   void set_eot_level(bool eot_level) { this->eot_level_ = eot_level; } | ||||||
|  |   void digital_write(bool value); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|   Trigger<> *get_transmit_trigger() const { return this->transmit_trigger_; }; |   Trigger<> *get_transmit_trigger() const { return this->transmit_trigger_; }; | ||||||
|   Trigger<> *get_complete_trigger() const { return this->complete_trigger_; }; |   Trigger<> *get_complete_trigger() const { return this->complete_trigger_; }; | ||||||
|  |  | ||||||
| @@ -54,7 +66,16 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, | |||||||
|  |  | ||||||
|   uint32_t current_carrier_frequency_{38000}; |   uint32_t current_carrier_frequency_{38000}; | ||||||
|   bool initialized_{false}; |   bool initialized_{false}; | ||||||
|  | #if ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  |   std::vector<rmt_symbol_word_t> rmt_temp_; | ||||||
|  |   bool with_dma_{false}; | ||||||
|  |   bool one_wire_{false}; | ||||||
|  |   bool eot_level_{false}; | ||||||
|  |   rmt_channel_handle_t channel_{NULL}; | ||||||
|  |   rmt_encoder_handle_t encoder_{NULL}; | ||||||
|  | #else | ||||||
|   std::vector<rmt_item32_t> rmt_temp_; |   std::vector<rmt_item32_t> rmt_temp_; | ||||||
|  | #endif | ||||||
|   esp_err_t error_code_{ESP_OK}; |   esp_err_t error_code_{ESP_OK}; | ||||||
|   std::string error_string_{""}; |   std::string error_string_{""}; | ||||||
|   bool inverted_{false}; |   bool inverted_{false}; | ||||||
|   | |||||||
| @@ -9,13 +9,23 @@ namespace remote_transmitter { | |||||||
|  |  | ||||||
| static const char *const TAG = "remote_transmitter"; | static const char *const TAG = "remote_transmitter"; | ||||||
|  |  | ||||||
| void RemoteTransmitterComponent::setup() { this->configure_rmt_(); } | void RemoteTransmitterComponent::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up Remote Transmitter..."); | ||||||
|  |   this->inverted_ = this->pin_->is_inverted(); | ||||||
|  |   this->configure_rmt_(); | ||||||
|  | } | ||||||
|  |  | ||||||
| void RemoteTransmitterComponent::dump_config() { | void RemoteTransmitterComponent::dump_config() { | ||||||
|   ESP_LOGCONFIG(TAG, "Remote Transmitter..."); |   ESP_LOGCONFIG(TAG, "Remote Transmitter:"); | ||||||
|  | #if ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  |   ESP_LOGCONFIG(TAG, "  One wire: %s", this->one_wire_ ? "true" : "false"); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Clock resolution: %" PRIu32 " hz", this->clock_resolution_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  RMT symbols: %" PRIu32, this->rmt_symbols_); | ||||||
|  | #else | ||||||
|   ESP_LOGCONFIG(TAG, "  Channel: %d", this->channel_); |   ESP_LOGCONFIG(TAG, "  Channel: %d", this->channel_); | ||||||
|   ESP_LOGCONFIG(TAG, "  RMT memory blocks: %d", this->mem_block_num_); |   ESP_LOGCONFIG(TAG, "  RMT memory blocks: %d", this->mem_block_num_); | ||||||
|   ESP_LOGCONFIG(TAG, "  Clock divider: %u", this->clock_divider_); |   ESP_LOGCONFIG(TAG, "  Clock divider: %u", this->clock_divider_); | ||||||
|  | #endif | ||||||
|   LOG_PIN("  Pin: ", this->pin_); |   LOG_PIN("  Pin: ", this->pin_); | ||||||
|  |  | ||||||
|   if (this->current_carrier_frequency_ != 0 && this->carrier_duty_percent_ != 100) { |   if (this->current_carrier_frequency_ != 0 && this->carrier_duty_percent_ != 100) { | ||||||
| @@ -28,7 +38,99 @@ void RemoteTransmitterComponent::dump_config() { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #if ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  | void RemoteTransmitterComponent::digital_write(bool value) { | ||||||
|  |   rmt_symbol_word_t symbol = { | ||||||
|  |       .duration0 = 1, | ||||||
|  |       .level0 = value, | ||||||
|  |       .duration1 = 0, | ||||||
|  |       .level1 = value, | ||||||
|  |   }; | ||||||
|  |   rmt_transmit_config_t config; | ||||||
|  |   memset(&config, 0, sizeof(config)); | ||||||
|  |   config.loop_count = 0; | ||||||
|  |   config.flags.eot_level = value; | ||||||
|  |   esp_err_t error = rmt_transmit(this->channel_, this->encoder_, &symbol, sizeof(symbol), &config); | ||||||
|  |   if (error != ESP_OK) { | ||||||
|  |     ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error)); | ||||||
|  |     this->status_set_warning(); | ||||||
|  |   } | ||||||
|  |   error = rmt_tx_wait_all_done(this->channel_, -1); | ||||||
|  |   if (error != ESP_OK) { | ||||||
|  |     ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error)); | ||||||
|  |     this->status_set_warning(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  |  | ||||||
| void RemoteTransmitterComponent::configure_rmt_() { | void RemoteTransmitterComponent::configure_rmt_() { | ||||||
|  | #if ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  |   esp_err_t error; | ||||||
|  |  | ||||||
|  |   if (!this->initialized_) { | ||||||
|  |     rmt_tx_channel_config_t channel; | ||||||
|  |     memset(&channel, 0, sizeof(channel)); | ||||||
|  |     channel.clk_src = RMT_CLK_SRC_DEFAULT; | ||||||
|  |     channel.resolution_hz = this->clock_resolution_; | ||||||
|  |     channel.gpio_num = gpio_num_t(this->pin_->get_pin()); | ||||||
|  |     channel.mem_block_symbols = this->rmt_symbols_; | ||||||
|  |     channel.trans_queue_depth = 1; | ||||||
|  |     channel.flags.io_loop_back = this->one_wire_; | ||||||
|  |     channel.flags.io_od_mode = this->one_wire_; | ||||||
|  |     channel.flags.invert_out = 0; | ||||||
|  |     channel.flags.with_dma = this->with_dma_; | ||||||
|  |     channel.intr_priority = 0; | ||||||
|  |     error = rmt_new_tx_channel(&channel, &this->channel_); | ||||||
|  |     if (error != ESP_OK) { | ||||||
|  |       this->error_code_ = error; | ||||||
|  |       if (error == ESP_ERR_NOT_FOUND) { | ||||||
|  |         this->error_string_ = "out of RMT symbol memory"; | ||||||
|  |       } else { | ||||||
|  |         this->error_string_ = "in rmt_new_tx_channel"; | ||||||
|  |       } | ||||||
|  |       this->mark_failed(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     rmt_copy_encoder_config_t encoder; | ||||||
|  |     memset(&encoder, 0, sizeof(encoder)); | ||||||
|  |     error = rmt_new_copy_encoder(&encoder, &this->encoder_); | ||||||
|  |     if (error != ESP_OK) { | ||||||
|  |       this->error_code_ = error; | ||||||
|  |       this->error_string_ = "in rmt_new_copy_encoder"; | ||||||
|  |       this->mark_failed(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     error = rmt_enable(this->channel_); | ||||||
|  |     if (error != ESP_OK) { | ||||||
|  |       this->error_code_ = error; | ||||||
|  |       this->error_string_ = "in rmt_enable"; | ||||||
|  |       this->mark_failed(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     this->digital_write(this->one_wire_ || this->inverted_); | ||||||
|  |     this->initialized_ = true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->current_carrier_frequency_ == 0 || this->carrier_duty_percent_ == 100) { | ||||||
|  |     error = rmt_apply_carrier(this->channel_, nullptr); | ||||||
|  |   } else { | ||||||
|  |     rmt_carrier_config_t carrier; | ||||||
|  |     memset(&carrier, 0, sizeof(carrier)); | ||||||
|  |     carrier.frequency_hz = this->current_carrier_frequency_; | ||||||
|  |     carrier.duty_cycle = (float) this->carrier_duty_percent_ / 100.0f; | ||||||
|  |     carrier.flags.polarity_active_low = this->inverted_; | ||||||
|  |     carrier.flags.always_on = 1; | ||||||
|  |     error = rmt_apply_carrier(this->channel_, &carrier); | ||||||
|  |   } | ||||||
|  |   if (error != ESP_OK) { | ||||||
|  |     this->error_code_ = error; | ||||||
|  |     this->error_string_ = "in rmt_apply_carrier"; | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | #else | ||||||
|   rmt_config_t c{}; |   rmt_config_t c{}; | ||||||
|  |  | ||||||
|   this->config_rmt(c); |   this->config_rmt(c); | ||||||
| @@ -45,13 +147,12 @@ void RemoteTransmitterComponent::configure_rmt_() { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   c.tx_config.idle_output_en = true; |   c.tx_config.idle_output_en = true; | ||||||
|   if (!this->pin_->is_inverted()) { |   if (!this->inverted_) { | ||||||
|     c.tx_config.carrier_level = RMT_CARRIER_LEVEL_HIGH; |     c.tx_config.carrier_level = RMT_CARRIER_LEVEL_HIGH; | ||||||
|     c.tx_config.idle_level = RMT_IDLE_LEVEL_LOW; |     c.tx_config.idle_level = RMT_IDLE_LEVEL_LOW; | ||||||
|   } else { |   } else { | ||||||
|     c.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW; |     c.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW; | ||||||
|     c.tx_config.idle_level = RMT_IDLE_LEVEL_HIGH; |     c.tx_config.idle_level = RMT_IDLE_LEVEL_HIGH; | ||||||
|     this->inverted_ = true; |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   esp_err_t error = rmt_config(&c); |   esp_err_t error = rmt_config(&c); | ||||||
| @@ -76,6 +177,7 @@ void RemoteTransmitterComponent::configure_rmt_() { | |||||||
|     } |     } | ||||||
|     this->initialized_ = true; |     this->initialized_ = true; | ||||||
|   } |   } | ||||||
|  | #endif | ||||||
| } | } | ||||||
|  |  | ||||||
| void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { | void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { | ||||||
| @@ -90,7 +192,11 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen | |||||||
|   this->rmt_temp_.clear(); |   this->rmt_temp_.clear(); | ||||||
|   this->rmt_temp_.reserve((this->temp_.get_data().size() + 1) / 2); |   this->rmt_temp_.reserve((this->temp_.get_data().size() + 1) / 2); | ||||||
|   uint32_t rmt_i = 0; |   uint32_t rmt_i = 0; | ||||||
|  | #if ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  |   rmt_symbol_word_t rmt_item; | ||||||
|  | #else | ||||||
|   rmt_item32_t rmt_item; |   rmt_item32_t rmt_item; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|   for (int32_t val : this->temp_.get_data()) { |   for (int32_t val : this->temp_.get_data()) { | ||||||
|     bool level = val >= 0; |     bool level = val >= 0; | ||||||
| @@ -125,6 +231,29 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   this->transmit_trigger_->trigger(); |   this->transmit_trigger_->trigger(); | ||||||
|  | #if ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|  |   for (uint32_t i = 0; i < send_times; i++) { | ||||||
|  |     rmt_transmit_config_t config; | ||||||
|  |     memset(&config, 0, sizeof(config)); | ||||||
|  |     config.loop_count = 0; | ||||||
|  |     config.flags.eot_level = this->eot_level_; | ||||||
|  |     esp_err_t error = rmt_transmit(this->channel_, this->encoder_, this->rmt_temp_.data(), | ||||||
|  |                                    this->rmt_temp_.size() * sizeof(rmt_symbol_word_t), &config); | ||||||
|  |     if (error != ESP_OK) { | ||||||
|  |       ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error)); | ||||||
|  |       this->status_set_warning(); | ||||||
|  |     } else { | ||||||
|  |       this->status_clear_warning(); | ||||||
|  |     } | ||||||
|  |     error = rmt_tx_wait_all_done(this->channel_, -1); | ||||||
|  |     if (error != ESP_OK) { | ||||||
|  |       ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error)); | ||||||
|  |       this->status_set_warning(); | ||||||
|  |     } | ||||||
|  |     if (i + 1 < send_times) | ||||||
|  |       delayMicroseconds(send_wait); | ||||||
|  |   } | ||||||
|  | #else | ||||||
|   for (uint32_t i = 0; i < send_times; i++) { |   for (uint32_t i = 0; i < send_times; i++) { | ||||||
|     esp_err_t error = rmt_write_items(this->channel_, this->rmt_temp_.data(), this->rmt_temp_.size(), true); |     esp_err_t error = rmt_write_items(this->channel_, this->rmt_temp_.data(), this->rmt_temp_.size(), true); | ||||||
|     if (error != ESP_OK) { |     if (error != ESP_OK) { | ||||||
| @@ -136,6 +265,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen | |||||||
|     if (i + 1 < send_times) |     if (i + 1 < send_times) | ||||||
|       delayMicroseconds(send_wait); |       delayMicroseconds(send_wait); | ||||||
|   } |   } | ||||||
|  | #endif | ||||||
|   this->complete_trigger_->trigger(); |   this->complete_trigger_->trigger(); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -93,13 +93,17 @@ void IRAM_ATTR HOT RotaryEncoderSensorStore::gpio_intr(RotaryEncoderSensorStore | |||||||
|   int8_t rotation_dir = 0; |   int8_t rotation_dir = 0; | ||||||
|   uint16_t new_state = STATE_LOOKUP_TABLE[input_state]; |   uint16_t new_state = STATE_LOOKUP_TABLE[input_state]; | ||||||
|   if ((new_state & arg->resolution & STATE_HAS_INCREMENTED) != 0) { |   if ((new_state & arg->resolution & STATE_HAS_INCREMENTED) != 0) { | ||||||
|     if (arg->counter < arg->max_value) |     if (arg->counter < arg->max_value) { | ||||||
|       arg->counter++; |       auto x = arg->counter + 1; | ||||||
|  |       arg->counter = x; | ||||||
|  |     } | ||||||
|     rotation_dir = 1; |     rotation_dir = 1; | ||||||
|   } |   } | ||||||
|   if ((new_state & arg->resolution & STATE_HAS_DECREMENTED) != 0) { |   if ((new_state & arg->resolution & STATE_HAS_DECREMENTED) != 0) { | ||||||
|     if (arg->counter > arg->min_value) |     if (arg->counter > arg->min_value) { | ||||||
|       arg->counter--; |       auto x = arg->counter - 1; | ||||||
|  |       arg->counter = x; | ||||||
|  |     } | ||||||
|     rotation_dir = -1; |     rotation_dir = -1; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										41
									
								
								esphome/components/seeed_mr60bha2/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								esphome/components/seeed_mr60bha2/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components import uart | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import CONF_ID | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@limengdu"] | ||||||
|  | DEPENDENCIES = ["uart"] | ||||||
|  | MULTI_CONF = True | ||||||
|  |  | ||||||
|  | mr60bha2_ns = cg.esphome_ns.namespace("seeed_mr60bha2") | ||||||
|  |  | ||||||
|  | MR60BHA2Component = mr60bha2_ns.class_( | ||||||
|  |     "MR60BHA2Component", cg.Component, uart.UARTDevice | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONF_MR60BHA2_ID = "mr60bha2_id" | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(MR60BHA2Component), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(uart.UART_DEVICE_SCHEMA) | ||||||
|  |     .extend(cv.COMPONENT_SCHEMA) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( | ||||||
|  |     "seeed_mr60bha2", | ||||||
|  |     require_tx=True, | ||||||
|  |     require_rx=True, | ||||||
|  |     baud_rate=115200, | ||||||
|  |     parity="NONE", | ||||||
|  |     stop_bits=1, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await uart.register_uart_device(var, config) | ||||||
							
								
								
									
										173
									
								
								esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,173 @@ | |||||||
|  | #include "seeed_mr60bha2.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | #include <utility> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace seeed_mr60bha2 { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "seeed_mr60bha2"; | ||||||
|  |  | ||||||
|  | // Prints the component's configuration data. dump_config() prints all of the component's configuration | ||||||
|  | // items in an easy-to-read format, including the configuration key-value pairs. | ||||||
|  | void MR60BHA2Component::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "MR60BHA2:"); | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |   LOG_SENSOR(" ", "Breath Rate Sensor", this->breath_rate_sensor_); | ||||||
|  |   LOG_SENSOR(" ", "Heart Rate Sensor", this->heart_rate_sensor_); | ||||||
|  |   LOG_SENSOR(" ", "Distance Sensor", this->distance_sensor_); | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // main loop | ||||||
|  | void MR60BHA2Component::loop() { | ||||||
|  |   uint8_t byte; | ||||||
|  |  | ||||||
|  |   // Is there data on the serial port | ||||||
|  |   while (this->available()) { | ||||||
|  |     this->read_byte(&byte); | ||||||
|  |     this->rx_message_.push_back(byte); | ||||||
|  |     if (!this->validate_message_()) { | ||||||
|  |       this->rx_message_.clear(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Calculate the checksum for a byte array. | ||||||
|  |  * | ||||||
|  |  * This function calculates the checksum for the provided byte array using an | ||||||
|  |  * XOR-based checksum algorithm. | ||||||
|  |  * | ||||||
|  |  * @param data The byte array to calculate the checksum for. | ||||||
|  |  * @param len The length of the byte array. | ||||||
|  |  * @return The calculated checksum. | ||||||
|  |  */ | ||||||
|  | static uint8_t calculate_checksum(const uint8_t *data, size_t len) { | ||||||
|  |   uint8_t checksum = 0; | ||||||
|  |   for (size_t i = 0; i < len; i++) { | ||||||
|  |     checksum ^= data[i]; | ||||||
|  |   } | ||||||
|  |   checksum = ~checksum; | ||||||
|  |   return checksum; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Validate the checksum of a byte array. | ||||||
|  |  * | ||||||
|  |  * This function validates the checksum of the provided byte array by comparing | ||||||
|  |  * it to the expected checksum. | ||||||
|  |  * | ||||||
|  |  * @param data The byte array to validate. | ||||||
|  |  * @param len The length of the byte array. | ||||||
|  |  * @param expected_checksum The expected checksum. | ||||||
|  |  * @return True if the checksum is valid, false otherwise. | ||||||
|  |  */ | ||||||
|  | static bool validate_checksum(const uint8_t *data, size_t len, uint8_t expected_checksum) { | ||||||
|  |   return calculate_checksum(data, len) == expected_checksum; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool MR60BHA2Component::validate_message_() { | ||||||
|  |   size_t at = this->rx_message_.size() - 1; | ||||||
|  |   auto *data = &this->rx_message_[0]; | ||||||
|  |   uint8_t new_byte = data[at]; | ||||||
|  |  | ||||||
|  |   if (at == 0) { | ||||||
|  |     return new_byte == FRAME_HEADER_BUFFER; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (at <= 2) { | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |   uint16_t frame_id = encode_uint16(data[1], data[2]); | ||||||
|  |  | ||||||
|  |   if (at <= 4) { | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint16_t length = encode_uint16(data[3], data[4]); | ||||||
|  |  | ||||||
|  |   if (at <= 6) { | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint16_t frame_type = encode_uint16(data[5], data[6]); | ||||||
|  |  | ||||||
|  |   if (frame_type != BREATH_RATE_TYPE_BUFFER && frame_type != HEART_RATE_TYPE_BUFFER && | ||||||
|  |       frame_type != DISTANCE_TYPE_BUFFER) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint8_t header_checksum = new_byte; | ||||||
|  |  | ||||||
|  |   if (at == 7) { | ||||||
|  |     if (!validate_checksum(data, 7, header_checksum)) { | ||||||
|  |       ESP_LOGE(TAG, "HEAD_CKSUM_FRAME ERROR: 0x%02x", header_checksum); | ||||||
|  |       ESP_LOGV(TAG, "GET FRAME: %s", format_hex_pretty(data, 8).c_str()); | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Wait until all data is read | ||||||
|  |   if (at - 8 < length) { | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint8_t data_checksum = new_byte; | ||||||
|  |   if (at == 8 + length) { | ||||||
|  |     if (!validate_checksum(data + 8, length, data_checksum)) { | ||||||
|  |       ESP_LOGE(TAG, "DATA_CKSUM_FRAME ERROR: 0x%02x", data_checksum); | ||||||
|  |       ESP_LOGV(TAG, "GET FRAME: %s", format_hex_pretty(data, 8 + length).c_str()); | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   const uint8_t *frame_data = data + 8; | ||||||
|  |   ESP_LOGV(TAG, "Received Frame: ID: 0x%04x, Type: 0x%04x, Data: [%s] Raw Data: [%s]", frame_id, frame_type, | ||||||
|  |            format_hex_pretty(frame_data, length).c_str(), format_hex_pretty(this->rx_message_).c_str()); | ||||||
|  |   this->process_frame_(frame_id, frame_type, data + 8, length); | ||||||
|  |  | ||||||
|  |   // Return false to reset rx buffer | ||||||
|  |   return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MR60BHA2Component::process_frame_(uint16_t frame_id, uint16_t frame_type, const uint8_t *data, size_t length) { | ||||||
|  |   switch (frame_type) { | ||||||
|  |     case BREATH_RATE_TYPE_BUFFER: | ||||||
|  |       if (this->breath_rate_sensor_ != nullptr && length >= 4) { | ||||||
|  |         uint32_t current_breath_rate_int = encode_uint32(data[3], data[2], data[1], data[0]); | ||||||
|  |         if (current_breath_rate_int != 0) { | ||||||
|  |           float breath_rate_float; | ||||||
|  |           memcpy(&breath_rate_float, ¤t_breath_rate_int, sizeof(float)); | ||||||
|  |           this->breath_rate_sensor_->publish_state(breath_rate_float); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       break; | ||||||
|  |     case HEART_RATE_TYPE_BUFFER: | ||||||
|  |       if (this->heart_rate_sensor_ != nullptr && length >= 4) { | ||||||
|  |         uint32_t current_heart_rate_int = encode_uint32(data[3], data[2], data[1], data[0]); | ||||||
|  |         if (current_heart_rate_int != 0) { | ||||||
|  |           float heart_rate_float; | ||||||
|  |           memcpy(&heart_rate_float, ¤t_heart_rate_int, sizeof(float)); | ||||||
|  |           this->heart_rate_sensor_->publish_state(heart_rate_float); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       break; | ||||||
|  |     case DISTANCE_TYPE_BUFFER: | ||||||
|  |       if (!data[0]) { | ||||||
|  |         if (this->distance_sensor_ != nullptr && length >= 8) { | ||||||
|  |           uint32_t current_distance_int = encode_uint32(data[7], data[6], data[5], data[4]); | ||||||
|  |           float distance_float; | ||||||
|  |           memcpy(&distance_float, ¤t_distance_int, sizeof(float)); | ||||||
|  |           this->distance_sensor_->publish_state(distance_float); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       break; | ||||||
|  |     default: | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace seeed_mr60bha2 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										61
									
								
								esphome/components/seeed_mr60bha2/seeed_mr60bha2.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								esphome/components/seeed_mr60bha2/seeed_mr60bha2.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | |||||||
|  | #pragma once | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/defines.h" | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  | #endif | ||||||
|  | #include "esphome/components/uart/uart.h" | ||||||
|  | #include "esphome/core/automation.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
|  | #include <map> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace seeed_mr60bha2 { | ||||||
|  |  | ||||||
|  | static const uint8_t DATA_BUF_MAX_SIZE = 12; | ||||||
|  | static const uint8_t FRAME_BUF_MAX_SIZE = 21; | ||||||
|  | static const uint8_t LEN_TO_HEAD_CKSUM = 8; | ||||||
|  | static const uint8_t LEN_TO_DATA_FRAME = 9; | ||||||
|  |  | ||||||
|  | static const uint8_t FRAME_HEADER_BUFFER = 0x01; | ||||||
|  | static const uint16_t BREATH_RATE_TYPE_BUFFER = 0x0A14; | ||||||
|  | static const uint16_t HEART_RATE_TYPE_BUFFER = 0x0A15; | ||||||
|  | static const uint16_t DISTANCE_TYPE_BUFFER = 0x0A16; | ||||||
|  |  | ||||||
|  | enum FrameLocation { | ||||||
|  |   LOCATE_FRAME_HEADER, | ||||||
|  |   LOCATE_ID_FRAME1, | ||||||
|  |   LOCATE_ID_FRAME2, | ||||||
|  |   LOCATE_LENGTH_FRAME_H, | ||||||
|  |   LOCATE_LENGTH_FRAME_L, | ||||||
|  |   LOCATE_TYPE_FRAME1, | ||||||
|  |   LOCATE_TYPE_FRAME2, | ||||||
|  |   LOCATE_HEAD_CKSUM_FRAME,  // Header checksum: [from the first byte to the previous byte of the HEAD_CKSUM bit] | ||||||
|  |   LOCATE_DATA_FRAME, | ||||||
|  |   LOCATE_DATA_CKSUM_FRAME,  // Data checksum: [from the first to the previous byte of the DATA_CKSUM bit] | ||||||
|  |   LOCATE_PROCESS_FRAME, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class MR60BHA2Component : public Component, | ||||||
|  |                           public uart::UARTDevice {  // The class name must be the name defined by text_sensor.py | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |   SUB_SENSOR(breath_rate); | ||||||
|  |   SUB_SENSOR(heart_rate); | ||||||
|  |   SUB_SENSOR(distance); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |  public: | ||||||
|  |   float get_setup_priority() const override { return esphome::setup_priority::LATE; } | ||||||
|  |   void dump_config() override; | ||||||
|  |   void loop() override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   bool validate_message_(); | ||||||
|  |   void process_frame_(uint16_t frame_id, uint16_t frame_type, const uint8_t *data, size_t length); | ||||||
|  |  | ||||||
|  |   std::vector<uint8_t> rx_message_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace seeed_mr60bha2 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										57
									
								
								esphome/components/seeed_mr60bha2/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								esphome/components/seeed_mr60bha2/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components import sensor | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_DISTANCE, | ||||||
|  |     DEVICE_CLASS_DISTANCE, | ||||||
|  |     ICON_HEART_PULSE, | ||||||
|  |     ICON_PULSE, | ||||||
|  |     ICON_SIGNAL, | ||||||
|  |     STATE_CLASS_MEASUREMENT, | ||||||
|  |     UNIT_BEATS_PER_MINUTE, | ||||||
|  |     UNIT_CENTIMETER, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | from . import CONF_MR60BHA2_ID, MR60BHA2Component | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["seeed_mr60bha2"] | ||||||
|  |  | ||||||
|  | CONF_BREATH_RATE = "breath_rate" | ||||||
|  | CONF_HEART_RATE = "heart_rate" | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(CONF_MR60BHA2_ID): cv.use_id(MR60BHA2Component), | ||||||
|  |         cv.Optional(CONF_BREATH_RATE): sensor.sensor_schema( | ||||||
|  |             accuracy_decimals=2, | ||||||
|  |             state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             icon=ICON_PULSE, | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_HEART_RATE): sensor.sensor_schema( | ||||||
|  |             accuracy_decimals=0, | ||||||
|  |             icon=ICON_HEART_PULSE, | ||||||
|  |             state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             unit_of_measurement=UNIT_BEATS_PER_MINUTE, | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_DISTANCE): sensor.sensor_schema( | ||||||
|  |             device_class=DEVICE_CLASS_DISTANCE, | ||||||
|  |             state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             unit_of_measurement=UNIT_CENTIMETER, | ||||||
|  |             accuracy_decimals=2, | ||||||
|  |             icon=ICON_SIGNAL, | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     mr60bha2_component = await cg.get_variable(config[CONF_MR60BHA2_ID]) | ||||||
|  |     if breath_rate_config := config.get(CONF_BREATH_RATE): | ||||||
|  |         sens = await sensor.new_sensor(breath_rate_config) | ||||||
|  |         cg.add(mr60bha2_component.set_breath_rate_sensor(sens)) | ||||||
|  |     if heart_rate_config := config.get(CONF_HEART_RATE): | ||||||
|  |         sens = await sensor.new_sensor(heart_rate_config) | ||||||
|  |         cg.add(mr60bha2_component.set_heart_rate_sensor(sens)) | ||||||
|  |     if distance_config := config.get(CONF_DISTANCE): | ||||||
|  |         sens = await sensor.new_sensor(distance_config) | ||||||
|  |         cg.add(mr60bha2_component.set_distance_sensor(sens)) | ||||||
| @@ -1,19 +1,19 @@ | |||||||
| import esphome.codegen as cg |  | ||||||
| import esphome.config_validation as cv |  | ||||||
| from esphome.components import i2c, sensor, sensirion_common |  | ||||||
| from esphome import automation | from esphome import automation | ||||||
| from esphome.automation import maybe_simple_id | from esphome.automation import maybe_simple_id | ||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components import i2c, sensirion_common, sensor | ||||||
|  | import esphome.config_validation as cv | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_HUMIDITY, |     CONF_HUMIDITY, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     CONF_OFFSET, |     CONF_OFFSET, | ||||||
|     CONF_PM_1_0, |     CONF_PM_1_0, | ||||||
|     CONF_PM_10_0, |  | ||||||
|     CONF_PM_2_5, |     CONF_PM_2_5, | ||||||
|     CONF_PM_4_0, |     CONF_PM_4_0, | ||||||
|  |     CONF_PM_10_0, | ||||||
|     CONF_STORE_BASELINE, |     CONF_STORE_BASELINE, | ||||||
|     CONF_TEMPERATURE, |     CONF_TEMPERATURE, | ||||||
|  |     CONF_TEMPERATURE_COMPENSATION, | ||||||
|     DEVICE_CLASS_AQI, |     DEVICE_CLASS_AQI, | ||||||
|     DEVICE_CLASS_HUMIDITY, |     DEVICE_CLASS_HUMIDITY, | ||||||
|     DEVICE_CLASS_PM1, |     DEVICE_CLASS_PM1, | ||||||
| @@ -51,7 +51,6 @@ CONF_LEARNING_TIME_OFFSET_HOURS = "learning_time_offset_hours" | |||||||
| CONF_NORMALIZED_OFFSET_SLOPE = "normalized_offset_slope" | CONF_NORMALIZED_OFFSET_SLOPE = "normalized_offset_slope" | ||||||
| CONF_NOX = "nox" | CONF_NOX = "nox" | ||||||
| CONF_STD_INITIAL = "std_initial" | CONF_STD_INITIAL = "std_initial" | ||||||
| CONF_TEMPERATURE_COMPENSATION = "temperature_compensation" |  | ||||||
| CONF_TIME_CONSTANT = "time_constant" | CONF_TIME_CONSTANT = "time_constant" | ||||||
| CONF_VOC = "voc" | CONF_VOC = "voc" | ||||||
| CONF_VOC_BASELINE = "voc_baseline" | CONF_VOC_BASELINE = "voc_baseline" | ||||||
|   | |||||||
| @@ -1,23 +1,22 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
|  | from esphome.components import i2c, sensirion_common, sensor | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import i2c, sensor, sensirion_common |  | ||||||
|  |  | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_COMPENSATION, |  | ||||||
|     CONF_ID, |  | ||||||
|     CONF_BASELINE, |     CONF_BASELINE, | ||||||
|  |     CONF_COMPENSATION, | ||||||
|     CONF_ECO2, |     CONF_ECO2, | ||||||
|  |     CONF_ID, | ||||||
|     CONF_STORE_BASELINE, |     CONF_STORE_BASELINE, | ||||||
|     CONF_TEMPERATURE_SOURCE, |     CONF_TEMPERATURE_SOURCE, | ||||||
|     CONF_TVOC, |     CONF_TVOC, | ||||||
|     ICON_RADIATOR, |  | ||||||
|     DEVICE_CLASS_CARBON_DIOXIDE, |     DEVICE_CLASS_CARBON_DIOXIDE, | ||||||
|     DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, |     DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, | ||||||
|     STATE_CLASS_MEASUREMENT, |  | ||||||
|     UNIT_PARTS_PER_MILLION, |  | ||||||
|     UNIT_PARTS_PER_BILLION, |  | ||||||
|     ICON_MOLECULE_CO2, |  | ||||||
|     ENTITY_CATEGORY_DIAGNOSTIC, |     ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |     ICON_MOLECULE_CO2, | ||||||
|  |     ICON_RADIATOR, | ||||||
|  |     STATE_CLASS_MEASUREMENT, | ||||||
|  |     UNIT_PARTS_PER_BILLION, | ||||||
|  |     UNIT_PARTS_PER_MILLION, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| DEPENDENCIES = ["i2c"] | DEPENDENCIES = ["i2c"] | ||||||
| @@ -77,7 +76,7 @@ CONFIG_SCHEMA = ( | |||||||
|             ), |             ), | ||||||
|         } |         } | ||||||
|     ) |     ) | ||||||
|     .extend(cv.polling_component_schema("1s")) |     .extend(cv.polling_component_schema("60s")) | ||||||
|     .extend(i2c.i2c_device_schema(0x58)) |     .extend(i2c.i2c_device_schema(0x58)) | ||||||
| ) | ) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| #include "sgp30.h" | #include "sgp30.h" | ||||||
|  | #include <cinttypes> | ||||||
|  | #include "esphome/core/application.h" | ||||||
| #include "esphome/core/hal.h" | #include "esphome/core/hal.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
| #include "esphome/core/application.h" |  | ||||||
| #include <cinttypes> |  | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace sgp30 { | namespace sgp30 { | ||||||
| @@ -295,10 +295,6 @@ void SGP30Component::update() { | |||||||
|     if (this->tvoc_sensor_ != nullptr) |     if (this->tvoc_sensor_ != nullptr) | ||||||
|       this->tvoc_sensor_->publish_state(tvoc); |       this->tvoc_sensor_->publish_state(tvoc); | ||||||
|  |  | ||||||
|     if (this->get_update_interval() != 1000) { |  | ||||||
|       ESP_LOGW(TAG, "Update interval for SGP30 sensor must be set to 1s for optimized readout"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     this->status_clear_warning(); |     this->status_clear_warning(); | ||||||
|     this->send_env_data_(); |     this->send_env_data_(); | ||||||
|     this->read_iaq_baseline_(); |     this->read_iaq_baseline_(); | ||||||
|   | |||||||
| @@ -1,9 +1,9 @@ | |||||||
| #ifdef USE_ESP32_FRAMEWORK_ARDUINO | #ifdef USE_ESP32_FRAMEWORK_ARDUINO | ||||||
|  | #include "uart_component_esp32_arduino.h" | ||||||
| #include "esphome/core/application.h" | #include "esphome/core/application.h" | ||||||
| #include "esphome/core/defines.h" | #include "esphome/core/defines.h" | ||||||
| #include "esphome/core/helpers.h" | #include "esphome/core/helpers.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
| #include "uart_component_esp32_arduino.h" |  | ||||||
|  |  | ||||||
| #ifdef USE_LOGGER | #ifdef USE_LOGGER | ||||||
| #include "esphome/components/logger/logger.h" | #include "esphome/components/logger/logger.h" | ||||||
| @@ -118,7 +118,7 @@ void ESP32ArduinoUARTComponent::setup() { | |||||||
|     } |     } | ||||||
| #endif  // USE_LOGGER | #endif  // USE_LOGGER | ||||||
|  |  | ||||||
|     if (next_uart_num >= UART_NUM_MAX) { |     if (next_uart_num >= SOC_UART_NUM) { | ||||||
|       ESP_LOGW(TAG, "Maximum number of UART components created already."); |       ESP_LOGW(TAG, "Maximum number of UART components created already."); | ||||||
|       this->mark_failed(); |       this->mark_failed(); | ||||||
|       return; |       return; | ||||||
|   | |||||||
| @@ -1,11 +1,11 @@ | |||||||
| #ifdef USE_ESP_IDF | #ifdef USE_ESP_IDF | ||||||
|  |  | ||||||
| #include "uart_component_esp_idf.h" | #include "uart_component_esp_idf.h" | ||||||
|  | #include <cinttypes> | ||||||
| #include "esphome/core/application.h" | #include "esphome/core/application.h" | ||||||
| #include "esphome/core/defines.h" | #include "esphome/core/defines.h" | ||||||
| #include "esphome/core/helpers.h" | #include "esphome/core/helpers.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
| #include <cinttypes> |  | ||||||
|  |  | ||||||
| #ifdef USE_LOGGER | #ifdef USE_LOGGER | ||||||
| #include "esphome/components/logger/logger.h" | #include "esphome/components/logger/logger.h" | ||||||
| @@ -84,7 +84,7 @@ void IDFUARTComponent::setup() { | |||||||
|   } |   } | ||||||
| #endif  // USE_LOGGER | #endif  // USE_LOGGER | ||||||
|  |  | ||||||
|   if (next_uart_num >= UART_NUM_MAX) { |   if (next_uart_num >= SOC_UART_NUM) { | ||||||
|     ESP_LOGW(TAG, "Maximum number of UART components created already."); |     ESP_LOGW(TAG, "Maximum number of UART components created already."); | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   | |||||||
| @@ -1,11 +1,12 @@ | |||||||
| import esphome.codegen as cg |  | ||||||
| from esphome import automation | from esphome import automation | ||||||
| import esphome.config_validation as cv | import esphome.codegen as cg | ||||||
| from esphome.components import i2c, sensor | from esphome.components import i2c, sensor | ||||||
|  | import esphome.config_validation as cv | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_ID, |  | ||||||
|     CONF_EC, |     CONF_EC, | ||||||
|  |     CONF_ID, | ||||||
|     CONF_TEMPERATURE, |     CONF_TEMPERATURE, | ||||||
|  |     CONF_TEMPERATURE_COMPENSATION, | ||||||
|     DEVICE_CLASS_EMPTY, |     DEVICE_CLASS_EMPTY, | ||||||
|     DEVICE_CLASS_TEMPERATURE, |     DEVICE_CLASS_TEMPERATURE, | ||||||
|     ICON_EMPTY, |     ICON_EMPTY, | ||||||
| @@ -18,7 +19,6 @@ DEPENDENCIES = ["i2c"] | |||||||
|  |  | ||||||
| CONF_SOLUTION = "solution" | CONF_SOLUTION = "solution" | ||||||
| CONF_TEMPERATURE_SENSOR = "temperature_sensor" | CONF_TEMPERATURE_SENSOR = "temperature_sensor" | ||||||
| CONF_TEMPERATURE_COMPENSATION = "temperature_compensation" |  | ||||||
| CONF_TEMPERATURE_COEFFICIENT = "temperature_coefficient" | CONF_TEMPERATURE_COEFFICIENT = "temperature_coefficient" | ||||||
|  |  | ||||||
| ufire_ec_ns = cg.esphome_ns.namespace("ufire_ec") | ufire_ec_ns = cg.esphome_ns.namespace("ufire_ec") | ||||||
|   | |||||||
| @@ -1660,6 +1660,12 @@ class SplitDefault(Optional): | |||||||
|         esp32_c3=vol.UNDEFINED, |         esp32_c3=vol.UNDEFINED, | ||||||
|         esp32_c3_arduino=vol.UNDEFINED, |         esp32_c3_arduino=vol.UNDEFINED, | ||||||
|         esp32_c3_idf=vol.UNDEFINED, |         esp32_c3_idf=vol.UNDEFINED, | ||||||
|  |         esp32_c6=vol.UNDEFINED, | ||||||
|  |         esp32_c6_arduino=vol.UNDEFINED, | ||||||
|  |         esp32_c6_idf=vol.UNDEFINED, | ||||||
|  |         esp32_h2=vol.UNDEFINED, | ||||||
|  |         esp32_h2_arduino=vol.UNDEFINED, | ||||||
|  |         esp32_h2_idf=vol.UNDEFINED, | ||||||
|         rp2040=vol.UNDEFINED, |         rp2040=vol.UNDEFINED, | ||||||
|         bk72xx=vol.UNDEFINED, |         bk72xx=vol.UNDEFINED, | ||||||
|         rtl87xx=vol.UNDEFINED, |         rtl87xx=vol.UNDEFINED, | ||||||
| @@ -1691,6 +1697,18 @@ class SplitDefault(Optional): | |||||||
|         self._esp32_c3_idf_default = vol.default_factory( |         self._esp32_c3_idf_default = vol.default_factory( | ||||||
|             _get_priority_default(esp32_c3_idf, esp32_c3, esp32_idf, esp32) |             _get_priority_default(esp32_c3_idf, esp32_c3, esp32_idf, esp32) | ||||||
|         ) |         ) | ||||||
|  |         self._esp32_c6_arduino_default = vol.default_factory( | ||||||
|  |             _get_priority_default(esp32_c6_arduino, esp32_c6, esp32_arduino, esp32) | ||||||
|  |         ) | ||||||
|  |         self._esp32_c6_idf_default = vol.default_factory( | ||||||
|  |             _get_priority_default(esp32_c6_idf, esp32_c6, esp32_idf, esp32) | ||||||
|  |         ) | ||||||
|  |         self._esp32_h2_arduino_default = vol.default_factory( | ||||||
|  |             _get_priority_default(esp32_h2_arduino, esp32_h2, esp32_arduino, esp32) | ||||||
|  |         ) | ||||||
|  |         self._esp32_h2_idf_default = vol.default_factory( | ||||||
|  |             _get_priority_default(esp32_h2_idf, esp32_h2, esp32_idf, esp32) | ||||||
|  |         ) | ||||||
|         self._rp2040_default = vol.default_factory(rp2040) |         self._rp2040_default = vol.default_factory(rp2040) | ||||||
|         self._bk72xx_default = vol.default_factory(bk72xx) |         self._bk72xx_default = vol.default_factory(bk72xx) | ||||||
|         self._rtl87xx_default = vol.default_factory(rtl87xx) |         self._rtl87xx_default = vol.default_factory(rtl87xx) | ||||||
| @@ -1704,6 +1722,8 @@ class SplitDefault(Optional): | |||||||
|             from esphome.components.esp32 import get_esp32_variant |             from esphome.components.esp32 import get_esp32_variant | ||||||
|             from esphome.components.esp32.const import ( |             from esphome.components.esp32.const import ( | ||||||
|                 VARIANT_ESP32C3, |                 VARIANT_ESP32C3, | ||||||
|  |                 VARIANT_ESP32C6, | ||||||
|  |                 VARIANT_ESP32H2, | ||||||
|                 VARIANT_ESP32S2, |                 VARIANT_ESP32S2, | ||||||
|                 VARIANT_ESP32S3, |                 VARIANT_ESP32S3, | ||||||
|             ) |             ) | ||||||
| @@ -1724,6 +1744,16 @@ class SplitDefault(Optional): | |||||||
|                     return self._esp32_c3_arduino_default |                     return self._esp32_c3_arduino_default | ||||||
|                 if CORE.using_esp_idf: |                 if CORE.using_esp_idf: | ||||||
|                     return self._esp32_c3_idf_default |                     return self._esp32_c3_idf_default | ||||||
|  |             elif variant == VARIANT_ESP32C6: | ||||||
|  |                 if CORE.using_arduino: | ||||||
|  |                     return self._esp32_c6_arduino_default | ||||||
|  |                 if CORE.using_esp_idf: | ||||||
|  |                     return self._esp32_c6_idf_default | ||||||
|  |             elif variant == VARIANT_ESP32H2: | ||||||
|  |                 if CORE.using_arduino: | ||||||
|  |                     return self._esp32_h2_arduino_default | ||||||
|  |                 if CORE.using_esp_idf: | ||||||
|  |                     return self._esp32_h2_idf_default | ||||||
|             else: |             else: | ||||||
|                 if CORE.using_arduino: |                 if CORE.using_arduino: | ||||||
|                     return self._esp32_arduino_default |                     return self._esp32_arduino_default | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| """Constants used by esphome.""" | """Constants used by esphome.""" | ||||||
|  |  | ||||||
| __version__ = "2024.12.0-dev" | __version__ = "2025.1.0-dev" | ||||||
|  |  | ||||||
| ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" | ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" | ||||||
| VALID_SUBSTITUTIONS_CHARACTERS = ( | VALID_SUBSTITUTIONS_CHARACTERS = ( | ||||||
| @@ -131,7 +131,9 @@ CONF_CLIENT_CERTIFICATE = "client_certificate" | |||||||
| CONF_CLIENT_CERTIFICATE_KEY = "client_certificate_key" | CONF_CLIENT_CERTIFICATE_KEY = "client_certificate_key" | ||||||
| CONF_CLIENT_ID = "client_id" | CONF_CLIENT_ID = "client_id" | ||||||
| CONF_CLK_PIN = "clk_pin" | CONF_CLK_PIN = "clk_pin" | ||||||
|  | CONF_CLOCK_DIVIDER = "clock_divider" | ||||||
| CONF_CLOCK_PIN = "clock_pin" | CONF_CLOCK_PIN = "clock_pin" | ||||||
|  | CONF_CLOCK_RESOLUTION = "clock_resolution" | ||||||
| CONF_CLOSE_ACTION = "close_action" | CONF_CLOSE_ACTION = "close_action" | ||||||
| CONF_CLOSE_DURATION = "close_duration" | CONF_CLOSE_DURATION = "close_duration" | ||||||
| CONF_CLOSE_ENDSTOP = "close_endstop" | CONF_CLOSE_ENDSTOP = "close_endstop" | ||||||
| @@ -739,6 +741,7 @@ CONF_RGB_ORDER = "rgb_order" | |||||||
| CONF_RGBW = "rgbw" | CONF_RGBW = "rgbw" | ||||||
| CONF_RISING_EDGE = "rising_edge" | CONF_RISING_EDGE = "rising_edge" | ||||||
| CONF_RMT_CHANNEL = "rmt_channel" | CONF_RMT_CHANNEL = "rmt_channel" | ||||||
|  | CONF_RMT_SYMBOLS = "rmt_symbols" | ||||||
| CONF_ROTATION = "rotation" | CONF_ROTATION = "rotation" | ||||||
| CONF_ROW = "row" | CONF_ROW = "row" | ||||||
| CONF_RS_PIN = "rs_pin" | CONF_RS_PIN = "rs_pin" | ||||||
| @@ -864,6 +867,7 @@ CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC = "target_temperature_low_command_topi | |||||||
| CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC = "target_temperature_low_state_topic" | CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC = "target_temperature_low_state_topic" | ||||||
| CONF_TARGET_TEMPERATURE_STATE_TOPIC = "target_temperature_state_topic" | CONF_TARGET_TEMPERATURE_STATE_TOPIC = "target_temperature_state_topic" | ||||||
| CONF_TEMPERATURE = "temperature" | CONF_TEMPERATURE = "temperature" | ||||||
|  | CONF_TEMPERATURE_COMPENSATION = "temperature_compensation" | ||||||
| CONF_TEMPERATURE_OFFSET = "temperature_offset" | CONF_TEMPERATURE_OFFSET = "temperature_offset" | ||||||
| CONF_TEMPERATURE_SOURCE = "temperature_source" | CONF_TEMPERATURE_SOURCE = "temperature_source" | ||||||
| CONF_TEMPERATURE_STEP = "temperature_step" | CONF_TEMPERATURE_STEP = "temperature_step" | ||||||
| @@ -917,6 +921,7 @@ CONF_UPDATE_ON_BOOT = "update_on_boot" | |||||||
| CONF_URL = "url" | CONF_URL = "url" | ||||||
| CONF_USE_ABBREVIATIONS = "use_abbreviations" | CONF_USE_ABBREVIATIONS = "use_abbreviations" | ||||||
| CONF_USE_ADDRESS = "use_address" | CONF_USE_ADDRESS = "use_address" | ||||||
|  | CONF_USE_DMA = "use_dma" | ||||||
| CONF_USE_FAHRENHEIT = "use_fahrenheit" | CONF_USE_FAHRENHEIT = "use_fahrenheit" | ||||||
| CONF_USERNAME = "username" | CONF_USERNAME = "username" | ||||||
| CONF_UUID = "uuid" | CONF_UUID = "uuid" | ||||||
| @@ -1001,6 +1006,7 @@ ICON_GRAIN = "mdi:grain" | |||||||
| ICON_GYROSCOPE_X = "mdi:axis-x-rotate-clockwise" | ICON_GYROSCOPE_X = "mdi:axis-x-rotate-clockwise" | ||||||
| ICON_GYROSCOPE_Y = "mdi:axis-y-rotate-clockwise" | ICON_GYROSCOPE_Y = "mdi:axis-y-rotate-clockwise" | ||||||
| ICON_GYROSCOPE_Z = "mdi:axis-z-rotate-clockwise" | ICON_GYROSCOPE_Z = "mdi:axis-z-rotate-clockwise" | ||||||
|  | ICON_HEART_PULSE = "mdi:heart-pulse" | ||||||
| ICON_HEATING_COIL = "mdi:heating-coil" | ICON_HEATING_COIL = "mdi:heating-coil" | ||||||
| ICON_KEY_PLUS = "mdi:key-plus" | ICON_KEY_PLUS = "mdi:key-plus" | ||||||
| ICON_LIGHTBULB = "mdi:lightbulb" | ICON_LIGHTBULB = "mdi:lightbulb" | ||||||
| @@ -1040,6 +1046,7 @@ ICON_WEATHER_WINDY = "mdi:weather-windy" | |||||||
| ICON_WIFI = "mdi:wifi" | ICON_WIFI = "mdi:wifi" | ||||||
|  |  | ||||||
| UNIT_AMPERE = "A" | UNIT_AMPERE = "A" | ||||||
|  | UNIT_BEATS_PER_MINUTE = "bpm" | ||||||
| UNIT_BECQUEREL_PER_CUBIC_METER = "Bq/m³" | UNIT_BECQUEREL_PER_CUBIC_METER = "Bq/m³" | ||||||
| UNIT_BYTES = "B" | UNIT_BYTES = "B" | ||||||
| UNIT_CELSIUS = "°C" | UNIT_CELSIUS = "°C" | ||||||
|   | |||||||
| @@ -767,7 +767,8 @@ bool mac_address_is_valid(const uint8_t *mac) { | |||||||
|   return !(is_all_zeros || is_all_ones); |   return !(is_all_zeros || is_all_ones); | ||||||
| } | } | ||||||
|  |  | ||||||
| void delay_microseconds_safe(uint32_t us) {  // avoids CPU locks that could trigger WDT or affect WiFi/BT stability | void IRAM_ATTR HOT delay_microseconds_safe(uint32_t us) { | ||||||
|  |   // avoids CPU locks that could trigger WDT or affect WiFi/BT stability | ||||||
|   uint32_t start = micros(); |   uint32_t start = micros(); | ||||||
|  |  | ||||||
|   const uint32_t lag = 5000;  // microseconds, specifies the maximum time for a CPU busy-loop. |   const uint32_t lag = 5000;  // microseconds, specifies the maximum time for a CPU busy-loop. | ||||||
|   | |||||||
| @@ -11,6 +11,14 @@ | |||||||
|  |  | ||||||
| #include "esphome/core/optional.h" | #include "esphome/core/optional.h" | ||||||
|  |  | ||||||
|  | #ifdef USE_ESP8266 | ||||||
|  | #include <Esp.h> | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #ifdef USE_RP2040 | ||||||
|  | #include <Arduino.h> | ||||||
|  | #endif | ||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
| #include <esp_heap_caps.h> | #include <esp_heap_caps.h> | ||||||
| #endif | #endif | ||||||
| @@ -684,20 +692,23 @@ template<class T> class RAMAllocator { | |||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   RAMAllocator() = default; |   RAMAllocator() = default; | ||||||
|   RAMAllocator(uint8_t flags) : flags_{flags} {} |   RAMAllocator(uint8_t flags) { | ||||||
|  |     // default is both external and internal | ||||||
|  |     flags &= ALLOC_INTERNAL | ALLOC_EXTERNAL; | ||||||
|  |     if (flags != 0) | ||||||
|  |       this->flags_ = flags; | ||||||
|  |   } | ||||||
|   template<class U> constexpr RAMAllocator(const RAMAllocator<U> &other) : flags_{other.flags_} {} |   template<class U> constexpr RAMAllocator(const RAMAllocator<U> &other) : flags_{other.flags_} {} | ||||||
|  |  | ||||||
|   T *allocate(size_t n) { |   T *allocate(size_t n) { | ||||||
|     size_t size = n * sizeof(T); |     size_t size = n * sizeof(T); | ||||||
|     T *ptr = nullptr; |     T *ptr = nullptr; | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|     // External allocation by default or if explicitely requested |     if (this->flags_ & Flags::ALLOC_EXTERNAL) { | ||||||
|     if ((this->flags_ & Flags::ALLOC_EXTERNAL) || ((this->flags_ & Flags::ALLOC_INTERNAL) == 0)) { |  | ||||||
|       ptr = static_cast<T *>(heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT)); |       ptr = static_cast<T *>(heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT)); | ||||||
|     } |     } | ||||||
|     // Fallback to internal allocation if explicitely requested or no flag is specified |     if (ptr == nullptr && this->flags_ & Flags::ALLOC_INTERNAL) { | ||||||
|     if (ptr == nullptr && ((this->flags_ & Flags::ALLOC_INTERNAL) || (this->flags_ & Flags::ALLOC_EXTERNAL) == 0)) { |       ptr = static_cast<T *>(heap_caps_malloc(size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)); | ||||||
|       ptr = static_cast<T *>(malloc(size));  // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc) |  | ||||||
|     } |     } | ||||||
| #else | #else | ||||||
|     // Ignore ALLOC_EXTERNAL/ALLOC_INTERNAL flags if external allocation is not supported |     // Ignore ALLOC_EXTERNAL/ALLOC_INTERNAL flags if external allocation is not supported | ||||||
| @@ -710,8 +721,46 @@ template<class T> class RAMAllocator { | |||||||
|     free(p);  // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc) |     free(p);  // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Return the total heap space available via this allocator | ||||||
|  |    */ | ||||||
|  |   size_t get_free_heap_size() const { | ||||||
|  | #ifdef USE_ESP8266 | ||||||
|  |     return ESP.getFreeHeap();  // NOLINT(readability-static-accessed-through-instance) | ||||||
|  | #elif defined(USE_ESP32) | ||||||
|  |     auto max_internal = | ||||||
|  |         this->flags_ & ALLOC_INTERNAL ? heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL) : 0; | ||||||
|  |     auto max_external = | ||||||
|  |         this->flags_ & ALLOC_EXTERNAL ? heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM) : 0; | ||||||
|  |     return max_internal + max_external; | ||||||
|  | #elif defined(USE_RP2040) | ||||||
|  |     return ::rp2040.getFreeHeap(); | ||||||
|  | #elif defined(USE_LIBRETINY) | ||||||
|  |     return lt_heap_get_free(); | ||||||
|  | #else | ||||||
|  |     return 100000; | ||||||
|  | #endif | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Return the maximum size block this allocator could allocate. This may be an approximation on some platforms | ||||||
|  |    */ | ||||||
|  |   size_t get_max_free_block_size() const { | ||||||
|  | #ifdef USE_ESP8266 | ||||||
|  |     return ESP.getMaxFreeBlockSize();  // NOLINT(readability-static-accessed-through-instance) | ||||||
|  | #elif defined(USE_ESP32) | ||||||
|  |     auto max_internal = | ||||||
|  |         this->flags_ & ALLOC_INTERNAL ? heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL) : 0; | ||||||
|  |     auto max_external = | ||||||
|  |         this->flags_ & ALLOC_EXTERNAL ? heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM) : 0; | ||||||
|  |     return std::max(max_internal, max_external); | ||||||
|  | #else | ||||||
|  |     return this->get_free_heap_size(); | ||||||
|  | #endif | ||||||
|  |   } | ||||||
|  |  | ||||||
|  private: |  private: | ||||||
|   uint8_t flags_{Flags::ALLOW_FAILURE}; |   uint8_t flags_{ALLOC_INTERNAL | ALLOC_EXTERNAL}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| template<class T> using ExternalRAMAllocator = RAMAllocator<T>; | template<class T> using ExternalRAMAllocator = RAMAllocator<T>; | ||||||
|   | |||||||
| @@ -13,8 +13,8 @@ static const char *const TAG = "ring_buffer"; | |||||||
|  |  | ||||||
| RingBuffer::~RingBuffer() { | RingBuffer::~RingBuffer() { | ||||||
|   if (this->handle_ != nullptr) { |   if (this->handle_ != nullptr) { | ||||||
|     vStreamBufferDelete(this->handle_); |     vRingbufferDelete(this->handle_); | ||||||
|     ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); |     RAMAllocator<uint8_t> allocator(RAMAllocator<uint8_t>::ALLOW_FAILURE); | ||||||
|     allocator.deallocate(this->storage_, this->size_); |     allocator.deallocate(this->storage_, this->size_); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @@ -22,26 +22,49 @@ RingBuffer::~RingBuffer() { | |||||||
| std::unique_ptr<RingBuffer> RingBuffer::create(size_t len) { | std::unique_ptr<RingBuffer> RingBuffer::create(size_t len) { | ||||||
|   std::unique_ptr<RingBuffer> rb = make_unique<RingBuffer>(); |   std::unique_ptr<RingBuffer> rb = make_unique<RingBuffer>(); | ||||||
|  |  | ||||||
|   rb->size_ = len + 1; |   rb->size_ = len; | ||||||
|  |  | ||||||
|   ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); |   RAMAllocator<uint8_t> allocator(RAMAllocator<uint8_t>::ALLOW_FAILURE); | ||||||
|   rb->storage_ = allocator.allocate(rb->size_); |   rb->storage_ = allocator.allocate(rb->size_); | ||||||
|   if (rb->storage_ == nullptr) { |   if (rb->storage_ == nullptr) { | ||||||
|     return nullptr; |     return nullptr; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   rb->handle_ = xStreamBufferCreateStatic(rb->size_, 1, rb->storage_, &rb->structure_); |   rb->handle_ = xRingbufferCreateStatic(rb->size_, RINGBUF_TYPE_BYTEBUF, rb->storage_, &rb->structure_); | ||||||
|   ESP_LOGD(TAG, "Created ring buffer with size %u", len); |   ESP_LOGD(TAG, "Created ring buffer with size %u", len); | ||||||
|  |  | ||||||
|   return rb; |   return rb; | ||||||
| } | } | ||||||
|  |  | ||||||
| size_t RingBuffer::read(void *data, size_t len, TickType_t ticks_to_wait) { | size_t RingBuffer::read(void *data, size_t len, TickType_t ticks_to_wait) { | ||||||
|   if (ticks_to_wait > 0) |   size_t bytes_read = 0; | ||||||
|     xStreamBufferSetTriggerLevel(this->handle_, len); |  | ||||||
|  |  | ||||||
|   size_t bytes_read = xStreamBufferReceive(this->handle_, data, len, ticks_to_wait); |   void *buffer_data = xRingbufferReceiveUpTo(this->handle_, &bytes_read, ticks_to_wait, len); | ||||||
|  |  | ||||||
|   xStreamBufferSetTriggerLevel(this->handle_, 1); |   if (buffer_data == nullptr) { | ||||||
|  |     return 0; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   std::memcpy(data, buffer_data, bytes_read); | ||||||
|  |  | ||||||
|  |   vRingbufferReturnItem(this->handle_, buffer_data); | ||||||
|  |  | ||||||
|  |   if (bytes_read < len) { | ||||||
|  |     // Data may have wrapped around, so read a second time to receive the remainder | ||||||
|  |     size_t follow_up_bytes_read = 0; | ||||||
|  |     size_t bytes_remaining = len - bytes_read; | ||||||
|  |  | ||||||
|  |     buffer_data = xRingbufferReceiveUpTo(this->handle_, &follow_up_bytes_read, 0, bytes_remaining); | ||||||
|  |  | ||||||
|  |     if (buffer_data == nullptr) { | ||||||
|  |       return bytes_read; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     std::memcpy((void *) ((uint8_t *) (data) + bytes_read), buffer_data, follow_up_bytes_read); | ||||||
|  |  | ||||||
|  |     vRingbufferReturnItem(this->handle_, buffer_data); | ||||||
|  |     bytes_read += follow_up_bytes_read; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   return bytes_read; |   return bytes_read; | ||||||
| } | } | ||||||
| @@ -49,22 +72,55 @@ size_t RingBuffer::read(void *data, size_t len, TickType_t ticks_to_wait) { | |||||||
| size_t RingBuffer::write(const void *data, size_t len) { | size_t RingBuffer::write(const void *data, size_t len) { | ||||||
|   size_t free = this->free(); |   size_t free = this->free(); | ||||||
|   if (free < len) { |   if (free < len) { | ||||||
|     size_t needed = len - free; |     // Free enough space in the ring buffer to fit the new data | ||||||
|     uint8_t discard[needed]; |     this->discard_bytes_(len - free); | ||||||
|     xStreamBufferReceive(this->handle_, discard, needed, 0); |  | ||||||
|   } |   } | ||||||
|   return xStreamBufferSend(this->handle_, data, len, 0); |   return this->write_without_replacement(data, len, 0); | ||||||
| } | } | ||||||
|  |  | ||||||
| size_t RingBuffer::write_without_replacement(const void *data, size_t len, TickType_t ticks_to_wait) { | size_t RingBuffer::write_without_replacement(const void *data, size_t len, TickType_t ticks_to_wait) { | ||||||
|   return xStreamBufferSend(this->handle_, data, len, ticks_to_wait); |   if (!xRingbufferSend(this->handle_, data, len, ticks_to_wait)) { | ||||||
|  |     // Couldn't fit all the data, so only write what will fit | ||||||
|  |     size_t free = std::min(this->free(), len); | ||||||
|  |     if (xRingbufferSend(this->handle_, data, free, 0)) { | ||||||
|  |       return free; | ||||||
|  |     } | ||||||
|  |     return 0; | ||||||
|  |   } | ||||||
|  |   return len; | ||||||
| } | } | ||||||
|  |  | ||||||
| size_t RingBuffer::available() const { return xStreamBufferBytesAvailable(this->handle_); } | size_t RingBuffer::available() const { | ||||||
|  |   UBaseType_t ux_items_waiting = 0; | ||||||
|  |   vRingbufferGetInfo(this->handle_, nullptr, nullptr, nullptr, nullptr, &ux_items_waiting); | ||||||
|  |   return ux_items_waiting; | ||||||
|  | } | ||||||
|  |  | ||||||
| size_t RingBuffer::free() const { return xStreamBufferSpacesAvailable(this->handle_); } | size_t RingBuffer::free() const { return xRingbufferGetCurFreeSize(this->handle_); } | ||||||
|  |  | ||||||
| BaseType_t RingBuffer::reset() { return xStreamBufferReset(this->handle_); } | BaseType_t RingBuffer::reset() { | ||||||
|  |   // Discards all the available data | ||||||
|  |   return this->discard_bytes_(this->available()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool RingBuffer::discard_bytes_(size_t discard_bytes) { | ||||||
|  |   size_t bytes_read = 0; | ||||||
|  |  | ||||||
|  |   void *buffer_data = xRingbufferReceiveUpTo(this->handle_, &bytes_read, 0, discard_bytes); | ||||||
|  |   if (buffer_data != nullptr) | ||||||
|  |     vRingbufferReturnItem(this->handle_, buffer_data); | ||||||
|  |  | ||||||
|  |   if (bytes_read < discard_bytes) { | ||||||
|  |     size_t wrapped_bytes_read = 0; | ||||||
|  |     buffer_data = xRingbufferReceiveUpTo(this->handle_, &wrapped_bytes_read, 0, discard_bytes - bytes_read); | ||||||
|  |     if (buffer_data != nullptr) { | ||||||
|  |       vRingbufferReturnItem(this->handle_, buffer_data); | ||||||
|  |       bytes_read += wrapped_bytes_read; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return (bytes_read == discard_bytes); | ||||||
|  | } | ||||||
|  |  | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
| #include <freertos/FreeRTOS.h> | #include <freertos/FreeRTOS.h> | ||||||
| #include <freertos/stream_buffer.h> | #include <freertos/ringbuf.h> | ||||||
|  |  | ||||||
| #include <cinttypes> | #include <cinttypes> | ||||||
| #include <memory> | #include <memory> | ||||||
| @@ -82,9 +82,14 @@ class RingBuffer { | |||||||
|   static std::unique_ptr<RingBuffer> create(size_t len); |   static std::unique_ptr<RingBuffer> create(size_t len); | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   StreamBufferHandle_t handle_; |   /// @brief Discards data from the ring buffer. | ||||||
|   StaticStreamBuffer_t structure_; |   /// @param discard_bytes amount of bytes to discard | ||||||
|   uint8_t *storage_; |   /// @return True if all bytes were successfully discarded, false otherwise | ||||||
|  |   bool discard_bytes_(size_t discard_bytes); | ||||||
|  |  | ||||||
|  |   RingbufHandle_t handle_{nullptr}; | ||||||
|  |   StaticRingbuffer_t structure_; | ||||||
|  |   uint8_t *storage_{nullptr}; | ||||||
|   size_t size_{0}; |   size_t size_{0}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -108,6 +108,12 @@ def is_authenticated(handler: BaseHandler) -> bool: | |||||||
|             return True |             return True | ||||||
|  |  | ||||||
|     if settings.using_auth: |     if settings.using_auth: | ||||||
|  |         if auth_header := handler.request.headers.get("Authorization"): | ||||||
|  |             assert isinstance(auth_header, str) | ||||||
|  |             if auth_header.startswith("Basic "): | ||||||
|  |                 auth_decoded = base64.b64decode(auth_header[6:]).decode() | ||||||
|  |                 username, password = auth_decoded.split(":", 1) | ||||||
|  |                 return settings.check_password(username, password) | ||||||
|         return handler.get_secure_cookie(AUTH_COOKIE_NAME) == COOKIE_AUTHENTICATED_YES |         return handler.get_secure_cookie(AUTH_COOKIE_NAME) == COOKIE_AUTHENTICATED_YES | ||||||
|  |  | ||||||
|     return True |     return True | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ pyserial==3.5 | |||||||
| platformio==6.1.16  # When updating platformio, also update Dockerfile | platformio==6.1.16  # When updating platformio, also update Dockerfile | ||||||
| esptool==4.7.0 | esptool==4.7.0 | ||||||
| click==8.1.7 | click==8.1.7 | ||||||
| esphome-dashboard==20241120.0 | esphome-dashboard==20241217.1 | ||||||
| aioesphomeapi==24.6.2 | aioesphomeapi==24.6.2 | ||||||
| zeroconf==0.132.2 | zeroconf==0.132.2 | ||||||
| puremagic==1.27 | puremagic==1.27 | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								tests/components/adc/test.bk72xx-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								tests/components/adc/test.bk72xx-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | sensor: | ||||||
|  |   - platform: adc | ||||||
|  |     pin: P23 | ||||||
|  |     name: Basic ADC Test | ||||||
| @@ -3,14 +3,12 @@ light: | |||||||
|     id: led_strip |     id: led_strip | ||||||
|     pin: 4 |     pin: 4 | ||||||
|     num_leds: 60 |     num_leds: 60 | ||||||
|     rmt_channel: 0 |  | ||||||
|     rgb_order: GRB |     rgb_order: GRB | ||||||
|     chipset: ws2812 |     chipset: ws2812 | ||||||
|   - platform: esp32_rmt_led_strip |   - platform: esp32_rmt_led_strip | ||||||
|     id: led_strip2 |     id: led_strip2 | ||||||
|     pin: 5 |     pin: 5 | ||||||
|     num_leds: 60 |     num_leds: 60 | ||||||
|     rmt_channel: 1 |  | ||||||
|     rgb_order: RGB |     rgb_order: RGB | ||||||
|     bit0_high: 100µs |     bit0_high: 100µs | ||||||
|     bit0_low: 100µs |     bit0_low: 100µs | ||||||
|   | |||||||
| @@ -3,14 +3,12 @@ light: | |||||||
|     id: led_strip |     id: led_strip | ||||||
|     pin: 13 |     pin: 13 | ||||||
|     num_leds: 60 |     num_leds: 60 | ||||||
|     rmt_channel: 6 |  | ||||||
|     rgb_order: GRB |     rgb_order: GRB | ||||||
|     chipset: ws2812 |     chipset: ws2812 | ||||||
|   - platform: esp32_rmt_led_strip |   - platform: esp32_rmt_led_strip | ||||||
|     id: led_strip2 |     id: led_strip2 | ||||||
|     pin: 14 |     pin: 14 | ||||||
|     num_leds: 60 |     num_leds: 60 | ||||||
|     rmt_channel: 2 |  | ||||||
|     rgb_order: RGB |     rgb_order: RGB | ||||||
|     bit0_high: 100µs |     bit0_high: 100µs | ||||||
|     bit0_low: 100µs |     bit0_low: 100µs | ||||||
|   | |||||||
| @@ -165,6 +165,11 @@ lvgl: | |||||||
|               - Nov |               - Nov | ||||||
|               - Dec |               - Dec | ||||||
|             selected_index: 1 |             selected_index: 1 | ||||||
|  |             on_change: | ||||||
|  |               then: | ||||||
|  |                 - logger.log: | ||||||
|  |                     format: "Roller changed = %d: %s" | ||||||
|  |                     args: [x, text.c_str()] | ||||||
|             on_value: |             on_value: | ||||||
|               then: |               then: | ||||||
|                 - logger.log: |                 - logger.log: | ||||||
| @@ -451,6 +456,7 @@ lvgl: | |||||||
|             src: cat_image |             src: cat_image | ||||||
|             align: top_left |             align: top_left | ||||||
|             y: "50" |             y: "50" | ||||||
|  |             mode: real | ||||||
|         - tileview: |         - tileview: | ||||||
|             id: tileview_id |             id: tileview_id | ||||||
|             scrollbar_mode: active |             scrollbar_mode: active | ||||||
| @@ -647,6 +653,7 @@ lvgl: | |||||||
|                       grid_cell_row_pos: 0 |                       grid_cell_row_pos: 0 | ||||||
|                       grid_cell_column_pos: 0 |                       grid_cell_column_pos: 0 | ||||||
|                       src: !lambda return dog_image; |                       src: !lambda return dog_image; | ||||||
|  |                       mode: virtual | ||||||
|                       on_click: |                       on_click: | ||||||
|                         then: |                         then: | ||||||
|                           - lvgl.tabview.select: |                           - lvgl.tabview.select: | ||||||
|   | |||||||
| @@ -1,5 +1,7 @@ | |||||||
| esphome: | esphome: | ||||||
|   on_boot: |   on_boot: | ||||||
|  |     - lambda: 'ESP_LOGD("display","is_connected(): %s", YESNO(id(main_lcd).is_connected()));' | ||||||
|  |  | ||||||
|     # Binary sensor publish action tests |     # Binary sensor publish action tests | ||||||
|     - binary_sensor.nextion.publish: |     - binary_sensor.nextion.publish: | ||||||
|         id: r0_sensor |         id: r0_sensor | ||||||
|   | |||||||
| @@ -16,6 +16,19 @@ opentherm: | |||||||
|   summer_mode_active: true |   summer_mode_active: true | ||||||
|   dhw_block: true |   dhw_block: true | ||||||
|   sync_mode: true |   sync_mode: true | ||||||
|  |   controller_product_type: 63 | ||||||
|  |   controller_product_version: 1 | ||||||
|  |   opentherm_version_controller: 2.2 | ||||||
|  |   controller_id: 1 | ||||||
|  |   controller_configuration: 1 | ||||||
|  |   before_send: | ||||||
|  |     then: | ||||||
|  |       - lambda: |- | ||||||
|  |           ESP_LOGW("OT", ">> Sending message %d", x.id); | ||||||
|  |   before_process_response: | ||||||
|  |     then: | ||||||
|  |       - lambda: |- | ||||||
|  |           ESP_LOGW("OT", "<< Processing response %d", x.id); | ||||||
|  |  | ||||||
| output: | output: | ||||||
|   - platform: opentherm |   - platform: opentherm | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user