mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Merge branch 'lwip_locking_fix' into integration
This commit is contained in:
		| @@ -11,6 +11,15 @@ namespace esphome { | ||||
| namespace api { | ||||
|  | ||||
| template<typename... X> class TemplatableStringValue : public TemplatableValue<std::string, X...> { | ||||
|  private: | ||||
|   // Helper to convert value to string - handles the case where value is already a string | ||||
|   template<typename T> static std::string value_to_string(T &&val) { return to_string(std::forward<T>(val)); } | ||||
|  | ||||
|   // Overloads for string types - needed because std::to_string doesn't support them | ||||
|   static std::string value_to_string(const char *val) { return std::string(val); }  // For lambdas returning .c_str() | ||||
|   static std::string value_to_string(const std::string &val) { return val; } | ||||
|   static std::string value_to_string(std::string &&val) { return std::move(val); } | ||||
|  | ||||
|  public: | ||||
|   TemplatableStringValue() : TemplatableValue<std::string, X...>() {} | ||||
|  | ||||
| @@ -19,7 +28,7 @@ template<typename... X> class TemplatableStringValue : public TemplatableValue<s | ||||
|  | ||||
|   template<typename F, enable_if_t<is_invocable<F, X...>::value, int> = 0> | ||||
|   TemplatableStringValue(F f) | ||||
|       : TemplatableValue<std::string, X...>([f](X... x) -> std::string { return to_string(f(x...)); }) {} | ||||
|       : TemplatableValue<std::string, X...>([f](X... x) -> std::string { return value_to_string(f(x...)); }) {} | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class TemplatableKeyValuePair { | ||||
|   | ||||
| @@ -309,19 +309,19 @@ def _format_framework_espidf_version( | ||||
|  | ||||
| # The default/recommended arduino framework version | ||||
| #  - https://github.com/espressif/arduino-esp32/releases | ||||
| RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 2, 1) | ||||
| RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 1, 3) | ||||
| # The platform-espressif32 version to use for arduino frameworks | ||||
| #  - https://github.com/pioarduino/platform-espressif32/releases | ||||
| ARDUINO_PLATFORM_VERSION = cv.Version(54, 3, 21) | ||||
| ARDUINO_PLATFORM_VERSION = cv.Version(53, 3, 13) | ||||
|  | ||||
| # The default/recommended esp-idf framework version | ||||
| #  - https://github.com/espressif/esp-idf/releases | ||||
| #  - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf | ||||
| RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(5, 4, 2) | ||||
| RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(5, 3, 2) | ||||
| # The platformio/espressif32 version to use for esp-idf frameworks | ||||
| #  - https://github.com/platformio/platform-espressif32/releases | ||||
| #  - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 | ||||
| ESP_IDF_PLATFORM_VERSION = cv.Version(54, 3, 21) | ||||
| ESP_IDF_PLATFORM_VERSION = cv.Version(53, 3, 13) | ||||
|  | ||||
| # List based on https://registry.platformio.org/tools/platformio/framework-espidf/versions | ||||
| SUPPORTED_PLATFORMIO_ESP_IDF_5X = [ | ||||
| @@ -356,8 +356,8 @@ SUPPORTED_PIOARDUINO_ESP_IDF_5X = [ | ||||
| def _arduino_check_versions(value): | ||||
|     value = value.copy() | ||||
|     lookups = { | ||||
|         "dev": (cv.Version(3, 2, 1), "https://github.com/espressif/arduino-esp32.git"), | ||||
|         "latest": (cv.Version(3, 2, 1), None), | ||||
|         "dev": (cv.Version(3, 1, 3), "https://github.com/espressif/arduino-esp32.git"), | ||||
|         "latest": (cv.Version(3, 1, 3), None), | ||||
|         "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), | ||||
|     } | ||||
|  | ||||
| @@ -395,8 +395,8 @@ def _arduino_check_versions(value): | ||||
| def _esp_idf_check_versions(value): | ||||
|     value = value.copy() | ||||
|     lookups = { | ||||
|         "dev": (cv.Version(5, 4, 2), "https://github.com/espressif/esp-idf.git"), | ||||
|         "latest": (cv.Version(5, 2, 2), None), | ||||
|         "dev": (cv.Version(5, 3, 2), "https://github.com/espressif/esp-idf.git"), | ||||
|         "latest": (cv.Version(5, 3, 2), None), | ||||
|         "recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None), | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -39,15 +39,14 @@ LwIPLock::LwIPLock() { | ||||
|   // Only lock if we're not already in the TCPIP thread | ||||
|   if (!sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER)) { | ||||
|     LOCK_TCPIP_CORE(); | ||||
|     locked_ = true; | ||||
|   } | ||||
| #endif | ||||
| } | ||||
|  | ||||
| LwIPLock::~LwIPLock() { | ||||
| #ifdef CONFIG_LWIP_TCPIP_CORE_LOCKING | ||||
|   // Only unlock if we locked it | ||||
|   if (locked_ && sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER)) { | ||||
|   // Only unlock if we hold the lock | ||||
|   if (sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER)) { | ||||
|     UNLOCK_TCPIP_CORE(); | ||||
|   } | ||||
| #endif | ||||
|   | ||||
| @@ -25,9 +25,6 @@ | ||||
| #include "driver/gpio.h" | ||||
| #include "esp_rom_gpio.h" | ||||
| #include "esp_rom_sys.h" | ||||
| #include "esp_idf_version.h" | ||||
|  | ||||
| #if defined(USE_ARDUINO) || ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 2) | ||||
|  | ||||
| static const char *TAG = "jl1101"; | ||||
| #define PHY_CHECK(a, str, goto_tag, ...) \ | ||||
| @@ -339,6 +336,4 @@ esp_eth_phy_t *esp_eth_phy_new_jl1101(const eth_phy_config_t *config) { | ||||
| err: | ||||
|   return NULL; | ||||
| } | ||||
|  | ||||
| #endif /* USE_ARDUINO */ | ||||
| #endif /* USE_ESP32 */ | ||||
|   | ||||
| @@ -11,7 +11,6 @@ | ||||
| #include "esp_eth_mac.h" | ||||
| #include "esp_netif.h" | ||||
| #include "esp_mac.h" | ||||
| #include "esp_idf_version.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ethernet { | ||||
| @@ -154,10 +153,7 @@ class EthernetComponent : public Component { | ||||
|  | ||||
| // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) | ||||
| extern EthernetComponent *global_eth_component; | ||||
|  | ||||
| #if defined(USE_ARDUINO) || ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 2) | ||||
| extern "C" esp_eth_phy_t *esp_eth_phy_new_jl1101(const eth_phy_config_t *config); | ||||
| #endif | ||||
|  | ||||
| }  // namespace ethernet | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -3,6 +3,7 @@ import esphome.codegen as cg | ||||
| from esphome.components import i2c, sensirion_common, sensor | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_ALTITUDE_COMPENSATION, | ||||
|     CONF_AMBIENT_PRESSURE_COMPENSATION, | ||||
|     CONF_AUTOMATIC_SELF_CALIBRATION, | ||||
|     CONF_CO2, | ||||
| @@ -35,8 +36,6 @@ ForceRecalibrationWithReference = scd30_ns.class_( | ||||
|     "ForceRecalibrationWithReference", automation.Action | ||||
| ) | ||||
|  | ||||
| CONF_ALTITUDE_COMPENSATION = "altitude_compensation" | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import esphome.codegen as cg | ||||
| from esphome.components import i2c, sensirion_common, sensor | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_ALTITUDE_COMPENSATION, | ||||
|     CONF_AMBIENT_PRESSURE_COMPENSATION, | ||||
|     CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE, | ||||
|     CONF_AUTOMATIC_SELF_CALIBRATION, | ||||
| @@ -49,9 +50,6 @@ PerformForcedCalibrationAction = scd4x_ns.class_( | ||||
| ) | ||||
| FactoryResetAction = scd4x_ns.class_("FactoryResetAction", automation.Action) | ||||
|  | ||||
|  | ||||
| CONF_ALTITUDE_COMPENSATION = "altitude_compensation" | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|   | ||||
| @@ -96,6 +96,7 @@ CONF_ALL = "all" | ||||
| CONF_ALLOW_OTHER_USES = "allow_other_uses" | ||||
| CONF_ALPHA = "alpha" | ||||
| CONF_ALTITUDE = "altitude" | ||||
| CONF_ALTITUDE_COMPENSATION = "altitude_compensation" | ||||
| CONF_AMBIENT_LIGHT = "ambient_light" | ||||
| CONF_AMBIENT_PRESSURE_COMPENSATION = "ambient_pressure_compensation" | ||||
| CONF_AMBIENT_PRESSURE_COMPENSATION_SOURCE = "ambient_pressure_compensation_source" | ||||
|   | ||||
| @@ -163,11 +163,12 @@ | ||||
| #define USE_WIFI_11KV_SUPPORT | ||||
|  | ||||
| #ifdef USE_ARDUINO | ||||
| #define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 2, 1) | ||||
| #define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 1, 3) | ||||
| #define USE_ETHERNET | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_ESP_IDF | ||||
| #define USE_ESP_IDF_VERSION_CODE VERSION_CODE(5, 3, 2) | ||||
| #define USE_MICRO_WAKE_WORD | ||||
| #define USE_MICRO_WAKE_WORD_VAD | ||||
| #if defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) | ||||
|   | ||||
| @@ -695,11 +695,6 @@ class LwIPLock { | ||||
|  public: | ||||
|   LwIPLock(); | ||||
|   ~LwIPLock(); | ||||
|  | ||||
|  protected: | ||||
| #if defined(USE_ESP32) | ||||
|   bool locked_{false}; | ||||
| #endif | ||||
| }; | ||||
|  | ||||
| /** Helper class to request `loop()` to be called as fast as possible. | ||||
|   | ||||
| @@ -125,9 +125,9 @@ extra_scripts = post:esphome/components/esp8266/post_build.py.script | ||||
| ; This are common settings for the ESP32 (all variants) using Arduino. | ||||
| [common:esp32-arduino] | ||||
| extends = common:arduino | ||||
| platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.21/platform-espressif32.zip | ||||
| platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.13/platform-espressif32.zip | ||||
| platform_packages = | ||||
|     pioarduino/framework-arduinoespressif32@https://github.com/espressif/arduino-esp32/releases/download/3.2.1/esp32-3.2.1.zip | ||||
|     pioarduino/framework-arduinoespressif32@https://github.com/espressif/arduino-esp32/releases/download/3.1.3/esp32-3.1.3.zip | ||||
|  | ||||
| framework = arduino | ||||
| lib_deps = | ||||
| @@ -161,9 +161,9 @@ extra_scripts = post:esphome/components/esp32/post_build.py.script | ||||
| ; This are common settings for the ESP32 (all variants) using IDF. | ||||
| [common:esp32-idf] | ||||
| extends = common:idf | ||||
| platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.21/platform-espressif32.zip | ||||
| platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.13/platform-espressif32.zip | ||||
| platform_packages = | ||||
|     pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v5.4.2/esp-idf-v5.4.2.zip | ||||
|     pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v5.3.2/esp-idf-v5.3.2.zip | ||||
|  | ||||
| framework = espidf | ||||
| lib_deps = | ||||
|   | ||||
							
								
								
									
										64
									
								
								tests/integration/fixtures/api_string_lambda.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								tests/integration/fixtures/api_string_lambda.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| esphome: | ||||
|   name: api-string-lambda-test | ||||
| host: | ||||
|  | ||||
| api: | ||||
|   actions: | ||||
|     # Service that tests string lambda functionality | ||||
|     - action: test_string_lambda | ||||
|       variables: | ||||
|         input_string: string | ||||
|       then: | ||||
|         # Log the input to verify service was called | ||||
|         - logger.log: | ||||
|             format: "Service called with string: %s" | ||||
|             args: [input_string.c_str()] | ||||
|  | ||||
|         # This is the key test - using a lambda that returns x.c_str() | ||||
|         # where x is already a string. This would fail to compile in 2025.7.0b5 | ||||
|         # with "no matching function for call to 'to_string(std::string)'" | ||||
|         # This is the exact case from issue #9539 | ||||
|         - homeassistant.tag_scanned: !lambda 'return input_string.c_str();' | ||||
|  | ||||
|         # Also test with homeassistant.event to verify our fix works with data fields | ||||
|         - homeassistant.event: | ||||
|             event: esphome.test_string_lambda | ||||
|             data: | ||||
|               value: !lambda 'return input_string.c_str();' | ||||
|  | ||||
|     # Service that tests int lambda functionality | ||||
|     - action: test_int_lambda | ||||
|       variables: | ||||
|         input_number: int | ||||
|       then: | ||||
|         # Log the input to verify service was called | ||||
|         - logger.log: | ||||
|             format: "Service called with int: %d" | ||||
|             args: [input_number] | ||||
|  | ||||
|         # Test that int lambdas still work correctly with to_string | ||||
|         # The TemplatableStringValue should automatically convert int to string | ||||
|         - homeassistant.event: | ||||
|             event: esphome.test_int_lambda | ||||
|             data: | ||||
|               value: !lambda 'return input_number;' | ||||
|  | ||||
|     # Service that tests float lambda functionality | ||||
|     - action: test_float_lambda | ||||
|       variables: | ||||
|         input_float: float | ||||
|       then: | ||||
|         # Log the input to verify service was called | ||||
|         - logger.log: | ||||
|             format: "Service called with float: %.2f" | ||||
|             args: [input_float] | ||||
|  | ||||
|         # Test that float lambdas still work correctly with to_string | ||||
|         # The TemplatableStringValue should automatically convert float to string | ||||
|         - homeassistant.event: | ||||
|             event: esphome.test_float_lambda | ||||
|             data: | ||||
|               value: !lambda 'return input_float;' | ||||
|  | ||||
| logger: | ||||
|   level: DEBUG | ||||
							
								
								
									
										85
									
								
								tests/integration/test_api_string_lambda.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								tests/integration/test_api_string_lambda.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | ||||
| """Integration test for TemplatableStringValue with string lambdas.""" | ||||
|  | ||||
| from __future__ import annotations | ||||
|  | ||||
| import asyncio | ||||
| import re | ||||
|  | ||||
| import pytest | ||||
|  | ||||
| from .types import APIClientConnectedFactory, RunCompiledFunction | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_api_string_lambda( | ||||
|     yaml_config: str, | ||||
|     run_compiled: RunCompiledFunction, | ||||
|     api_client_connected: APIClientConnectedFactory, | ||||
| ) -> None: | ||||
|     """Test TemplatableStringValue works with lambdas that return different types.""" | ||||
|     loop = asyncio.get_running_loop() | ||||
|  | ||||
|     # Track log messages for all three service calls | ||||
|     string_called_future = loop.create_future() | ||||
|     int_called_future = loop.create_future() | ||||
|     float_called_future = loop.create_future() | ||||
|  | ||||
|     # Patterns to match in logs - confirms the lambdas compiled and executed | ||||
|     string_pattern = re.compile(r"Service called with string: STRING_FROM_LAMBDA") | ||||
|     int_pattern = re.compile(r"Service called with int: 42") | ||||
|     float_pattern = re.compile(r"Service called with float: 3\.14") | ||||
|  | ||||
|     def check_output(line: str) -> None: | ||||
|         """Check log output for expected messages.""" | ||||
|         if not string_called_future.done() and string_pattern.search(line): | ||||
|             string_called_future.set_result(True) | ||||
|         if not int_called_future.done() and int_pattern.search(line): | ||||
|             int_called_future.set_result(True) | ||||
|         if not float_called_future.done() and float_pattern.search(line): | ||||
|             float_called_future.set_result(True) | ||||
|  | ||||
|     # Run with log monitoring | ||||
|     async with ( | ||||
|         run_compiled(yaml_config, line_callback=check_output), | ||||
|         api_client_connected() as client, | ||||
|     ): | ||||
|         # Verify device info | ||||
|         device_info = await client.device_info() | ||||
|         assert device_info is not None | ||||
|         assert device_info.name == "api-string-lambda-test" | ||||
|  | ||||
|         # List services to find our test services | ||||
|         _, services = await client.list_entities_services() | ||||
|  | ||||
|         # Find all test services | ||||
|         string_service = next( | ||||
|             (s for s in services if s.name == "test_string_lambda"), None | ||||
|         ) | ||||
|         assert string_service is not None, "test_string_lambda service not found" | ||||
|  | ||||
|         int_service = next((s for s in services if s.name == "test_int_lambda"), None) | ||||
|         assert int_service is not None, "test_int_lambda service not found" | ||||
|  | ||||
|         float_service = next( | ||||
|             (s for s in services if s.name == "test_float_lambda"), None | ||||
|         ) | ||||
|         assert float_service is not None, "test_float_lambda service not found" | ||||
|  | ||||
|         # Execute all three services to test different lambda return types | ||||
|         client.execute_service(string_service, {"input_string": "STRING_FROM_LAMBDA"}) | ||||
|         client.execute_service(int_service, {"input_number": 42}) | ||||
|         client.execute_service(float_service, {"input_float": 3.14}) | ||||
|  | ||||
|         # Wait for all service log messages | ||||
|         # This confirms the lambdas compiled successfully and executed | ||||
|         try: | ||||
|             await asyncio.wait_for( | ||||
|                 asyncio.gather( | ||||
|                     string_called_future, int_called_future, float_called_future | ||||
|                 ), | ||||
|                 timeout=5.0, | ||||
|             ) | ||||
|         except TimeoutError: | ||||
|             pytest.fail( | ||||
|                 "One or more service log messages not received - lambda may have failed to compile or execute" | ||||
|             ) | ||||
		Reference in New Issue
	
	Block a user