mirror of
https://github.com/esphome/esphome.git
synced 2025-08-03 08:57:47 +00:00
etst
This commit is contained in:
parent
b67a88027d
commit
413969300b
24
tests/integration/fixtures/api_custom_services.yaml
Normal file
24
tests/integration/fixtures/api_custom_services.yaml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
esphome:
|
||||||
|
name: api-custom-services-test
|
||||||
|
host:
|
||||||
|
|
||||||
|
# This is required for CustomAPIDevice to work
|
||||||
|
api:
|
||||||
|
custom_services: true
|
||||||
|
# Also test that YAML services still work
|
||||||
|
actions:
|
||||||
|
- action: test_yaml_service
|
||||||
|
then:
|
||||||
|
- logger.log: "YAML service called"
|
||||||
|
|
||||||
|
logger:
|
||||||
|
level: DEBUG
|
||||||
|
|
||||||
|
# External component that uses CustomAPIDevice
|
||||||
|
external_components:
|
||||||
|
- source:
|
||||||
|
type: local
|
||||||
|
path: EXTERNAL_COMPONENT_PATH
|
||||||
|
components: [custom_api_device_component]
|
||||||
|
|
||||||
|
custom_api_device_component:
|
@ -0,0 +1,19 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import CONF_ID
|
||||||
|
|
||||||
|
custom_api_device_component_ns = cg.esphome_ns.namespace("custom_api_device_component")
|
||||||
|
CustomAPIDeviceComponent = custom_api_device_component_ns.class_(
|
||||||
|
"CustomAPIDeviceComponent", cg.Component
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(CustomAPIDeviceComponent),
|
||||||
|
}
|
||||||
|
).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
@ -0,0 +1,55 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome.h"
|
||||||
|
|
||||||
|
#ifdef USE_API
|
||||||
|
namespace esphome {
|
||||||
|
namespace custom_api_device_component {
|
||||||
|
|
||||||
|
using namespace api;
|
||||||
|
|
||||||
|
class CustomAPIDeviceComponent : public Component, public CustomAPIDevice {
|
||||||
|
public:
|
||||||
|
void setup() override {
|
||||||
|
// Register services using CustomAPIDevice
|
||||||
|
register_service(&CustomAPIDeviceComponent::on_test_service, "custom_test_service");
|
||||||
|
|
||||||
|
register_service(&CustomAPIDeviceComponent::on_service_with_args, "custom_service_with_args",
|
||||||
|
{"arg_string", "arg_int", "arg_bool", "arg_float"});
|
||||||
|
|
||||||
|
// Test array types
|
||||||
|
register_service(&CustomAPIDeviceComponent::on_service_with_arrays, "custom_service_with_arrays",
|
||||||
|
{"bool_array", "int_array", "float_array", "string_array"});
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_test_service() { ESP_LOGI("custom_api", "Custom test service called!"); }
|
||||||
|
|
||||||
|
void on_service_with_args(std::string arg_string, int32_t arg_int, bool arg_bool, float arg_float) {
|
||||||
|
ESP_LOGI("custom_api", "Custom service called with: %s, %d, %d, %.2f", arg_string.c_str(), arg_int, arg_bool,
|
||||||
|
arg_float);
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_service_with_arrays(std::vector<bool> bool_array, std::vector<int32_t> int_array,
|
||||||
|
std::vector<float> float_array, std::vector<std::string> string_array) {
|
||||||
|
ESP_LOGI("custom_api", "Array service called with %zu bools, %zu ints, %zu floats, %zu strings", bool_array.size(),
|
||||||
|
int_array.size(), float_array.size(), string_array.size());
|
||||||
|
|
||||||
|
// Log first element of each array if not empty
|
||||||
|
if (!bool_array.empty()) {
|
||||||
|
ESP_LOGI("custom_api", "First bool: %d", bool_array[0]);
|
||||||
|
}
|
||||||
|
if (!int_array.empty()) {
|
||||||
|
ESP_LOGI("custom_api", "First int: %d", int_array[0]);
|
||||||
|
}
|
||||||
|
if (!float_array.empty()) {
|
||||||
|
ESP_LOGI("custom_api", "First float: %.2f", float_array[0]);
|
||||||
|
}
|
||||||
|
if (!string_array.empty()) {
|
||||||
|
ESP_LOGI("custom_api", "First string: %s", string_array[0].c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace custom_api_device_component
|
||||||
|
} // namespace esphome
|
||||||
|
#endif // USE_API
|
144
tests/integration/test_api_custom_services.py
Normal file
144
tests/integration/test_api_custom_services.py
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
"""Integration test for API custom services using CustomAPIDevice."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
|
||||||
|
from aioesphomeapi import UserService, UserServiceArgType
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_api_custom_services(
|
||||||
|
yaml_config: str,
|
||||||
|
run_compiled: RunCompiledFunction,
|
||||||
|
api_client_connected: APIClientConnectedFactory,
|
||||||
|
) -> None:
|
||||||
|
"""Test CustomAPIDevice services work correctly with custom_services: true."""
|
||||||
|
# Get the path to the external components directory
|
||||||
|
external_components_path = str(
|
||||||
|
Path(__file__).parent / "fixtures" / "external_components"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Replace the placeholder in the YAML config with the actual path
|
||||||
|
yaml_config = yaml_config.replace(
|
||||||
|
"EXTERNAL_COMPONENT_PATH", external_components_path
|
||||||
|
)
|
||||||
|
|
||||||
|
loop = asyncio.get_running_loop()
|
||||||
|
|
||||||
|
# Track log messages
|
||||||
|
yaml_service_future = loop.create_future()
|
||||||
|
custom_service_future = loop.create_future()
|
||||||
|
custom_args_future = loop.create_future()
|
||||||
|
custom_arrays_future = loop.create_future()
|
||||||
|
|
||||||
|
# Patterns to match in logs
|
||||||
|
yaml_service_pattern = re.compile(r"YAML service called")
|
||||||
|
custom_service_pattern = re.compile(r"Custom test service called!")
|
||||||
|
custom_args_pattern = re.compile(
|
||||||
|
r"Custom service called with: test_string, 456, 1, 78\.90"
|
||||||
|
)
|
||||||
|
custom_arrays_pattern = re.compile(
|
||||||
|
r"Array service called with 2 bools, 3 ints, 2 floats, 2 strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
def check_output(line: str) -> None:
|
||||||
|
"""Check log output for expected messages."""
|
||||||
|
if not yaml_service_future.done() and yaml_service_pattern.search(line):
|
||||||
|
yaml_service_future.set_result(True)
|
||||||
|
elif not custom_service_future.done() and custom_service_pattern.search(line):
|
||||||
|
custom_service_future.set_result(True)
|
||||||
|
elif not custom_args_future.done() and custom_args_pattern.search(line):
|
||||||
|
custom_args_future.set_result(True)
|
||||||
|
elif not custom_arrays_future.done() and custom_arrays_pattern.search(line):
|
||||||
|
custom_arrays_future.set_result(True)
|
||||||
|
|
||||||
|
# Run with log monitoring
|
||||||
|
async with run_compiled(yaml_config, line_callback=check_output):
|
||||||
|
async with 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-custom-services-test"
|
||||||
|
|
||||||
|
# List services
|
||||||
|
_, services = await client.list_entities_services()
|
||||||
|
|
||||||
|
# Should have 4 services: 1 YAML + 3 CustomAPIDevice
|
||||||
|
assert len(services) == 4, f"Expected 4 services, found {len(services)}"
|
||||||
|
|
||||||
|
# Find our services
|
||||||
|
yaml_service: UserService | None = None
|
||||||
|
custom_service: UserService | None = None
|
||||||
|
custom_args_service: UserService | None = None
|
||||||
|
custom_arrays_service: UserService | None = None
|
||||||
|
|
||||||
|
for service in services:
|
||||||
|
if service.name == "test_yaml_service":
|
||||||
|
yaml_service = service
|
||||||
|
elif service.name == "custom_test_service":
|
||||||
|
custom_service = service
|
||||||
|
elif service.name == "custom_service_with_args":
|
||||||
|
custom_args_service = service
|
||||||
|
elif service.name == "custom_service_with_arrays":
|
||||||
|
custom_arrays_service = service
|
||||||
|
|
||||||
|
assert yaml_service is not None, "test_yaml_service not found"
|
||||||
|
assert custom_service is not None, "custom_test_service not found"
|
||||||
|
assert custom_args_service is not None, "custom_service_with_args not found"
|
||||||
|
assert custom_arrays_service is not None, (
|
||||||
|
"custom_service_with_arrays not found"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test YAML service
|
||||||
|
client.execute_service(yaml_service, {})
|
||||||
|
await asyncio.wait_for(yaml_service_future, timeout=5.0)
|
||||||
|
|
||||||
|
# Test simple CustomAPIDevice service
|
||||||
|
client.execute_service(custom_service, {})
|
||||||
|
await asyncio.wait_for(custom_service_future, timeout=5.0)
|
||||||
|
|
||||||
|
# Verify custom_args_service arguments
|
||||||
|
assert len(custom_args_service.args) == 4
|
||||||
|
arg_types = {arg.name: arg.type for arg in custom_args_service.args}
|
||||||
|
assert arg_types["arg_string"] == UserServiceArgType.STRING
|
||||||
|
assert arg_types["arg_int"] == UserServiceArgType.INT
|
||||||
|
assert arg_types["arg_bool"] == UserServiceArgType.BOOL
|
||||||
|
assert arg_types["arg_float"] == UserServiceArgType.FLOAT
|
||||||
|
|
||||||
|
# Test CustomAPIDevice service with arguments
|
||||||
|
client.execute_service(
|
||||||
|
custom_args_service,
|
||||||
|
{
|
||||||
|
"arg_string": "test_string",
|
||||||
|
"arg_int": 456,
|
||||||
|
"arg_bool": True,
|
||||||
|
"arg_float": 78.9,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await asyncio.wait_for(custom_args_future, timeout=5.0)
|
||||||
|
|
||||||
|
# Verify array service arguments
|
||||||
|
assert len(custom_arrays_service.args) == 4
|
||||||
|
array_arg_types = {arg.name: arg.type for arg in custom_arrays_service.args}
|
||||||
|
assert array_arg_types["bool_array"] == UserServiceArgType.BOOL_ARRAY
|
||||||
|
assert array_arg_types["int_array"] == UserServiceArgType.INT_ARRAY
|
||||||
|
assert array_arg_types["float_array"] == UserServiceArgType.FLOAT_ARRAY
|
||||||
|
assert array_arg_types["string_array"] == UserServiceArgType.STRING_ARRAY
|
||||||
|
|
||||||
|
# Test CustomAPIDevice service with arrays
|
||||||
|
client.execute_service(
|
||||||
|
custom_arrays_service,
|
||||||
|
{
|
||||||
|
"bool_array": [True, False],
|
||||||
|
"int_array": [1, 2, 3],
|
||||||
|
"float_array": [1.1, 2.2],
|
||||||
|
"string_array": ["hello", "world"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await asyncio.wait_for(custom_arrays_future, timeout=5.0)
|
Loading…
x
Reference in New Issue
Block a user