ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
ota_backend.cpp
Go to the documentation of this file.
1#include "ota_backend.h"
2
3#ifdef ESPHOME_THREAD_MULTI_ATOMICS
4#include <atomic>
5#endif
6
7namespace esphome::ota {
8
9bool version_is_older(const char *candidate, const char *reference) {
10 if (candidate == nullptr || reference == nullptr)
11 return false;
12 while (true) {
13 uint32_t a = 0;
14 while (*candidate >= '0' && *candidate <= '9') {
15 a = a * 10 + static_cast<uint32_t>(*candidate - '0');
16 candidate++;
17 }
18 uint32_t b = 0;
19 while (*reference >= '0' && *reference <= '9') {
20 b = b * 10 + static_cast<uint32_t>(*reference - '0');
21 reference++;
22 }
23 if (a != b)
24 return a < b;
25 // Components equal so far; advance past a single separator on each side.
26 const bool a_more = (*candidate == '.');
27 const bool b_more = (*reference == '.');
28 if (a_more)
29 candidate++;
30 if (b_more)
31 reference++;
32 if (!a_more && !b_more)
33 return false; // Both strings exhausted with all components equal.
34 }
35}
36
37// Non-blocking; may be released by another task than the one that took it.
38#ifdef ESPHOME_THREAD_MULTI_ATOMICS
39// uint32_t: narrower atomics compile to a masked CAS on Xtensa.
40static std::atomic<uint32_t> update_locked{0}; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
41
42static bool update_lock_test_and_set() { return update_locked.exchange(1) == 0; }
43static void update_lock_clear() { update_locked.store(0); }
44#else
45static bool update_locked = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
46
47#ifdef ESPHOME_THREAD_MULTI_NO_ATOMICS
48// BK72xx has no atomic exchange.
49static Mutex &update_lock_mutex() {
50 static Mutex mutex;
51 return mutex;
52}
53#endif
54
55static bool update_lock_test_and_set() {
56#ifdef ESPHOME_THREAD_MULTI_NO_ATOMICS
57 LockGuard guard(update_lock_mutex());
58#else
59 // lwIP callbacks may run from an interrupt (RP2040).
60 InterruptLock guard;
61#endif
62 if (update_locked)
63 return false;
64 update_locked = true;
65 return true;
66}
67
68static void update_lock_clear() {
69#ifdef ESPHOME_THREAD_MULTI_NO_ATOMICS
70 LockGuard guard(update_lock_mutex());
71#endif
72 update_locked = false;
73}
74#endif
75
77 if (!this->held_)
78 this->held_ = update_lock_test_and_set();
79 return this->held_;
80}
81
83 if (!this->held_)
84 return;
85 update_lock_clear();
86 this->held_ = false;
87}
88
89void UpdateLock::force_release() { update_lock_clear(); }
90
91#ifdef USE_OTA_STATE_LISTENER
92OTAGlobalCallback *global_ota_callback{nullptr}; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
93
95 if (global_ota_callback == nullptr) {
96 global_ota_callback = new OTAGlobalCallback(); // NOLINT(cppcoreguidelines-owning-memory)
97 }
99}
100
101void OTAComponent::notify_state_deferred_(OTAState state, float progress, uint8_t error) {
102 // Pack state, error, and progress into a single uint32_t so the lambda
103 // captures only [this, packed] (8 bytes) — fits in std::function SBO.
104 // Layout: [state:8][error:8][progress_fixed:16] where progress is 0–10000 (0.01% resolution)
105 static_assert(OTA_ERROR <= 0xFF, "OTAState must fit in 8 bits for packing");
106 uint32_t packed = (static_cast<uint32_t>(state) << 24) | (static_cast<uint32_t>(error) << 16) |
107 static_cast<uint16_t>(progress * 100.0f);
108 this->defer([this, packed]() {
109 this->notify_state_(static_cast<OTAState>(packed >> 24), static_cast<float>(packed & 0xFFFF) / 100.0f,
110 static_cast<uint8_t>(packed >> 16));
111 });
112}
113
114void OTAComponent::notify_state_(OTAState state, float progress, uint8_t error) {
115 for (auto *listener : this->state_listeners_) {
116 listener->on_ota_state(state, progress, error);
117 }
118 get_global_ota_callback()->notify_ota_state(state, progress, error, this);
119}
120#endif
121
122} // namespace esphome::ota
void defer(const char *name, std::function< void()> &&f)
Defer a callback to the next loop() call with a const char* name.
void notify_state_deferred_(OTAState state, float progress, uint8_t error)
Notify state with deferral to main loop (for thread safety).
std::vector< OTAStateListener * > state_listeners_
void notify_state_(OTAState state, float progress, uint8_t error)
Global callback that aggregates OTA state from all OTA components.
void notify_ota_state(OTAState state, float progress, uint8_t error, OTAComponent *component)
static void force_release()
Tests only.
bool try_acquire()
Returns false if held elsewhere.
bool state
Definition fan.h:2
bool version_is_older(const char *candidate, const char *reference)
Compare two dotted-numeric version strings (such as "1.2.3").
OTAGlobalCallback * get_global_ota_callback()
OTAGlobalCallback * global_ota_callback
static void uint32_t