From 3c56b9e66e1070b732c0fec1e779f6d1bbe64d43 Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Sun, 23 Aug 2026 20:05:35 -0500 Subject: [PATCH] [wifi] Share the scan list dedupe helper with improv_serial (#18612) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston Co-authored-by: Bluetooth Devices Bot --- .../captive_portal/captive_portal.cpp | 4 +-- .../improv_serial/improv_serial_component.cpp | 25 +++++-------------- .../{captive_portal => wifi}/scan_list.h | 4 +-- tests/components/captive_portal/__init__.py | 10 -------- .../scan_list_test.cpp | 12 ++++----- 5 files changed, 16 insertions(+), 39 deletions(-) rename esphome/components/{captive_portal => wifi}/scan_list.h (92%) delete mode 100644 tests/components/captive_portal/__init__.py rename tests/components/{captive_portal => wifi}/scan_list_test.cpp (93%) diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index ffd121499b..e80f9e669f 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -4,9 +4,9 @@ #include "esphome/core/application.h" #include "esphome/core/helpers.h" #include "esphome/core/string_ref.h" +#include "esphome/components/wifi/scan_list.h" #include "esphome/components/wifi/wifi_component.h" #include "captive_index.h" -#include "scan_list.h" namespace esphome::captive_portal { @@ -37,7 +37,7 @@ void CaptivePortal::handle_config(AsyncWebServerRequest *request) { const auto &results = wifi::global_wifi_component->get_scan_result(); for (const auto &scan : results) { bool with_auth = false; - if (!should_show_scan_entry(results, scan, with_auth)) + if (!wifi::should_show_scan_entry(results, scan, with_auth)) continue; json_escape_into_buffer(escaped_ssid, scan.get_ssid()); diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index a191889138..de9c7899cd 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -7,6 +7,7 @@ #include "esphome/core/version.h" #include "esphome/components/logger/logger.h" +#include "esphome/components/wifi/scan_list.h" namespace esphome::improv_serial { @@ -230,31 +231,17 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command return true; } case improv::GET_WIFI_NETWORKS: { - std::vector networks; const auto &results = wifi::global_wifi_component->get_scan_result(); - for (auto &scan : results) { - if (scan.get_is_hidden()) + for (const auto &scan : results) { + bool with_auth = false; + if (!wifi::should_show_scan_entry(results, scan, with_auth)) continue; - const char *ssid_cstr = scan.get_ssid().c_str(); - // Check if we've already sent this SSID - bool duplicate = false; - for (const auto &seen : networks) { - if (strcmp(seen.c_str(), ssid_cstr) == 0) { - duplicate = true; - break; - } - } - if (duplicate) - continue; - // Only allocate std::string after confirming it's not a duplicate - std::string ssid(ssid_cstr); // Send each ssid separately to avoid overflowing the buffer char rssi_buf[5]; // int8_t: -128 to 127, max 4 chars + null *int8_to_str(rssi_buf, scan.get_rssi()) = '\0'; - std::vector data = - improv::build_rpc_response(improv::GET_WIFI_NETWORKS, {ssid, rssi_buf, YESNO(scan.get_with_auth())}, false); + std::vector data = improv::build_rpc_response( + improv::GET_WIFI_NETWORKS, {scan.get_ssid().str(), rssi_buf, YESNO(with_auth)}, false); this->send_response_(data); - networks.push_back(std::move(ssid)); } // Send empty response to signify the end of the list. std::vector data = diff --git a/esphome/components/captive_portal/scan_list.h b/esphome/components/wifi/scan_list.h similarity index 92% rename from esphome/components/captive_portal/scan_list.h rename to esphome/components/wifi/scan_list.h index d24a88a670..8a11c745da 100644 --- a/esphome/components/captive_portal/scan_list.h +++ b/esphome/components/wifi/scan_list.h @@ -1,7 +1,7 @@ #pragma once #include -namespace esphome::captive_portal { +namespace esphome::wifi { // A scan lists every BSSID, so one SSID can appear several times. Returns true for // the strongest entry per SSID (earliest on ties), never for hidden entries. scan @@ -25,4 +25,4 @@ bool should_show_scan_entry(const Results &results, const Entry &scan, bool &wit return true; } -} // namespace esphome::captive_portal +} // namespace esphome::wifi diff --git a/tests/components/captive_portal/__init__.py b/tests/components/captive_portal/__init__.py deleted file mode 100644 index 1ac0704a59..0000000000 --- a/tests/components/captive_portal/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from tests.testing_helpers import ComponentManifestOverride - - -def override_manifest(manifest: ComponentManifestOverride) -> None: - # The scan list helper is header-only and needs none of the component's real - # dependencies. Pulling them in breaks the host build: web_server_base - # includes ESPAsyncWebServer.h and ota.web_server includes md5/md5.h, neither - # of which exists there. - manifest.dependencies = [] - manifest.auto_load = [] diff --git a/tests/components/captive_portal/scan_list_test.cpp b/tests/components/wifi/scan_list_test.cpp similarity index 93% rename from tests/components/captive_portal/scan_list_test.cpp rename to tests/components/wifi/scan_list_test.cpp index f67581dc0b..47427f063f 100644 --- a/tests/components/captive_portal/scan_list_test.cpp +++ b/tests/components/wifi/scan_list_test.cpp @@ -4,13 +4,13 @@ #include #include -#include "esphome/components/captive_portal/scan_list.h" +#include "esphome/components/wifi/scan_list.h" -namespace esphome::captive_portal::testing { +namespace esphome::wifi::testing { namespace { -// Stand-in for wifi::WiFiScanResult, which does not compile on the host. +// Stand-in for WiFiScanResult, which does not compile on the host. struct Entry { std::string ssid; int8_t rssi; @@ -24,7 +24,7 @@ struct Entry { bool get_is_hidden() const { return this->is_hidden; } }; -// One row as the portal would emit it. +// One network as a consumer would emit it. struct Row { std::string ssid; int8_t rssi; @@ -33,7 +33,7 @@ struct Row { bool operator==(const Row &rhs) const { return ssid == rhs.ssid && rssi == rhs.rssi && lock == rhs.lock; } }; -// Walk the results the way handle_config does and collect the rows that survive. +// Walk the results the way the consumers do and collect the rows that survive. std::vector rows(const std::vector &results) { std::vector out; for (size_t i = 0; i < results.size(); i++) { @@ -127,4 +127,4 @@ TEST(ScanList, EmptyListShowsNothing) { EXPECT_TRUE(rows(results).empty()); } -} // namespace esphome::captive_portal::testing +} // namespace esphome::wifi::testing