[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 <nick@koston.org>
Co-authored-by: Bluetooth Devices Bot <bluetooth@koston.org>
This commit is contained in:
Brandon Harvey
2026-08-24 01:05:35 +00:00
committed by GitHub
co-authored by pre-commit-ci-lite[bot] J. Nick Koston Bluetooth Devices Bot
parent 1ceaf699c2
commit 3c56b9e66e
5 changed files with 16 additions and 39 deletions
@@ -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());
@@ -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<std::string> 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<uint8_t> data =
improv::build_rpc_response(improv::GET_WIFI_NETWORKS, {ssid, rssi_buf, YESNO(scan.get_with_auth())}, false);
std::vector<uint8_t> 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<uint8_t> data =
@@ -1,7 +1,7 @@
#pragma once
#include <cstdint>
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
@@ -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 = []
@@ -4,13 +4,13 @@
#include <string>
#include <vector>
#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<Row> rows(const std::vector<Entry> &results) {
std::vector<Row> 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