mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Merge branch 'esphome:dev' into modem_component
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: | ||||
|           python-version: "3.9" | ||||
|       - 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 | ||||
|         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" | ||||
|       - "!.yamllint" | ||||
|       - "!.github/dependabot.yml" | ||||
|       - "!docker/**" | ||||
|   merge_group: | ||||
|  | ||||
| permissions: | ||||
|   | ||||
							
								
								
									
										6
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -90,7 +90,7 @@ jobs: | ||||
|           python-version: "3.9" | ||||
|  | ||||
|       - 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 | ||||
|         if: matrix.platform != 'linux/amd64' | ||||
|         uses: docker/setup-qemu-action@v3.2.0 | ||||
| @@ -141,7 +141,7 @@ jobs: | ||||
|           echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT | ||||
|  | ||||
|       - name: Upload digests | ||||
|         uses: actions/upload-artifact@v4.4.3 | ||||
|         uses: actions/upload-artifact@v4.5.0 | ||||
|         with: | ||||
|           name: digests-${{ steps.sanitize.outputs.name }} | ||||
|           path: /tmp/digests | ||||
| @@ -184,7 +184,7 @@ jobs: | ||||
|           merge-multiple: true | ||||
|  | ||||
|       - 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 | ||||
|         if: matrix.registry == 'dockerhub' | ||||
|   | ||||
| @@ -1,11 +1,6 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import pins | ||||
| from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER | ||||
|  | ||||
| from esphome.core import CORE | ||||
| import esphome.codegen as cg | ||||
| from esphome.components.esp32 import get_esp32_variant | ||||
| from esphome.const import PLATFORM_ESP8266 | ||||
| from esphome.components.esp32.const import ( | ||||
|     VARIANT_ESP32, | ||||
|     VARIANT_ESP32C2, | ||||
| @@ -15,6 +10,9 @@ from esphome.components.esp32.const import ( | ||||
|     VARIANT_ESP32S2, | ||||
|     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"] | ||||
|  | ||||
| @@ -102,11 +100,11 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = { | ||||
|         6: adc1_channel_t.ADC1_CHANNEL_6, | ||||
|     }, | ||||
|     VARIANT_ESP32H2: { | ||||
|         0: adc1_channel_t.ADC1_CHANNEL_0, | ||||
|         1: adc1_channel_t.ADC1_CHANNEL_1, | ||||
|         2: adc1_channel_t.ADC1_CHANNEL_2, | ||||
|         3: adc1_channel_t.ADC1_CHANNEL_3, | ||||
|         4: adc1_channel_t.ADC1_CHANNEL_4, | ||||
|         1: adc1_channel_t.ADC1_CHANNEL_0, | ||||
|         2: adc1_channel_t.ADC1_CHANNEL_1, | ||||
|         3: adc1_channel_t.ADC1_CHANNEL_2, | ||||
|         4: adc1_channel_t.ADC1_CHANNEL_3, | ||||
|         5: adc1_channel_t.ADC1_CHANNEL_4, | ||||
|     }, | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										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 | ||||
| @@ -25,8 +25,7 @@ void BLEClient::loop() { | ||||
|  | ||||
| void BLEClient::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "BLE Client:"); | ||||
|   ESP_LOGCONFIG(TAG, "  Address: %s", this->address_str().c_str()); | ||||
|   ESP_LOGCONFIG(TAG, "  Auto-Connect: %s", TRUEFALSE(this->auto_connect_)); | ||||
|   BLEClientBase::dump_config(); | ||||
| } | ||||
|  | ||||
| bool BLEClient::parse_device(const espbt::ESPBTDevice &device) { | ||||
|   | ||||
| @@ -13,6 +13,11 @@ namespace bluetooth_proxy { | ||||
|  | ||||
| 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, | ||||
|                                               esp_ble_gattc_cb_param_t *param) { | ||||
|   if (!BLEClientBase::gattc_event_handler(event, gattc_if, param)) | ||||
|   | ||||
| @@ -11,6 +11,7 @@ class BluetoothProxy; | ||||
|  | ||||
| class BluetoothConnection : public esp32_ble_client::BLEClientBase { | ||||
|  public: | ||||
|   void dump_config() override; | ||||
|   bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||
|                            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; | ||||
|   | ||||
| @@ -37,8 +37,9 @@ void ClimateIR::setup() { | ||||
|       this->publish_state(); | ||||
|     }); | ||||
|     this->current_temperature = this->sensor_->state; | ||||
|   } else | ||||
|   } else { | ||||
|     this->current_temperature = NAN; | ||||
|   } | ||||
|   // restore set points | ||||
|   auto restore = this->restore_state_(); | ||||
|   if (restore.has_value()) { | ||||
|   | ||||
| @@ -131,8 +131,9 @@ bool CoolixClimate::on_coolix(climate::Climate *parent, remote_base::RemoteRecei | ||||
|       } else { | ||||
|         parent->mode = climate::CLIMATE_MODE_FAN_ONLY; | ||||
|       } | ||||
|     } else | ||||
|     } else { | ||||
|       parent->mode = climate::CLIMATE_MODE_COOL; | ||||
|     } | ||||
|  | ||||
|     // Fan Speed | ||||
|     if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || parent->mode == climate::CLIMATE_MODE_HEAT_COOL || | ||||
|   | ||||
| @@ -30,109 +30,162 @@ static const char *const TAG = "debug"; | ||||
|  | ||||
| std::string DebugComponent::get_reset_reason_() { | ||||
|   std::string reset_reason; | ||||
|   switch (rtc_get_reset_reason(0)) { | ||||
|     case POWERON_RESET: | ||||
|       reset_reason = "Power On Reset"; | ||||
|   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)) { | ||||
|         case POWERON_RESET: | ||||
|           reset_reason = "Power On Reset"; | ||||
|           break; | ||||
| #if defined(USE_ESP32_VARIANT_ESP32) | ||||
|     case SW_RESET: | ||||
|         case SW_RESET: | ||||
| #elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \ | ||||
|     defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6) | ||||
|     case RTC_SW_SYS_RESET: | ||||
|         case RTC_SW_SYS_RESET: | ||||
| #endif | ||||
|       reset_reason = "Software Reset Digital Core"; | ||||
|       break; | ||||
|           reset_reason = "Software Reset Digital Core"; | ||||
|           break; | ||||
| #if defined(USE_ESP32_VARIANT_ESP32) | ||||
|     case OWDT_RESET: | ||||
|       reset_reason = "Watch Dog Reset Digital Core"; | ||||
|       break; | ||||
|         case OWDT_RESET: | ||||
|           reset_reason = "Watch Dog Reset Digital Core"; | ||||
|           break; | ||||
| #endif | ||||
|     case DEEPSLEEP_RESET: | ||||
|       reset_reason = "Deep Sleep Reset Digital Core"; | ||||
|       break; | ||||
|         case DEEPSLEEP_RESET: | ||||
|           reset_reason = "Deep Sleep Reset Digital Core"; | ||||
|           break; | ||||
| #if defined(USE_ESP32_VARIANT_ESP32) | ||||
|     case SDIO_RESET: | ||||
|       reset_reason = "SLC Module Reset Digital Core"; | ||||
|       break; | ||||
|         case SDIO_RESET: | ||||
|           reset_reason = "SLC Module Reset Digital Core"; | ||||
|           break; | ||||
| #endif | ||||
|     case TG0WDT_SYS_RESET: | ||||
|       reset_reason = "Timer Group 0 Watch Dog Reset Digital Core"; | ||||
|       break; | ||||
|     case TG1WDT_SYS_RESET: | ||||
|       reset_reason = "Timer Group 1 Watch Dog Reset Digital Core"; | ||||
|       break; | ||||
|     case RTCWDT_SYS_RESET: | ||||
|       reset_reason = "RTC Watch Dog Reset Digital Core"; | ||||
|       break; | ||||
|         case TG0WDT_SYS_RESET: | ||||
|           reset_reason = "Timer Group 0 Watch Dog Reset Digital Core"; | ||||
|           break; | ||||
|         case TG1WDT_SYS_RESET: | ||||
|           reset_reason = "Timer Group 1 Watch Dog Reset Digital Core"; | ||||
|           break; | ||||
|         case RTCWDT_SYS_RESET: | ||||
|           reset_reason = "RTC Watch Dog Reset Digital Core"; | ||||
|           break; | ||||
| #if !defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2) | ||||
|     case INTRUSION_RESET: | ||||
|       reset_reason = "Intrusion Reset CPU"; | ||||
|       break; | ||||
|         case INTRUSION_RESET: | ||||
|           reset_reason = "Intrusion Reset CPU"; | ||||
|           break; | ||||
| #endif | ||||
| #if defined(USE_ESP32_VARIANT_ESP32) | ||||
|     case TGWDT_CPU_RESET: | ||||
|       reset_reason = "Timer Group Reset CPU"; | ||||
|       break; | ||||
|         case TGWDT_CPU_RESET: | ||||
|           reset_reason = "Timer Group Reset CPU"; | ||||
|           break; | ||||
| #elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \ | ||||
|     defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6) | ||||
|     case TG0WDT_CPU_RESET: | ||||
|       reset_reason = "Timer Group 0 Reset CPU"; | ||||
|       break; | ||||
|         case TG0WDT_CPU_RESET: | ||||
|           reset_reason = "Timer Group 0 Reset CPU"; | ||||
|           break; | ||||
| #endif | ||||
| #if defined(USE_ESP32_VARIANT_ESP32) | ||||
|     case SW_CPU_RESET: | ||||
|         case SW_CPU_RESET: | ||||
| #elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \ | ||||
|     defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6) | ||||
|     case RTC_SW_CPU_RESET: | ||||
|         case RTC_SW_CPU_RESET: | ||||
| #endif | ||||
|       reset_reason = "Software Reset CPU"; | ||||
|       break; | ||||
|     case RTCWDT_CPU_RESET: | ||||
|       reset_reason = "RTC Watch Dog Reset CPU"; | ||||
|       break; | ||||
|           reset_reason = "Software Reset CPU"; | ||||
|           break; | ||||
|         case RTCWDT_CPU_RESET: | ||||
|           reset_reason = "RTC Watch Dog Reset CPU"; | ||||
|           break; | ||||
| #if defined(USE_ESP32_VARIANT_ESP32) | ||||
|     case EXT_CPU_RESET: | ||||
|       reset_reason = "External CPU Reset"; | ||||
|       break; | ||||
|         case EXT_CPU_RESET: | ||||
|           reset_reason = "External CPU Reset"; | ||||
|           break; | ||||
| #endif | ||||
|     case RTCWDT_BROWN_OUT_RESET: | ||||
|       reset_reason = "Voltage Unstable Reset"; | ||||
|       break; | ||||
|     case RTCWDT_RTC_RESET: | ||||
|       reset_reason = "RTC Watch Dog Reset Digital Core And RTC Module"; | ||||
|       break; | ||||
|         case RTCWDT_BROWN_OUT_RESET: | ||||
|           reset_reason = "Voltage Unstable Reset"; | ||||
|           break; | ||||
|         case RTCWDT_RTC_RESET: | ||||
|           reset_reason = "RTC Watch Dog Reset Digital Core And RTC Module"; | ||||
|           break; | ||||
| #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \ | ||||
|     defined(USE_ESP32_VARIANT_ESP32C6) | ||||
|     case TG1WDT_CPU_RESET: | ||||
|       reset_reason = "Timer Group 1 Reset CPU"; | ||||
|       break; | ||||
|     case SUPER_WDT_RESET: | ||||
|       reset_reason = "Super Watchdog Reset Digital Core And RTC Module"; | ||||
|       break; | ||||
|     case EFUSE_RESET: | ||||
|       reset_reason = "eFuse Reset Digital Core"; | ||||
|       break; | ||||
|         case TG1WDT_CPU_RESET: | ||||
|           reset_reason = "Timer Group 1 Reset CPU"; | ||||
|           break; | ||||
|         case SUPER_WDT_RESET: | ||||
|           reset_reason = "Super Watchdog Reset Digital Core And RTC Module"; | ||||
|           break; | ||||
|         case EFUSE_RESET: | ||||
|           reset_reason = "eFuse Reset Digital Core"; | ||||
|           break; | ||||
| #endif | ||||
| #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) | ||||
|     case GLITCH_RTC_RESET: | ||||
|       reset_reason = "Glitch Reset Digital Core And RTC Module"; | ||||
|       break; | ||||
|         case GLITCH_RTC_RESET: | ||||
|           reset_reason = "Glitch Reset Digital Core And RTC Module"; | ||||
|           break; | ||||
| #endif | ||||
| #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6) | ||||
|     case USB_UART_CHIP_RESET: | ||||
|       reset_reason = "USB UART Reset Digital Core"; | ||||
|       break; | ||||
|     case USB_JTAG_CHIP_RESET: | ||||
|       reset_reason = "USB JTAG Reset Digital Core"; | ||||
|       break; | ||||
|         case USB_UART_CHIP_RESET: | ||||
|           reset_reason = "USB UART Reset Digital Core"; | ||||
|           break; | ||||
|         case USB_JTAG_CHIP_RESET: | ||||
|           reset_reason = "USB JTAG Reset Digital Core"; | ||||
|           break; | ||||
| #endif | ||||
| #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3) | ||||
|     case POWER_GLITCH_RESET: | ||||
|       reset_reason = "Power Glitch Reset Digital Core And RTC Module"; | ||||
|       break; | ||||
|         case POWER_GLITCH_RESET: | ||||
|           reset_reason = "Power Glitch Reset Digital Core And RTC Module"; | ||||
|           break; | ||||
| #endif | ||||
|     default: | ||||
|       reset_reason = "Unknown Reset Reason"; | ||||
|         default: | ||||
|           reset_reason = "Unknown Reset Reason"; | ||||
|       } | ||||
|       break; | ||||
|   } | ||||
|   ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str()); | ||||
|   return reset_reason; | ||||
| @@ -294,4 +347,4 @@ void DebugComponent::update_platform_() { | ||||
|  | ||||
| }  // namespace debug | ||||
| }  // namespace esphome | ||||
| #endif | ||||
| #endif  // USE_ESP32 | ||||
|   | ||||
| @@ -118,8 +118,9 @@ std::unique_ptr<Command> CircularCommandQueue::dequeue() { | ||||
|   if (front_ == rear_) { | ||||
|     front_ = -1; | ||||
|     rear_ = -1; | ||||
|   } else | ||||
|   } else { | ||||
|     front_ = (front_ + 1) % COMMAND_QUEUE_SIZE; | ||||
|   } | ||||
|  | ||||
|   return dequeued_cmd; | ||||
| } | ||||
|   | ||||
| @@ -157,8 +157,9 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r | ||||
|       if (bit == 0) { | ||||
|         bit = 7; | ||||
|         byte++; | ||||
|       } else | ||||
|       } else { | ||||
|         bit--; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   if (!report_errors && error_code != 0) | ||||
|   | ||||
| @@ -266,8 +266,9 @@ void Display::filled_gauge(int center_x, int center_y, int radius1, int radius2, | ||||
|         if (dymax < float(-dxmax) * tan_a) { | ||||
|           upd_dxmax = ceil(float(dymax) / tan_a); | ||||
|           hline_width = -dxmax - upd_dxmax + 1; | ||||
|         } else | ||||
|         } else { | ||||
|           hline_width = 0; | ||||
|         } | ||||
|       } | ||||
|       if (hline_width > 0) | ||||
|         this->horizontal_line(center_x + dxmax, center_y - dymax, hline_width, color); | ||||
|   | ||||
| @@ -90,8 +90,9 @@ void Rect::info(const std::string &prefix) { | ||||
|   if (this->is_set()) { | ||||
|     ESP_LOGI(TAG, "%s [%3d,%3d,%3d,%3d] (%3d,%3d)", prefix.c_str(), this->x, this->y, this->w, this->h, this->x2(), | ||||
|              this->y2()); | ||||
|   } else | ||||
|   } else { | ||||
|     ESP_LOGI(TAG, "%s ** IS NOT SET **", prefix.c_str()); | ||||
|   } | ||||
| } | ||||
|  | ||||
| }  // namespace display | ||||
|   | ||||
| @@ -602,6 +602,9 @@ async def to_code(config): | ||||
|         cg.add_platformio_option( | ||||
|             "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_CUSTOM", True) | ||||
|         add_idf_sdkconfig_option( | ||||
|   | ||||
| @@ -27,6 +27,9 @@ namespace 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() { | ||||
|   global_ble = this; | ||||
|   ESP_LOGCONFIG(TAG, "Setting up BLE..."); | ||||
| @@ -322,7 +325,8 @@ void ESP32BLE::loop() { | ||||
|       default: | ||||
|         break; | ||||
|     } | ||||
|     delete ble_event;  // NOLINT(cppcoreguidelines-owning-memory) | ||||
|     ble_event->~BLEEvent(); | ||||
|     EVENT_ALLOCATOR.deallocate(ble_event, 1); | ||||
|     ble_event = this->ble_events_.pop(); | ||||
|   } | ||||
|   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) { | ||||
|   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); | ||||
| }  // 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) { | ||||
|   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, | ||||
|                                    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); | ||||
| }  // 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, | ||||
|                                          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, | ||||
|                                    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); | ||||
| }  // 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, | ||||
|                                          esp_ble_gattc_cb_param_t *param) { | ||||
|   | ||||
| @@ -83,7 +83,7 @@ esp_err_t BLEAdvertising::services_advertisement_() { | ||||
|   esp_err_t err; | ||||
|  | ||||
|   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_; | ||||
|   err = esp_ble_gap_config_adv_data(&this->advertising_data_); | ||||
|   if (err != ESP_OK) { | ||||
|   | ||||
| @@ -26,10 +26,10 @@ template<class T> class Queue { | ||||
|   void push(T *element) { | ||||
|     if (element == nullptr) | ||||
|       return; | ||||
|     if (xSemaphoreTake(m_, 5L / portTICK_PERIOD_MS)) { | ||||
|       q_.push(element); | ||||
|       xSemaphoreGive(m_); | ||||
|     } | ||||
|     // It is not called from main loop. Thus it won't block main thread. | ||||
|     xSemaphoreTake(m_, portMAX_DELAY); | ||||
|     q_.push(element); | ||||
|     xSemaphoreGive(m_); | ||||
|   } | ||||
|  | ||||
|   T *pop() { | ||||
|   | ||||
| @@ -44,6 +44,50 @@ void BLEClientBase::loop() { | ||||
|  | ||||
| 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) { | ||||
|   if (!this->auto_connect_) | ||||
|     return false; | ||||
| @@ -129,6 +173,8 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ | ||||
|       } else { | ||||
|         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->status_ = param->reg.status; | ||||
|         this->mark_failed(); | ||||
|       } | ||||
|       break; | ||||
|     } | ||||
|   | ||||
| @@ -26,6 +26,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { | ||||
|   void setup() override; | ||||
|   void loop() override; | ||||
|   float get_setup_priority() const override; | ||||
|   void dump_config() override; | ||||
|  | ||||
|   void run_later(std::function<void()> &&f);  // NOLINT | ||||
|   bool parse_device(const espbt::ESPBTDevice &device) override; | ||||
| @@ -103,6 +104,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { | ||||
|   bool paired_{false}; | ||||
|   espbt::ConnectionType connection_type_{espbt::ConnectionType::V1}; | ||||
|   std::vector<BLEService *> services_; | ||||
|   esp_gatt_status_t status_{ESP_GATT_OK}; | ||||
|  | ||||
|   void log_event_(const char *name); | ||||
| }; | ||||
|   | ||||
| @@ -58,7 +58,6 @@ void ESP32BLETracker::setup() { | ||||
|   global_esp32_ble_tracker = this; | ||||
|   this->scan_result_lock_ = xSemaphoreCreateMutex(); | ||||
|   this->scan_end_lock_ = xSemaphoreCreateMutex(); | ||||
|   this->scanner_idle_ = true; | ||||
|  | ||||
| #ifdef USE_OTA | ||||
|   ota::get_global_ota_callback()->add_on_state_callback( | ||||
| @@ -107,6 +106,15 @@ void ESP32BLETracker::loop() { | ||||
|         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; | ||||
|  | ||||
|   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_fail_count_ == 255) { | ||||
|         ESP_LOGE(TAG, "ESP-IDF BLE scan could not restart after 255 attempts, rebooting to restore BLE stack..."); | ||||
|       if (this->scan_start_fail_count_ == std::numeric_limits<uint8_t>::max()) { | ||||
|         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(); | ||||
|       } | ||||
|       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_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_); | ||||
|   if (err != ESP_OK) { | ||||
|     ESP_LOGE(TAG, "esp_ble_gap_set_scan_params failed: %d", err); | ||||
| @@ -293,11 +308,6 @@ void ESP32BLETracker::start_scan_(bool first) { | ||||
|     return; | ||||
|   } | ||||
|   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_() { | ||||
| @@ -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) { | ||||
|   ESP_LOGV(TAG, "gap_scan_set_param_complete - status %d", param.status); | ||||
|   if (param.status == ESP_BT_STATUS_DONE) { | ||||
|     this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS; | ||||
|   } 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) { | ||||
|   ESP_LOGV(TAG, "gap_scan_start_complete - status %d", param.status); | ||||
|   this->scan_start_failed_ = param.status; | ||||
|   if (param.status == ESP_BT_STATUS_SUCCESS) { | ||||
|     this->scan_start_fail_count_ = 0; | ||||
|   } else { | ||||
|     this->scan_start_fail_count_++; | ||||
|     if (this->scan_start_fail_count_ != std::numeric_limits<uint8_t>::max()) { | ||||
|       this->scan_start_fail_count_++; | ||||
|     } | ||||
|     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) { | ||||
|   ESP_LOGV(TAG, "gap_scan_stop_complete - status %d", param.status); | ||||
|   xSemaphoreGive(this->scan_end_lock_); | ||||
| } | ||||
|  | ||||
| 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 (xSemaphoreTake(this->scan_result_lock_, 0L)) { | ||||
|       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 Window: %.1f ms", this->scan_window_ * 0.625f); | ||||
|   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) { | ||||
|   | ||||
| @@ -178,7 +178,7 @@ class ESPBTClient : public ESPBTDeviceListener { | ||||
|   int app_id; | ||||
|  | ||||
|  protected: | ||||
|   ClientState state_; | ||||
|   ClientState state_{ClientState::INIT}; | ||||
| }; | ||||
|  | ||||
| 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. | ||||
|   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 | ||||
|   std::vector<uint64_t> already_discovered_; | ||||
| @@ -242,10 +242,10 @@ class ESP32BLETracker : public Component, | ||||
|   uint32_t scan_duration_; | ||||
|   uint32_t scan_interval_; | ||||
|   uint32_t scan_window_; | ||||
|   uint8_t scan_start_fail_count_; | ||||
|   uint8_t scan_start_fail_count_{0}; | ||||
|   bool scan_continuous_; | ||||
|   bool scan_active_; | ||||
|   bool scanner_idle_; | ||||
|   bool scanner_idle_{true}; | ||||
|   bool ble_was_disabled_{true}; | ||||
|   bool raw_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_bt_status_t scan_start_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 | ||||
|   | ||||
| @@ -112,7 +112,7 @@ void ESP32ImprovComponent::loop() { | ||||
|         this->set_state_(improv::STATE_AUTHORIZED); | ||||
|       } else | ||||
| #else | ||||
|       this->set_state_(improv::STATE_AUTHORIZED); | ||||
|       { this->set_state_(improv::STATE_AUTHORIZED); } | ||||
| #endif | ||||
|       { | ||||
|         if (!this->check_identify_()) | ||||
|   | ||||
| @@ -1,7 +1,8 @@ | ||||
| import esphome.config_validation as cv | ||||
| import esphome.codegen as cg | ||||
|  | ||||
| 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"] | ||||
|  | ||||
| @@ -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 | ||||
|  | ||||
|     def _validator(value): | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| #include <cinttypes> | ||||
| #include "led_strip.h" | ||||
| #include <cinttypes> | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
|  | ||||
| @@ -13,9 +13,13 @@ namespace 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 uint8_t RMT_CLK_DIV = 2; | ||||
| #endif | ||||
|  | ||||
| void ESP32RMTLEDStripLightOutput::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up ESP32 LED Strip..."); | ||||
| @@ -29,6 +33,7 @@ void ESP32RMTLEDStripLightOutput::setup() { | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|   memset(this->buf_, 0, buffer_size); | ||||
|  | ||||
|   this->effect_data_ = allocator.allocate(this->num_leds_); | ||||
|   if (this->effect_data_ == nullptr) { | ||||
| @@ -37,9 +42,48 @@ void ESP32RMTLEDStripLightOutput::setup() { | ||||
|     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); | ||||
|   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; | ||||
|   memset(&config, 0, sizeof(config)); | ||||
| @@ -64,6 +108,7 @@ void ESP32RMTLEDStripLightOutput::setup() { | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
| #endif | ||||
| } | ||||
|  | ||||
| void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high, | ||||
| @@ -100,7 +145,12 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) { | ||||
|  | ||||
|   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"); | ||||
|     this->status_set_warning(); | ||||
|     return; | ||||
| @@ -112,7 +162,11 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) { | ||||
|   size_t size = 0; | ||||
|   size_t len = 0; | ||||
|   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_; | ||||
| #endif | ||||
|   while (size < buffer_size) { | ||||
|     uint8_t b = *psrc; | ||||
|     for (int i = 0; i < 8; i++) { | ||||
| @@ -130,7 +184,16 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) { | ||||
|     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"); | ||||
|     this->status_set_warning(); | ||||
|     return; | ||||
| @@ -186,7 +249,11 @@ light::ESPColorView ESP32RMTLEDStripLightOutput::get_view_internal(int32_t index | ||||
| void ESP32RMTLEDStripLightOutput::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "ESP32 RMT LED Strip:"); | ||||
|   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_); | ||||
| #endif | ||||
|   const char *rgb_order; | ||||
|   switch (this->rgb_order_) { | ||||
|     case ORDER_RGB: | ||||
|   | ||||
| @@ -9,8 +9,14 @@ | ||||
| #include "esphome/core/helpers.h" | ||||
|  | ||||
| #include <driver/gpio.h> | ||||
| #include <driver/rmt.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 esp32_rmt_led_strip { | ||||
| @@ -54,7 +60,11 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { | ||||
|                       uint32_t reset_time_high, uint32_t reset_time_low); | ||||
|  | ||||
|   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; } | ||||
| #endif | ||||
|  | ||||
|   void clear_effect_data() override { | ||||
|     for (int i = 0; i < this->size(); i++) | ||||
| @@ -70,7 +80,17 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { | ||||
|  | ||||
|   uint8_t *buf_{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 bit0_, bit1_, reset_; | ||||
|   rmt_channel_t channel_{RMT_CHANNEL_0}; | ||||
| #endif | ||||
|  | ||||
|   uint8_t pin_; | ||||
|   uint16_t num_leds_; | ||||
| @@ -78,9 +98,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { | ||||
|   bool is_wrgb_; | ||||
|   bool use_psram_; | ||||
|  | ||||
|   rmt_item32_t bit0_, bit1_, reset_; | ||||
|   RGBOrder rgb_order_; | ||||
|   rmt_channel_t channel_; | ||||
|  | ||||
|   uint32_t last_refresh_{0}; | ||||
|   optional<uint32_t> max_refresh_rate_{}; | ||||
|   | ||||
| @@ -13,6 +13,7 @@ from esphome.const import ( | ||||
|     CONF_PIN, | ||||
|     CONF_RGB_ORDER, | ||||
|     CONF_RMT_CHANNEL, | ||||
|     CONF_RMT_SYMBOLS, | ||||
| ) | ||||
|  | ||||
| CODEOWNERS = ["@jesserockz"] | ||||
| @@ -23,8 +24,6 @@ ESP32RMTLEDStripLightOutput = esp32_rmt_led_strip_ns.class_( | ||||
|     "ESP32RMTLEDStripLightOutput", light.AddressableLight | ||||
| ) | ||||
|  | ||||
| rmt_channel_t = cg.global_ns.enum("rmt_channel_t") | ||||
|  | ||||
| RGBOrder = esp32_rmt_led_strip_ns.enum("RGBOrder") | ||||
|  | ||||
| RGB_ORDERS = { | ||||
| @@ -65,6 +64,13 @@ CONF_RESET_HIGH = "reset_high" | ||||
| 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( | ||||
|     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_NUM_LEDS): cv.positive_not_null_int, | ||||
|             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_CHIPSET): cv.one_of(*CHIPSETS, upper=True), | ||||
|             cv.Optional(CONF_IS_RGBW, default=False): cv.boolean, | ||||
| @@ -148,8 +165,12 @@ async def to_code(config): | ||||
|     cg.add(var.set_is_wrgb(config[CONF_IS_WRGB])) | ||||
|     cg.add(var.set_use_psram(config[CONF_USE_PSRAM])) | ||||
|  | ||||
|     cg.add( | ||||
|         var.set_rmt_channel( | ||||
|             getattr(rmt_channel_t, f"RMT_CHANNEL_{config[CONF_RMT_CHANNEL]}") | ||||
|     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( | ||||
|             var.set_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 | ||||
| class FontCache(dict): | ||||
|     def __missing__(self, key): | ||||
|         res = self[key] = freetype.Face(key) | ||||
|         return res | ||||
|         try: | ||||
|             res = self[key] = freetype.Face(key) | ||||
|             return res | ||||
|         except freetype.FT_Exception as e: | ||||
|             raise cv.Invalid(f"Could not load Font file {key}: {e}") from e | ||||
|  | ||||
|  | ||||
| FONT_CACHE = FontCache() | ||||
|   | ||||
| @@ -97,8 +97,9 @@ void GCJA5Component::parse_data_() { | ||||
|   if (this->rx_message_[0] != 0x02 || this->rx_message_[31] != 0x03 || !this->calculate_checksum_()) { | ||||
|     ESP_LOGVV(TAG, "Discarding bad packet - failed checks."); | ||||
|     return; | ||||
|   } else | ||||
|   } else { | ||||
|     ESP_LOGVV(TAG, "Good packet found."); | ||||
|   } | ||||
|  | ||||
|   this->have_good_data_ = true; | ||||
|   uint8_t status = this->rx_message_[29]; | ||||
|   | ||||
| @@ -342,8 +342,9 @@ bool HaierClimateBase::prepare_pending_action() { | ||||
|         this->action_request_.reset(); | ||||
|         return false; | ||||
|     } | ||||
|   } else | ||||
|   } else { | ||||
|     return false; | ||||
|   } | ||||
| } | ||||
|  | ||||
| ClimateTraits HaierClimateBase::traits() { return traits_; } | ||||
|   | ||||
| @@ -710,8 +710,9 @@ void HonClimate::process_alarm_message_(const uint8_t *packet, uint8_t size, boo | ||||
|             alarm_code++; | ||||
|           } | ||||
|           active_alarms_[i] = packet[2 + i]; | ||||
|         } else | ||||
|         } else { | ||||
|           alarm_code += 8; | ||||
|         } | ||||
|       } | ||||
|     } else { | ||||
|       float alarm_count = 0.0f; | ||||
|   | ||||
| @@ -87,8 +87,9 @@ void HeatpumpIRClimate::setup() { | ||||
|       this->publish_state(); | ||||
|     }); | ||||
|     this->current_temperature = this->sensor_->state; | ||||
|   } else | ||||
|   } else { | ||||
|     this->current_temperature = NAN; | ||||
|   } | ||||
| } | ||||
|  | ||||
| void HeatpumpIRClimate::transmit_state() { | ||||
|   | ||||
| @@ -25,11 +25,13 @@ void I2SAudioMicrophone::setup() { | ||||
|     } | ||||
|   } else | ||||
| #endif | ||||
|       if (this->pdm_) { | ||||
|     if (this->parent_->get_port() != I2S_NUM_0) { | ||||
|       ESP_LOGE(TAG, "PDM only works on I2S0!"); | ||||
|       this->mark_failed(); | ||||
|       return; | ||||
|   { | ||||
|     if (this->pdm_) { | ||||
|       if (this->parent_->get_port() != I2S_NUM_0) { | ||||
|         ESP_LOGE(TAG, "PDM only works on I2S0!"); | ||||
|         this->mark_failed(); | ||||
|         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 | ||||
|   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))) { | ||||
|     // Failed to allocate buffers | ||||
|   | ||||
| @@ -1,38 +1,20 @@ | ||||
| #include "json_util.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 json { | ||||
|  | ||||
| static const char *const TAG = "json"; | ||||
|  | ||||
| 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) { | ||||
|   // Here we are allocating up to 5kb of memory, | ||||
|   // with the heap size minus 2kb to be safe if less than 5kb | ||||
|   // as we can not have a true dynamic sized document. | ||||
|   // The excess memory is freed below with `shrinkToFit()` | ||||
| #ifdef USE_ESP8266 | ||||
|   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 | ||||
|  | ||||
|   auto free_heap = ALLOCATOR.get_max_free_block_size(); | ||||
|   size_t request_size = std::min(free_heap, (size_t) 512); | ||||
|   while (true) { | ||||
|     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 | ||||
|   // as we can not have a true dynamic sized document. | ||||
|   // The excess memory is freed below with `shrinkToFit()` | ||||
| #ifdef USE_ESP8266 | ||||
|   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 | ||||
|   auto free_heap = ALLOCATOR.get_max_free_block_size(); | ||||
|   size_t request_size = std::min(free_heap, (size_t) (data.size() * 1.5)); | ||||
|   while (true) { | ||||
|     DynamicJsonDocument json_document(request_size); | ||||
|     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); | ||||
|       return false; | ||||
|     } | ||||
|   | ||||
| @@ -36,7 +36,7 @@ inline static uint8_t to_uint8_scale(float x) { return static_cast<uint8_t>(roun | ||||
|  *   range as set in the traits, so the output needs to do this. | ||||
|  * | ||||
|  * For COLD_WARM_WHITE capability: | ||||
|  * - cold_white, warm_white: The brightness of the cald and warm white channels of the light. | ||||
|  * - cold_white, warm_white: The brightness of the light's cold and warm white channels. | ||||
|  * | ||||
|  * All values (except color temperature) are represented using floats in the range 0.0 (off) to 1.0 (on), and are | ||||
|  * automatically clamped to this range. Properties not used in the current color mode can still have (invalid) values | ||||
|   | ||||
| @@ -23,7 +23,7 @@ from esphome.helpers import write_file_if_changed | ||||
|  | ||||
| from . import defines as df, helpers, lv_validation as lvalid | ||||
| from .automation import disp_update, focused_widgets, update_to_code | ||||
| from .defines import add_define | ||||
| from .defines import CONF_DRAW_ROUNDING, add_define | ||||
| from .encoders import ( | ||||
|     ENCODERS_CONFIG, | ||||
|     encoders_to_code, | ||||
| @@ -205,6 +205,10 @@ def final_validation(configs): | ||||
|                 raise cv.Invalid( | ||||
|                     "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] | ||||
|         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") | ||||
|   | ||||
| @@ -11,15 +11,17 @@ void MicroNovaSwitch::write_state(bool state) { | ||||
|         if (this->micronova_->get_current_stove_state() == 0) { | ||||
|           this->micronova_->write_address(this->memory_location_, this->memory_address_, this->memory_data_on_); | ||||
|           this->publish_state(true); | ||||
|         } else | ||||
|         } else { | ||||
|           ESP_LOGW(TAG, "Unable to turn stove on, invalid state: %d", micronova_->get_current_stove_state()); | ||||
|         } | ||||
|       } else { | ||||
|         // don't send power-off when status is Off or Final cleaning | ||||
|         if (this->micronova_->get_current_stove_state() != 0 && micronova_->get_current_stove_state() != 6) { | ||||
|           this->micronova_->write_address(this->memory_location_, this->memory_address_, this->memory_data_off_); | ||||
|           this->publish_state(false); | ||||
|         } else | ||||
|         } else { | ||||
|           ESP_LOGW(TAG, "Unable to turn stove off, invalid state: %d", micronova_->get_current_stove_state()); | ||||
|         } | ||||
|       } | ||||
|       this->micronova_->update(); | ||||
|       break; | ||||
|   | ||||
| @@ -19,10 +19,12 @@ template<typename... Ts> class MideaActionBase : public Action<Ts...> { | ||||
|  | ||||
| template<typename... Ts> class FollowMeAction : public MideaActionBase<Ts...> { | ||||
|   TEMPLATABLE_VALUE(float, temperature) | ||||
|   TEMPLATABLE_VALUE(bool, use_fahrenheit) | ||||
|   TEMPLATABLE_VALUE(bool, beeper) | ||||
|  | ||||
|   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 | ||||
|  | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "air_conditioner.h" | ||||
| #include "ac_adapter.h" | ||||
| #include <cmath> | ||||
| @@ -121,7 +122,7 @@ void AirConditioner::dump_config() { | ||||
|  | ||||
| /* 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 | ||||
|   // Check if temperature is finite (not NaN or infinite) | ||||
|   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 | ||||
|   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, | ||||
|            temp_uint8); | ||||
|   char temp_symbol = use_fahrenheit ? 'F' : 'C'; | ||||
|   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 | ||||
|   IrFollowMeData data(temp_uint8, beeper); | ||||
|   IrFollowMeData data(temp_uint8, use_fahrenheit, beeper); | ||||
|   this->transmitter_.transmit(data); | ||||
| #else | ||||
|   ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component"); | ||||
|   | ||||
| @@ -32,7 +32,7 @@ class AirConditioner : public ApplianceBase<dudanov::midea::ac::AirConditioner>, | ||||
|   /* ### 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_swing_step(); | ||||
|   void do_beeper_on() { this->set_beeper_feedback(true); } | ||||
|   | ||||
| @@ -18,6 +18,7 @@ from esphome.const import ( | ||||
|     CONF_SUPPORTED_SWING_MODES, | ||||
|     CONF_TIMEOUT, | ||||
|     CONF_TEMPERATURE, | ||||
|     CONF_USE_FAHRENHEIT, | ||||
|     DEVICE_CLASS_POWER, | ||||
|     DEVICE_CLASS_TEMPERATURE, | ||||
|     DEVICE_CLASS_HUMIDITY, | ||||
| @@ -172,11 +173,10 @@ MIDEA_ACTION_BASE_SCHEMA = cv.Schema( | ||||
| ) | ||||
|  | ||||
| # FollowMe action | ||||
| MIDEA_FOLLOW_ME_MIN = 0 | ||||
| MIDEA_FOLLOW_ME_MAX = 37 | ||||
| MIDEA_FOLLOW_ME_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         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), | ||||
|     } | ||||
| ) | ||||
| @@ -186,6 +186,8 @@ MIDEA_FOLLOW_ME_SCHEMA = cv.Schema( | ||||
| async def follow_me_to_code(var, config, args): | ||||
|     template_ = await cg.templatable(config[CONF_BEEPER], args, cg.bool_) | ||||
|     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_) | ||||
|     cg.add(var.set_temperature(template_)) | ||||
|  | ||||
|   | ||||
| @@ -16,22 +16,53 @@ class IrFollowMeData : public IrData { | ||||
|   IrFollowMeData() : IrData({MIDEA_TYPE_FOLLOW_ME, 0x82, 0x48, 0x7F, 0x1F}) {} | ||||
|   // Copy from Base | ||||
|   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() { | ||||
|     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); | ||||
|   } | ||||
|  | ||||
|   /* TEMPERATURE */ | ||||
|   uint8_t temp() const { return this->get_value_(4) - 1; } | ||||
|   void set_temp(uint8_t val) { this->set_value_(4, std::min(MAX_TEMP, val) + 1); } | ||||
|   uint8_t temp() const { | ||||
|     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 */ | ||||
|   bool beeper() const { return this->get_value_(3, 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: | ||||
|   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 { | ||||
|   | ||||
| @@ -119,17 +119,17 @@ async def to_code(config): | ||||
|             cg.add_library("ESP8266HTTPClient", None) | ||||
|  | ||||
|     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: | ||||
|         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: | ||||
|         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])) | ||||
|  | ||||
|   | ||||
| @@ -40,7 +40,7 @@ bool Nextion::send_command_(const std::string &command) { | ||||
| } | ||||
|  | ||||
| bool Nextion::check_connect_() { | ||||
|   if (this->get_is_connected_()) | ||||
|   if (this->is_connected_) | ||||
|     return true; | ||||
|  | ||||
|   // 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 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) { | ||||
|       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); | ||||
|  | ||||
|   /** | ||||
|    * 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. | ||||
|    * @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 set_touch_sleep_timeout_internal(uint32_t touch_sleep_timeout) { | ||||
|     this->touch_sleep_timeout_ = touch_sleep_timeout; | ||||
|   } | ||||
|   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; } | ||||
|   void set_auto_wake_on_touch_internal(bool auto_wake_on_touch) { this->auto_wake_on_touch_ = auto_wake_on_touch; } | ||||
|   void set_exit_reparse_on_start_internal(bool exit_reparse_on_start) { | ||||
|     this->exit_reparse_on_start_ = exit_reparse_on_start; | ||||
|   } | ||||
|   /** | ||||
|    * 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(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. | ||||
| @@ -1217,6 +1207,25 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe | ||||
|    */ | ||||
|   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: | ||||
|   std::deque<NextionQueue *> nextion_queue_; | ||||
|   std::deque<NextionQueue *> waveform_queue_; | ||||
| @@ -1315,8 +1324,6 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe | ||||
|  | ||||
| #endif  // USE_NEXTION_TFT_UPLOAD | ||||
|  | ||||
|   bool get_is_connected_() { return this->is_connected_; } | ||||
|  | ||||
|   bool check_connect_(); | ||||
|  | ||||
|   std::vector<NextionComponentBase *> touch_; | ||||
|   | ||||
| @@ -10,19 +10,19 @@ static const char *const TAG = "nextion"; | ||||
| // Sleep safe commands | ||||
| void Nextion::soft_reset() { this->send_command_("rest"); } | ||||
|  | ||||
| void Nextion::set_wake_up_page(uint8_t page_id) { | ||||
|   this->add_no_result_to_queue_with_set_internal_("wake_up_page", "wup", page_id, true); | ||||
| void Nextion::set_wake_up_page(uint8_t wake_up_page) { | ||||
|   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(uint16_t timeout) { | ||||
|   if (timeout < 3) { | ||||
| void Nextion::set_touch_sleep_timeout(uint32_t touch_sleep_timeout) { | ||||
|   if (touch_sleep_timeout < 3) { | ||||
|     ESP_LOGD(TAG, "Sleep timeout out of bounds, range 3-65535"); | ||||
|     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) { | ||||
| @@ -54,7 +54,6 @@ bool Nextion::set_protocol_reparse_mode(bool active_mode) { | ||||
|   this->ignore_is_setup_ = false; | ||||
|   return all_commands_sent; | ||||
| } | ||||
| void Nextion::set_exit_reparse_on_start(bool exit_reparse) { this->exit_reparse_on_start_ = exit_reparse; } | ||||
|  | ||||
| // Set Colors - Background | ||||
| 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)); | ||||
| } | ||||
|  | ||||
| void Nextion::set_auto_wake_on_touch(bool auto_wake) { | ||||
|   this->add_no_result_to_queue_with_set("auto_wake_on_touch", "thup", auto_wake ? 1 : 0); | ||||
| void Nextion::set_auto_wake_on_touch(bool auto_wake_on_touch) { | ||||
|   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 | ||||
|   | ||||
| @@ -80,15 +80,7 @@ bool OnlineImage::resize_(int width_in, int height_in) { | ||||
|     this->width_ = width; | ||||
|     ESP_LOGD(TAG, "New size: (%d, %d)", width, height); | ||||
|   } else { | ||||
| #if defined(USE_ESP8266) | ||||
|     // 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); | ||||
|     ESP_LOGE(TAG, "allocation failed. Biggest block in heap: %zu Bytes", this->allocator_.get_max_free_block_size()); | ||||
|     this->end_connection_(); | ||||
|     return false; | ||||
|   } | ||||
|   | ||||
| @@ -1,10 +1,12 @@ | ||||
| from typing import Any | ||||
|  | ||||
| import logging | ||||
| from esphome import automation | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import pins | ||||
| 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 | ||||
|  | ||||
| CODEOWNERS = ["@olegtarasov"] | ||||
| @@ -20,7 +22,21 @@ CONF_CH2_ACTIVE = "ch2_active" | ||||
| CONF_SUMMER_MODE_ACTIVE = "summer_mode_active" | ||||
| CONF_DHW_BLOCK = "dhw_block" | ||||
| 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( | ||||
|     cv.Schema( | ||||
| @@ -36,7 +52,19 @@ CONFIG_SCHEMA = cv.All( | ||||
|             cv.Optional(CONF_SUMMER_MODE_ACTIVE, False): cv.boolean, | ||||
|             cv.Optional(CONF_DHW_BLOCK, 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( | ||||
| @@ -44,6 +72,11 @@ CONFIG_SCHEMA = cv.All( | ||||
|             schema.INPUTS, (lambda _: cv.use_id(sensor.Sensor)) | ||||
|         ) | ||||
|     ) | ||||
|     .extend( | ||||
|         validate.create_entities_schema( | ||||
|             schema.SETTINGS, (lambda s: s.validation_schema) | ||||
|         ) | ||||
|     ) | ||||
|     .extend(cv.COMPONENT_SCHEMA), | ||||
|     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]) | ||||
|     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 = [] | ||||
|     settings = [] | ||||
|     for key, value in config.items(): | ||||
|         if key in non_sensors: | ||||
|             continue | ||||
|         if key in schema.INPUTS: | ||||
|             input_sensor = await cg.get_variable(value) | ||||
|             cg.add( | ||||
|                 getattr(var, f"set_{key}_{const.INPUT_SENSOR.lower()}")(input_sensor) | ||||
|             ) | ||||
|             cg.add(getattr(var, f"set_{key}_{const.INPUT_SENSOR}")(input_sensor)) | ||||
|             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: | ||||
|             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)) | ||||
|  | ||||
|     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.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" | ||||
| OUTPUT = "output" | ||||
| INPUT_SENSOR = "input_sensor" | ||||
| SETTING = "setting" | ||||
|   | ||||
| @@ -1,13 +1,14 @@ | ||||
| from collections.abc import Awaitable | ||||
| from typing import Any, Callable | ||||
| from typing import Any, Callable, Optional | ||||
|  | ||||
| import esphome.codegen as cg | ||||
| from esphome.const import CONF_ID | ||||
| from . import const | ||||
| from .schema import TSchema | ||||
| from .schema import TSchema, SettingSchema | ||||
|  | ||||
| opentherm_ns = cg.esphome_ns.namespace("opentherm") | ||||
| OpenthermHub = opentherm_ns.class_("OpenthermHub", cg.Component) | ||||
| OpenthermData = opentherm_ns.class_("OpenthermData") | ||||
|  | ||||
|  | ||||
| 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}") | ||||
|  | ||||
|  | ||||
| # 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( | ||||
|     component_type: str, keys: list[str], schemas: dict[str, TSchema] | ||||
| ) -> None: | ||||
| @@ -74,16 +93,30 @@ def define_readers(component_type: str, keys: list[str]) -> None: | ||||
|         ) | ||||
|  | ||||
|  | ||||
| def add_messages(hub: cg.MockObj, keys: list[str], schemas: dict[str, TSchema]): | ||||
|     messages: set[tuple[str, bool]] = set() | ||||
| def define_setting_readers(component_type: str, keys: list[str]) -> None: | ||||
|     for key in keys: | ||||
|         messages.add((schemas[key].message, schemas[key].keep_updated)) | ||||
|     for msg, keep_updated in messages: | ||||
|         cg.add_define( | ||||
|             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}") | ||||
|         if keep_updated: | ||||
|             cg.add(hub.add_repeating_message(msg_expr)) | ||||
|         else: | ||||
|             cg.add(hub.add_initial_message(msg_expr)) | ||||
|             if order is not None: | ||||
|                 cg.add(hub.add_initial_message(msg_expr, order)) | ||||
|             else: | ||||
|                 cg.add(hub.add_initial_message(msg_expr)) | ||||
|  | ||||
|  | ||||
| def add_property_set(var: cg.MockObj, config_key: str, config: dict[str, Any]) -> None: | ||||
|   | ||||
| @@ -63,7 +63,7 @@ void write_f88(const float value, OpenthermData &data) { data.f88(value); } | ||||
| OpenthermData OpenthermHub::build_request_(MessageId request_id) const { | ||||
|   OpenthermData data; | ||||
|   data.type = 0; | ||||
|   data.id = 0; | ||||
|   data.id = request_id; | ||||
|   data.valueHB = 0; | ||||
|   data.valueLB = 0; | ||||
|  | ||||
| @@ -82,28 +82,13 @@ OpenthermData OpenthermHub::build_request_(MessageId request_id) const { | ||||
|     // NOLINTEND | ||||
|  | ||||
|     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) | | ||||
|                    (summer_mode_is_active << 5) | (dhw_blocked << 6); | ||||
|  | ||||
|     return data; | ||||
|   } | ||||
|  | ||||
|   // Another special case is OpenTherm version number which is configured at hub level as a constant | ||||
|   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, | ||||
|   // Next, we start with write requests from switches and other inputs, | ||||
|   // 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 | ||||
|   // supported). | ||||
| @@ -116,14 +101,23 @@ OpenthermData OpenthermHub::build_request_(MessageId request_id) const { | ||||
|                                       OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, ) | ||||
|     OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_WRITE_MESSAGE, OPENTHERM_MESSAGE_WRITE_ENTITY, , | ||||
|                                             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. | ||||
|   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) { | ||||
|     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. | ||||
|   // 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 | ||||
|   // good practice anyway. | ||||
|   this->add_repeating_message(MessageId::STATUS); | ||||
|  | ||||
|   // Also ensure that we start communication with the STATUS message | ||||
|   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(); | ||||
|   this->write_initial_messages_(this->messages_); | ||||
|   this->message_iterator_ = this->messages_.begin(); | ||||
| } | ||||
|  | ||||
| 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() { | ||||
|   if (this->sync_mode_) { | ||||
|     this->sync_loop_(); | ||||
| @@ -184,29 +196,18 @@ void OpenthermHub::loop() { | ||||
|  | ||||
|   auto cur_time = millis(); | ||||
|   auto const cur_mode = this->opentherm_->get_mode(); | ||||
|  | ||||
|   if (this->handle_error_(cur_mode)) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   switch (cur_mode) { | ||||
|     case OperationMode::WRITE: | ||||
|     case OperationMode::READ: | ||||
|     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; | ||||
|     case OperationMode::IDLE: | ||||
|       this->check_timings_(cur_time); | ||||
|       if (this->should_skip_loop_(cur_time)) { | ||||
|         break; | ||||
|       } | ||||
| @@ -219,6 +220,28 @@ void OpenthermHub::loop() { | ||||
|     case OperationMode::RECEIVED: | ||||
|       this->read_response_(); | ||||
|       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_(); | ||||
|   // 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(); })) { | ||||
|     ESP_LOGE(TAG, "Hub timeout triggered during send"); | ||||
|     this->stop_opentherm_(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (this->opentherm_->is_error()) { | ||||
|     this->handle_protocol_write_error_(); | ||||
|     this->stop_opentherm_(); | ||||
|   // Check for errors and ensure we are in the right state (message sent successfully) | ||||
|   if (this->handle_error_(this->opentherm_->get_mode())) { | ||||
|     return; | ||||
|   } else if (!this->opentherm_->is_sent()) { | ||||
|     ESP_LOGW(TAG, "Unexpected state after sending request: %s", | ||||
| @@ -257,19 +284,20 @@ void OpenthermHub::sync_loop_() { | ||||
|  | ||||
|   // Listen for the response | ||||
|   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(); })) { | ||||
|     ESP_LOGE(TAG, "Hub timeout triggered during receive"); | ||||
|     this->stop_opentherm_(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (this->opentherm_->is_timeout()) { | ||||
|     this->handle_timeout_error_(); | ||||
|     this->stop_opentherm_(); | ||||
|     return; | ||||
|   } else if (this->opentherm_->is_protocol_error()) { | ||||
|     this->handle_protocol_read_error_(); | ||||
|     this->stop_opentherm_(); | ||||
|   // Check for errors and ensure we are in the right state (message received successfully) | ||||
|   if (this->handle_error_(this->opentherm_->get_mode())) { | ||||
|     return; | ||||
|   } else if (!this->opentherm_->has_message()) { | ||||
|     ESP_LOGW(TAG, "Unexpected state after receiving response: %s", | ||||
| @@ -281,17 +309,13 @@ void OpenthermHub::sync_loop_() { | ||||
|   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) { | ||||
|     ESP_LOGW(TAG, | ||||
|              "%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.", | ||||
|              (int) (cur_time - this->last_conversation_start_)); | ||||
|     this->stop_opentherm_(); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| 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_() { | ||||
|   if (this->sending_initial_ && this->current_message_iterator_ == this->initial_messages_.end()) { | ||||
|     this->sending_initial_ = false; | ||||
|     this->current_message_iterator_ = this->repeating_messages_.begin(); | ||||
|   } else if (this->current_message_iterator_ == this->repeating_messages_.end()) { | ||||
|     this->current_message_iterator_ = this->repeating_messages_.begin(); | ||||
|   if (this->message_iterator_ == this->messages_.end()) { | ||||
|     if (this->sending_initial_) { | ||||
|       this->sending_initial_ = false; | ||||
|       this->write_repeating_messages_(this->messages_); | ||||
|     } | ||||
|     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, | ||||
|            this->opentherm_->message_id_to_str((MessageId) request.id)); | ||||
| @@ -331,37 +358,48 @@ void OpenthermHub::read_response_() { | ||||
|  | ||||
|   this->stop_opentherm_(); | ||||
|  | ||||
|   this->before_process_response_callback_.call(response); | ||||
|   this->process_response(response); | ||||
|  | ||||
|   this->current_message_iterator_++; | ||||
|   this->message_iterator_++; | ||||
| } | ||||
|  | ||||
| void OpenthermHub::stop_opentherm_() { | ||||
|   this->opentherm_->stop(); | ||||
|   this->last_conversation_end_ = millis(); | ||||
| } | ||||
| void OpenthermHub::handle_protocol_write_error_() { | ||||
|   ESP_LOGW(TAG, "Error while sending request: %s", | ||||
|            this->opentherm_->operation_mode_to_str(this->opentherm_->get_mode())); | ||||
|   this->opentherm_->debug_data(this->last_request_); | ||||
| } | ||||
| void OpenthermHub::handle_protocol_read_error_() { | ||||
|  | ||||
| void OpenthermHub::handle_protocol_error_() { | ||||
|   OpenThermError error; | ||||
|   this->opentherm_->get_protocol_error(error); | ||||
|   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); | ||||
| } | ||||
| void OpenthermHub::handle_timeout_error_() { | ||||
|   ESP_LOGW(TAG, "Receive response timed out at a protocol level"); | ||||
|   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() { | ||||
|   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:"); | ||||
|   LOG_PIN("  In: ", this->in_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, "  Binary sensors: %s", SHOW(OPENTHERM_BINARY_SENSOR_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, "  Numbers: %s", SHOW(OPENTHERM_NUMBER_LIST(ID, ))); | ||||
|   ESP_LOGCONFIG(TAG, "  Initial requests:"); | ||||
|   for (auto type : this->initial_messages_) { | ||||
|     ESP_LOGCONFIG(TAG, "  - %d (%s)", type, this->opentherm_->message_id_to_str((type))); | ||||
|   for (auto type : initial_messages) { | ||||
|     ESP_LOGCONFIG(TAG, "  - %d (%s)", type, this->opentherm_->message_id_to_str(type)); | ||||
|   } | ||||
|   ESP_LOGCONFIG(TAG, "  Repeating requests:"); | ||||
|   for (auto type : this->repeating_messages_) { | ||||
|     ESP_LOGCONFIG(TAG, "  - %d (%s)", type, this->opentherm_->message_id_to_str((type))); | ||||
|   for (auto type : repeating_messages) { | ||||
|     ESP_LOGCONFIG(TAG, "  - %d (%s)", type, this->opentherm_->message_id_to_str(type)); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -38,6 +38,9 @@ | ||||
| namespace esphome { | ||||
| namespace opentherm { | ||||
|  | ||||
| static const uint8_t REPEATING_MESSAGE_ORDER = 255; | ||||
| static const uint8_t INITIAL_UNORDERED_MESSAGE_ORDER = 254; | ||||
|  | ||||
| // OpenTherm component for ESPHome | ||||
| class OpenthermHub : public Component { | ||||
|  protected: | ||||
| @@ -58,15 +61,12 @@ class OpenthermHub : public Component { | ||||
|  | ||||
|   OPENTHERM_INPUT_SENSOR_LIST(OPENTHERM_DECLARE_INPUT_SENSOR, ) | ||||
|  | ||||
|   // The set of initial messages to send on starting communication with the boiler | ||||
|   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 | ||||
|   OPENTHERM_SETTING_LIST(OPENTHERM_DECLARE_SETTING, ) | ||||
|  | ||||
|   bool sending_initial_ = true; | ||||
|   // Index for the current request in one of the _requests sets. | ||||
|   std::vector<MessageId>::const_iterator current_message_iterator_; | ||||
|   std::unordered_map<MessageId, uint8_t> configured_messages_; | ||||
|   std::vector<MessageId> messages_; | ||||
|   std::vector<MessageId>::const_iterator message_iterator_; | ||||
|  | ||||
|   uint32_t last_conversation_start_ = 0; | ||||
|   uint32_t last_conversation_end_ = 0; | ||||
| @@ -78,20 +78,25 @@ class OpenthermHub : public Component { | ||||
|   // Very likely to happen while using Dallas temperature sensors. | ||||
|   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 | ||||
|   OpenthermData build_request_(MessageId request_id) const; | ||||
|   void handle_protocol_write_error_(); | ||||
|   void handle_protocol_read_error_(); | ||||
|   bool handle_error_(OperationMode mode); | ||||
|   void handle_protocol_error_(); | ||||
|   void handle_timeout_error_(); | ||||
|   void handle_timer_error_(); | ||||
|   void stop_opentherm_(); | ||||
|   void start_conversation_(); | ||||
|   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; | ||||
|   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) { | ||||
|     auto start_time = millis(); | ||||
|     while (func()) { | ||||
| @@ -127,13 +132,18 @@ class OpenthermHub : public Component { | ||||
|  | ||||
|   OPENTHERM_INPUT_SENSOR_LIST(OPENTHERM_SET_INPUT_SENSOR, ) | ||||
|  | ||||
|   OPENTHERM_SETTING_LIST(OPENTHERM_SET_SETTING, ) | ||||
|  | ||||
|   // 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 | ||||
|   // 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 | ||||
|   // 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, | ||||
|   // 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_dhw_block(bool value) { this->dhw_block = value; } | ||||
|   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; } | ||||
|  | ||||
|   | ||||
| @@ -52,7 +52,9 @@ bool OpenTherm::initialize() { | ||||
|   OpenTherm::instance = this; | ||||
| #endif | ||||
|   this->in_pin_->pin_mode(gpio::FLAG_INPUT); | ||||
|   this->in_pin_->setup(); | ||||
|   this->out_pin_->pin_mode(gpio::FLAG_OUTPUT); | ||||
|   this->out_pin_->setup(); | ||||
|   this->out_pin_->digital_write(true); | ||||
|  | ||||
| #if defined(ESP32) || defined(USE_ESP_IDF) | ||||
| @@ -182,7 +184,7 @@ bool IRAM_ATTR OpenTherm::timer_isr(OpenTherm *arg) { | ||||
|       } | ||||
|       arg->capture_ = 1;  // reset counter | ||||
|     } 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->error_type_ = ProtocolErrorType::NO_CHANGE_TOO_LONG; | ||||
|       arg->stop_timer_(); | ||||
| @@ -312,21 +314,31 @@ bool OpenTherm::init_esp32_timer_() { | ||||
| } | ||||
|  | ||||
| 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); | ||||
|   if (result != ESP_OK) { | ||||
|     const auto *error = esp_err_to_name(result); | ||||
|     ESP_LOGE(TAG, "Failed to set alarm value. Error: %s", error); | ||||
|   this->timer_error_ = timer_set_alarm_value(this->timer_group_, this->timer_idx_, alarm_value); | ||||
|   if (this->timer_error_ != ESP_OK) { | ||||
|     this->timer_error_type_ = TimerErrorType::SET_ALARM_VALUE_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; | ||||
|   } | ||||
|  | ||||
|   result = timer_start(this->timer_group_, this->timer_idx_); | ||||
|   if (result != ESP_OK) { | ||||
|     const auto *error = esp_err_to_name(result); | ||||
|     ESP_LOGE(TAG, "Failed to start the timer. Error: %s", error); | ||||
|     return; | ||||
|   } | ||||
|   ESP_LOGE(TAG, "Error occured while manipulating timer (%s): %s", this->timer_error_to_str(this->timer_error_type_), | ||||
|            esp_err_to_name(this->timer_error_)); | ||||
|  | ||||
|   this->timer_error_ = ESP_OK; | ||||
|   this->timer_error_type_ = NO_TIMER_ERROR; | ||||
| } | ||||
|  | ||||
| // 5 kHz timer_ | ||||
| @@ -343,21 +355,18 @@ void IRAM_ATTR OpenTherm::start_write_timer_() { | ||||
|  | ||||
| void IRAM_ATTR OpenTherm::stop_timer_() { | ||||
|   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; | ||||
|  | ||||
|   result = timer_pause(this->timer_group_, this->timer_idx_); | ||||
|   if (result != ESP_OK) { | ||||
|     const auto *error = esp_err_to_name(result); | ||||
|     ESP_LOGE(TAG, "Failed to pause the timer. Error: %s", error); | ||||
|   this->timer_error_ = timer_pause(this->timer_group_, this->timer_idx_); | ||||
|   if (this->timer_error_ != ESP_OK) { | ||||
|     this->timer_error_type_ = TimerErrorType::TIMER_PAUSE_ERROR; | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   result = timer_set_counter_value(this->timer_group_, this->timer_idx_, 0); | ||||
|   if (result != ESP_OK) { | ||||
|     const auto *error = esp_err_to_name(result); | ||||
|     ESP_LOGE(TAG, "Failed to set timer counter to 0 after pausing. Error: %s", error); | ||||
|     return; | ||||
|   this->timer_error_ = timer_set_counter_value(this->timer_group_, this->timer_idx_, 0); | ||||
|   if (this->timer_error_ != ESP_OK) { | ||||
|     this->timer_error_type_ = TimerErrorType::SET_COUNTER_VALUE_ERROR; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -386,6 +395,9 @@ void IRAM_ATTR OpenTherm::stop_timer_() { | ||||
|   timer1_detachInterrupt(); | ||||
| } | ||||
|  | ||||
| // There is nothing to report on ESP8266 | ||||
| void OpenTherm::report_and_reset_timer_error() {} | ||||
|  | ||||
| #endif  // END ESP8266 | ||||
|  | ||||
| // 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(ERROR_PROTOCOL) | ||||
|     TO_STRING_MEMBER(ERROR_TIMEOUT) | ||||
|     TO_STRING_MEMBER(ERROR_TIMER) | ||||
|     default: | ||||
|       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) { | ||||
|     TO_STRING_MEMBER(NO_ERROR) | ||||
|     TO_STRING_MEMBER(NO_TRANSITION) | ||||
| @@ -427,6 +440,17 @@ const char *OpenTherm::protocol_error_to_to_str(ProtocolErrorType error_type) { | ||||
|       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) { | ||||
|   switch (message_type) { | ||||
|     TO_STRING_MEMBER(READ_DATA) | ||||
|   | ||||
| @@ -36,11 +36,12 @@ enum OperationMode { | ||||
|   READ = 2,      // reading 32-bit data frame | ||||
|   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 | ||||
|  | ||||
|   ERROR_PROTOCOL = 8,  // manchester protocol data transfer error | ||||
|   ERROR_TIMEOUT = 9    // read timeout | ||||
|   ERROR_PROTOCOL = 8,  // protocol error, can happed only during READ | ||||
|   ERROR_TIMEOUT = 9,   // timeout while waiting for response from device, only during LISTEN | ||||
|   ERROR_TIMER = 10     // error operating the ESP32 timer | ||||
| }; | ||||
|  | ||||
| enum ProtocolErrorType { | ||||
| @@ -51,6 +52,14 @@ enum ProtocolErrorType { | ||||
|   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 { | ||||
|   READ_DATA = 0, | ||||
|   READ_ACK = 4, | ||||
| @@ -299,7 +308,9 @@ class OpenTherm { | ||||
|    * | ||||
|    * @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 | ||||
| @@ -313,14 +324,22 @@ class OpenTherm { | ||||
|    */ | ||||
|   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; } | ||||
|  | ||||
|   OperationMode get_mode() { return mode_; } | ||||
|  | ||||
|   void debug_data(OpenthermData &data); | ||||
|   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 *operation_mode_to_str(OperationMode mode); | ||||
|   const char *message_id_to_str(MessageId id); | ||||
| @@ -349,10 +368,12 @@ class OpenTherm { | ||||
|   uint32_t data_; | ||||
|   uint8_t bit_pos_; | ||||
|   int32_t timeout_counter_;  // <0 no timeout | ||||
|  | ||||
|   int32_t device_timeout_; | ||||
|  | ||||
| #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_(); | ||||
|   void start_esp32_timer_(uint64_t alarm_value); | ||||
| #endif | ||||
|   | ||||
| @@ -28,6 +28,9 @@ namespace opentherm { | ||||
| #ifndef OPENTHERM_INPUT_SENSOR_LIST | ||||
| #define OPENTHERM_INPUT_SENSOR_LIST(F, sep) | ||||
| #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 | ||||
| #define OPENTHERM_DECLARE_SENSOR(entity) sensor::Sensor *entity; | ||||
| @@ -36,6 +39,7 @@ namespace opentherm { | ||||
| #define OPENTHERM_DECLARE_NUMBER(entity) OpenthermNumber *entity; | ||||
| #define OPENTHERM_DECLARE_OUTPUT(entity) OpenthermOutput *entity; | ||||
| #define OPENTHERM_DECLARE_INPUT_SENSOR(entity) sensor::Sensor *entity; | ||||
| #define OPENTHERM_DECLARE_SETTING(type, entity, def) type entity = def; | ||||
|  | ||||
| // Setter macros | ||||
| #define OPENTHERM_SET_SENSOR(entity) \ | ||||
| @@ -56,6 +60,9 @@ namespace opentherm { | ||||
| #define OPENTHERM_SET_INPUT_SENSOR(entity) \ | ||||
|   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 ===== | ||||
|  | ||||
| // *_MESSAGE_HANDLERS are generated in defines.h and look like this: | ||||
| @@ -85,6 +92,9 @@ namespace opentherm { | ||||
| #ifndef OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS | ||||
| #define OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS(MESSAGE, ENTITY, entity_sep, postscript, msg_sep) | ||||
| #endif | ||||
| #ifndef OPENTHERM_SETTING_MESSAGE_HANDLERS | ||||
| #define OPENTHERM_SETTING_MESSAGE_HANDLERS(MESSAGE, ENTITY, entity_sep, postscript, msg_sep) | ||||
| #endif | ||||
|  | ||||
| // Write data request builders | ||||
| #define OPENTHERM_MESSAGE_WRITE_MESSAGE(msg) \ | ||||
| @@ -92,6 +102,7 @@ namespace opentherm { | ||||
|     data.type = MessageType::WRITE_DATA; \ | ||||
|     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_SETTING(key, msg_data) message_data::write_##msg_data(this->key, data); | ||||
| #define OPENTHERM_MESSAGE_WRITE_POSTSCRIPT \ | ||||
|   return data; \ | ||||
|   } | ||||
|   | ||||
| @@ -2,8 +2,9 @@ | ||||
| # inputs of the OpenTherm component. | ||||
|  | ||||
| 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 ( | ||||
|     UNIT_CELSIUS, | ||||
|     UNIT_EMPTY, | ||||
| @@ -64,6 +65,7 @@ class SensorSchema(EntitySchema): | ||||
|     icon: Optional[str] = None | ||||
|     device_class: Optional[str] = None | ||||
|     disabled_by_default: bool = False | ||||
|     order: Optional[int] = None | ||||
|  | ||||
|  | ||||
| SENSORS: dict[str, SensorSchema] = { | ||||
| @@ -399,6 +401,7 @@ SENSORS: dict[str, SensorSchema] = { | ||||
|         message="OT_VERSION_DEVICE", | ||||
|         keep_updated=False, | ||||
|         message_data="f88", | ||||
|         order=2, | ||||
|     ), | ||||
|     "device_type": SensorSchema( | ||||
|         description="Device product type", | ||||
| @@ -409,6 +412,7 @@ SENSORS: dict[str, SensorSchema] = { | ||||
|         message="VERSION_DEVICE", | ||||
|         keep_updated=False, | ||||
|         message_data="u8_hb", | ||||
|         order=0, | ||||
|     ), | ||||
|     "device_version": SensorSchema( | ||||
|         description="Device product version", | ||||
| @@ -419,6 +423,7 @@ SENSORS: dict[str, SensorSchema] = { | ||||
|         message="VERSION_DEVICE", | ||||
|         keep_updated=False, | ||||
|         message_data="u8_lb", | ||||
|         order=0, | ||||
|     ), | ||||
|     "device_id": SensorSchema( | ||||
|         description="Device ID code", | ||||
| @@ -429,6 +434,7 @@ SENSORS: dict[str, SensorSchema] = { | ||||
|         message="DEVICE_CONFIG", | ||||
|         keep_updated=False, | ||||
|         message_data="u8_lb", | ||||
|         order=4, | ||||
|     ), | ||||
|     "otc_hc_ratio_ub": SensorSchema( | ||||
|         description="OTC heat curve ratio upper bound", | ||||
| @@ -457,6 +463,7 @@ SENSORS: dict[str, SensorSchema] = { | ||||
| class BinarySensorSchema(EntitySchema): | ||||
|     icon: Optional[str] = None | ||||
|     device_class: Optional[str] = None | ||||
|     order: Optional[int] = None | ||||
|  | ||||
|  | ||||
| BINARY_SENSORS: dict[str, BinarySensorSchema] = { | ||||
| @@ -525,48 +532,56 @@ BINARY_SENSORS: dict[str, BinarySensorSchema] = { | ||||
|         message="DEVICE_CONFIG", | ||||
|         keep_updated=False, | ||||
|         message_data="flag8_hb_0", | ||||
|         order=4, | ||||
|     ), | ||||
|     "control_type_on_off": BinarySensorSchema( | ||||
|         description="Configuration: Control type is on/off", | ||||
|         message="DEVICE_CONFIG", | ||||
|         keep_updated=False, | ||||
|         message_data="flag8_hb_1", | ||||
|         order=4, | ||||
|     ), | ||||
|     "cooling_supported": BinarySensorSchema( | ||||
|         description="Configuration: Cooling supported", | ||||
|         message="DEVICE_CONFIG", | ||||
|         keep_updated=False, | ||||
|         message_data="flag8_hb_2", | ||||
|         order=4, | ||||
|     ), | ||||
|     "dhw_storage_tank": BinarySensorSchema( | ||||
|         description="Configuration: DHW storage tank", | ||||
|         message="DEVICE_CONFIG", | ||||
|         keep_updated=False, | ||||
|         message_data="flag8_hb_3", | ||||
|         order=4, | ||||
|     ), | ||||
|     "controller_pump_control_allowed": BinarySensorSchema( | ||||
|         description="Configuration: Controller pump control allowed", | ||||
|         message="DEVICE_CONFIG", | ||||
|         keep_updated=False, | ||||
|         message_data="flag8_hb_4", | ||||
|         order=4, | ||||
|     ), | ||||
|     "ch2_present": BinarySensorSchema( | ||||
|         description="Configuration: CH2 present", | ||||
|         message="DEVICE_CONFIG", | ||||
|         keep_updated=False, | ||||
|         message_data="flag8_hb_5", | ||||
|         order=4, | ||||
|     ), | ||||
|     "water_filling": BinarySensorSchema( | ||||
|         description="Configuration: Remote water filling", | ||||
|         message="DEVICE_CONFIG", | ||||
|         keep_updated=False, | ||||
|         message_data="flag8_hb_6", | ||||
|         order=4, | ||||
|     ), | ||||
|     "heat_mode": BinarySensorSchema( | ||||
|         description="Configuration: Heating or cooling", | ||||
|         message="DEVICE_CONFIG", | ||||
|         keep_updated=False, | ||||
|         message_data="flag8_hb_7", | ||||
|         order=4, | ||||
|     ), | ||||
|     "dhw_setpoint_transfer_enabled": BinarySensorSchema( | ||||
|         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"), | ||||
|     ), | ||||
| } | ||||
|  | ||||
|  | ||||
| @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( | ||||
|     entities: dict[str, schema.EntitySchema], | ||||
|     entities: dict[str, TSchema], | ||||
|     get_entity_validation_schema: Callable[[TSchema], cv.Schema], | ||||
| ) -> Schema: | ||||
|     entity_schema = {} | ||||
|     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) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -13,9 +13,9 @@ PulseCounterStorageBase *get_storage(bool hw_pcnt) { | ||||
|   return (hw_pcnt ? (PulseCounterStorageBase *) (new HwPulseCounterStorage) | ||||
|                   : (PulseCounterStorageBase *) (new BasicPulseCounterStorage)); | ||||
| } | ||||
| #else | ||||
| #else   // HAS_PCNT | ||||
| PulseCounterStorageBase *get_storage(bool) { return new BasicPulseCounterStorage; } | ||||
| #endif | ||||
| #endif  // HAS_PCNT | ||||
|  | ||||
| void IRAM_ATTR BasicPulseCounterStorage::gpio_intr(BasicPulseCounterStorage *arg) { | ||||
|   const uint32_t now = micros(); | ||||
| @@ -28,14 +28,17 @@ void IRAM_ATTR BasicPulseCounterStorage::gpio_intr(BasicPulseCounterStorage *arg | ||||
|   switch (mode) { | ||||
|     case PULSE_COUNTER_DISABLE: | ||||
|       break; | ||||
|     case PULSE_COUNTER_INCREMENT: | ||||
|       arg->counter++; | ||||
|       break; | ||||
|     case PULSE_COUNTER_DECREMENT: | ||||
|       arg->counter--; | ||||
|       break; | ||||
|     case PULSE_COUNTER_INCREMENT: { | ||||
|       auto x = arg->counter + 1; | ||||
|       arg->counter = x; | ||||
|     } break; | ||||
|     case PULSE_COUNTER_DECREMENT: { | ||||
|       auto x = arg->counter - 1; | ||||
|       arg->counter = x; | ||||
|     } break; | ||||
|   } | ||||
| } | ||||
|  | ||||
| bool BasicPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) { | ||||
|   this->pin = pin; | ||||
|   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); | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| pulse_counter_t BasicPulseCounterStorage::read_raw_value() { | ||||
|   pulse_counter_t counter = this->counter; | ||||
|   pulse_counter_t ret = counter - this->last_value; | ||||
| @@ -141,6 +145,7 @@ bool HwPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) { | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| pulse_counter_t HwPulseCounterStorage::read_raw_value() { | ||||
|   pulse_counter_t counter; | ||||
|   pcnt_get_counter_value(this->pcnt_unit, &counter); | ||||
| @@ -148,7 +153,7 @@ pulse_counter_t HwPulseCounterStorage::read_raw_value() { | ||||
|   this->last_value = counter; | ||||
|   return ret; | ||||
| } | ||||
| #endif | ||||
| #endif  // HAS_PCNT | ||||
|  | ||||
| void PulseCounterSensor::setup() { | ||||
|   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) | ||||
| #include <driver/pcnt.h> | ||||
| #define HAS_PCNT | ||||
| #endif | ||||
| #endif  // defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) | ||||
|  | ||||
| namespace esphome { | ||||
| namespace pulse_counter { | ||||
| @@ -22,9 +22,9 @@ enum PulseCounterCountMode { | ||||
|  | ||||
| #ifdef HAS_PCNT | ||||
| using pulse_counter_t = int16_t; | ||||
| #else | ||||
| #else   // HAS_PCNT | ||||
| using pulse_counter_t = int32_t; | ||||
| #endif | ||||
| #endif  // HAS_PCNT | ||||
|  | ||||
| struct PulseCounterStorageBase { | ||||
|   virtual bool pulse_counter_setup(InternalGPIOPin *pin) = 0; | ||||
| @@ -57,7 +57,7 @@ struct HwPulseCounterStorage : public PulseCounterStorageBase { | ||||
|   pcnt_unit_t pcnt_unit; | ||||
|   pcnt_channel_t pcnt_channel; | ||||
| }; | ||||
| #endif | ||||
| #endif  // HAS_PCNT | ||||
|  | ||||
| PulseCounterStorageBase *get_storage(bool hw_pcnt = false); | ||||
|  | ||||
|   | ||||
| @@ -1 +1,4 @@ | ||||
| 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 . import CONF_DRAW_FROM_ORIGIN, CONF_DRAW_ROUNDING | ||||
| from .models import DriverChip | ||||
|  | ||||
| DEPENDENCIES = ["spi"] | ||||
| @@ -41,7 +42,6 @@ COLOR_ORDERS = { | ||||
| } | ||||
| DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema | ||||
|  | ||||
| CONF_DRAW_FROM_ORIGIN = "draw_from_origin" | ||||
| DELAY_FLAG = 0xFF | ||||
|  | ||||
|  | ||||
| @@ -78,56 +78,81 @@ def _validate(config): | ||||
|     return config | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     display.FULL_DISPLAY_SCHEMA.extend( | ||||
|         cv.Schema( | ||||
|             { | ||||
|                 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.Required(CONF_DIMENSIONS): cv.Any( | ||||
|                     cv.dimensions, | ||||
|                     cv.Schema( | ||||
|                         { | ||||
|                             cv.Required(CONF_WIDTH): validate_dimension, | ||||
|                             cv.Required(CONF_HEIGHT): validate_dimension, | ||||
|                             cv.Optional( | ||||
|                                 CONF_OFFSET_HEIGHT, default=0 | ||||
|                             ): validate_dimension, | ||||
|                             cv.Optional( | ||||
|                                 CONF_OFFSET_WIDTH, default=0 | ||||
|                             ): validate_dimension, | ||||
|                         } | ||||
|                     ), | ||||
|                 ), | ||||
|                 cv.Optional(CONF_TRANSFORM): cv.Schema( | ||||
| def power_of_two(value): | ||||
|     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.GenerateID(): cv.declare_id(QSPI_DBI), | ||||
|             cv.Optional(CONF_INIT_SEQUENCE): cv.ensure_list(map_sequence), | ||||
|             cv.Required(CONF_DIMENSIONS): cv.Any( | ||||
|                 cv.dimensions, | ||||
|                 cv.Schema( | ||||
|                     { | ||||
|                         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.Required(CONF_WIDTH): validate_dimension, | ||||
|                         cv.Required(CONF_HEIGHT): validate_dimension, | ||||
|                         cv.Optional(CONF_OFFSET_HEIGHT, default=0): validate_dimension, | ||||
|                         cv.Optional(CONF_OFFSET_WIDTH, default=0): validate_dimension, | ||||
|                     } | ||||
|                 ), | ||||
|                 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_ENABLE_PIN): pins.gpio_output_pin_schema, | ||||
|                 cv.Optional(CONF_BRIGHTNESS, default=0xD0): cv.int_range( | ||||
|                     0, 0xFF, min_included=True, max_included=True | ||||
|                 ), | ||||
|                 cv.Optional(CONF_DRAW_FROM_ORIGIN, default=False): cv.boolean, | ||||
|             } | ||||
|         ).extend( | ||||
|             spi.spi_device_schema( | ||||
|                 cs_pin_required=False, | ||||
|                 default_mode="MODE0", | ||||
|                 default_data_rate=10e6, | ||||
|                 quad=True, | ||||
|             ) | ||||
|             ), | ||||
|             cv.Optional(CONF_DRAW_FROM_ORIGIN, default=False): cv.boolean, | ||||
|             cv.Optional(CONF_RESET_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( | ||||
|                 0, 0xFF, min_included=True, max_included=True | ||||
|             ), | ||||
|         } | ||||
|     ).extend( | ||||
|         spi.spi_device_schema( | ||||
|             cs_pin_required=False, | ||||
|             default_mode="MODE0", | ||||
|             default_data_rate=10e6, | ||||
|             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, | ||||
| ) | ||||
| @@ -152,6 +177,7 @@ async def to_code(config): | ||||
|     cg.add(var.set_brightness(config[CONF_BRIGHTNESS])) | ||||
|     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_rounding(config[CONF_DRAW_ROUNDING])) | ||||
|     if enable_pin := config.get(CONF_ENABLE_PIN): | ||||
|         enable = await cg.gpio_pin_expression(enable_pin) | ||||
|         cg.add(var.set_enable_pin(enable)) | ||||
| @@ -163,7 +189,8 @@ async def to_code(config): | ||||
|     if transform := config.get(CONF_TRANSFORM): | ||||
|         cg.add(var.set_mirror_x(transform[CONF_MIRROR_X])) | ||||
|         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: | ||||
|         dimensions = config[CONF_DIMENSIONS] | ||||
|   | ||||
| @@ -1,5 +1,10 @@ | ||||
| # Commands | ||||
| from esphome.const import CONF_INVERT_COLORS, CONF_SWAP_XY | ||||
|  | ||||
| from . import CONF_DRAW_ROUNDING | ||||
|  | ||||
| SW_RESET_CMD = 0x01 | ||||
| SLEEP_IN = 0x10 | ||||
| SLEEP_OUT = 0x11 | ||||
| NORON = 0x13 | ||||
| INVERT_OFF = 0x20 | ||||
| @@ -24,11 +29,12 @@ PAGESEL = 0xFE | ||||
| class DriverChip: | ||||
|     chips = {} | ||||
|  | ||||
|     def __init__(self, name: str): | ||||
|     def __init__(self, name: str, defaults=None): | ||||
|         name = name.upper() | ||||
|         self.name = name | ||||
|         self.chips[name] = self | ||||
|         self.initsequence = [] | ||||
|         self.defaults = defaults or {} | ||||
|  | ||||
|     def cmd(self, c, *args): | ||||
|         """ | ||||
| @@ -59,9 +65,246 @@ chip.cmd(TEON, 0x00) | ||||
| chip.cmd(PIXFMT, 0x55) | ||||
| 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(0xC1, 0x33) | ||||
| 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") | ||||
|   | ||||
| @@ -33,19 +33,12 @@ void QspiDbi::update() { | ||||
|   this->do_update_(); | ||||
|   if (this->buffer_ == nullptr || this->x_low_ > this->x_high_ || this->y_low_ > this->y_high_) | ||||
|     return; | ||||
|   // Start addresses and widths/heights must be divisible by 2 (CASET/RASET restriction in datasheet) | ||||
|   if (this->x_low_ % 2 == 1) { | ||||
|     this->x_low_--; | ||||
|   } | ||||
|   if (this->x_high_ % 2 == 0) { | ||||
|     this->x_high_++; | ||||
|   } | ||||
|   if (this->y_low_ % 2 == 1) { | ||||
|     this->y_low_--; | ||||
|   } | ||||
|   if (this->y_high_ % 2 == 0) { | ||||
|     this->y_high_++; | ||||
|   } | ||||
|   // Some chips require that the drawing window be aligned on certain boundaries | ||||
|   auto dr = this->draw_rounding_; | ||||
|   this->x_low_ = this->x_low_ / dr * dr; | ||||
|   this->y_low_ = this->y_low_ / dr * dr; | ||||
|   this->x_high_ = (this->x_high_ + dr) / dr * dr - 1; | ||||
|   this->y_high_ = (this->y_high_ + dr) / dr * dr - 1; | ||||
|   if (this->draw_from_origin_) { | ||||
|     this->x_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); | ||||
|   } else { | ||||
|     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++) { | ||||
|       this->write_cmd_addr_data(8, 0x32, 24, cmd, ptr + ((y + y_offset) * stride + x_offset) * 2, w * 2, 4); | ||||
|       cmd = 0x3C00; | ||||
|       this->write_cmd_addr_data(0, 0, 0, 0, ptr + ((y + y_offset) * stride + x_offset) * 2, w * 2, 4); | ||||
|     } | ||||
|   } | ||||
|   this->disable(); | ||||
| @@ -220,6 +212,7 @@ void QspiDbi::dump_config() { | ||||
|   ESP_LOGCONFIG("", "Model: %s", this->model_); | ||||
|   ESP_LOGCONFIG(TAG, "  Height: %u", this->height_); | ||||
|   ESP_LOGCONFIG(TAG, "  Width: %u", this->width_); | ||||
|   ESP_LOGCONFIG(TAG, "  Draw rounding: %u", this->draw_rounding_); | ||||
|   LOG_PIN("  CS Pin: ", this->cs_); | ||||
|   LOG_PIN("  Reset Pin: ", this->reset_pin_); | ||||
|   ESP_LOGCONFIG(TAG, "  SPI Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000)); | ||||
|   | ||||
| @@ -4,12 +4,10 @@ | ||||
| #pragma once | ||||
|  | ||||
| #ifdef USE_ESP_IDF | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/spi/spi.h" | ||||
| #include "esphome/components/display/display.h" | ||||
| #include "esphome/components/display/display_buffer.h" | ||||
| #include "esphome/components/display/display_color_utils.h" | ||||
| #include "esp_lcd_panel_ops.h" | ||||
|  | ||||
| #include "esp_lcd_panel_rgb.h" | ||||
|  | ||||
| @@ -105,6 +103,7 @@ class QspiDbi : public display::DisplayBuffer, | ||||
|   int get_height_internal() override { return this->height_; } | ||||
|   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 set_draw_rounding(unsigned rounding) { this->draw_rounding_ = rounding; } | ||||
|  | ||||
|  protected: | ||||
|   void check_buffer_() { | ||||
| @@ -161,6 +160,7 @@ class QspiDbi : public display::DisplayBuffer, | ||||
|   bool mirror_x_{}; | ||||
|   bool mirror_y_{}; | ||||
|   bool draw_from_origin_{false}; | ||||
|   unsigned draw_rounding_{2}; | ||||
|   uint8_t brightness_{0xD0}; | ||||
|   const char *model_{"Unknown"}; | ||||
|   std::vector<std::vector<uint8_t>> init_sequences_{}; | ||||
|   | ||||
| @@ -8,7 +8,7 @@ namespace 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) { | ||||
|   static rmt_channel_t next_rmt_channel = RMT_CHANNEL_0; | ||||
|   this->channel_ = next_rmt_channel; | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/hal.h" | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
| #if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR < 5 | ||||
| #include <driver/rmt.h> | ||||
| #endif | ||||
|  | ||||
| @@ -112,25 +112,43 @@ class RemoteComponentBase { | ||||
| #ifdef USE_ESP32 | ||||
| class RemoteRMTChannel { | ||||
|  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(rmt_channel_t channel, uint8_t mem_block_num = 1); | ||||
|  | ||||
|   void config_rmt(rmt_config_t &rmt); | ||||
|   void set_clock_divider(uint8_t clock_divider) { this->clock_divider_ = clock_divider; } | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   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; | ||||
| #endif | ||||
|     return us * ticks_per_ten_us / 10; | ||||
|   } | ||||
|   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; | ||||
| #endif | ||||
|     return (ticks * 10) / ticks_per_ten_us; | ||||
|   } | ||||
|   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}; | ||||
|   uint8_t mem_block_num_; | ||||
|   uint8_t clock_divider_{80}; | ||||
| #endif | ||||
| }; | ||||
| #endif | ||||
|  | ||||
|   | ||||
| @@ -1,23 +1,28 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| 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 ( | ||||
|     CONF_BUFFER_SIZE, | ||||
|     CONF_CLOCK_DIVIDER, | ||||
|     CONF_CLOCK_RESOLUTION, | ||||
|     CONF_DUMP, | ||||
|     CONF_FILTER, | ||||
|     CONF_ID, | ||||
|     CONF_IDLE, | ||||
|     CONF_MEMORY_BLOCKS, | ||||
|     CONF_PIN, | ||||
|     CONF_RMT_CHANNEL, | ||||
|     CONF_RMT_SYMBOLS, | ||||
|     CONF_TOLERANCE, | ||||
|     CONF_TYPE, | ||||
|     CONF_MEMORY_BLOCKS, | ||||
|     CONF_RMT_CHANNEL, | ||||
|     CONF_USE_DMA, | ||||
|     CONF_VALUE, | ||||
| ) | ||||
| 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"] | ||||
| 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.Range(max=TimePeriod(microseconds=4294967295)), | ||||
|             ), | ||||
|             cv.SplitDefault(CONF_CLOCK_DIVIDER, esp32=80): cv.All( | ||||
|                 cv.only_on_esp32, cv.Range(min=1, max=255) | ||||
|             cv.SplitDefault(CONF_CLOCK_DIVIDER, esp32_arduino=80): cv.All( | ||||
|                 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.positive_time_period_microseconds, | ||||
|                 cv.Range(max=TimePeriod(microseconds=4294967295)), | ||||
|             ), | ||||
|             cv.Optional(CONF_MEMORY_BLOCKS, default=3): cv.Range(min=1, max=8), | ||||
|             cv.Optional(CONF_RMT_CHANNEL): esp32_rmt.validate_rmt_channel(tx=False), | ||||
|             cv.SplitDefault(CONF_MEMORY_BLOCKS, esp32_arduino=3): cv.All( | ||||
|                 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) | ||||
| ) | ||||
| @@ -115,13 +148,27 @@ CONFIG_SCHEMA = remote_base.validate_triggers( | ||||
| async def to_code(config): | ||||
|     pin = await cg.gpio_pin_expression(config[CONF_PIN]) | ||||
|     if CORE.is_esp32: | ||||
|         if (rmt_channel := config.get(CONF_RMT_CHANNEL, None)) is not None: | ||||
|             var = cg.new_Pvariable( | ||||
|                 config[CONF_ID], pin, rmt_channel, config[CONF_MEMORY_BLOCKS] | ||||
|             ) | ||||
|         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: | ||||
|             var = cg.new_Pvariable(config[CONF_ID], pin, config[CONF_MEMORY_BLOCKS]) | ||||
|         cg.add(var.set_clock_divider(config[CONF_CLOCK_DIVIDER])) | ||||
|             if (rmt_channel := config.get(CONF_RMT_CHANNEL, None)) is not None: | ||||
|                 var = cg.new_Pvariable( | ||||
|                     config[CONF_ID], pin, rmt_channel, config[CONF_MEMORY_BLOCKS] | ||||
|                 ) | ||||
|             else: | ||||
|                 var = cg.new_Pvariable(config[CONF_ID], pin, config[CONF_MEMORY_BLOCKS]) | ||||
|             cg.add(var.set_clock_divider(config[CONF_CLOCK_DIVIDER])) | ||||
|     else: | ||||
|         var = cg.new_Pvariable(config[CONF_ID], pin) | ||||
|  | ||||
|   | ||||
| @@ -5,6 +5,10 @@ | ||||
|  | ||||
| #include <cinttypes> | ||||
|  | ||||
| #if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR >= 5 | ||||
| #include <driver/rmt_rx.h> | ||||
| #endif | ||||
|  | ||||
| namespace esphome { | ||||
| namespace remote_receiver { | ||||
|  | ||||
| @@ -25,6 +29,21 @@ struct RemoteReceiverComponentStore { | ||||
|   uint32_t filter_us{10}; | ||||
|   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 | ||||
|  | ||||
| class RemoteReceiverComponent : public remote_base::RemoteReceiverBase, | ||||
| @@ -33,9 +52,10 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase, | ||||
|     , | ||||
|                                 public remote_base::RemoteRMTChannel | ||||
| #endif | ||||
|  | ||||
| { | ||||
|  public: | ||||
| #ifdef USE_ESP32 | ||||
| #if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR < 5 | ||||
|   RemoteReceiverComponent(InternalGPIOPin *pin, uint8_t mem_block_num = 1) | ||||
|       : RemoteReceiverBase(pin), remote_base::RemoteRMTChannel(mem_block_num) {} | ||||
|  | ||||
| @@ -49,19 +69,32 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase, | ||||
|   void loop() override; | ||||
|   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_filter_us(uint32_t filter_us) { this->filter_us_ = filter_us; } | ||||
|   void set_idle_us(uint32_t idle_us) { this->idle_us_ = idle_us; } | ||||
|  | ||||
|  protected: | ||||
| #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_; | ||||
| #endif | ||||
|   esp_err_t error_code_{ESP_OK}; | ||||
|   std::string error_string_{""}; | ||||
| #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_; | ||||
|   HighFrequencyLoopRequester high_freq_; | ||||
| #endif | ||||
|   | ||||
| @@ -2,15 +2,104 @@ | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
| #include <driver/rmt.h> | ||||
|  | ||||
| namespace esphome { | ||||
| namespace remote_receiver { | ||||
|  | ||||
| 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() { | ||||
|   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(); | ||||
|   rmt_config_t rmt{}; | ||||
|   this->config_rmt(rmt); | ||||
| @@ -59,10 +148,18 @@ void RemoteReceiverComponent::setup() { | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
| #endif | ||||
| } | ||||
|  | ||||
| void RemoteReceiverComponent::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "Remote Receiver:"); | ||||
|   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()) { | ||||
|     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!"); | ||||
| @@ -70,6 +167,7 @@ void RemoteReceiverComponent::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "  Channel: %d", this->channel_); | ||||
|   ESP_LOGCONFIG(TAG, "  RMT memory blocks: %d", this->mem_block_num_); | ||||
|   ESP_LOGCONFIG(TAG, "  Clock divider: %u", this->clock_divider_); | ||||
| #endif | ||||
|   ESP_LOGCONFIG(TAG, "  Tolerance: %" PRIu32 "%s", this->tolerance_, | ||||
|                 (this->tolerance_mode_ == remote_base::TOLERANCE_MODE_TIME) ? " 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() { | ||||
| #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; | ||||
|   auto *item = (rmt_item32_t *) xRingbufferReceive(this->ringbuf_, &len, 0); | ||||
|   if (item != nullptr) { | ||||
|     this->decode_rmt_(item, len); | ||||
|     this->decode_rmt_(item, len / sizeof(rmt_item32_t)); | ||||
|     vRingbufferReturnItem(this->ringbuf_, item); | ||||
|  | ||||
|     if (this->temp_.empty()) | ||||
| @@ -93,13 +219,18 @@ void RemoteReceiverComponent::loop() { | ||||
|     this->temp_.push_back(-this->idle_us_); | ||||
|     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; | ||||
|   uint32_t prev_length = 0; | ||||
|   this->temp_.clear(); | ||||
|   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_); | ||||
|  | ||||
|   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 | ||||
|   for (size_t i = 0; i < item_count; i++) { | ||||
|     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)) { | ||||
|       prev_length += item[i].duration0; | ||||
|     } else { | ||||
| @@ -140,7 +272,8 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) { | ||||
|     } | ||||
|  | ||||
|     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)) { | ||||
|       prev_length += item[i].duration1; | ||||
|     } else { | ||||
|   | ||||
| @@ -2,12 +2,25 @@ from esphome import automation, pins | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import esp32_rmt, remote_base | ||||
| 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"] | ||||
|  | ||||
| CONF_EOT_LEVEL = "eot_level" | ||||
| CONF_ON_TRANSMIT = "on_transmit" | ||||
| CONF_ON_COMPLETE = "on_complete" | ||||
| CONF_ONE_WIRE = "one_wire" | ||||
|  | ||||
| remote_transmitter_ns = cg.esphome_ns.namespace("remote_transmitter") | ||||
| RemoteTransmitterComponent = remote_transmitter_ns.class_( | ||||
| @@ -22,7 +35,29 @@ CONFIG_SCHEMA = cv.Schema( | ||||
|         cv.Required(CONF_CARRIER_DUTY_PERCENT): cv.All( | ||||
|             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_COMPLETE): automation.validate_automation(single=True), | ||||
|     } | ||||
| @@ -31,8 +66,30 @@ CONFIG_SCHEMA = cv.Schema( | ||||
|  | ||||
| async def to_code(config): | ||||
|     pin = await cg.gpio_pin_expression(config[CONF_PIN]) | ||||
|     if (rmt_channel := config.get(CONF_RMT_CHANNEL, None)) is not None: | ||||
|         var = cg.new_Pvariable(config[CONF_ID], pin, rmt_channel) | ||||
|     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: | ||||
|                 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: | ||||
|         var = cg.new_Pvariable(config[CONF_ID], pin) | ||||
|     await cg.register_component(var, config) | ||||
|   | ||||
| @@ -5,6 +5,10 @@ | ||||
|  | ||||
| #include <vector> | ||||
|  | ||||
| #if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR >= 5 | ||||
| #include <driver/rmt_tx.h> | ||||
| #endif | ||||
|  | ||||
| namespace esphome { | ||||
| namespace remote_transmitter { | ||||
|  | ||||
| @@ -16,7 +20,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, | ||||
| #endif | ||||
| { | ||||
|  public: | ||||
| #ifdef USE_ESP32 | ||||
| #if defined(USE_ESP32) && ESP_IDF_VERSION_MAJOR < 5 | ||||
|   RemoteTransmitterComponent(InternalGPIOPin *pin, uint8_t mem_block_num = 1) | ||||
|       : remote_base::RemoteTransmitterBase(pin), remote_base::RemoteRMTChannel(mem_block_num) {} | ||||
|  | ||||
| @@ -29,10 +33,18 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, | ||||
|  | ||||
|   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; } | ||||
|  | ||||
| #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_complete_trigger() const { return this->complete_trigger_; }; | ||||
|  | ||||
| @@ -54,7 +66,16 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, | ||||
|  | ||||
|   uint32_t current_carrier_frequency_{38000}; | ||||
|   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_; | ||||
| #endif | ||||
|   esp_err_t error_code_{ESP_OK}; | ||||
|   std::string error_string_{""}; | ||||
|   bool inverted_{false}; | ||||
|   | ||||
| @@ -9,13 +9,23 @@ namespace 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() { | ||||
|   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, "  RMT memory blocks: %d", this->mem_block_num_); | ||||
|   ESP_LOGCONFIG(TAG, "  Clock divider: %u", this->clock_divider_); | ||||
| #endif | ||||
|   LOG_PIN("  Pin: ", this->pin_); | ||||
|  | ||||
|   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_() { | ||||
| #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{}; | ||||
|  | ||||
|   this->config_rmt(c); | ||||
| @@ -45,13 +147,12 @@ void RemoteTransmitterComponent::configure_rmt_() { | ||||
|   } | ||||
|  | ||||
|   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.idle_level = RMT_IDLE_LEVEL_LOW; | ||||
|   } else { | ||||
|     c.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW; | ||||
|     c.tx_config.idle_level = RMT_IDLE_LEVEL_HIGH; | ||||
|     this->inverted_ = true; | ||||
|   } | ||||
|  | ||||
|   esp_err_t error = rmt_config(&c); | ||||
| @@ -76,6 +177,7 @@ void RemoteTransmitterComponent::configure_rmt_() { | ||||
|     } | ||||
|     this->initialized_ = true; | ||||
|   } | ||||
| #endif | ||||
| } | ||||
|  | ||||
| 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_.reserve((this->temp_.get_data().size() + 1) / 2); | ||||
|   uint32_t rmt_i = 0; | ||||
| #if ESP_IDF_VERSION_MAJOR >= 5 | ||||
|   rmt_symbol_word_t rmt_item; | ||||
| #else | ||||
|   rmt_item32_t rmt_item; | ||||
| #endif | ||||
|  | ||||
|   for (int32_t val : this->temp_.get_data()) { | ||||
|     bool level = val >= 0; | ||||
| @@ -125,6 +231,29 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen | ||||
|     return; | ||||
|   } | ||||
|   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++) { | ||||
|     esp_err_t error = rmt_write_items(this->channel_, this->rmt_temp_.data(), this->rmt_temp_.size(), true); | ||||
|     if (error != ESP_OK) { | ||||
| @@ -136,6 +265,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen | ||||
|     if (i + 1 < send_times) | ||||
|       delayMicroseconds(send_wait); | ||||
|   } | ||||
| #endif | ||||
|   this->complete_trigger_->trigger(); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -93,13 +93,17 @@ void IRAM_ATTR HOT RotaryEncoderSensorStore::gpio_intr(RotaryEncoderSensorStore | ||||
|   int8_t rotation_dir = 0; | ||||
|   uint16_t new_state = STATE_LOOKUP_TABLE[input_state]; | ||||
|   if ((new_state & arg->resolution & STATE_HAS_INCREMENTED) != 0) { | ||||
|     if (arg->counter < arg->max_value) | ||||
|       arg->counter++; | ||||
|     if (arg->counter < arg->max_value) { | ||||
|       auto x = arg->counter + 1; | ||||
|       arg->counter = x; | ||||
|     } | ||||
|     rotation_dir = 1; | ||||
|   } | ||||
|   if ((new_state & arg->resolution & STATE_HAS_DECREMENTED) != 0) { | ||||
|     if (arg->counter > arg->min_value) | ||||
|       arg->counter--; | ||||
|     if (arg->counter > arg->min_value) { | ||||
|       auto x = arg->counter - 1; | ||||
|       arg->counter = x; | ||||
|     } | ||||
|     rotation_dir = -1; | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -1,23 +1,22 @@ | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import i2c, sensirion_common, sensor | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c, sensor, sensirion_common | ||||
|  | ||||
| from esphome.const import ( | ||||
|     CONF_COMPENSATION, | ||||
|     CONF_ID, | ||||
|     CONF_BASELINE, | ||||
|     CONF_COMPENSATION, | ||||
|     CONF_ECO2, | ||||
|     CONF_ID, | ||||
|     CONF_STORE_BASELINE, | ||||
|     CONF_TEMPERATURE_SOURCE, | ||||
|     CONF_TVOC, | ||||
|     ICON_RADIATOR, | ||||
|     DEVICE_CLASS_CARBON_DIOXIDE, | ||||
|     DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     UNIT_PARTS_PER_MILLION, | ||||
|     UNIT_PARTS_PER_BILLION, | ||||
|     ICON_MOLECULE_CO2, | ||||
|     ENTITY_CATEGORY_DIAGNOSTIC, | ||||
|     ICON_MOLECULE_CO2, | ||||
|     ICON_RADIATOR, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     UNIT_PARTS_PER_BILLION, | ||||
|     UNIT_PARTS_PER_MILLION, | ||||
| ) | ||||
|  | ||||
| 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)) | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| #include "sgp30.h" | ||||
| #include <cinttypes> | ||||
| #include "esphome/core/application.h" | ||||
| #include "esphome/core/hal.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/application.h" | ||||
| #include <cinttypes> | ||||
|  | ||||
| namespace esphome { | ||||
| namespace sgp30 { | ||||
| @@ -295,10 +295,6 @@ void SGP30Component::update() { | ||||
|     if (this->tvoc_sensor_ != nullptr) | ||||
|       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->send_env_data_(); | ||||
|     this->read_iaq_baseline_(); | ||||
|   | ||||
| @@ -106,8 +106,9 @@ void ToshibaClimate::setup() { | ||||
|       this->publish_state(); | ||||
|     }); | ||||
|     this->current_temperature = this->sensor_->state; | ||||
|   } else | ||||
|   } else { | ||||
|     this->current_temperature = NAN; | ||||
|   } | ||||
|   // restore set points | ||||
|   auto restore = this->restore_state_(); | ||||
|   if (restore.has_value()) { | ||||
|   | ||||
| @@ -120,8 +120,9 @@ light::LightTraits TuyaLight::get_traits() { | ||||
|         traits.set_supported_color_modes( | ||||
|             {light::ColorMode::RGB_COLOR_TEMPERATURE, light::ColorMode::COLOR_TEMPERATURE}); | ||||
|       } | ||||
|     } else | ||||
|     } else { | ||||
|       traits.set_supported_color_modes({light::ColorMode::COLOR_TEMPERATURE}); | ||||
|     } | ||||
|     traits.set_min_mireds(this->cold_white_temperature_); | ||||
|     traits.set_max_mireds(this->warm_white_temperature_); | ||||
|   } else if (this->color_id_.has_value()) { | ||||
| @@ -131,8 +132,9 @@ light::LightTraits TuyaLight::get_traits() { | ||||
|       } else { | ||||
|         traits.set_supported_color_modes({light::ColorMode::RGB_WHITE}); | ||||
|       } | ||||
|     } else | ||||
|     } else { | ||||
|       traits.set_supported_color_modes({light::ColorMode::RGB}); | ||||
|     } | ||||
|   } else if (this->dimmer_id_.has_value()) { | ||||
|     traits.set_supported_color_modes({light::ColorMode::BRIGHTNESS}); | ||||
|   } else { | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| #ifdef USE_ESP32_FRAMEWORK_ARDUINO | ||||
| #include "uart_component_esp32_arduino.h" | ||||
| #include "esphome/core/application.h" | ||||
| #include "esphome/core/defines.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "uart_component_esp32_arduino.h" | ||||
|  | ||||
| #ifdef USE_LOGGER | ||||
| #include "esphome/components/logger/logger.h" | ||||
| @@ -118,7 +118,7 @@ void ESP32ArduinoUARTComponent::setup() { | ||||
|     } | ||||
| #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."); | ||||
|       this->mark_failed(); | ||||
|       return; | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| #ifdef USE_ESP_IDF | ||||
|  | ||||
| #include "uart_component_esp_idf.h" | ||||
| #include <cinttypes> | ||||
| #include "esphome/core/application.h" | ||||
| #include "esphome/core/defines.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include <cinttypes> | ||||
|  | ||||
| #ifdef USE_LOGGER | ||||
| #include "esphome/components/logger/logger.h" | ||||
| @@ -84,7 +84,7 @@ void IDFUARTComponent::setup() { | ||||
|   } | ||||
| #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."); | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   | ||||
| @@ -104,8 +104,9 @@ void YashimaClimate::setup() { | ||||
|       this->publish_state(); | ||||
|     }); | ||||
|     this->current_temperature = this->sensor_->state; | ||||
|   } else | ||||
|   } else { | ||||
|     this->current_temperature = NAN; | ||||
|   } | ||||
|   // restore set points | ||||
|   auto restore = this->restore_state_(); | ||||
|   if (restore.has_value()) { | ||||
|   | ||||
| @@ -1660,6 +1660,12 @@ class SplitDefault(Optional): | ||||
|         esp32_c3=vol.UNDEFINED, | ||||
|         esp32_c3_arduino=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, | ||||
|         bk72xx=vol.UNDEFINED, | ||||
|         rtl87xx=vol.UNDEFINED, | ||||
| @@ -1691,6 +1697,18 @@ class SplitDefault(Optional): | ||||
|         self._esp32_c3_idf_default = vol.default_factory( | ||||
|             _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._bk72xx_default = vol.default_factory(bk72xx) | ||||
|         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.const import ( | ||||
|                 VARIANT_ESP32C3, | ||||
|                 VARIANT_ESP32C6, | ||||
|                 VARIANT_ESP32H2, | ||||
|                 VARIANT_ESP32S2, | ||||
|                 VARIANT_ESP32S3, | ||||
|             ) | ||||
| @@ -1724,6 +1744,16 @@ class SplitDefault(Optional): | ||||
|                     return self._esp32_c3_arduino_default | ||||
|                 if CORE.using_esp_idf: | ||||
|                     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: | ||||
|                 if CORE.using_arduino: | ||||
|                     return self._esp32_arduino_default | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| """Constants used by esphome.""" | ||||
|  | ||||
| __version__ = "2024.12.0-dev" | ||||
| __version__ = "2025.1.0-dev" | ||||
|  | ||||
| ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" | ||||
| VALID_SUBSTITUTIONS_CHARACTERS = ( | ||||
| @@ -131,7 +131,9 @@ CONF_CLIENT_CERTIFICATE = "client_certificate" | ||||
| CONF_CLIENT_CERTIFICATE_KEY = "client_certificate_key" | ||||
| CONF_CLIENT_ID = "client_id" | ||||
| CONF_CLK_PIN = "clk_pin" | ||||
| CONF_CLOCK_DIVIDER = "clock_divider" | ||||
| CONF_CLOCK_PIN = "clock_pin" | ||||
| CONF_CLOCK_RESOLUTION = "clock_resolution" | ||||
| CONF_CLOSE_ACTION = "close_action" | ||||
| CONF_CLOSE_DURATION = "close_duration" | ||||
| CONF_CLOSE_ENDSTOP = "close_endstop" | ||||
| @@ -740,6 +742,7 @@ CONF_RGB_ORDER = "rgb_order" | ||||
| CONF_RGBW = "rgbw" | ||||
| CONF_RISING_EDGE = "rising_edge" | ||||
| CONF_RMT_CHANNEL = "rmt_channel" | ||||
| CONF_RMT_SYMBOLS = "rmt_symbols" | ||||
| CONF_ROTATION = "rotation" | ||||
| CONF_ROW = "row" | ||||
| CONF_RS_PIN = "rs_pin" | ||||
| @@ -919,6 +922,7 @@ CONF_UPDATE_ON_BOOT = "update_on_boot" | ||||
| CONF_URL = "url" | ||||
| CONF_USE_ABBREVIATIONS = "use_abbreviations" | ||||
| CONF_USE_ADDRESS = "use_address" | ||||
| CONF_USE_DMA = "use_dma" | ||||
| CONF_USE_FAHRENHEIT = "use_fahrenheit" | ||||
| CONF_USERNAME = "username" | ||||
| CONF_UUID = "uuid" | ||||
|   | ||||
| @@ -126,19 +126,21 @@ uint16_t crc16(const uint8_t *data, uint16_t len, uint16_t crc, uint16_t reverse | ||||
|     } | ||||
|   } else | ||||
| #endif | ||||
|       if (reverse_poly == 0xa001) { | ||||
|     while (len--) { | ||||
|       uint8_t combo = crc ^ (uint8_t) *data++; | ||||
|       crc = (crc >> 8) ^ CRC16_A001_LE_LUT_L[combo & 0x0F] ^ CRC16_A001_LE_LUT_H[combo >> 4]; | ||||
|     } | ||||
|   } else { | ||||
|     while (len--) { | ||||
|       crc ^= *data++; | ||||
|       for (uint8_t i = 0; i < 8; i++) { | ||||
|         if (crc & 0x0001) { | ||||
|           crc = (crc >> 1) ^ reverse_poly; | ||||
|         } else { | ||||
|           crc >>= 1; | ||||
|   { | ||||
|     if (reverse_poly == 0xa001) { | ||||
|       while (len--) { | ||||
|         uint8_t combo = crc ^ (uint8_t) *data++; | ||||
|         crc = (crc >> 8) ^ CRC16_A001_LE_LUT_L[combo & 0x0F] ^ CRC16_A001_LE_LUT_H[combo >> 4]; | ||||
|       } | ||||
|     } else { | ||||
|       while (len--) { | ||||
|         crc ^= *data++; | ||||
|         for (uint8_t i = 0; i < 8; i++) { | ||||
|           if (crc & 0x0001) { | ||||
|             crc = (crc >> 1) ^ reverse_poly; | ||||
|           } else { | ||||
|             crc >>= 1; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| @@ -767,7 +769,8 @@ bool mac_address_is_valid(const uint8_t *mac) { | ||||
|   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(); | ||||
|  | ||||
|   const uint32_t lag = 5000;  // microseconds, specifies the maximum time for a CPU busy-loop. | ||||
|   | ||||
| @@ -11,6 +11,14 @@ | ||||
|  | ||||
| #include "esphome/core/optional.h" | ||||
|  | ||||
| #ifdef USE_ESP8266 | ||||
| #include <Esp.h> | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_RP2040 | ||||
| #include <Arduino.h> | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
| #include <esp_heap_caps.h> | ||||
| #endif | ||||
| @@ -684,20 +692,23 @@ template<class T> class RAMAllocator { | ||||
|   }; | ||||
|  | ||||
|   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_} {} | ||||
|  | ||||
|   T *allocate(size_t n) { | ||||
|     size_t size = n * sizeof(T); | ||||
|     T *ptr = nullptr; | ||||
| #ifdef USE_ESP32 | ||||
|     // External allocation by default or if explicitely requested | ||||
|     if ((this->flags_ & Flags::ALLOC_EXTERNAL) || ((this->flags_ & Flags::ALLOC_INTERNAL) == 0)) { | ||||
|     if (this->flags_ & Flags::ALLOC_EXTERNAL) { | ||||
|       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) || (this->flags_ & Flags::ALLOC_EXTERNAL) == 0)) { | ||||
|       ptr = static_cast<T *>(malloc(size));  // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc) | ||||
|     if (ptr == nullptr && this->flags_ & Flags::ALLOC_INTERNAL) { | ||||
|       ptr = static_cast<T *>(heap_caps_malloc(size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)); | ||||
|     } | ||||
| #else | ||||
|     // 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) | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * 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: | ||||
|   uint8_t flags_{Flags::ALLOW_FAILURE}; | ||||
|   uint8_t flags_{ALLOC_INTERNAL | ALLOC_EXTERNAL}; | ||||
| }; | ||||
|  | ||||
| template<class T> using ExternalRAMAllocator = RAMAllocator<T>; | ||||
|   | ||||
| @@ -13,8 +13,8 @@ static const char *const TAG = "ring_buffer"; | ||||
|  | ||||
| RingBuffer::~RingBuffer() { | ||||
|   if (this->handle_ != nullptr) { | ||||
|     vStreamBufferDelete(this->handle_); | ||||
|     ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); | ||||
|     vRingbufferDelete(this->handle_); | ||||
|     RAMAllocator<uint8_t> allocator(RAMAllocator<uint8_t>::ALLOW_FAILURE); | ||||
|     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> 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_); | ||||
|   if (rb->storage_ == 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); | ||||
|  | ||||
|   return rb; | ||||
| } | ||||
|  | ||||
| size_t RingBuffer::read(void *data, size_t len, TickType_t ticks_to_wait) { | ||||
|   if (ticks_to_wait > 0) | ||||
|     xStreamBufferSetTriggerLevel(this->handle_, len); | ||||
|   size_t bytes_read = 0; | ||||
|  | ||||
|   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; | ||||
| } | ||||
| @@ -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 free = this->free(); | ||||
|   if (free < len) { | ||||
|     size_t needed = len - free; | ||||
|     uint8_t discard[needed]; | ||||
|     xStreamBufferReceive(this->handle_, discard, needed, 0); | ||||
|     // Free enough space in the ring buffer to fit the new data | ||||
|     this->discard_bytes_(len - free); | ||||
|   } | ||||
|   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) { | ||||
|   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 | ||||
|  | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
| #ifdef USE_ESP32 | ||||
|  | ||||
| #include <freertos/FreeRTOS.h> | ||||
| #include <freertos/stream_buffer.h> | ||||
| #include <freertos/ringbuf.h> | ||||
|  | ||||
| #include <cinttypes> | ||||
| #include <memory> | ||||
| @@ -82,9 +82,14 @@ class RingBuffer { | ||||
|   static std::unique_ptr<RingBuffer> create(size_t len); | ||||
|  | ||||
|  protected: | ||||
|   StreamBufferHandle_t handle_; | ||||
|   StaticStreamBuffer_t structure_; | ||||
|   uint8_t *storage_; | ||||
|   /// @brief Discards data from the ring buffer. | ||||
|   /// @param discard_bytes amount of bytes to discard | ||||
|   /// @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}; | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -108,6 +108,12 @@ def is_authenticated(handler: BaseHandler) -> bool: | ||||
|             return True | ||||
|  | ||||
|     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 True | ||||
|   | ||||
| @@ -12,7 +12,7 @@ pyserial==3.5 | ||||
| platformio==6.1.16  # When updating platformio, also update Dockerfile | ||||
| esptool==4.7.0 | ||||
| click==8.1.7 | ||||
| esphome-dashboard==20241120.0 | ||||
| esphome-dashboard==20241217.1 | ||||
| aioesphomeapi==24.6.2 | ||||
| zeroconf==0.132.2 | ||||
| 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 | ||||
| @@ -6,7 +6,6 @@ light: | ||||
|     rgb_order: GRB | ||||
|     num_leds: 256 | ||||
|     pin: 2 | ||||
|     rmt_channel: 0 | ||||
|  | ||||
| display: | ||||
|   - platform: addressable_light | ||||
|   | ||||
| @@ -6,7 +6,6 @@ light: | ||||
|     rgb_order: GRB | ||||
|     num_leds: 256 | ||||
|     pin: 2 | ||||
|     rmt_channel: 0 | ||||
|  | ||||
| display: | ||||
|   - platform: addressable_light | ||||
|   | ||||
| @@ -12,7 +12,6 @@ light: | ||||
|     rgb_order: GRB | ||||
|     num_leds: 256 | ||||
|     pin: 2 | ||||
|     rmt_channel: 0 | ||||
|     effects: | ||||
|       - e131: | ||||
|           universe: 1 | ||||
|   | ||||
| @@ -12,7 +12,6 @@ light: | ||||
|     rgb_order: GRB | ||||
|     num_leds: 256 | ||||
|     pin: 2 | ||||
|     rmt_channel: 0 | ||||
|     effects: | ||||
|       - e131: | ||||
|           universe: 1 | ||||
|   | ||||
| @@ -3,14 +3,12 @@ light: | ||||
|     id: led_strip | ||||
|     pin: 4 | ||||
|     num_leds: 60 | ||||
|     rmt_channel: 0 | ||||
|     rgb_order: GRB | ||||
|     chipset: ws2812 | ||||
|   - platform: esp32_rmt_led_strip | ||||
|     id: led_strip2 | ||||
|     pin: 5 | ||||
|     num_leds: 60 | ||||
|     rmt_channel: 1 | ||||
|     rgb_order: RGB | ||||
|     bit0_high: 100µs | ||||
|     bit0_low: 100µs | ||||
|   | ||||
| @@ -3,14 +3,12 @@ light: | ||||
|     id: led_strip | ||||
|     pin: 13 | ||||
|     num_leds: 60 | ||||
|     rmt_channel: 6 | ||||
|     rgb_order: GRB | ||||
|     chipset: ws2812 | ||||
|   - platform: esp32_rmt_led_strip | ||||
|     id: led_strip2 | ||||
|     pin: 14 | ||||
|     num_leds: 60 | ||||
|     rmt_channel: 2 | ||||
|     rgb_order: RGB | ||||
|     bit0_high: 100µs | ||||
|     bit0_low: 100µs | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| esphome: | ||||
|   on_boot: | ||||
|     - lambda: 'ESP_LOGD("display","is_connected(): %s", YESNO(id(main_lcd).is_connected()));' | ||||
|  | ||||
|     # Binary sensor publish action tests | ||||
|     - binary_sensor.nextion.publish: | ||||
|         id: r0_sensor | ||||
|   | ||||
| @@ -16,6 +16,19 @@ opentherm: | ||||
|   summer_mode_active: true | ||||
|   dhw_block: 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: | ||||
|   - 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