Merge remote-tracking branch 'upstream/dev' into integration

This commit is contained in:
J. Nick Koston
2026-05-03 18:20:20 -05:00
4 changed files with 105 additions and 12 deletions
+33 -12
View File
@@ -72,17 +72,35 @@ APIUnregisterServiceCallAction = api_ns.class_(
UserServiceTrigger = api_ns.class_("UserServiceTrigger", automation.Trigger)
ListEntitiesServicesArgument = api_ns.class_("ListEntitiesServicesArgument")
SERVICE_ARG_NATIVE_TYPES: dict[str, MockObj] = {
# Owning element type for each YAML service variable type. Used to derive both
# the zero-copy native types and the owning fallback types below.
_SERVICE_ARG_SCALAR_TYPES: dict[str, MockObj] = {
"bool": cg.bool_,
"int": cg.int32,
"float": cg.float_,
"string": cg.std_string,
}
SERVICE_ARG_NATIVE_TYPES: dict[str, MockObj] = {
# Scalars are passed by value; string uses a non-owning view into rx_buf_.
**_SERVICE_ARG_SCALAR_TYPES,
"string": cg.StringRef,
"bool[]": cg.FixedVector.template(cg.bool_).operator("const").operator("ref"),
"int[]": cg.FixedVector.template(cg.int32).operator("const").operator("ref"),
"float[]": cg.FixedVector.template(cg.float_).operator("const").operator("ref"),
"string[]": cg.FixedVector.template(cg.std_string)
.operator("const")
.operator("ref"),
# Arrays are passed as non-owning const references into rx_buf_.
**{
f"{name}[]": cg.FixedVector.template(t).operator("const").operator("ref")
for name, t in _SERVICE_ARG_SCALAR_TYPES.items()
},
}
# Owning fallback types used when the action chain contains non-synchronous actions
# (delay, wait_until, script.wait, etc.). The default non-owning types reference
# storage in the receive buffer, which is reused once the synchronous portion of
# the chain returns. FixedVector is also non-copyable, so the deferred lambda
# capture in DelayAction::play_complex would fail to compile.
SERVICE_ARG_FALLBACK_TYPES: dict[str, MockObj] = {
"string": cg.std_string,
**{
f"{name}[]": cg.std_vector.template(t)
for name, t in _SERVICE_ARG_SCALAR_TYPES.items()
},
}
CONF_ENCRYPTION = "encryption"
CONF_BATCH_DELAY = "batch_delay"
@@ -381,17 +399,20 @@ async def to_code(config: ConfigType) -> None:
func_args.append((cg.bool_, "return_response"))
# Check if action chain has non-synchronous actions that would make
# non-owning StringRef dangle (rx_buf_ reused after delay)
# non-owning args (StringRef, const FixedVector&) dangle once the
# rx_buf_ is reused after a delay/wait_until/script.wait/etc. The
# FixedVector references would also fail to compile because they
# are non-copyable and DelayAction captures args by value.
has_non_synchronous = automation.has_non_synchronous_actions(
conf.get(CONF_THEN, [])
)
service_arg_names: list[str] = []
for name, var_ in conf[CONF_VARIABLES].items():
native = SERVICE_ARG_NATIVE_TYPES[var_]
# Fall back to std::string for string args if non-synchronous actions exist
if has_non_synchronous and native is cg.StringRef:
native = cg.std_string
if has_non_synchronous and var_ in SERVICE_ARG_FALLBACK_TYPES:
native = SERVICE_ARG_FALLBACK_TYPES[var_]
else:
native = SERVICE_ARG_NATIVE_TYPES[var_]
service_template_args.append(native)
func_args.append((native, name))
service_arg_names.append(name)
@@ -88,6 +88,33 @@ int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) {
this->write_array(buffer, buffer_size);
App.feed_wdt();
this->recv_ret_string_(recv_string, NEXTION_UPLOAD_ACK_TIMEOUT_MS, true);
// Some Nextion firmware variants (notably bootloader/recovery mode on panels
// with no installed TFT) emit the 5-byte 0x08+position fast-mode ack with a
// multi-second gap between the leading 0x08 byte and the 4 trailing position
// bytes. recv_ret_string_ returns after the first byte; manually drain the
// trailing bytes from the UART before continuing.
if (!recv_string.empty() && recv_string[0] == 0x08 && recv_string.size() < 5) {
const uint32_t deadline = millis() + NEXTION_UPLOAD_ACK_TIMEOUT_MS;
while (recv_string.size() < 5 && millis() < deadline) {
if (this->available()) {
uint8_t b = 0;
if (this->read_byte(&b)) {
recv_string.push_back(static_cast<char>(b));
}
} else {
delay(5); // NOLINT
App.feed_wdt();
}
}
if (recv_string.size() < 5) {
ESP_LOGE(TAG, "Truncated 0x08 response: got %zu bytes within %" PRIu32 "ms", recv_string.size(),
NEXTION_UPLOAD_ACK_TIMEOUT_MS);
allocator.deallocate(buffer, 4096);
buffer = nullptr;
return -1;
}
}
this->content_length_ -= read_len;
const float upload_percentage = 100.0f * (this->tft_size_ - this->content_length_) / this->tft_size_;
ESP_LOGD(TAG, "Upload: %0.2f%% (%" PRIu32 " left, heap: %" PRIu32 ")", upload_percentage, this->content_length_,
@@ -104,6 +104,33 @@ int Nextion::upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &r
this->write_array(buffer, buffer_size);
App.feed_wdt();
this->recv_ret_string_(recv_string, NEXTION_UPLOAD_ACK_TIMEOUT_MS, true);
// Some Nextion firmware variants (notably bootloader/recovery mode on panels
// with no installed TFT) emit the 5-byte 0x08+position fast-mode ack with a
// multi-second gap between the leading 0x08 byte and the 4 trailing position
// bytes. recv_ret_string_ returns after the first byte; manually drain the
// trailing bytes from the UART before continuing.
if (!recv_string.empty() && recv_string[0] == 0x08 && recv_string.size() < 5) {
const uint32_t deadline = millis() + NEXTION_UPLOAD_ACK_TIMEOUT_MS;
while (recv_string.size() < 5 && millis() < deadline) {
if (this->available()) {
uint8_t b = 0;
if (this->read_byte(&b)) {
recv_string.push_back(static_cast<char>(b));
}
} else {
vTaskDelay(pdMS_TO_TICKS(5)); // NOLINT
App.feed_wdt();
}
}
if (recv_string.size() < 5) {
ESP_LOGE(TAG, "Truncated 0x08 response: got %zu bytes within %" PRIu32 "ms", recv_string.size(),
NEXTION_UPLOAD_ACK_TIMEOUT_MS);
allocator.deallocate(buffer, 4096);
buffer = nullptr;
return -1;
}
}
this->content_length_ -= read_len;
const float upload_percentage = 100.0f * (this->tft_size_ - this->content_length_) / this->tft_size_;
#ifdef USE_PSRAM
+18
View File
@@ -91,6 +91,24 @@ api:
- float_arr.size()
- string_arr[0].c_str()
- string_arr.size()
# Test array + string args used after a non-synchronous action (delay).
# The default non-owning types (StringRef, const FixedVector&) would
# dangle once rx_buf_ is reused, and FixedVector is non-copyable so
# DelayAction's lambda capture would fail to compile. The api codegen
# must fall back to owning std::string / std::vector here.
- action: array_with_delay
variables:
name: string
int_arr: int[]
string_arr: string[]
then:
- delay: 20ms
- logger.log:
format: "Delayed: %s (%u ints, %u strings)"
args:
- name.c_str()
- int_arr.size()
- string_arr.size()
# Test ContinuationAction (IfAction with then/else branches)
- action: test_if_action
variables: