mirror of
https://github.com/esphome/esphome.git
synced 2026-11-15 09:36:04 +00:00
Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2cf3a58d3a | ||
|
|
e4d02d8201 | ||
|
|
50abc38c2b | ||
|
|
4b98b3f072 | ||
|
|
59b96ae8a0 | ||
|
|
2052d251f2 | ||
|
|
f6e68770ae | ||
|
|
3bbeb4a713 | ||
|
|
29d8309161 | ||
|
|
be298de41a | ||
|
|
33331c3221 |
No files matched your search
@@ -646,7 +646,8 @@ def parent_ref(var: MockObj) -> MockObj:
|
||||
return MockObj(f"::{var}", "->")
|
||||
|
||||
|
||||
async def _apply_parent(config: ConfigType, id_key: str = CONF_ID) -> str:
|
||||
async def apply_parent(config: ConfigType, id_key: str = CONF_ID) -> str:
|
||||
"""The object ``config[id_key]`` names, as ``parent_ref`` spells it."""
|
||||
return str(parent_ref(await cg.get_variable(config[id_key])))
|
||||
|
||||
|
||||
@@ -868,7 +869,7 @@ def apply_action_builder(
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
parent = await _apply_parent(config, id_key)
|
||||
parent = await apply_parent(config, id_key)
|
||||
receiver = "apply_call." if call else f"{parent}->"
|
||||
statements: list[str] = []
|
||||
for spec in statements_spec:
|
||||
@@ -1014,7 +1015,7 @@ def register_apply_condition(
|
||||
) -> MockObj:
|
||||
if defaults:
|
||||
config = {**defaults, **config}
|
||||
parent = await _apply_parent(config, id_key)
|
||||
parent = await apply_parent(config, id_key)
|
||||
values = _apply_values(config, members)
|
||||
check = await _render_call(
|
||||
name, call.target, members, values, config, parent, args, compare=True
|
||||
|
||||
@@ -834,22 +834,16 @@ HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
async def _new_service_call_action(
|
||||
server_id: ID,
|
||||
action_id: ID,
|
||||
async def _service_call_table(
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
is_event: bool,
|
||||
service: Any,
|
||||
data: dict[str, Any],
|
||||
data_template: dict[str, Any],
|
||||
variables: dict[str, Any],
|
||||
) -> MockObj:
|
||||
"""Create the action with its name and fields in one shared flash table."""
|
||||
groups: tuple[dict[str, Any], ...],
|
||||
) -> list[Expression | int]:
|
||||
"""Return the name and the data, data_template and variables ``groups`` in one shared
|
||||
flash table, then the size of each map."""
|
||||
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
|
||||
serv = await cg.get_variable(server_id)
|
||||
field_type = HomeAssistantField.template(template_arg)
|
||||
groups = [data, data_template, variables]
|
||||
# A lambda may keep static state, so a table with lambdas is never shared
|
||||
has_lambda = cg.is_template(service) or any(
|
||||
cg.is_template(value) for group in groups for value in group.values()
|
||||
@@ -873,8 +867,30 @@ async def _new_service_call_action(
|
||||
cg.ArrayInitializer(*entries, multiline=True),
|
||||
share=not has_lambda,
|
||||
)
|
||||
return cg.new_Pvariable(
|
||||
action_id, template_arg, serv, is_event, table, *(len(g) for g in groups)
|
||||
return [table, *(len(g) for g in groups)]
|
||||
|
||||
|
||||
async def _send_service_call(
|
||||
config: ConfigType,
|
||||
action_id: ID,
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
is_event: bool,
|
||||
service: Any,
|
||||
groups: tuple[dict[str, Any], ...],
|
||||
) -> MockObj:
|
||||
"""Send the action or event from a generated function, without waiting for a response."""
|
||||
server = MockObj(await automation.apply_parent(config))
|
||||
table, *counts = await _service_call_table(template_arg, args, service, groups)
|
||||
call = api_ns.send_homeassistant_fields(
|
||||
server,
|
||||
is_event,
|
||||
automation.parent_ref(table),
|
||||
*counts,
|
||||
*(MockObj(name) for _, name in args),
|
||||
)
|
||||
return automation.apply_statements_action(
|
||||
action_id, template_arg, args, [f"{call};"]
|
||||
)
|
||||
|
||||
|
||||
@@ -887,13 +903,13 @@ def _service_call_fields(config: ConfigType) -> tuple[dict[str, Any], ...]:
|
||||
# (StringRef into the API receive buffer) must not be used.
|
||||
@automation.register_action(
|
||||
"homeassistant.action",
|
||||
HomeAssistantServiceCallAction,
|
||||
automation.ApplyAction,
|
||||
HOMEASSISTANT_ACTION_ACTION_SCHEMA,
|
||||
synchronous=False,
|
||||
)
|
||||
@automation.register_action(
|
||||
"homeassistant.service",
|
||||
HomeAssistantServiceCallAction,
|
||||
automation.ApplyAction,
|
||||
HOMEASSISTANT_ACTION_ACTION_SCHEMA,
|
||||
synchronous=False,
|
||||
)
|
||||
@@ -903,29 +919,40 @@ async def homeassistant_service_to_code(
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
var = await _new_service_call_action(
|
||||
config[CONF_ID],
|
||||
on_error = config.get(CONF_ON_ERROR)
|
||||
on_success = config.get(CONF_ON_SUCCESS)
|
||||
if not on_error and not on_success:
|
||||
return await _send_service_call(
|
||||
config,
|
||||
action_id,
|
||||
template_arg,
|
||||
args,
|
||||
False,
|
||||
config[CONF_ACTION],
|
||||
_service_call_fields(config),
|
||||
)
|
||||
|
||||
# Waiting for the response needs an object that holds the triggers
|
||||
cg.add_define("USE_API_HOMEASSISTANT_ACTION_RESPONSES")
|
||||
action_id.type = HomeAssistantServiceCallAction
|
||||
var = cg.new_Pvariable(
|
||||
action_id,
|
||||
template_arg,
|
||||
args,
|
||||
False,
|
||||
config[CONF_ACTION],
|
||||
*_service_call_fields(config),
|
||||
await cg.get_variable(config[CONF_ID]),
|
||||
*await _service_call_table(
|
||||
template_arg, args, config[CONF_ACTION], _service_call_fields(config)
|
||||
),
|
||||
)
|
||||
|
||||
if on_error := config.get(CONF_ON_ERROR):
|
||||
cg.add_define("USE_API_HOMEASSISTANT_ACTION_RESPONSES")
|
||||
if on_error:
|
||||
cg.add_define("USE_API_HOMEASSISTANT_ACTION_RESPONSES_ERRORS")
|
||||
cg.add(var.set_wants_status())
|
||||
await automation.build_automation(
|
||||
var.get_error_trigger(),
|
||||
[(cg.std_string, "error"), *args],
|
||||
on_error,
|
||||
)
|
||||
|
||||
if on_success := config.get(CONF_ON_SUCCESS):
|
||||
cg.add_define("USE_API_HOMEASSISTANT_ACTION_RESPONSES")
|
||||
cg.add(var.set_wants_status())
|
||||
if on_success:
|
||||
if config[CONF_CAPTURE_RESPONSE]:
|
||||
cg.add(var.set_wants_response())
|
||||
cg.add_define("USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON")
|
||||
@@ -974,7 +1001,7 @@ HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema(
|
||||
# so play() never stores the trigger args.
|
||||
@automation.register_action(
|
||||
"homeassistant.event",
|
||||
HomeAssistantServiceCallAction,
|
||||
automation.ApplyAction,
|
||||
HOMEASSISTANT_EVENT_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
)
|
||||
@@ -984,14 +1011,14 @@ async def homeassistant_event_to_code(
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
return await _new_service_call_action(
|
||||
config[CONF_ID],
|
||||
return await _send_service_call(
|
||||
config,
|
||||
action_id,
|
||||
template_arg,
|
||||
args,
|
||||
True,
|
||||
config[CONF_EVENT],
|
||||
*_service_call_fields(config),
|
||||
_service_call_fields(config),
|
||||
)
|
||||
|
||||
|
||||
@@ -1006,7 +1033,7 @@ HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value(
|
||||
|
||||
@automation.register_action(
|
||||
"homeassistant.tag_scanned",
|
||||
HomeAssistantServiceCallAction,
|
||||
automation.ApplyAction,
|
||||
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
)
|
||||
@@ -1016,16 +1043,14 @@ async def homeassistant_tag_scanned_to_code(
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
return await _new_service_call_action(
|
||||
config[CONF_ID],
|
||||
return await _send_service_call(
|
||||
config,
|
||||
action_id,
|
||||
template_arg,
|
||||
args,
|
||||
True,
|
||||
"esphome.tag_scanned",
|
||||
{"tag_id": config[CONF_TAG]},
|
||||
{},
|
||||
{},
|
||||
({"tag_id": config[CONF_TAG]}, {}, {}),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -81,86 +81,102 @@ class ActionResponse {
|
||||
template<typename... Ts> using ActionResponseCallback = std::function<void(const ActionResponse &, Ts...)>;
|
||||
#endif
|
||||
|
||||
/// Called with the request just before it is sent, and the context passed with it.
|
||||
using HomeAssistantPrepareFn = void (*)(HomeassistantActionRequest &, void *);
|
||||
|
||||
/// Builds the request from `fields` and the trigger args, calls `prepare` (when set), then sends it.
|
||||
/// `fields` is a codegen table: the action or event name (no key), then the data, data_template
|
||||
/// and variables entries.
|
||||
template<typename... Ts>
|
||||
void send_homeassistant_fields_with(HomeAssistantPrepareFn prepare, void *context, APIServer *server, bool is_event,
|
||||
const HomeAssistantField<Ts...> *fields, uint8_t data_count,
|
||||
uint8_t data_template_count, uint8_t variables_count,
|
||||
const std::type_identity_t<Ts> &...x) {
|
||||
using Field = HomeAssistantField<Ts...>;
|
||||
const size_t total = 1 + data_count + data_template_count + variables_count;
|
||||
|
||||
// Lambda results must live until the send; the constant strings are encoded from flash where they are
|
||||
size_t lambda_count = 0;
|
||||
for (size_t i = 0; i < total; i++)
|
||||
lambda_count += fields[i].fn != nullptr;
|
||||
FixedVector<std::string> results;
|
||||
results.init(lambda_count);
|
||||
auto string_ref = [](const char *str) { return ProgmemStringRef(str, ESPHOME_strlen_P(str)); };
|
||||
auto value_ref = [&](const Field &field) {
|
||||
if (field.fn == nullptr)
|
||||
return string_ref(field.value);
|
||||
results.push_back(field.fn(x...));
|
||||
return ProgmemStringRef(StringRef(results.back()));
|
||||
};
|
||||
auto fill = [&](FixedVector<HomeassistantServiceMap> &dest, uint8_t count) {
|
||||
dest.init(count);
|
||||
for (uint8_t i = 0; i < count; i++, fields++) {
|
||||
auto &kv = dest.emplace_back();
|
||||
kv.key = string_ref(fields->key);
|
||||
kv.value = value_ref(*fields);
|
||||
}
|
||||
};
|
||||
|
||||
HomeassistantActionRequest resp;
|
||||
resp.service = value_ref(*fields++);
|
||||
resp.is_event = is_event;
|
||||
fill(resp.data, data_count);
|
||||
fill(resp.data_template, data_template_count);
|
||||
fill(resp.variables, variables_count);
|
||||
if (prepare != nullptr)
|
||||
prepare(resp, context);
|
||||
server->send_homeassistant_action(resp);
|
||||
}
|
||||
|
||||
/// Sends an action or event that does not wait for a response; generated actions call it.
|
||||
template<typename... Ts>
|
||||
void send_homeassistant_fields(APIServer *server, bool is_event, const HomeAssistantField<Ts...> *fields,
|
||||
uint8_t data_count, uint8_t data_template_count, uint8_t variables_count,
|
||||
const std::type_identity_t<Ts> &...x) {
|
||||
send_homeassistant_fields_with(nullptr, nullptr, server, is_event, fields, data_count, data_template_count,
|
||||
variables_count, x...);
|
||||
}
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
/// An action whose on_success or on_error automation runs when Home Assistant responds.
|
||||
template<typename... Ts> class HomeAssistantServiceCallAction final : public Action<Ts...> {
|
||||
public:
|
||||
using Field = HomeAssistantField<Ts...>;
|
||||
|
||||
/// `fields` is a codegen table: the action or event name (no key), then the data, data_template
|
||||
/// and variables entries.
|
||||
HomeAssistantServiceCallAction(APIServer *parent, bool is_event, const Field *fields, uint8_t data_count,
|
||||
/// `fields` is a table as for `send_homeassistant_fields_with`.
|
||||
HomeAssistantServiceCallAction(APIServer *parent, const Field *fields, uint8_t data_count,
|
||||
uint8_t data_template_count, uint8_t variables_count)
|
||||
: parent_(parent),
|
||||
fields_(fields),
|
||||
data_count_(data_count),
|
||||
data_template_count_(data_template_count),
|
||||
variables_count_(variables_count) {
|
||||
this->flags_.is_event = is_event;
|
||||
}
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
template<typename T> void set_response_template(T response_template) {
|
||||
this->response_template_ = response_template;
|
||||
this->flags_.has_response_template = true;
|
||||
}
|
||||
|
||||
void set_wants_status() { this->flags_.wants_status = true; }
|
||||
void set_wants_response() { this->flags_.wants_response = true; }
|
||||
variables_count_(variables_count) {}
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||
template<typename T> void set_response_template(T response_template) { this->response_template_ = response_template; }
|
||||
|
||||
void set_wants_response() { this->wants_response_ = true; }
|
||||
|
||||
Trigger<JsonObjectConst, Ts...> *get_success_trigger_with_response() { return &this->success_trigger_with_response_; }
|
||||
#endif
|
||||
Trigger<Ts...> *get_success_trigger() { return &this->success_trigger_; }
|
||||
Trigger<std::string, Ts...> *get_error_trigger() { return &this->error_trigger_; }
|
||||
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
|
||||
void play(const Ts &...x) override {
|
||||
const Field *fields = this->fields_;
|
||||
const size_t total = 1 + this->data_count_ + this->data_template_count_ + this->variables_count_;
|
||||
|
||||
// Lambda results must live until the send; the constant strings are encoded from flash where they are
|
||||
size_t lambda_count = 0;
|
||||
for (size_t i = 0; i < total; i++)
|
||||
lambda_count += fields[i].fn != nullptr;
|
||||
FixedVector<std::string> results;
|
||||
results.init(lambda_count);
|
||||
auto string_ref = [](const char *str) { return ProgmemStringRef(str, ESPHOME_strlen_P(str)); };
|
||||
auto value_ref = [&](const Field &field) {
|
||||
if (field.fn == nullptr)
|
||||
return string_ref(field.value);
|
||||
results.push_back(field.fn(x...));
|
||||
return ProgmemStringRef(StringRef(results.back()));
|
||||
};
|
||||
auto fill = [&](FixedVector<HomeassistantServiceMap> &dest, uint8_t count) {
|
||||
dest.init(count);
|
||||
for (uint8_t i = 0; i < count; i++, fields++) {
|
||||
auto &kv = dest.emplace_back();
|
||||
kv.key = string_ref(fields->key);
|
||||
kv.value = value_ref(*fields);
|
||||
}
|
||||
};
|
||||
|
||||
HomeassistantActionRequest resp;
|
||||
resp.service = value_ref(*fields++);
|
||||
resp.is_event = this->flags_.is_event;
|
||||
fill(resp.data, this->data_count_);
|
||||
fill(resp.data_template, this->data_template_count_);
|
||||
fill(resp.variables, this->variables_count_);
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||
// IMPORTANT: Declare at outer scope so it lives until send_homeassistant_action returns.
|
||||
std::string response_template_value;
|
||||
#endif
|
||||
if (this->flags_.wants_status) {
|
||||
auto prepare = [&](HomeassistantActionRequest &resp) {
|
||||
// Generate a unique call ID for this service call
|
||||
static uint32_t call_id_counter = 1;
|
||||
uint32_t call_id = call_id_counter++;
|
||||
resp.call_id = call_id;
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||
if (this->flags_.wants_response) {
|
||||
if (this->wants_response_) {
|
||||
resp.wants_response = true;
|
||||
// Set response template if provided
|
||||
if (this->flags_.has_response_template) {
|
||||
if (this->response_template_.has_value()) {
|
||||
response_template_value = this->response_template_.value(x...);
|
||||
resp.response_template = StringRef(response_template_value);
|
||||
}
|
||||
@@ -173,7 +189,7 @@ template<typename... Ts> class HomeAssistantServiceCallAction final : public Act
|
||||
[this, &response](auto &&...args) {
|
||||
if (response.is_success()) {
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||
if (this->flags_.wants_response) {
|
||||
if (this->wants_response_) {
|
||||
this->success_trigger_with_response_.trigger(response.get_json(), args...);
|
||||
} else
|
||||
#endif
|
||||
@@ -186,10 +202,12 @@ template<typename... Ts> class HomeAssistantServiceCallAction final : public Act
|
||||
},
|
||||
captured_args);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
this->parent_->send_homeassistant_action(resp);
|
||||
};
|
||||
// One body per Ts, shared with the sends that do not wait for a response
|
||||
send_homeassistant_fields_with(
|
||||
[](HomeassistantActionRequest &resp, void *context) { (*static_cast<decltype(prepare) *>(context))(resp); },
|
||||
&prepare, this->parent_, false, this->fields_, this->data_count_, this->data_template_count_,
|
||||
this->variables_count_, x...);
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -198,22 +216,15 @@ template<typename... Ts> class HomeAssistantServiceCallAction final : public Act
|
||||
uint8_t data_count_;
|
||||
uint8_t data_template_count_;
|
||||
uint8_t variables_count_;
|
||||
struct Flags {
|
||||
uint8_t is_event : 1;
|
||||
uint8_t wants_status : 1;
|
||||
uint8_t wants_response : 1;
|
||||
uint8_t has_response_template : 1;
|
||||
uint8_t reserved : 4;
|
||||
} flags_{0};
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||
bool wants_response_{false};
|
||||
TemplatableValue<std::string, Ts...> response_template_{};
|
||||
Trigger<JsonObjectConst, Ts...> success_trigger_with_response_;
|
||||
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||
Trigger<Ts...> success_trigger_;
|
||||
Trigger<std::string, Ts...> error_trigger_;
|
||||
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
};
|
||||
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
|
||||
} // namespace esphome::api
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from esphome.automation import parent_ref
|
||||
import esphome.codegen as cg
|
||||
from esphome.automation import apply_parent
|
||||
from esphome.components import button, output
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_DURATION, CONF_OUTPUT
|
||||
@@ -14,7 +13,7 @@ CONFIG_SCHEMA = button.button_schema(button.ApplyButton).extend(
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
out = parent_ref(await cg.get_variable(config[CONF_OUTPUT]))
|
||||
out = await apply_parent(config, CONF_OUTPUT)
|
||||
duration = config[CONF_DURATION].total_milliseconds
|
||||
await button.sub_buttons(config).create(
|
||||
config,
|
||||
|
||||
@@ -253,7 +253,7 @@ async def script_execute_action_to_code(config, action_id, template_arg, args):
|
||||
_, script_params = parameters_to_template(params)
|
||||
script_args = await get_ordered_args(config, script_params)
|
||||
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
parent = await automation.apply_parent(config)
|
||||
# Separate statements, because the order of evaluation inside one call is not defined
|
||||
statements = [
|
||||
f"auto esphome__arg_{index} = {automation.immediate_lambda_call(arg)};"
|
||||
@@ -262,7 +262,7 @@ async def script_execute_action_to_code(config, action_id, template_arg, args):
|
||||
names = ", ".join(
|
||||
f"std::move(esphome__arg_{index})" for index in range(len(script_args))
|
||||
)
|
||||
statements.append(f"{automation.parent_ref(paren)}->execute({names});")
|
||||
statements.append(f"{parent}->execute({names});")
|
||||
return automation.apply_statements_action(action_id, template_arg, args, statements)
|
||||
|
||||
|
||||
|
||||
@@ -19,10 +19,12 @@ def test_field_tables(generate_main: Callable[[str | Path], str]) -> None:
|
||||
assert main_cpp.count('PROGMEM = "notify.notify";') == 1
|
||||
assert 'ESPHOME_F("message")' not in main_cpp
|
||||
|
||||
assert main_cpp.count("(api_apiserver_id, false, ha_action_fields, 1, 0, 0);") == 2
|
||||
assert (
|
||||
main_cpp.count("(::api_apiserver_id, false, ::ha_action_fields, 1, 0, 0);") == 2
|
||||
)
|
||||
|
||||
assert "(api_apiserver_id, true, ha_action_fields_2, 1, 0, 0);" in main_cpp
|
||||
assert "(api_apiserver_id, true, ha_action_fields_3, 1, 0, 0);" in main_cpp
|
||||
assert "(::api_apiserver_id, true, ::ha_action_fields_2, 1, 0, 0);" in main_cpp
|
||||
assert "(::api_apiserver_id, true, ::ha_action_fields_3, 1, 0, 0);" in main_cpp
|
||||
|
||||
|
||||
@pytest.mark.parametrize("schema", [DATA_FIELDS_SCHEMA, VARIABLES_FIELDS_SCHEMA])
|
||||
|
||||
@@ -6,6 +6,8 @@ import re
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.component_tests.helpers import static_function_body
|
||||
|
||||
CONFIG = "tests/component_tests/api/test_homeassistant_send.yaml"
|
||||
|
||||
|
||||
@@ -38,24 +40,18 @@ def test_field_order(main_cpp: str) -> None:
|
||||
assert 'progmem_str_6[] PROGMEM = "name";' in main_cpp
|
||||
|
||||
|
||||
SEND = "api::send_homeassistant_fields(::api_apiserver_id, "
|
||||
|
||||
|
||||
def test_action_without_response(main_cpp: str) -> None:
|
||||
"""An action with no response handling is sent with the counts of each map."""
|
||||
assert (
|
||||
"api::HomeAssistantServiceCallAction<>"
|
||||
"(api_apiserver_id, false, ha_action_fields, 1, 1, 1);"
|
||||
) in main_cpp
|
||||
assert f"{SEND}false, ::ha_action_fields, 1, 1, 1);" in main_cpp
|
||||
|
||||
|
||||
def test_event_and_tag_scanned(main_cpp: str) -> None:
|
||||
"""Events and scanned tags are sent as events."""
|
||||
assert (
|
||||
"api::HomeAssistantServiceCallAction<>"
|
||||
"(api_apiserver_id, true, ha_action_fields_5, 0, 0, 0);"
|
||||
) in main_cpp
|
||||
assert (
|
||||
"api::HomeAssistantServiceCallAction<>"
|
||||
"(api_apiserver_id, true, ha_action_fields_6, 1, 0, 0);"
|
||||
) in main_cpp
|
||||
assert f"{SEND}true, ::ha_action_fields_5, 0, 0, 0);" in main_cpp
|
||||
assert f"{SEND}true, ::ha_action_fields_6, 1, 0, 0);" in main_cpp
|
||||
assert " {progmem_str_13, progmem_str_14, nullptr}," in _table(
|
||||
main_cpp, "ha_action_fields_6"
|
||||
)
|
||||
@@ -78,48 +74,47 @@ def test_lambdas_take_args_by_value(main_cpp: str, table: str, body: str) -> Non
|
||||
|
||||
|
||||
def test_script_actions(main_cpp: str) -> None:
|
||||
"""Script actions take the script parameters."""
|
||||
assert (
|
||||
"api::HomeAssistantServiceCallAction<int>"
|
||||
"(api_apiserver_id, false, ha_action_fields_2, 2, 0, 0);"
|
||||
) in main_cpp
|
||||
assert (
|
||||
"api::HomeAssistantServiceCallAction<int>"
|
||||
"(api_apiserver_id, true, ha_action_fields_3, 1, 0, 0);"
|
||||
) in main_cpp
|
||||
"""Script actions pass the script parameters to the field lambdas."""
|
||||
assert f"{SEND}false, ::ha_action_fields_2, 2, 0, 0, level);" in main_cpp
|
||||
assert f"{SEND}true, ::ha_action_fields_3, 1, 0, 0, level);" in main_cpp
|
||||
|
||||
|
||||
def test_action_with_response_is_an_object(main_cpp: str) -> None:
|
||||
"""on_error waits for the response, so the action keeps its object and trigger."""
|
||||
match = re.search(
|
||||
r"new\((\w+)\) api::HomeAssistantServiceCallAction<>"
|
||||
r"\(api_apiserver_id, false, ha_action_fields_4, 0, 0, 0\);",
|
||||
r"\(api_apiserver_id, ha_action_fields_4, 0, 0, 0\);",
|
||||
main_cpp,
|
||||
)
|
||||
assert match is not None
|
||||
action = match.group(1)
|
||||
assert f"{action}->set_wants_status();" in main_cpp
|
||||
assert f"Automation<std::string>({action}->get_error_trigger());" in main_cpp
|
||||
|
||||
|
||||
def test_action_then_log(main_cpp: str) -> None:
|
||||
"""The action sends before the action after it runs."""
|
||||
actions = re.findall(r"\bautomation_id->add_action\((\w+)\);", main_cpp)
|
||||
assert actions == ["api_homeassistantservicecallaction_id", "lambdaaction_id"]
|
||||
"""The send is an if-free statement, so the action merged after it still runs."""
|
||||
assert re.findall(r"\bautomation_id->add_action\((\w+)\);", main_cpp) == [
|
||||
"applyaction_id"
|
||||
]
|
||||
assert "ApplyRunAction<esphome__applyaction_id__fn>();" in main_cpp
|
||||
body = static_function_body(main_cpp, "esphome__applyaction_id__fn")
|
||||
assert body.index(f"{SEND}false, ::ha_action_fields,") < body.index(
|
||||
'"after action"'
|
||||
)
|
||||
# The only return is the stop check between the two actions
|
||||
assert body.count("return;") == 1
|
||||
|
||||
|
||||
def test_button_on_press(main_cpp: str) -> None:
|
||||
"""A button sends its event and scanned tag from its press callback."""
|
||||
assert re.search(
|
||||
r"add_on_press_callback\(TriggerForwarder<>\{automation_id_5\}\);", main_cpp
|
||||
"""A press callback that only sends calls one generated function, with no objects."""
|
||||
match = re.search(
|
||||
r"add_on_press_callback\(\[\]\(\) -> void \{\n (\w+)\(\);\n\}\);",
|
||||
main_cpp,
|
||||
)
|
||||
sends = {
|
||||
match.group(2): match.group(1)
|
||||
for match in re.finditer(
|
||||
r"new\((\w+)\) api::HomeAssistantServiceCallAction<>"
|
||||
r"\(api_apiserver_id, true, (ha_action_fields_[56]), [01], 0, 0\);",
|
||||
main_cpp,
|
||||
)
|
||||
}
|
||||
actions = re.findall(r"\bautomation_id_5->add_action\((\w+)\);", main_cpp)
|
||||
assert actions == [sends["ha_action_fields_5"], sends["ha_action_fields_6"]]
|
||||
assert match is not None
|
||||
body = static_function_body(main_cpp, match.group(1))
|
||||
assert body.index(f"{SEND}true, ::ha_action_fields_5, 0, 0, 0);") < body.index(
|
||||
f"{SEND}true, ::ha_action_fields_6, 1, 0, 0);"
|
||||
)
|
||||
assert "TriggerForwarder" not in main_cpp
|
||||
assert "Automation<>()" not in main_cpp
|
||||
@@ -31,6 +31,17 @@ def extract_packed_value(main_cpp: str, var_name: str) -> int:
|
||||
return int(match.group(1))
|
||||
|
||||
|
||||
def static_function_body(main_cpp: str, name: str) -> str:
|
||||
"""Body of the generated ``static void name(...)`` function, without its braces."""
|
||||
match = re.search(
|
||||
rf"static void {re.escape(name)}\([^;{{]*\) \{{\n(.*?)\n\}}",
|
||||
main_cpp,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert match is not None, f"{name} not defined"
|
||||
return match.group(1)
|
||||
|
||||
|
||||
def get_define_value(name: str) -> str | None:
|
||||
"""Rendered value of a CORE define, or None when absent.
|
||||
|
||||
|
||||
@@ -12,19 +12,11 @@ import re
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.component_tests.helpers import static_function_body
|
||||
|
||||
YAML = Path(__file__).parent / "button_dispatch.yaml"
|
||||
|
||||
|
||||
def _fn_body(main_cpp: str, fn: str) -> str:
|
||||
match = re.search(
|
||||
rf"static void {fn}\(button::Button \* entity\) \{{\n(.*?)\n\}}",
|
||||
main_cpp,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert match is not None, f"{fn} not defined"
|
||||
return match.group(1)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("fn", "type_expr", "hub"),
|
||||
[
|
||||
@@ -58,7 +50,7 @@ def test_dispatch_function(
|
||||
main_cpp = generate_main(YAML)
|
||||
|
||||
assert main_cpp.count(f"static void {fn}(button::Button * entity) {{") == 1
|
||||
assert _fn_body(main_cpp, fn) == (
|
||||
assert static_function_body(main_cpp, fn) == (
|
||||
f" const auto type = {type_expr};\n ::{hub}->press_button(type);"
|
||||
)
|
||||
|
||||
|
||||
Reference in new issue
Block a user