[wifi_info] Use callbacks instead of polling (#10748)

Co-authored-by: J. Nick Koston <nick+github@koston.org>
This commit is contained in:
Keith Burzinski
2025-11-24 20:23:10 -06:00
committed by GitHub
parent 7f1a9a611f
commit 2bc8a4a779
12 changed files with 417 additions and 244 deletions

View File

@@ -608,6 +608,7 @@ async def wifi_disable_to_code(config, action_id, template_arg, args):
KEEP_SCAN_RESULTS_KEY = "wifi_keep_scan_results"
RUNTIME_POWER_SAVE_KEY = "wifi_runtime_power_save"
WIFI_CALLBACKS_KEY = "wifi_callbacks"
def request_wifi_scan_results():
@@ -633,6 +634,17 @@ def enable_runtime_power_save_control():
CORE.data[RUNTIME_POWER_SAVE_KEY] = True
def request_wifi_callbacks() -> None:
"""Request that WiFi callbacks be compiled in.
Components that need to be notified about WiFi state changes (IP address changes,
scan results, connection state) should call this function during their code generation.
This enables the add_on_ip_state_callback(), add_on_wifi_scan_state_callback(),
and add_on_wifi_connect_state_callback() APIs.
"""
CORE.data[WIFI_CALLBACKS_KEY] = True
@coroutine_with_priority(CoroPriority.FINAL)
async def final_step():
"""Final code generation step to configure optional WiFi features."""
@@ -642,6 +654,8 @@ async def final_step():
)
if CORE.data.get(RUNTIME_POWER_SAVE_KEY, False):
cg.add_define("USE_WIFI_RUNTIME_POWER_SAVE")
if CORE.data.get(WIFI_CALLBACKS_KEY, False):
cg.add_define("USE_WIFI_CALLBACKS")
@automation.register_action(

View File

@@ -0,0 +1,116 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_WIFI
#include "wifi_component.h"
namespace esphome::wifi {
template<typename... Ts> class WiFiConnectedCondition : public Condition<Ts...> {
public:
bool check(const Ts &...x) override { return global_wifi_component->is_connected(); }
};
template<typename... Ts> class WiFiEnabledCondition : public Condition<Ts...> {
public:
bool check(const Ts &...x) override { return !global_wifi_component->is_disabled(); }
};
template<typename... Ts> class WiFiAPActiveCondition : public Condition<Ts...> {
public:
bool check(const Ts &...x) override { return global_wifi_component->is_ap_active(); }
};
template<typename... Ts> class WiFiEnableAction : public Action<Ts...> {
public:
void play(const Ts &...x) override { global_wifi_component->enable(); }
};
template<typename... Ts> class WiFiDisableAction : public Action<Ts...> {
public:
void play(const Ts &...x) override { global_wifi_component->disable(); }
};
template<typename... Ts> class WiFiConfigureAction : public Action<Ts...>, public Component {
public:
TEMPLATABLE_VALUE(std::string, ssid)
TEMPLATABLE_VALUE(std::string, password)
TEMPLATABLE_VALUE(bool, save)
TEMPLATABLE_VALUE(uint32_t, connection_timeout)
void play(const Ts &...x) override {
auto ssid = this->ssid_.value(x...);
auto password = this->password_.value(x...);
// Avoid multiple calls
if (this->connecting_)
return;
// If already connected to the same AP, do nothing
if (global_wifi_component->wifi_ssid() == ssid) {
// Callback to notify the user that the connection was successful
this->connect_trigger_->trigger();
return;
}
// Create a new WiFiAP object with the new SSID and password
this->new_sta_.set_ssid(ssid);
this->new_sta_.set_password(password);
// Save the current STA
this->old_sta_ = global_wifi_component->get_sta();
// Disable WiFi
global_wifi_component->disable();
// Set the state to connecting
this->connecting_ = true;
// Store the new STA so once the WiFi is enabled, it will connect to it
// This is necessary because the WiFiComponent will raise an error and fallback to the saved STA
// if trying to connect to a new STA while already connected to another one
if (this->save_.value(x...)) {
global_wifi_component->save_wifi_sta(new_sta_.get_ssid(), new_sta_.get_password());
} else {
global_wifi_component->set_sta(new_sta_);
}
// Enable WiFi
global_wifi_component->enable();
// Set timeout for the connection
this->set_timeout("wifi-connect-timeout", this->connection_timeout_.value(x...), [this, x...]() {
// If the timeout is reached, stop connecting and revert to the old AP
global_wifi_component->disable();
global_wifi_component->save_wifi_sta(old_sta_.get_ssid(), old_sta_.get_password());
global_wifi_component->enable();
// Start a timeout for the fallback if the connection to the old AP fails
this->set_timeout("wifi-fallback-timeout", this->connection_timeout_.value(x...), [this]() {
this->connecting_ = false;
this->error_trigger_->trigger();
});
});
}
Trigger<> *get_connect_trigger() const { return this->connect_trigger_; }
Trigger<> *get_error_trigger() const { return this->error_trigger_; }
void loop() override {
if (!this->connecting_)
return;
if (global_wifi_component->is_connected()) {
// The WiFi is connected, stop the timeout and reset the connecting flag
this->cancel_timeout("wifi-connect-timeout");
this->cancel_timeout("wifi-fallback-timeout");
this->connecting_ = false;
if (global_wifi_component->wifi_ssid() == this->new_sta_.get_ssid()) {
// Callback to notify the user that the connection was successful
this->connect_trigger_->trigger();
} else {
// Callback to notify the user that the connection failed
this->error_trigger_->trigger();
}
}
}
protected:
bool connecting_{false};
WiFiAP new_sta_;
WiFiAP old_sta_;
Trigger<> *connect_trigger_{new Trigger<>()};
Trigger<> *error_trigger_{new Trigger<>()};
};
} // namespace esphome::wifi
#endif

View File

@@ -37,8 +37,7 @@
#include "esphome/components/esp32_improv/esp32_improv_component.h"
#endif
namespace esphome {
namespace wifi {
namespace esphome::wifi {
static const char *const TAG = "wifi";
@@ -1813,6 +1812,5 @@ bool WiFiScanResult::operator==(const WiFiScanResult &rhs) const { return this->
WiFiComponent *global_wifi_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace wifi
} // namespace esphome
} // namespace esphome::wifi
#endif

View File

@@ -54,8 +54,7 @@ extern "C" {
#include <freertos/semphr.h>
#endif
namespace esphome {
namespace wifi {
namespace esphome::wifi {
/// Sentinel value for RSSI when WiFi is not connected
static constexpr int8_t WIFI_RSSI_DISCONNECTED = -127;
@@ -370,6 +369,27 @@ class WiFiComponent : public Component {
int32_t get_wifi_channel();
#ifdef USE_WIFI_CALLBACKS
/// Add a callback that will be called on configuration changes (IP change, SSID change, etc.)
/// @param callback The callback to be called; template arguments are:
/// - IP addresses
/// - DNS address 1
/// - DNS address 2
void add_on_ip_state_callback(
std::function<void(network::IPAddresses, network::IPAddress, network::IPAddress)> &&callback) {
this->ip_state_callback_.add(std::move(callback));
}
/// - Wi-Fi scan results
void add_on_wifi_scan_state_callback(std::function<void(wifi_scan_vector_t<WiFiScanResult> &)> &&callback) {
this->wifi_scan_state_callback_.add(std::move(callback));
}
/// - Wi-Fi SSID
/// - Wi-Fi BSSID
void add_on_wifi_connect_state_callback(std::function<void(std::string, wifi::bssid_t)> &&callback) {
this->wifi_connect_state_callback_.add(std::move(callback));
}
#endif // USE_WIFI_CALLBACKS
#ifdef USE_WIFI_RUNTIME_POWER_SAVE
/** Request high-performance mode (no power saving) for improved WiFi latency.
*
@@ -526,6 +546,11 @@ class WiFiComponent : public Component {
WiFiAP ap_;
#endif
optional<float> output_power_;
#ifdef USE_WIFI_CALLBACKS
CallbackManager<void(network::IPAddresses, network::IPAddress, network::IPAddress)> ip_state_callback_;
CallbackManager<void(wifi_scan_vector_t<WiFiScanResult> &)> wifi_scan_state_callback_;
CallbackManager<void(std::string, wifi::bssid_t)> wifi_connect_state_callback_;
#endif // USE_WIFI_CALLBACKS
ESPPreferenceObject pref_;
#ifdef USE_WIFI_FAST_CONNECT
ESPPreferenceObject fast_connect_pref_;
@@ -590,112 +615,5 @@ class WiFiComponent : public Component {
extern WiFiComponent *global_wifi_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
template<typename... Ts> class WiFiConnectedCondition : public Condition<Ts...> {
public:
bool check(const Ts &...x) override { return global_wifi_component->is_connected(); }
};
template<typename... Ts> class WiFiEnabledCondition : public Condition<Ts...> {
public:
bool check(const Ts &...x) override { return !global_wifi_component->is_disabled(); }
};
template<typename... Ts> class WiFiAPActiveCondition : public Condition<Ts...> {
public:
bool check(const Ts &...x) override { return global_wifi_component->is_ap_active(); }
};
template<typename... Ts> class WiFiEnableAction : public Action<Ts...> {
public:
void play(const Ts &...x) override { global_wifi_component->enable(); }
};
template<typename... Ts> class WiFiDisableAction : public Action<Ts...> {
public:
void play(const Ts &...x) override { global_wifi_component->disable(); }
};
template<typename... Ts> class WiFiConfigureAction : public Action<Ts...>, public Component {
public:
TEMPLATABLE_VALUE(std::string, ssid)
TEMPLATABLE_VALUE(std::string, password)
TEMPLATABLE_VALUE(bool, save)
TEMPLATABLE_VALUE(uint32_t, connection_timeout)
void play(const Ts &...x) override {
auto ssid = this->ssid_.value(x...);
auto password = this->password_.value(x...);
// Avoid multiple calls
if (this->connecting_)
return;
// If already connected to the same AP, do nothing
if (global_wifi_component->wifi_ssid() == ssid) {
// Callback to notify the user that the connection was successful
this->connect_trigger_->trigger();
return;
}
// Create a new WiFiAP object with the new SSID and password
this->new_sta_.set_ssid(ssid);
this->new_sta_.set_password(password);
// Save the current STA
this->old_sta_ = global_wifi_component->get_sta();
// Disable WiFi
global_wifi_component->disable();
// Set the state to connecting
this->connecting_ = true;
// Store the new STA so once the WiFi is enabled, it will connect to it
// This is necessary because the WiFiComponent will raise an error and fallback to the saved STA
// if trying to connect to a new STA while already connected to another one
if (this->save_.value(x...)) {
global_wifi_component->save_wifi_sta(new_sta_.get_ssid(), new_sta_.get_password());
} else {
global_wifi_component->set_sta(new_sta_);
}
// Enable WiFi
global_wifi_component->enable();
// Set timeout for the connection
this->set_timeout("wifi-connect-timeout", this->connection_timeout_.value(x...), [this, x...]() {
// If the timeout is reached, stop connecting and revert to the old AP
global_wifi_component->disable();
global_wifi_component->save_wifi_sta(old_sta_.get_ssid(), old_sta_.get_password());
global_wifi_component->enable();
// Start a timeout for the fallback if the connection to the old AP fails
this->set_timeout("wifi-fallback-timeout", this->connection_timeout_.value(x...), [this]() {
this->connecting_ = false;
this->error_trigger_->trigger();
});
});
}
Trigger<> *get_connect_trigger() const { return this->connect_trigger_; }
Trigger<> *get_error_trigger() const { return this->error_trigger_; }
void loop() override {
if (!this->connecting_)
return;
if (global_wifi_component->is_connected()) {
// The WiFi is connected, stop the timeout and reset the connecting flag
this->cancel_timeout("wifi-connect-timeout");
this->cancel_timeout("wifi-fallback-timeout");
this->connecting_ = false;
if (global_wifi_component->wifi_ssid() == this->new_sta_.get_ssid()) {
// Callback to notify the user that the connection was successful
this->connect_trigger_->trigger();
} else {
// Callback to notify the user that the connection failed
this->error_trigger_->trigger();
}
}
}
protected:
bool connecting_{false};
WiFiAP new_sta_;
WiFiAP old_sta_;
Trigger<> *connect_trigger_{new Trigger<>()};
Trigger<> *error_trigger_{new Trigger<>()};
};
} // namespace wifi
} // namespace esphome
} // namespace esphome::wifi
#endif

View File

@@ -38,8 +38,7 @@ extern "C" {
#include "esphome/core/log.h"
#include "esphome/core/util.h"
namespace esphome {
namespace wifi {
namespace esphome::wifi {
static const char *const TAG = "wifi_esp8266";
@@ -514,6 +513,10 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
ESP_LOGV(TAG, "Connected ssid='%s' bssid=%s channel=%u", buf, format_mac_address_pretty(it.bssid).c_str(),
it.channel);
s_sta_connected = true;
#ifdef USE_WIFI_CALLBACKS
global_wifi_component->wifi_connect_state_callback_.call(global_wifi_component->wifi_ssid(),
global_wifi_component->wifi_bssid());
#endif
break;
}
case EVENT_STAMODE_DISCONNECTED: {
@@ -533,6 +536,9 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
}
s_sta_connected = false;
s_sta_connecting = false;
#ifdef USE_WIFI_CALLBACKS
global_wifi_component->wifi_connect_state_callback_.call("", bssid_t({0, 0, 0, 0, 0, 0}));
#endif
break;
}
case EVENT_STAMODE_AUTHMODE_CHANGE: {
@@ -555,6 +561,11 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
ESP_LOGV(TAG, "static_ip=%s gateway=%s netmask=%s", format_ip_addr(it.ip).c_str(), format_ip_addr(it.gw).c_str(),
format_ip_addr(it.mask).c_str());
s_sta_got_ip = true;
#ifdef USE_WIFI_CALLBACKS
global_wifi_component->ip_state_callback_.call(global_wifi_component->wifi_sta_ip_addresses(),
global_wifi_component->get_dns_address(0),
global_wifi_component->get_dns_address(1));
#endif
break;
}
case EVENT_STAMODE_DHCP_TIMEOUT: {
@@ -729,6 +740,9 @@ void WiFiComponent::wifi_scan_done_callback_(void *arg, STATUS status) {
it->is_hidden != 0);
}
this->scan_done_ = true;
#ifdef USE_WIFI_CALLBACKS
global_wifi_component->wifi_scan_state_callback_.call(global_wifi_component->scan_result_);
#endif
}
#ifdef USE_WIFI_AP
@@ -885,8 +899,6 @@ network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {(const ip_addr_t
network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {(const ip_addr_t *) WiFi.dnsIP(num)}; }
void WiFiComponent::wifi_loop_() {}
} // namespace wifi
} // namespace esphome
} // namespace esphome::wifi
#endif
#endif

View File

@@ -41,8 +41,7 @@
#include "esphome/core/log.h"
#include "esphome/core/util.h"
namespace esphome {
namespace wifi {
namespace esphome::wifi {
static const char *const TAG = "wifi_esp32";
@@ -728,6 +727,9 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf,
format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
s_sta_connected = true;
#ifdef USE_WIFI_CALLBACKS
this->wifi_connect_state_callback_.call(this->wifi_ssid(), this->wifi_bssid());
#endif
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_DISCONNECTED) {
const auto &it = data->data.sta_disconnected;
@@ -751,6 +753,9 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
s_sta_connected = false;
s_sta_connecting = false;
error_from_callback_ = true;
#ifdef USE_WIFI_CALLBACKS
this->wifi_connect_state_callback_.call("", bssid_t({0, 0, 0, 0, 0, 0}));
#endif
} else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_GOT_IP) {
const auto &it = data->data.ip_got_ip;
@@ -759,12 +764,18 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
#endif /* USE_NETWORK_IPV6 */
ESP_LOGV(TAG, "static_ip=" IPSTR " gateway=" IPSTR, IP2STR(&it.ip_info.ip), IP2STR(&it.ip_info.gw));
this->got_ipv4_address_ = true;
#ifdef USE_WIFI_CALLBACKS
this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
#endif
#if USE_NETWORK_IPV6
} else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_GOT_IP6) {
const auto &it = data->data.ip_got_ip6;
ESP_LOGV(TAG, "IPv6 address=" IPV6STR, IPV62STR(it.ip6_info.ip));
this->num_ipv6_addresses_++;
#ifdef USE_WIFI_CALLBACKS
this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
#endif
#endif /* USE_NETWORK_IPV6 */
} else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_LOST_IP) {
@@ -804,6 +815,9 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
scan_result_.emplace_back(bssid, ssid, record.primary, record.rssi, record.authmode != WIFI_AUTH_OPEN,
ssid.empty());
}
#ifdef USE_WIFI_CALLBACKS
this->wifi_scan_state_callback_.call(this->scan_result_);
#endif
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_START) {
ESP_LOGV(TAG, "AP start");
@@ -1088,8 +1102,6 @@ network::IPAddress WiFiComponent::wifi_dns_ip_(int num) {
return network::IPAddress(dns_ip);
}
} // namespace wifi
} // namespace esphome
} // namespace esphome::wifi
#endif // USE_ESP32
#endif

View File

@@ -15,8 +15,7 @@
#include "esphome/core/log.h"
#include "esphome/core/util.h"
namespace esphome {
namespace wifi {
namespace esphome::wifi {
static const char *const TAG = "wifi_lt";
@@ -288,7 +287,9 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
buf[it.ssid_len] = '\0';
ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf,
format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
#ifdef USE_WIFI_CALLBACKS
this->wifi_connect_state_callback_.call(this->wifi_ssid(), this->wifi_bssid());
#endif
break;
}
case ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED: {
@@ -314,6 +315,9 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
}
s_sta_connecting = false;
#ifdef USE_WIFI_CALLBACKS
this->wifi_connect_state_callback_.call("", bssid_t({0, 0, 0, 0, 0, 0}));
#endif
break;
}
case ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE: {
@@ -335,11 +339,17 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
ESP_LOGV(TAG, "static_ip=%s gateway=%s", format_ip4_addr(WiFi.localIP()).c_str(),
format_ip4_addr(WiFi.gatewayIP()).c_str());
s_sta_connecting = false;
#ifdef USE_WIFI_CALLBACKS
this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
#endif
break;
}
case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6: {
// auto it = info.got_ip.ip_info;
ESP_LOGV(TAG, "Got IPv6");
#ifdef USE_WIFI_CALLBACKS
this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
#endif
break;
}
case ESPHOME_EVENT_ID_WIFI_STA_LOST_IP: {
@@ -433,6 +443,9 @@ void WiFiComponent::wifi_scan_done_callback_() {
}
WiFi.scanDelete();
this->scan_done_ = true;
#ifdef USE_WIFI_CALLBACKS
this->wifi_scan_state_callback_.call(this->scan_result_);
#endif
}
#ifdef USE_WIFI_AP
@@ -493,8 +506,6 @@ network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}
network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {WiFi.dnsIP(num)}; }
void WiFiComponent::wifi_loop_() {}
} // namespace wifi
} // namespace esphome
} // namespace esphome::wifi
#endif // USE_LIBRETINY
#endif

