diff --git a/esphome/components/api/homeassistant_service.h b/esphome/components/api/homeassistant_service.h index 223af132db..f765f1f806 100644 --- a/esphome/components/api/homeassistant_service.h +++ b/esphome/components/api/homeassistant_service.h @@ -16,6 +16,9 @@ template class TemplatableStringValue : public TemplatableValue static std::string value_to_string(T &&val) { return to_string(std::forward(val)); } // Overloads for string types - needed because std::to_string doesn't support them + static std::string value_to_string(char *val) { + return val ? std::string(val) : std::string(); + } // For lambdas returning char* (e.g., itoa) 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); } diff --git a/tests/integration/fixtures/api_string_lambda.yaml b/tests/integration/fixtures/api_string_lambda.yaml index 18440b9984..e2da4683c0 100644 --- a/tests/integration/fixtures/api_string_lambda.yaml +++ b/tests/integration/fixtures/api_string_lambda.yaml @@ -60,5 +60,28 @@ api: data: value: !lambda 'return input_float;' + # Service that tests char* lambda functionality (e.g., from itoa or sprintf) + - action: test_char_ptr_lambda + variables: + input_number: int + input_string: string + then: + # Log the input to verify service was called + - logger.log: + format: "Service called with number for char* test: %d" + args: [input_number] + + # Test that char* lambdas work correctly + # This would fail in issue #9628 with "invalid conversion from 'char*' to 'long long unsigned int'" + - homeassistant.event: + event: esphome.test_char_ptr_lambda + data: + # Test snprintf returning char* + decimal_value: !lambda 'static char buffer[20]; snprintf(buffer, sizeof(buffer), "%d", input_number); return buffer;' + # Test strdup returning char* (dynamically allocated) + string_copy: !lambda 'return strdup(input_string.c_str());' + # Test string literal (const char*) + literal: !lambda 'return "test literal";' + logger: level: DEBUG diff --git a/tests/integration/test_api_string_lambda.py b/tests/integration/test_api_string_lambda.py index 3bef2d86e2..f4ef77bad8 100644 --- a/tests/integration/test_api_string_lambda.py +++ b/tests/integration/test_api_string_lambda.py @@ -19,15 +19,17 @@ async def test_api_string_lambda( """Test TemplatableStringValue works with lambdas that return different types.""" loop = asyncio.get_running_loop() - # Track log messages for all three service calls + # Track log messages for all four service calls string_called_future = loop.create_future() int_called_future = loop.create_future() float_called_future = loop.create_future() + char_ptr_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") + char_ptr_pattern = re.compile(r"Service called with number for char\* test: 123") def check_output(line: str) -> None: """Check log output for expected messages.""" @@ -37,6 +39,8 @@ async def test_api_string_lambda( int_called_future.set_result(True) if not float_called_future.done() and float_pattern.search(line): float_called_future.set_result(True) + if not char_ptr_called_future.done() and char_ptr_pattern.search(line): + char_ptr_called_future.set_result(True) # Run with log monitoring async with ( @@ -65,17 +69,28 @@ async def test_api_string_lambda( ) assert float_service is not None, "test_float_lambda service not found" - # Execute all three services to test different lambda return types + char_ptr_service = next( + (s for s in services if s.name == "test_char_ptr_lambda"), None + ) + assert char_ptr_service is not None, "test_char_ptr_lambda service not found" + + # Execute all four 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}) + client.execute_service( + char_ptr_service, {"input_number": 123, "input_string": "test_string"} + ) # 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 + string_called_future, + int_called_future, + float_called_future, + char_ptr_called_future, ), timeout=5.0, )