mirror of
https://github.com/esphome/esphome.git
synced 2026-03-03 19:28:20 -07:00
Merge remote-tracking branch 'upstream/dev' into millis64-zephyr-native
# Conflicts: # esphome/core/scheduler.cpp # esphome/core/scheduler.h
This commit is contained in:
49
.github/workflows/ci.yml
vendored
49
.github/workflows/ci.yml
vendored
@@ -686,7 +686,7 @@ jobs:
|
||||
ram_usage: ${{ steps.extract.outputs.ram_usage }}
|
||||
flash_usage: ${{ steps.extract.outputs.flash_usage }}
|
||||
cache_hit: ${{ steps.cache-memory-analysis.outputs.cache-hit }}
|
||||
skip: ${{ steps.check-script.outputs.skip }}
|
||||
skip: ${{ steps.check-script.outputs.skip || steps.check-tests.outputs.skip }}
|
||||
steps:
|
||||
- name: Check out target branch
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
@@ -705,10 +705,39 @@ jobs:
|
||||
echo "::warning::ci_memory_impact_extract.py not found on target branch, skipping memory impact analysis"
|
||||
fi
|
||||
|
||||
# All remaining steps only run if script exists
|
||||
# Check if test files exist on the target branch for the requested
|
||||
# components and platform. When a PR adds new test files for a platform,
|
||||
# the target branch won't have them yet, so skip instead of failing.
|
||||
# This check must be done here (not in determine-jobs.py) because
|
||||
# determine-jobs runs on the PR branch and cannot see what the target
|
||||
# branch has.
|
||||
- name: Check for test files on target branch
|
||||
id: check-tests
|
||||
if: steps.check-script.outputs.skip != 'true'
|
||||
run: |
|
||||
components='${{ toJSON(fromJSON(needs.determine-jobs.outputs.memory_impact).components) }}'
|
||||
platform="${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}"
|
||||
found=false
|
||||
for component in $(echo "$components" | jq -r '.[]'); do
|
||||
# Check for test files matching the platform (test.platform.yaml or test-*.platform.yaml)
|
||||
for f in tests/components/${component}/test*.${platform}.yaml; do
|
||||
if [ -f "$f" ]; then
|
||||
found=true
|
||||
break 2
|
||||
fi
|
||||
done
|
||||
done
|
||||
if [ "$found" = false ]; then
|
||||
echo "skip=true" >> $GITHUB_OUTPUT
|
||||
echo "::warning::No test files found on target branch for platform ${platform}, skipping memory impact analysis"
|
||||
else
|
||||
echo "skip=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
# All remaining steps only run if script and tests exist
|
||||
- name: Generate cache key
|
||||
id: cache-key
|
||||
if: steps.check-script.outputs.skip != 'true'
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true'
|
||||
run: |
|
||||
# Get the commit SHA of the target branch
|
||||
target_sha=$(git rev-parse HEAD)
|
||||
@@ -735,14 +764,14 @@ jobs:
|
||||
|
||||
- name: Restore cached memory analysis
|
||||
id: cache-memory-analysis
|
||||
if: steps.check-script.outputs.skip != 'true'
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true'
|
||||
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
with:
|
||||
path: memory-analysis-target.json
|
||||
key: ${{ steps.cache-key.outputs.cache-key }}
|
||||
|
||||
- name: Cache status
|
||||
if: steps.check-script.outputs.skip != 'true'
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true'
|
||||
run: |
|
||||
if [ "${{ steps.cache-memory-analysis.outputs.cache-hit }}" == "true" ]; then
|
||||
echo "✓ Cache hit! Using cached memory analysis results."
|
||||
@@ -752,21 +781,21 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Restore Python
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
|
||||
- name: Cache platformio
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
|
||||
|
||||
- name: Build, compile, and analyze memory
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
|
||||
id: build
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
@@ -800,7 +829,7 @@ jobs:
|
||||
--platform "$platform"
|
||||
|
||||
- name: Save memory analysis to cache
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' && steps.build.outcome == 'success'
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' && steps.build.outcome == 'success'
|
||||
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
with:
|
||||
path: memory-analysis-target.json
|
||||
@@ -808,7 +837,7 @@ jobs:
|
||||
|
||||
- name: Extract memory usage for outputs
|
||||
id: extract
|
||||
if: steps.check-script.outputs.skip != 'true'
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true'
|
||||
run: |
|
||||
if [ -f memory-analysis-target.json ]; then
|
||||
ram=$(jq -r '.ram_bytes' memory-analysis-target.json)
|
||||
|
||||
@@ -21,6 +21,7 @@ CONF_ON_SAFE_MODE = "on_safe_mode"
|
||||
safe_mode_ns = cg.esphome_ns.namespace("safe_mode")
|
||||
SafeModeComponent = safe_mode_ns.class_("SafeModeComponent", cg.Component)
|
||||
SafeModeTrigger = safe_mode_ns.class_("SafeModeTrigger", automation.Trigger.template())
|
||||
MarkSuccessfulAction = safe_mode_ns.class_("MarkSuccessfulAction", automation.Action)
|
||||
|
||||
|
||||
def _remove_id_if_disabled(value):
|
||||
@@ -53,6 +54,22 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"safe_mode.mark_successful",
|
||||
MarkSuccessfulAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(SafeModeComponent),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def safe_mode_mark_successful_to_code(config, action_id, template_arg, args):
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
cg.add(var.set_parent(parent))
|
||||
return var
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.APPLICATION)
|
||||
async def to_code(config):
|
||||
if not config[CONF_DISABLED]:
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
#pragma once
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_SAFE_MODE_CALLBACK
|
||||
#include "safe_mode.h"
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "safe_mode.h"
|
||||
|
||||
namespace esphome::safe_mode {
|
||||
|
||||
#ifdef USE_SAFE_MODE_CALLBACK
|
||||
class SafeModeTrigger final : public Trigger<> {
|
||||
public:
|
||||
explicit SafeModeTrigger(SafeModeComponent *parent) {
|
||||
parent->add_on_safe_mode_callback([this]() { trigger(); });
|
||||
}
|
||||
};
|
||||
#endif // USE_SAFE_MODE_CALLBACK
|
||||
|
||||
template<typename... Ts> class MarkSuccessfulAction : public Action<Ts...>, public Parented<SafeModeComponent> {
|
||||
public:
|
||||
void play(const Ts &...x) override { this->parent_->mark_successful(); }
|
||||
};
|
||||
|
||||
} // namespace esphome::safe_mode
|
||||
|
||||
#endif // USE_SAFE_MODE_CALLBACK
|
||||
|
||||
@@ -63,18 +63,22 @@ void SafeModeComponent::dump_config() {
|
||||
|
||||
float SafeModeComponent::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
|
||||
|
||||
void SafeModeComponent::mark_successful() {
|
||||
this->clean_rtc();
|
||||
this->boot_successful_ = true;
|
||||
#if defined(USE_ESP32) && defined(USE_OTA_ROLLBACK)
|
||||
// Mark OTA partition as valid to prevent rollback
|
||||
esp_ota_mark_app_valid_cancel_rollback();
|
||||
#endif
|
||||
// Disable loop since we no longer need to check
|
||||
this->disable_loop();
|
||||
}
|
||||
|
||||
void SafeModeComponent::loop() {
|
||||
if (!this->boot_successful_ && (millis() - this->safe_mode_start_time_) > this->safe_mode_boot_is_good_after_) {
|
||||
// successful boot, reset counter
|
||||
ESP_LOGI(TAG, "Boot seems successful; resetting boot loop counter");
|
||||
this->clean_rtc();
|
||||
this->boot_successful_ = true;
|
||||
#if defined(USE_ESP32) && defined(USE_OTA_ROLLBACK)
|
||||
// Mark OTA partition as valid to prevent rollback
|
||||
esp_ota_mark_app_valid_cancel_rollback();
|
||||
#endif
|
||||
// Disable loop since we no longer need to check
|
||||
this->disable_loop();
|
||||
this->mark_successful();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ class SafeModeComponent final : public Component {
|
||||
|
||||
void on_safe_shutdown() override;
|
||||
|
||||
void mark_successful();
|
||||
|
||||
#ifdef USE_SAFE_MODE_CALLBACK
|
||||
void add_on_safe_mode_callback(std::function<void()> &&callback) {
|
||||
this->safe_mode_callback_.add(std::move(callback));
|
||||
|
||||
@@ -470,10 +470,6 @@ const LogString *get_disconnect_reason_str(uint8_t reason) {
|
||||
return LOG_STR("Unspecified");
|
||||
}
|
||||
|
||||
// TODO: This callback runs in ESP8266 system context with limited stack (~2KB).
|
||||
// All listener notifications should be deferred to wifi_loop_() via pending_ flags
|
||||
// to avoid stack overflow. Currently only connect_state is deferred; disconnect,
|
||||
// IP, and scan listeners still run in this context and should be migrated.
|
||||
void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
||||
switch (event->event) {
|
||||
case EVENT_STAMODE_CONNECTED: {
|
||||
|
||||
@@ -13,6 +13,19 @@
|
||||
#include <FreeRTOS.h>
|
||||
#include <queue.h>
|
||||
|
||||
#ifdef USE_BK72XX
|
||||
extern "C" {
|
||||
#include <wlan_ui_pub.h>
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_RTL87XX
|
||||
extern "C" {
|
||||
#include <wifi_conf.h>
|
||||
#include <wifi_structures.h>
|
||||
}
|
||||
#endif
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
@@ -760,10 +773,22 @@ bssid_t WiFiComponent::wifi_bssid() {
|
||||
}
|
||||
std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); }
|
||||
const char *WiFiComponent::wifi_ssid_to(std::span<char, SSID_BUFFER_SIZE> buffer) {
|
||||
// TODO: Find direct LibreTiny API to avoid Arduino String allocation
|
||||
#ifdef USE_BK72XX
|
||||
LinkStatusTypeDef link_status{};
|
||||
bk_wlan_get_link_status(&link_status);
|
||||
size_t len = strnlen(reinterpret_cast<const char *>(link_status.ssid), SSID_BUFFER_SIZE - 1);
|
||||
memcpy(buffer.data(), link_status.ssid, len);
|
||||
#elif defined(USE_RTL87XX)
|
||||
rtw_wifi_setting_t setting{};
|
||||
wifi_get_setting("wlan0", &setting);
|
||||
size_t len = strnlen(reinterpret_cast<const char *>(setting.ssid), SSID_BUFFER_SIZE - 1);
|
||||
memcpy(buffer.data(), setting.ssid, len);
|
||||
#else
|
||||
// LN882X: wifi_get_sta_conn_info() provides direct pointer access
|
||||
String ssid = WiFi.SSID();
|
||||
size_t len = std::min(static_cast<size_t>(ssid.length()), SSID_BUFFER_SIZE - 1);
|
||||
memcpy(buffer.data(), ssid.c_str(), len);
|
||||
#endif
|
||||
buffer[len] = '\0';
|
||||
return buffer.data();
|
||||
}
|
||||
|
||||
@@ -727,8 +727,17 @@ void Application::yield_with_select_(uint32_t delay_ms) {
|
||||
#error "Application placement new requires Itanium C++ ABI (GCC/Clang)"
|
||||
#endif
|
||||
static_assert(std::is_default_constructible<Application>::value, "Application must be default-constructible");
|
||||
// __USER_LABEL_PREFIX__ is "_" on Mach-O (macOS) and empty on ELF (embedded targets).
|
||||
// String literal concatenation produces the correct platform-specific mangled symbol.
|
||||
// Two-level macro needed: # stringifies before expansion, so the
|
||||
// indirection forces __USER_LABEL_PREFIX__ to expand first.
|
||||
#define ESPHOME_STRINGIFY_IMPL_(x) #x
|
||||
#define ESPHOME_STRINGIFY_(x) ESPHOME_STRINGIFY_IMPL_(x)
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
alignas(Application) char app_storage[sizeof(Application)] asm("_ZN7esphome3AppE");
|
||||
alignas(Application) char app_storage[sizeof(Application)] asm(
|
||||
ESPHOME_STRINGIFY_(__USER_LABEL_PREFIX__) "_ZN7esphome3AppE");
|
||||
#undef ESPHOME_STRINGIFY_
|
||||
#undef ESPHOME_STRINGIFY_IMPL_
|
||||
|
||||
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE)
|
||||
|
||||
|
||||
@@ -16,3 +16,7 @@ button:
|
||||
switch:
|
||||
- platform: safe_mode
|
||||
name: Safe Mode Switch
|
||||
|
||||
esphome:
|
||||
on_boot:
|
||||
- safe_mode.mark_successful
|
||||
|
||||
1
tests/components/wifi/test.bk72xx-ard.yaml
Normal file
1
tests/components/wifi/test.bk72xx-ard.yaml
Normal file
@@ -0,0 +1 @@
|
||||
<<: !include common.yaml
|
||||
1
tests/components/wifi/test.ln882x-ard.yaml
Normal file
1
tests/components/wifi/test.ln882x-ard.yaml
Normal file
@@ -0,0 +1 @@
|
||||
<<: !include common.yaml
|
||||
1
tests/components/wifi/test.rtl87xx-ard.yaml
Normal file
1
tests/components/wifi/test.rtl87xx-ard.yaml
Normal file
@@ -0,0 +1 @@
|
||||
<<: !include common.yaml
|
||||
Reference in New Issue
Block a user