[api] Fix compilation error with char* lambdas in HomeAssistant services (#9638)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
J. Nick Koston 2025-07-17 12:55:39 -10:00 committed by Jesse Hills
parent cdeed7afa7
commit 21e66b76e4
No known key found for this signature in database
GPG Key ID: BEAAE804EFD8E83A
3 changed files with 44 additions and 3 deletions

View File

@ -16,6 +16,9 @@ template<typename... X> class TemplatableStringValue : public TemplatableValue<s
template<typename T> static std::string value_to_string(T &&val) { return to_string(std::forward<T>(val)); } 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 // 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 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(const std::string &val) { return val; }
static std::string value_to_string(std::string &&val) { return std::move(val); } static std::string value_to_string(std::string &&val) { return std::move(val); }

View File

@ -60,5 +60,28 @@ api:
data: data:
value: !lambda 'return input_float;' 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: logger:
level: DEBUG level: DEBUG

View File

@ -19,15 +19,17 @@ async def test_api_string_lambda(
"""Test TemplatableStringValue works with lambdas that return different types.""" """Test TemplatableStringValue works with lambdas that return different types."""
loop = asyncio.get_running_loop() 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() string_called_future = loop.create_future()
int_called_future = loop.create_future() int_called_future = loop.create_future()
float_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 # Patterns to match in logs - confirms the lambdas compiled and executed
string_pattern = re.compile(r"Service called with string: STRING_FROM_LAMBDA") string_pattern = re.compile(r"Service called with string: STRING_FROM_LAMBDA")
int_pattern = re.compile(r"Service called with int: 42") int_pattern = re.compile(r"Service called with int: 42")
float_pattern = re.compile(r"Service called with float: 3\.14") 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: def check_output(line: str) -> None:
"""Check log output for expected messages.""" """Check log output for expected messages."""
@ -37,6 +39,8 @@ async def test_api_string_lambda(
int_called_future.set_result(True) int_called_future.set_result(True)
if not float_called_future.done() and float_pattern.search(line): if not float_called_future.done() and float_pattern.search(line):
float_called_future.set_result(True) 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 # Run with log monitoring
async with ( async with (
@ -65,17 +69,28 @@ async def test_api_string_lambda(
) )
assert float_service is not None, "test_float_lambda service not found" 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(string_service, {"input_string": "STRING_FROM_LAMBDA"})
client.execute_service(int_service, {"input_number": 42}) client.execute_service(int_service, {"input_number": 42})
client.execute_service(float_service, {"input_float": 3.14}) 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 # Wait for all service log messages
# This confirms the lambdas compiled successfully and executed # This confirms the lambdas compiled successfully and executed
try: try:
await asyncio.wait_for( await asyncio.wait_for(
asyncio.gather( 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, timeout=5.0,
) )