Merge remote-tracking branch 'upstream/dev' into proto-byte-buffer

# Conflicts:
#	esphome/components/api/proto.h
This commit is contained in:
J. Nick Koston
2026-03-07 22:11:15 -10:00
17 changed files with 481 additions and 39 deletions
+1
View File
@@ -587,6 +587,7 @@ esphome/components/xl9535/* @mreditor97
esphome/components/xpt2046/touchscreen/* @nielsnl68 @numo68
esphome/components/xxtea/* @clydebarrow
esphome/components/zephyr/* @tomaszduda23
esphome/components/zephyr_mcumgr/ota/* @tomaszduda23
esphome/components/zhlt01/* @cfeenstra1024
esphome/components/zigbee/* @tomaszduda23
esphome/components/zio_ultrasonic/* @kahrendt
+12
View File
@@ -8,6 +8,18 @@ namespace esphome::api {
static const char *const TAG = "api.proto";
uint32_t ProtoSize::varint_slow(uint32_t value) { return varint_wide(value); }
void ProtoWriteBuffer::encode_varint_raw_slow_(uint32_t value) {
do {
this->debug_check_bounds_(1);
*this->pos_++ = static_cast<uint8_t>(value | 0x80);
value >>= 7;
} while (value > 0x7F);
this->debug_check_bounds_(1);
*this->pos_++ = static_cast<uint8_t>(value);
}
#ifdef USE_API_VARINT64
optional<ProtoVarInt> ProtoVarInt::parse_wide(const uint8_t *buffer, uint32_t len, uint32_t *consumed,
uint32_t result32) {
+29 -22
View File
@@ -240,14 +240,13 @@ class ProtoWriteBuffer {
public:
ProtoWriteBuffer(APIBuffer *buffer) : buffer_(buffer), pos_(buffer->data() + buffer->size()) {}
ProtoWriteBuffer(APIBuffer *buffer, size_t write_pos) : buffer_(buffer), pos_(buffer->data() + write_pos) {}
void encode_varint_raw(uint32_t value) {
while (value > 0x7F) {
inline void ESPHOME_ALWAYS_INLINE encode_varint_raw(uint32_t value) {
if (value < 128) [[likely]] {
this->debug_check_bounds_(1);
*this->pos_++ = static_cast<uint8_t>(value | 0x80);
value >>= 7;
*this->pos_++ = static_cast<uint8_t>(value);
return;
}
this->debug_check_bounds_(1);
*this->pos_++ = static_cast<uint8_t>(value);
this->encode_varint_raw_slow_(value);
}
void encode_varint_raw_64(uint64_t value) {
while (value > 0x7F) {
@@ -378,6 +377,9 @@ class ProtoWriteBuffer {
APIBuffer *get_buffer() const { return buffer_; }
protected:
// Slow path for encode_varint_raw values >= 128, outlined to keep fast path small
void encode_varint_raw_slow_(uint32_t value) __attribute__((noinline));
#ifdef ESPHOME_DEBUG_API
void debug_check_bounds_(size_t bytes, const char *caller = __builtin_FUNCTION());
void debug_check_encode_size_(uint32_t field_id, uint32_t expected, ptrdiff_t actual);
@@ -511,24 +513,29 @@ class ProtoSize {
* @param value The uint32_t value to calculate size for
* @return The number of bytes needed to encode the value
*/
static constexpr uint32_t varint(uint32_t value) {
// Optimized varint size calculation using leading zeros
// Each 7 bits requires one byte in the varint encoding
if (value < 128)
return 1; // 7 bits, common case for small values
// For larger values, count bytes needed based on the position of the highest bit set
if (value < 16384) {
return 2; // 14 bits
} else if (value < 2097152) {
return 3; // 21 bits
} else if (value < 268435456) {
return 4; // 28 bits
} else {
return 5; // 32 bits (maximum for uint32_t)
}
static constexpr inline uint32_t ESPHOME_ALWAYS_INLINE varint(uint32_t value) {
if (value < 128) [[likely]]
return 1; // Fast path: 7 bits, most common case
if (__builtin_is_constant_evaluated())
return varint_wide(value);
return varint_slow(value);
}
private:
// Slow path for varint >= 128, outlined to keep fast path small
static uint32_t varint_slow(uint32_t value) __attribute__((noinline));
// Shared cascade for values >= 128 (used by both constexpr and noinline paths)
static constexpr inline uint32_t ESPHOME_ALWAYS_INLINE varint_wide(uint32_t value) {
if (value < 16384)
return 2;
if (value < 2097152)
return 3;
if (value < 268435456)
return 4;
return 5;
}
public:
/**
* @brief Calculates the size in bytes needed to encode a uint64_t value as a varint
*
+43 -3
View File
@@ -27,10 +27,15 @@ from esphome.components.zephyr.const import (
)
import esphome.config_validation as cv
from esphome.const import (
CONF_ADVANCED,
CONF_BOARD,
CONF_DISABLED,
CONF_ENABLE_OTA_ROLLBACK,
CONF_FRAMEWORK,
CONF_ID,
CONF_OTA,
CONF_RESET_PIN,
CONF_SAFE_MODE,
CONF_VERSION,
CONF_VOLTAGE,
KEY_CORE,
@@ -41,6 +46,7 @@ from esphome.const import (
ThreadModel,
)
from esphome.core import CORE, CoroPriority, EsphomeError, coroutine_with_priority
import esphome.final_validate as fv
from esphome.storage_json import StorageJSON
from esphome.types import ConfigType
@@ -133,6 +139,7 @@ CONF_UICR_ERASE = "uicr_erase"
VOLTAGE_LEVELS = [1.8, 2.1, 2.4, 2.7, 3.0, 3.3]
CONFIG_SCHEMA = cv.All(
_detect_bootloader,
set_core_data,
@@ -156,9 +163,19 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_UICR_ERASE, default=False): cv.boolean,
}
),
cv.Optional(CONF_FRAMEWORK, default={CONF_VERSION: "2.6.1-a"}): cv.Schema(
cv.Optional(
CONF_FRAMEWORK,
default={},
): cv.Schema(
{
cv.Required(CONF_VERSION): cv.string_strict,
cv.Optional(CONF_VERSION, default="2.6.1-a"): cv.string_strict,
cv.Optional(CONF_ADVANCED, default={}): cv.Schema(
{
cv.Optional(
CONF_ENABLE_OTA_ROLLBACK, default=True
): cv.boolean,
}
),
}
),
cv.GenerateID(CONF_CDC_ACM): cv.declare_id(CdcAcm),
@@ -181,6 +198,24 @@ def _final_validate(config):
_LOGGER.warning(
"Selected generic Adafruit bootloader. The board might crash. Consider settings `bootloader:`"
)
full_config = fv.full_config.get()
conf = config[CONF_FRAMEWORK]
advanced = conf[CONF_ADVANCED]
if advanced[CONF_ENABLE_OTA_ROLLBACK]:
# "disabled: false" means safe mode *is* enabled.
safe_mode_config = full_config.get(CONF_SAFE_MODE, {CONF_DISABLED: True})
safe_mode_enabled = not safe_mode_config[CONF_DISABLED]
ota_enabled = CONF_OTA in full_config
# Both need to be enabled for rollback to work
if not (ota_enabled and safe_mode_enabled):
# But only warn if ota is even possible
if ota_enabled:
_LOGGER.warning(
"OTA rollback requires safe_mode, disabling rollback support"
)
# disable the rollback feature anyway since it can't be used.
advanced[CONF_ENABLE_OTA_ROLLBACK] = False
FINAL_VALIDATE_SCHEMA = _final_validate
@@ -247,6 +282,11 @@ async def to_code(config: ConfigType) -> None:
if reg0_config[CONF_UICR_ERASE]:
cg.add_define("USE_NRF52_UICR_ERASE")
conf = config[CONF_FRAMEWORK]
advanced = conf[CONF_ADVANCED]
# Enable OTA rollback support
if advanced[CONF_ENABLE_OTA_ROLLBACK]:
cg.add_define("USE_OTA_ROLLBACK")
# c++ support
if framework_ver < cv.Version(2, 9, 2):
zephyr_add_prj_conf("CPLUSPLUS", True)
@@ -259,7 +299,7 @@ async def to_code(config: ConfigType) -> None:
zephyr_add_prj_conf("WDT_DISABLE_AT_BOOT", False)
# disable console
zephyr_add_prj_conf("UART_CONSOLE", False)
zephyr_add_prj_conf("CONSOLE", False)
zephyr_add_prj_conf("CONSOLE", False, False)
# use NFC pins as GPIO
if framework_ver < cv.Version(2, 9, 2):
zephyr_add_prj_conf("NFCT_PINS_AS_GPIOS", True)
+8 -1
View File
@@ -18,7 +18,14 @@ from esphome.coroutine import CoroPriority
OTA_STATE_LISTENER_KEY = "ota_state_listener"
CODEOWNERS = ["@esphome/core"]
AUTO_LOAD = ["md5", "safe_mode"]
def AUTO_LOAD() -> list[str]:
components = ["safe_mode"]
if not CORE.using_zephyr:
components.extend(["md5"])
return components
IS_PLATFORM_COMPONENT = True
+13 -2
View File
@@ -9,10 +9,14 @@
#include <cinttypes>
#include <cstdio>
#if defined(USE_ESP32) && defined(USE_OTA_ROLLBACK)
#ifdef USE_OTA_ROLLBACK
#ifdef USE_ZEPHYR
#include <zephyr/dfu/mcuboot.h>
#elif defined(USE_ESP32)
#include <esp_ota_ops.h>
#include <esp_system.h>
#endif
#endif
namespace esphome::safe_mode {
@@ -66,9 +70,16 @@ float SafeModeComponent::get_setup_priority() const { return setup_priority::AFT
void SafeModeComponent::mark_successful() {
this->clean_rtc();
this->boot_successful_ = true;
#if defined(USE_ESP32) && defined(USE_OTA_ROLLBACK)
#if defined(USE_OTA_ROLLBACK)
// Mark OTA partition as valid to prevent rollback
#if defined(USE_ZEPHYR)
if (!boot_is_img_confirmed()) {
boot_write_img_confirmed();
}
#elif defined(USE_ESP32)
// Mark OTA partition as valid to prevent rollback
esp_ota_mark_app_valid_cancel_rollback();
#endif
#endif
// Disable loop since we no longer need to check
this->disable_loop();
+5 -1
View File
@@ -1,7 +1,7 @@
import esphome.codegen as cg
from esphome.components import socket
from esphome.components.const import CONF_DATA_BITS, CONF_PARITY, CONF_STOP_BITS
from esphome.components.uart import CONF_DEBUG_PREFIX, UARTComponent
from esphome.components.uart import CONF_DEBUG_PREFIX, CONF_FLUSH_TIMEOUT, UARTComponent
from esphome.components.usb_host import register_usb_client, usb_device_schema
import esphome.config_validation as cv
from esphome.const import (
@@ -91,6 +91,9 @@ def channel_schema(channels, baud_rate_required):
cv.Optional(CONF_DUMMY_RECEIVER, default=False): cv.boolean,
cv.Optional(CONF_DEBUG, default=False): cv.boolean,
cv.Optional(CONF_DEBUG_PREFIX, default=""): cv.string,
cv.Optional(
CONF_FLUSH_TIMEOUT, default="100ms"
): cv.positive_time_period_milliseconds,
}
)
),
@@ -129,6 +132,7 @@ async def to_code(config):
cg.add(chvar.set_parity(channel[CONF_PARITY]))
cg.add(chvar.set_baud_rate(channel[CONF_BAUD_RATE]))
cg.add(chvar.set_dummy_receiver(channel[CONF_DUMMY_RECEIVER]))
cg.add(chvar.set_flush_timeout(channel[CONF_FLUSH_TIMEOUT]))
cg.add(chvar.set_debug(channel[CONF_DEBUG]))
if channel[CONF_DEBUG_PREFIX]:
cg.add(chvar.set_debug_prefix(channel[CONF_DEBUG_PREFIX]))
+6 -4
View File
@@ -169,10 +169,10 @@ void USBUartChannel::write_array(const uint8_t *data, size_t len) {
uart::FlushResult USBUartChannel::flush() {
// Spin until the output queue is drained and the last USB transfer completes.
// Safe to call from the main loop only.
// The 100 ms timeout guards against a device that stops responding mid-flush;
// The flush_timeout_ms_ timeout guards against a device that stops responding mid-flush;
// in that case the main loop is blocked for the full duration.
uint32_t start = millis(); // 100 ms safety timeout
while ((!this->output_queue_.empty() || this->output_started_.load()) && millis() - start < 100) {
uint32_t start = millis();
while ((!this->output_queue_.empty() || this->output_started_.load()) && millis() - start < this->flush_timeout_ms_) {
// Kick start_output() in case data arrived but no transfer is in flight yet.
this->parent_->start_output(this);
yield();
@@ -260,10 +260,12 @@ void USBUartComponent::dump_config() {
" Data Bits: %u\n"
" Parity: %s\n"
" Stop bits: %s\n"
" Flush Timeout: %" PRIu32 " ms\n"
" Debug: %s\n"
" Dummy receiver: %s",
channel->index_, channel->baud_rate_, channel->data_bits_, PARITY_NAMES[channel->parity_],
STOP_BITS_NAMES[channel->stop_bits_], YESNO(channel->debug_), YESNO(channel->dummy_receiver_));
STOP_BITS_NAMES[channel->stop_bits_], channel->flush_timeout_ms_, YESNO(channel->debug_),
YESNO(channel->dummy_receiver_));
}
}
void USBUartComponent::start_input(USBUartChannel *channel) {
+6 -5
View File
@@ -116,6 +116,7 @@ class USBUartChannel : public uart::UARTComponent, public Parented<USBUartCompon
void set_debug(bool debug) { this->debug_ = debug; }
void set_dummy_receiver(bool dummy_receiver) { this->dummy_receiver_ = dummy_receiver; }
void set_debug_prefix(const char *prefix) { this->debug_prefix_ = StringRef(prefix); }
void set_flush_timeout(uint32_t flush_timeout_ms) override { this->flush_timeout_ms_ = flush_timeout_ms; }
/// Register a callback invoked immediately after data is pushed to the input ring buffer.
/// Called from USBUartComponent::loop() in the main loop context.
@@ -124,23 +125,23 @@ class USBUartChannel : public uart::UARTComponent, public Parented<USBUartCompon
void set_rx_callback(std::function<void()> cb) { this->rx_callback_ = std::move(cb); }
protected:
// Larger structures first for better alignment
// Larger structures first (8+ bytes)
RingBuffer input_buffer_;
LockFreeQueue<UsbOutputChunk, USB_OUTPUT_CHUNK_COUNT> output_queue_;
EventPool<UsbOutputChunk, USB_OUTPUT_CHUNK_COUNT> output_pool_;
std::function<void()> rx_callback_{};
CdcEps cdc_dev_{};
// Enum (likely 4 bytes)
StringRef debug_prefix_{};
// 4-byte fields
UARTParityOptions parity_{UART_CONFIG_PARITY_NONE};
// Group atomics together (each 1 byte)
uint32_t flush_timeout_ms_{100};
// 1-byte fields (no padding between groups)
std::atomic<bool> input_started_{true};
std::atomic<bool> output_started_{true};
std::atomic<bool> initialised_{false};
// Group regular bytes together to minimize padding
const uint8_t index_;
bool debug_{};
bool dummy_receiver_{};
StringRef debug_prefix_{};
};
class USBUartComponent : public usb_host::USBClient {
@@ -0,0 +1,141 @@
import esphome.codegen as cg
from esphome.components.ota import BASE_OTA_SCHEMA, OTAComponent, ota_to_code
from esphome.components.zephyr import (
zephyr_add_cdc_acm,
zephyr_add_overlay,
zephyr_add_prj_conf,
zephyr_data,
)
from esphome.components.zephyr.const import BOOTLOADER_MCUBOOT, KEY_BOOTLOADER
import esphome.config_validation as cv
from esphome.const import CONF_HARDWARE_UART, CONF_ID, Framework
from esphome.core import CORE, coroutine_with_priority
from esphome.coroutine import CoroPriority
from esphome.types import ConfigType
CODEOWNERS = ["@tomaszduda23"]
DEPENDENCIES = ["zephyr"]
ZephyrMcumgrOTAComponent = cg.esphome_ns.namespace("zephyr_mcumgr").class_(
"OTAComponent", OTAComponent
)
CONF_BLE = "ble"
CONF_TRANSPORT = "transport"
def _validate_transport(conf: ConfigType) -> ConfigType:
transport = conf[CONF_TRANSPORT]
if transport[CONF_BLE] or CONF_HARDWARE_UART in transport:
return conf
raise cv.Invalid(
f"At least one transport protocol has to be enabled. Set '{CONF_BLE}: true' or '{CONF_HARDWARE_UART}'"
)
UARTS = {
"CDC": ("cdc_acm_uart0", 0),
"CDC1": ("cdc_acm_uart1", 1),
"UART0": ("uart0", -1),
"UART1": ("uart1", -1),
}
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(ZephyrMcumgrOTAComponent),
cv.Optional(CONF_TRANSPORT, default={CONF_BLE: True}): cv.Schema(
{
cv.Optional(CONF_BLE, default=False): cv.boolean,
cv.Optional(
CONF_HARDWARE_UART,
): cv.one_of(*UARTS, upper=True),
}
),
}
)
.extend(BASE_OTA_SCHEMA)
.extend(cv.COMPONENT_SCHEMA),
_validate_transport,
cv.only_with_framework(Framework.ZEPHYR),
)
def _validate_mcumgr_bootloader(config: ConfigType) -> None:
bootloader = zephyr_data()[KEY_BOOTLOADER]
if bootloader != BOOTLOADER_MCUBOOT:
raise cv.Invalid(f"'{bootloader}' bootloader does not support OTA")
KEY_ZEPHYR_BLE_SERVER = "zephyr_ble_server"
def _validate_ble_server(config: ConfigType) -> None:
if (
config[CONF_TRANSPORT][CONF_BLE]
and KEY_ZEPHYR_BLE_SERVER not in CORE.loaded_integrations
):
raise cv.Invalid(f"'{KEY_ZEPHYR_BLE_SERVER}' component is required for BLE OTA")
def _final_validate(config: ConfigType) -> None:
_validate_mcumgr_bootloader(config)
_validate_ble_server(config)
FINAL_VALIDATE_SCHEMA = _final_validate
@coroutine_with_priority(CoroPriority.OTA_UPDATES)
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await ota_to_code(var, config)
await cg.register_component(var, config)
zephyr_add_prj_conf("NET_BUF", True)
zephyr_add_prj_conf("ZCBOR", True)
zephyr_add_prj_conf("MCUMGR", True)
zephyr_add_prj_conf("MCUMGR_GRP_IMG", True)
zephyr_add_prj_conf("IMG_MANAGER", True)
zephyr_add_prj_conf("STREAM_FLASH", True)
zephyr_add_prj_conf("FLASH_MAP", True)
zephyr_add_prj_conf("FLASH", True)
zephyr_add_prj_conf("IMG_ERASE_PROGRESSIVELY", True)
zephyr_add_prj_conf("BOOTLOADER_MCUBOOT", True)
zephyr_add_prj_conf("MCUMGR_MGMT_NOTIFICATION_HOOKS", True)
zephyr_add_prj_conf("MCUMGR_GRP_IMG_STATUS_HOOKS", True)
zephyr_add_prj_conf("MCUMGR_GRP_IMG_UPLOAD_CHECK_HOOK", True)
transport = config[CONF_TRANSPORT]
if transport[CONF_BLE]:
zephyr_add_prj_conf("MCUMGR_TRANSPORT_BT", True)
zephyr_add_prj_conf("MCUMGR_TRANSPORT_BT_REASSEMBLY", True)
zephyr_add_prj_conf("MCUMGR_GRP_OS", True)
zephyr_add_prj_conf("MCUMGR_GRP_OS_MCUMGR_PARAMS", True)
zephyr_add_prj_conf("NCS_SAMPLE_MCUMGR_BT_OTA_DFU_SPEEDUP", True)
if CONF_HARDWARE_UART in transport:
uart = UARTS[transport[CONF_HARDWARE_UART]]
uart_name = uart[0]
cdc_id = uart[1]
if cdc_id >= 0:
zephyr_add_cdc_acm(config, cdc_id)
zephyr_add_prj_conf("MCUMGR_TRANSPORT_UART", True)
zephyr_add_prj_conf("BASE64", True)
zephyr_add_prj_conf("CONSOLE", True)
zephyr_add_overlay(
f"""
/ {{
chosen {{
zephyr,uart-mcumgr = &{uart_name};
}};
}};
"""
)
@@ -0,0 +1,143 @@
#ifdef USE_ZEPHYR
#include "ota_zephyr_mcumgr.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include <zephyr/sys/math_extras.h>
#include <zephyr/usb/usb_device.h>
#include <zephyr/dfu/mcuboot.h>
// It should be from below header but there is problem with internal includes.
// #include <zephyr/mgmt/mcumgr/grp/img_mgmt/img_mgmt.h>
// NOLINTBEGIN(readability-identifier-naming,google-runtime-int)
struct img_mgmt_upload_action {
/** The total size of the image. */
unsigned long long size;
};
struct img_mgmt_upload_req {
uint32_t image; /* 0 by default */
size_t off; /* SIZE_MAX if unspecified */
};
// NOLINTEND(readability-identifier-naming,google-runtime-int)
namespace esphome::zephyr_mcumgr {
static_assert(sizeof(struct img_mgmt_upload_action) == 8, "ABI mismatch");
static_assert(sizeof(struct img_mgmt_upload_req) == 8, "ABI mismatch");
static_assert(offsetof(struct img_mgmt_upload_req, image) == 0, "ABI mismatch");
static_assert(offsetof(struct img_mgmt_upload_req, off) == 4, "ABI mismatch");
static const char *const TAG = "zephyr_mcumgr";
static OTAComponent *global_ota_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static enum mgmt_cb_return mcumgr_img_mgmt_cb(uint32_t event, enum mgmt_cb_return prev_status, int32_t *rc,
uint16_t *group, bool *abort_more, void *data, size_t data_size) {
if (MGMT_EVT_OP_IMG_MGMT_DFU_CHUNK == event) {
const img_mgmt_upload_check &upload = *static_cast<img_mgmt_upload_check *>(data);
global_ota_component->update_chunk(upload);
} else if (MGMT_EVT_OP_IMG_MGMT_DFU_STARTED == event) {
global_ota_component->update_started();
} else if (MGMT_EVT_OP_IMG_MGMT_DFU_CHUNK_WRITE_COMPLETE == event) {
global_ota_component->update_chunk_wrote();
} else if (MGMT_EVT_OP_IMG_MGMT_DFU_PENDING == event) {
global_ota_component->update_pending();
} else if (MGMT_EVT_OP_IMG_MGMT_DFU_STOPPED == event) {
global_ota_component->update_stopped();
} else {
ESP_LOGD(TAG, "MCUmgr Image Management Event with the %d ID", u32_count_trailing_zeros(MGMT_EVT_GET_ID(event)));
}
return MGMT_CB_OK;
}
OTAComponent::OTAComponent() { global_ota_component = this; }
void OTAComponent::setup() {
this->img_mgmt_callback_.callback = mcumgr_img_mgmt_cb;
this->img_mgmt_callback_.event_id = MGMT_EVT_OP_IMG_MGMT_ALL;
mgmt_callback_register(&this->img_mgmt_callback_);
#ifdef CONFIG_USB_DEVICE_STACK
usb_enable(nullptr);
#endif
// Handle OTA rollback: mark partition valid immediately unless USE_OTA_ROLLBACK is enabled,
// in which case safe_mode will mark it valid after confirming successful boot.
#ifndef USE_OTA_ROLLBACK
if (!boot_is_img_confirmed()) {
boot_write_img_confirmed();
}
#endif
}
#ifdef ESPHOME_LOG_HAS_CONFIG
static const char *swap_type_str(uint8_t type) {
switch (type) {
case BOOT_SWAP_TYPE_NONE:
return "none";
case BOOT_SWAP_TYPE_TEST:
return "test";
case BOOT_SWAP_TYPE_PERM:
return "perm";
case BOOT_SWAP_TYPE_REVERT:
return "revert";
case BOOT_SWAP_TYPE_FAIL:
return "fail";
}
return "unknown";
}
#endif
void OTAComponent::dump_config() {
ESP_LOGCONFIG(TAG,
"Over-The-Air Updates:\n"
" swap type after reboot: %s\n"
" image confirmed: %s",
swap_type_str(mcuboot_swap_type()), YESNO(boot_is_img_confirmed()));
}
void OTAComponent::update_chunk(const img_mgmt_upload_check &upload) {
float percentage = (upload.req->off * 100.0f) / upload.action->size;
this->defer([this, percentage]() { this->percentage_ = percentage; });
}
void OTAComponent::update_started() {
this->defer([this]() {
ESP_LOGD(TAG, "Starting update");
#ifdef USE_OTA_STATE_LISTENER
this->notify_state_(ota::OTA_STARTED, 0.0f, 0);
#endif
});
}
void OTAComponent::update_chunk_wrote() {
uint32_t now = millis();
if (now - this->last_progress_ > 1000) {
this->last_progress_ = now;
this->defer([this]() {
ESP_LOGD(TAG, "OTA in progress: %0.1f%%", this->percentage_);
#ifdef USE_OTA_STATE_LISTENER
this->notify_state_(ota::OTA_IN_PROGRESS, this->percentage_, 0);
#endif
});
}
}
void OTAComponent::update_pending() {
this->defer([this]() {
ESP_LOGD(TAG, "OTA pending");
#ifdef USE_OTA_STATE_LISTENER
this->notify_state_(ota::OTA_COMPLETED, 100.0f, 0);
#endif
});
}
void OTAComponent::update_stopped() {
this->defer([this]() {
ESP_LOGD(TAG, "OTA stopped");
#ifdef USE_OTA_STATE_LISTENER
this->notify_state_(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(ota::OTA_RESPONSE_ERROR_UNKNOWN));
#endif
});
}
} // namespace esphome::zephyr_mcumgr
#endif
@@ -0,0 +1,29 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_ZEPHYR
#include "esphome/components/ota/ota_backend.h"
#include <zephyr/mgmt/mcumgr/mgmt/callbacks.h>
struct img_mgmt_upload_check;
namespace esphome::zephyr_mcumgr {
class OTAComponent : public ota::OTAComponent {
public:
OTAComponent();
void setup() override;
void dump_config() override;
void update_chunk(const img_mgmt_upload_check &upload);
void update_started();
void update_chunk_wrote();
void update_pending();
void update_stopped();
protected:
uint32_t last_progress_ = 0;
float percentage_ = 0;
mgmt_callback img_mgmt_callback_{};
};
} // namespace esphome::zephyr_mcumgr
#endif
+1
View File
@@ -367,6 +367,7 @@
#define USE_NRF52_DFU
#define USE_NRF52_REG0_VOUT 5
#define USE_NRF52_UICR_ERASE
#define USE_OTA_ROLLBACK
#define USE_SOFTDEVICE_ID 7
#define USE_SOFTDEVICE_VERSION 1
#define USE_ZIGBEE
+1 -1
View File
@@ -12,7 +12,7 @@ platformio==6.1.19
esptool==5.2.0
click==8.3.1
esphome-dashboard==20260210.0
aioesphomeapi==44.4.0
aioesphomeapi==44.5.0
zeroconf==0.148.0
puremagic==1.30
ruamel.yaml==0.19.1 # dashboard_import
+16
View File
@@ -28,6 +28,22 @@ extern "C" void zboss_signal_handler() {};
CONFIG_NEWLIB_LIBC=y
CONFIG_BT=y
CONFIG_ADC=y
#mcumgr begin
CONFIG_NET_BUF=y
CONFIG_ZCBOR=y
CONFIG_MCUMGR=y
CONFIG_MCUMGR_GRP_IMG=y
CONFIG_IMG_MANAGER=y
CONFIG_STREAM_FLASH=y
CONFIG_FLASH_MAP=y
CONFIG_FLASH=y
CONFIG_IMG_ERASE_PROGRESSIVELY=y
CONFIG_BOOTLOADER_MCUBOOT=y
CONFIG_MCUMGR_MGMT_NOTIFICATION_HOOKS=y
CONFIG_MCUMGR_GRP_IMG_STATUS_HOOKS=y
CONFIG_MCUMGR_GRP_IMG_UPLOAD_CHECK_HOOK=y
CONFIG_MCUMGR_TRANSPORT_UART=y
#mcumgr end
#zigbee begin
CONFIG_ZIGBEE=y
CONFIG_CRYPTO=y
@@ -0,0 +1,27 @@
zephyr_ble_server:
ota:
- platform: zephyr_mcumgr
transport:
ble: true
hardware_uart: CDC
on_begin:
then:
- logger.log: "OTA start"
on_progress:
then:
- logger.log:
format: "OTA progress %0.1f%%"
args: ["x"]
on_end:
then:
- logger.log: "OTA end"
on_error:
then:
- logger.log:
format: "OTA update error %d"
args: ["x"]
on_state_change:
then:
lambda: >-
ESP_LOGD("ota", "State %d", state);