View File

@@ -1,4 +1,3 @@
#include "wifi_component.h"
#ifdef USE_WIFI
@@ -15,11 +14,14 @@
#include "esphome/core/log.h"
#include "esphome/core/util.h"
namespace esphome {
namespace wifi {
namespace esphome::wifi {
static const char *const TAG = "wifi_pico_w";
// Track previous state for detecting changes
static bool s_sta_was_connected = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static bool s_sta_had_ip = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
bool WiFiComponent::wifi_mode_(optional<bool> sta, optional<bool> ap) {
if (sta.has_value()) {
if (sta.value()) {
@@ -51,7 +53,7 @@ bool WiFiComponent::wifi_apply_power_save_() {
return ret == 0;
}
// TODO: The driver doesnt seem to have an API for this
// TODO: The driver doesn't seem to have an API for this
bool WiFiComponent::wifi_apply_output_power_(float output_power) { return true; }
bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
@@ -219,16 +221,61 @@ network::IPAddress WiFiComponent::wifi_dns_ip_(int num) {
}
void WiFiComponent::wifi_loop_() {
// Handle scan completion
if (this->state_ == WIFI_COMPONENT_STATE_STA_SCANNING && !cyw43_wifi_scan_active(&cyw43_state)) {
this->scan_done_ = true;
ESP_LOGV(TAG, "Scan done");
#ifdef USE_WIFI_CALLBACKS
this->wifi_scan_state_callback_.call(this->scan_result_);
#endif
}
// Poll for connection state changes
// The arduino-pico WiFi library doesn't have event callbacks like ESP8266/ESP32,
// so we need to poll the link status to detect state changes
auto status = cyw43_tcpip_link_status(&cyw43_state, CYW43_ITF_STA);
bool is_connected = (status == CYW43_LINK_UP);
// Detect connection state change
if (is_connected && !s_sta_was_connected) {
// Just connected
s_sta_was_connected = true;
ESP_LOGV(TAG, "Connected");
#ifdef USE_WIFI_CALLBACKS
this->wifi_connect_state_callback_.call(this->wifi_ssid(), this->wifi_bssid());
#endif
} else if (!is_connected && s_sta_was_connected) {
// Just disconnected
s_sta_was_connected = false;
s_sta_had_ip = false;
ESP_LOGV(TAG, "Disconnected");
#ifdef USE_WIFI_CALLBACKS
this->wifi_connect_state_callback_.call("", bssid_t({0, 0, 0, 0, 0, 0}));
#endif
}
// Detect IP address changes (only when connected)
if (is_connected) {
bool has_ip = false;
// Check for any IP address (IPv4 or IPv6)
for (auto addr : addrList) {
has_ip = true;
break;
}
if (has_ip && !s_sta_had_ip) {
// Just got IP address
s_sta_had_ip = true;
ESP_LOGV(TAG, "Got IP address");
#ifdef USE_WIFI_CALLBACKS
this->ip_state_callback_.call(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
#endif
}
}
}
void WiFiComponent::wifi_pre_setup_() {}
} // namespace wifi
} // namespace esphome
} // namespace esphome::wifi
#endif
#endif

View File

@@ -15,31 +15,27 @@ DEPENDENCIES = ["wifi"]
wifi_info_ns = cg.esphome_ns.namespace("wifi_info")
IPAddressWiFiInfo = wifi_info_ns.class_(
"IPAddressWiFiInfo", text_sensor.TextSensor, cg.PollingComponent
"IPAddressWiFiInfo", text_sensor.TextSensor, cg.Component
)
ScanResultsWiFiInfo = wifi_info_ns.class_(
"ScanResultsWiFiInfo", text_sensor.TextSensor, cg.PollingComponent
)
SSIDWiFiInfo = wifi_info_ns.class_(
"SSIDWiFiInfo", text_sensor.TextSensor, cg.PollingComponent
"ScanResultsWiFiInfo", text_sensor.TextSensor, cg.Component
)
SSIDWiFiInfo = wifi_info_ns.class_("SSIDWiFiInfo", text_sensor.TextSensor, cg.Component)
BSSIDWiFiInfo = wifi_info_ns.class_(
"BSSIDWiFiInfo", text_sensor.TextSensor, cg.PollingComponent
"BSSIDWiFiInfo", text_sensor.TextSensor, cg.Component
)
MacAddressWifiInfo = wifi_info_ns.class_(
"MacAddressWifiInfo", text_sensor.TextSensor, cg.Component
)
DNSAddressWifiInfo = wifi_info_ns.class_(
"DNSAddressWifiInfo", text_sensor.TextSensor, cg.PollingComponent
"DNSAddressWifiInfo", text_sensor.TextSensor, cg.Component
)
CONFIG_SCHEMA = cv.Schema(
{
cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema(
IPAddressWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
)
.extend(cv.polling_component_schema("1s"))
.extend(
).extend(
{
cv.Optional(f"address_{x}"): text_sensor.text_sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
@@ -49,22 +45,31 @@ CONFIG_SCHEMA = cv.Schema(
),
cv.Optional(CONF_SCAN_RESULTS): text_sensor.text_sensor_schema(
ScanResultsWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
).extend(cv.polling_component_schema("60s")),
),
cv.Optional(CONF_SSID): text_sensor.text_sensor_schema(
SSIDWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
).extend(cv.polling_component_schema("1s")),
),
cv.Optional(CONF_BSSID): text_sensor.text_sensor_schema(
BSSIDWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
).extend(cv.polling_component_schema("1s")),
),
cv.Optional(CONF_MAC_ADDRESS): text_sensor.text_sensor_schema(
MacAddressWifiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
),
cv.Optional(CONF_DNS_ADDRESS): text_sensor.text_sensor_schema(
DNSAddressWifiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
).extend(cv.polling_component_schema("1s")),
),
}
)
# Keys that require WiFi callbacks
_NETWORK_INFO_KEYS = {
CONF_SSID,
CONF_BSSID,
CONF_IP_ADDRESS,
CONF_DNS_ADDRESS,
CONF_SCAN_RESULTS,
}
async def setup_conf(config, key):
if key in config:
@@ -74,6 +79,10 @@ async def setup_conf(config, key):
async def to_code(config):
# Request WiFi callbacks for any sensor that needs them
if _NETWORK_INFO_KEYS.intersection(config):
wifi.request_wifi_callbacks()
await setup_conf(config, CONF_SSID)
await setup_conf(config, CONF_BSSID)
await setup_conf(config, CONF_MAC_ADDRESS)

View File

@@ -2,18 +2,121 @@
#ifdef USE_WIFI
#include "esphome/core/log.h"
namespace esphome {
namespace wifi_info {
namespace esphome::wifi_info {
static const char *const TAG = "wifi_info";
static constexpr size_t MAX_STATE_LENGTH = 255;
/********************
* IPAddressWiFiInfo
*******************/
void IPAddressWiFiInfo::setup() {
wifi::global_wifi_component->add_on_ip_state_callback(
[this](const network::IPAddresses &ips, const network::IPAddress &dns1_ip, const network::IPAddress &dns2_ip) {
this->state_callback_(ips);
});
}
void IPAddressWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "IP Address", this); }
void ScanResultsWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "Scan Results", this); }
void SSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "SSID", this); }
void BSSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "BSSID", this); }
void MacAddressWifiInfo::dump_config() { LOG_TEXT_SENSOR("", "MAC Address", this); }
void IPAddressWiFiInfo::state_callback_(const network::IPAddresses &ips) {
this->publish_state(ips[0].str());
uint8_t sensor = 0;
for (const auto &ip : ips) {
if (ip.is_set()) {
if (this->ip_sensors_[sensor] != nullptr) {
this->ip_sensors_[sensor]->publish_state(ip.str());
}
sensor++;
}
}
}
/*********************
* DNSAddressWifiInfo
********************/
void DNSAddressWifiInfo::setup() {
wifi::global_wifi_component->add_on_ip_state_callback(
[this](const network::IPAddresses &ips, const network::IPAddress &dns1_ip, const network::IPAddress &dns2_ip) {
this->state_callback_(dns1_ip, dns2_ip);
});
}
void DNSAddressWifiInfo::dump_config() { LOG_TEXT_SENSOR("", "DNS Address", this); }
} // namespace wifi_info
} // namespace esphome
void DNSAddressWifiInfo::state_callback_(const network::IPAddress &dns1_ip, const network::IPAddress &dns2_ip) {
std::string dns_results = dns1_ip.str() + " " + dns2_ip.str();
this->publish_state(dns_results);
}
/**********************
* ScanResultsWiFiInfo
*********************/
void ScanResultsWiFiInfo::setup() {
wifi::global_wifi_component->add_on_wifi_scan_state_callback(
[this](const wifi::wifi_scan_vector_t<wifi::WiFiScanResult> &results) { this->state_callback_(results); });
}
void ScanResultsWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "Scan Results", this); }
void ScanResultsWiFiInfo::state_callback_(const wifi::wifi_scan_vector_t<wifi::WiFiScanResult> &results) {
std::string scan_results;
for (const auto &scan : results) {
if (scan.get_is_hidden())
continue;
scan_results += scan.get_ssid();
scan_results += ": ";
scan_results += esphome::to_string(scan.get_rssi());
scan_results += "dB\n";
}
// There's a limit of 255 characters per state; longer states just don't get sent so we truncate it
if (scan_results.length() > MAX_STATE_LENGTH) {
scan_results.resize(MAX_STATE_LENGTH);
}
this->publish_state(scan_results);
}
/***************
* SSIDWiFiInfo
**************/
void SSIDWiFiInfo::setup() {
wifi::global_wifi_component->add_on_wifi_connect_state_callback(
[this](const std::string &ssid, const wifi::bssid_t &bssid) { this->state_callback_(ssid); });
}
void SSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "SSID", this); }
void SSIDWiFiInfo::state_callback_(const std::string &ssid) { this->publish_state(ssid); }
/****************
* BSSIDWiFiInfo
***************/
void BSSIDWiFiInfo::setup() {
wifi::global_wifi_component->add_on_wifi_connect_state_callback(
[this](const std::string &ssid, const wifi::bssid_t &bssid) { this->state_callback_(bssid); });
}
void BSSIDWiFiInfo::dump_config() { LOG_TEXT_SENSOR("", "BSSID", this); }
void BSSIDWiFiInfo::state_callback_(const wifi::bssid_t &bssid) {
char buf[18] = "unknown";
if (mac_address_is_valid(bssid.data())) {
format_mac_addr_upper(bssid.data(), buf);
}
this->publish_state(buf);
}
/*********************
* MacAddressWifiInfo
********************/
void MacAddressWifiInfo::dump_config() { LOG_TEXT_SENSOR("", "MAC Address", this); }
} // namespace esphome::wifi_info
#endif

View File

@@ -7,121 +7,54 @@
#ifdef USE_WIFI
#include <array>
namespace esphome {
namespace wifi_info {
namespace esphome::wifi_info {
static constexpr size_t MAX_STATE_LENGTH = 255;
class IPAddressWiFiInfo : public PollingComponent, public text_sensor::TextSensor {
class IPAddressWiFiInfo : public Component, public text_sensor::TextSensor {
public:
void update() override {
auto ips = wifi::global_wifi_component->wifi_sta_ip_addresses();
if (ips != this->last_ips_) {
this->last_ips_ = ips;
this->publish_state(ips[0].str());
uint8_t sensor = 0;
for (auto &ip : ips) {
if (ip.is_set()) {
if (this->ip_sensors_[sensor] != nullptr) {
this->ip_sensors_[sensor]->publish_state(ip.str());
}
sensor++;
}
}
}
}
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
void setup() override;
void dump_config() override;
void add_ip_sensors(uint8_t index, text_sensor::TextSensor *s) { this->ip_sensors_[index] = s; }
protected:
network::IPAddresses last_ips_;
void state_callback_(const network::IPAddresses &ips);
std::array<text_sensor::TextSensor *, 5> ip_sensors_;
};
class DNSAddressWifiInfo : public PollingComponent, public text_sensor::TextSensor {
class DNSAddressWifiInfo : public Component, public text_sensor::TextSensor {
public:
void update() override {
auto dns_one = wifi::global_wifi_component->get_dns_address(0);
auto dns_two = wifi::global_wifi_component->get_dns_address(1);
std::string dns_results = dns_one.str() + " " + dns_two.str();
if (dns_results != this->last_results_) {
this->last_results_ = dns_results;
this->publish_state(dns_results);
}
}
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
void setup() override;
void dump_config() override;
protected:
std::string last_results_;
void state_callback_(const network::IPAddress &dns1_ip, const network::IPAddress &dns2_ip);
};
class ScanResultsWiFiInfo : public PollingComponent, public text_sensor::TextSensor {
class ScanResultsWiFiInfo : public Component, public text_sensor::TextSensor {
public:
void update() override {
std::string scan_results;
for (auto &scan : wifi::global_wifi_component->get_scan_result()) {
if (scan.get_is_hidden())
continue;
scan_results += scan.get_ssid();
scan_results += ": ";
scan_results += esphome::to_string(scan.get_rssi());
scan_results += "dB\n";
}
// There's a limit of 255 characters per state.
// Longer states just don't get sent so we truncate it.
if (scan_results.length() > MAX_STATE_LENGTH) {
scan_results.resize(MAX_STATE_LENGTH);
}
if (this->last_scan_results_ != scan_results) {
this->last_scan_results_ = scan_results;
this->publish_state(scan_results);
}
}
void setup() override;
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
void dump_config() override;
protected:
std::string last_scan_results_;
void state_callback_(const wifi::wifi_scan_vector_t<wifi::WiFiScanResult> &results);
};
class SSIDWiFiInfo : public PollingComponent, public text_sensor::TextSensor {
class SSIDWiFiInfo : public Component, public text_sensor::TextSensor {
public:
void update() override {
std::string ssid = wifi::global_wifi_component->wifi_ssid();
if (this->last_ssid_ != ssid) {
this->last_ssid_ = ssid;
this->publish_state(this->last_ssid_);
}
}
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
void setup() override;
void dump_config() override;
protected:
std::string last_ssid_;
void state_callback_(const std::string &ssid);
};
class BSSIDWiFiInfo : public PollingComponent, public text_sensor::TextSensor {
class BSSIDWiFiInfo : public Component, public text_sensor::TextSensor {
public:
void update() override {
wifi::bssid_t bssid = wifi::global_wifi_component->wifi_bssid();
if (memcmp(bssid.data(), last_bssid_.data(), 6) != 0) {
std::copy(bssid.begin(), bssid.end(), last_bssid_.begin());
char buf[18];
format_mac_addr_upper(bssid.data(), buf);
this->publish_state(buf);
}
}
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
void setup() override;
void dump_config() override;
protected:
wifi::bssid_t last_bssid_;
void state_callback_(const wifi::bssid_t &bssid);
};
class MacAddressWifiInfo : public Component, public text_sensor::TextSensor {
@@ -133,6 +66,5 @@ class MacAddressWifiInfo : public Component, public text_sensor::TextSensor {
void dump_config() override;
};
} // namespace wifi_info
} // namespace esphome
} // namespace esphome::wifi_info
#endif

View File

@@ -210,6 +210,7 @@
#define USE_WEBSERVER_SORTING
#define USE_WIFI_11KV_SUPPORT
#define USE_WIFI_FAST_CONNECT
#define USE_WIFI_CALLBACKS
#define USE_WIFI_RUNTIME_POWER_SAVE
#define USB_HOST_MAX_REQUESTS 16