[Network] Add connected/disconnected event handling for all interfaces

This commit is contained in:
Gijs Noorlander
2025-08-24 00:28:31 +06:00
parent 9f949f6e29
commit b65fba2775
19 changed files with 203 additions and 42 deletions
+2
View File
@@ -88,3 +88,5 @@ dependencies.lock
.cache/
src/src/CustomBuild/CompiletimeDefines_generated.h
bhutan_16m.bin
@@ -1,7 +1,11 @@
#include "../DataStructs/NWPluginData_static_runtime.h"
#include "../../../src/Globals/EventQueue.h"
#include "../../../src/Globals/Settings.h"
#include "../../../src/Helpers/NetworkStatusLED.h"
#include "../../../src/Helpers/StringConverter.h"
namespace ESPEasy {
namespace net {
@@ -25,9 +29,51 @@ void NWPluginData_static_runtime::clear(networkIndex_t networkIndex)
// FIXME TD-er: Should also clear dns cache?
}
void NWPluginData_static_runtime::processEvent_and_clear()
{
processEvents();
clear();
}
bool NWPluginData_static_runtime::operational() const
{
return _startStopStats.isOn() && connected() && hasIP();
return started() && connected() && hasIP()
&& Settings.getNetworkEnabled(_networkIndex);
// FIXME TD-er: WiFi STA keeps reporting it is
// connected and has IP even after call to networkdisable,1
}
void NWPluginData_static_runtime::processEvents()
{
if (_establishConnectStats.changedSinceLastCheck_and_clear())
{
if (_establishConnectStats.isOn()) {
log_connected();
// _establishConnectStats.resetCount();
} else {
log_disconnected();
}
}
_operationalStats.set(operational());
if (_operationalStats.changedSinceLastCheck_and_clear()) {
// Send out event
if (Settings.UseRules && _eventInterfaceName.length())
{
if (_operationalStats.isOn()) {
eventQueue.add(concat(
_eventInterfaceName,
F("#Connected")));
} else {
eventQueue.add(concat(
_eventInterfaceName,
F("#Disconnected")));
}
}
statusLED(true);
}
}
String NWPluginData_static_runtime::statusToString() const
@@ -2,6 +2,8 @@
#include "../DataTypes/NetworkIndex.h"
#include "../../../ESPEasy_common.h"
#include "../../../src/Helpers/LongTermOnOffTimer.h"
#include <IPAddress.h>
@@ -16,15 +18,27 @@ namespace net {
struct NWPluginData_static_runtime {
#ifdef ESP32
NWPluginData_static_runtime(NetworkInterface *netif) : _netif(netif) {}
NWPluginData_static_runtime(NetworkInterface *netif, const String& eventInterfaceName = EMPTY_STRING)
: _netif(netif), _eventInterfaceName(eventInterfaceName)
{
if (_eventInterfaceName.length() == 0 && _netif) {
_eventInterfaceName = _netif->desc();
_eventInterfaceName.toUpperCase();
}
}
#else // ifdef ESP32
NWPluginData_static_runtime(bool isSTA) : _isSTA(isSTA) {}
NWPluginData_static_runtime(bool isSTA, String& eventInterfaceName)
: _isSTA(isSTA), _eventInterfaceName(eventInterfaceName) {}
#endif // ifdef ESP32
void clear(networkIndex_t networkIndex = INVALID_NETWORK_INDEX);
void processEvent_and_clear();
bool started() const;
bool connected() const;
bool isDefaultRoute() const;
@@ -60,9 +74,10 @@ struct NWPluginData_static_runtime {
void mark_begin_establish_connection();
void mark_connected();
void log_connected();
void mark_disconnected();
void log_disconnected();
// =============================================
// Keep track of connection durations
@@ -85,6 +100,7 @@ struct NWPluginData_static_runtime {
uint32_t getConnectionFailures() const { return _connectionFailures; }
void processEvents();
// =============================================
// OnOffTimers for keeping track of:
@@ -101,6 +117,7 @@ struct NWPluginData_static_runtime {
#if FEATURE_USE_IPV6
LongTermOnOffTimer _gotIP6Stats{};
#endif
LongTermOnOffTimer _operationalStats{}; // is started, connected and had IP
// =============================================
// Cached DNS servers & Route Prio
@@ -121,6 +138,8 @@ private:
const bool _isSTA;
#endif
String _eventInterfaceName;
// Store durations it took to connect to some host.
// This depends on host and interface.
// Duration is negative when it was suggested but not actually set.
@@ -14,6 +14,12 @@
namespace ESPEasy {
namespace net {
bool NWPluginData_static_runtime::started() const
{
if (!_netif) { return false; }
return _netif->started();
}
bool NWPluginData_static_runtime::connected() const
{
if (!_netif) { return false; }
@@ -173,29 +179,37 @@ void NWPluginData_static_runtime::mark_connected()
_establishConnectStats.setOff();
_connectedStats.setOn();
#if NW_PLUGIN_LOG_EVENTS
if (logDuration) {
addLog(LOG_LEVEL_INFO, strformat(
F("%s: Connected, took: %s in %d attempts"),
_netif->desc(),
format_msec_duration_HMS(
_establishConnectStats.getLastOnDuration_ms()).c_str(),
_establishConnectStats.getCycleCount()));
} else {
addLog(LOG_LEVEL_INFO, strformat(
F("%s: Connected"),
_netif->desc()));
}
#endif
_establishConnectStats.resetCount();
}
void NWPluginData_static_runtime::log_connected()
{
if (_netif && loglevelActiveFor(LOG_LEVEL_INFO)) {
if (_establishConnectStats.getCycleCount()) {
// Log duration
addLog(LOG_LEVEL_INFO, strformat(
F("%s: Connected, took: %s in %d attempts"),
_netif->desc(),
format_msec_duration_HMS(
_establishConnectStats.getLastOnDuration_ms()).c_str(),
_establishConnectStats.getCycleCount()));
} else {
addLog(LOG_LEVEL_INFO, strformat(
F("%s: Connected"),
_netif->desc()));
}
}
}
void NWPluginData_static_runtime::mark_disconnected()
{
_connectedStats.setOff();
// TODO TD-er: Also clear _gotIPStats and _gotIP6Stats ?
#if NW_PLUGIN_LOG_EVENTS
}
void NWPluginData_static_runtime::log_disconnected()
{
if (_netif && loglevelActiveFor(LOG_LEVEL_INFO)) {
addLog(LOG_LEVEL_INFO, strformat(
F("%s: Disconnected. Connected for: %s"),
@@ -203,9 +217,9 @@ void NWPluginData_static_runtime::mark_disconnected()
format_msec_duration_HMS(
_connectedStats.getLastOnDuration_ms()).c_str()));
}
#endif
}
} // namespace net
} // namespace ESPEasy
@@ -7,6 +7,12 @@
namespace ESPEasy {
namespace net {
bool NWPluginData_static_runtime::started() const
{
// FIXME TD-er: Does this work reliable on ESP8266?
return _startStopStats.isOn();
}
bool NWPluginData_static_runtime::connected() const
{
if (_isSTA) { return WiFi.status() == WL_CONNECTED; }
@@ -70,14 +76,20 @@ void NWPluginData_static_runtime::mark_connected()
#endif
_establishConnectStats.setOff();
_connectedStats.setOn();
}
void NWPluginData_static_runtime::log_connected()
{
#ifndef BUILD_NO_DEBUG
if (logDuration) {
addLog(LOG_LEVEL_INFO, concat(
F("STA: Connected, took: "),
format_msec_duration_HMS(
_establishConnectStats.getLastOnDuration_ms())));
} else {
addLog(LOG_LEVEL_INFO, F("STA: Connected"));
if (loglevelActiveFor(LOG_LEVEL_INFO)) {
if (_establishConnectStats.getCycleCount()) {
addLog(LOG_LEVEL_INFO, concat(
F("STA: Connected, took: "),
format_msec_duration_HMS(
_establishConnectStats.getLastOnDuration_ms())));
} else {
addLog(LOG_LEVEL_INFO, F("STA: Connected"));
}
}
#endif
}
@@ -86,7 +98,13 @@ void NWPluginData_static_runtime::mark_disconnected()
{
_connectedStats.setOff();
#ifndef BUILD_NO_DEBUG
if (_isSTA) {
#endif
}
void NWPluginData_static_runtime::log_disconnected()
{
#ifndef BUILD_NO_DEBUG
if (_isSTA && loglevelActiveFor(LOG_LEVEL_INFO)) {
addLog(LOG_LEVEL_INFO, concat(
F("STA: Disconnected. Connected for: "),
format_msec_duration_HMS(_connectedStats.getLastOnDuration_ms())));
+28
View File
@@ -114,6 +114,7 @@ bool NWPluginCall(NWPlugin::Function Function, EventStruct *event, String& str)
case NWPlugin::Function::NWPLUGIN_RECORD_STATS:
case NWPlugin::Function::NWPLUGIN_WEBFORM_LOAD_SHOW_STATS:
#endif // if FEATURE_PLUGIN_STATS
case NWPlugin::Function::NWPLUGIN_PROCESS_EVENT:
case NWPlugin::Function::NWPLUGIN_GET_CONNECTED_DURATION:
{
bool success = false;
@@ -155,6 +156,15 @@ bool NWPluginCall(NWPlugin::Function Function, EventStruct *event, String& str)
}
#endif // if FEATURE_PLUGIN_STATS
case NWPlugin::Function::NWPLUGIN_PROCESS_EVENT:
{
auto runtimeData = NW_data->getNWPluginData_static_runtime();
if (runtimeData) {
runtimeData->processEvents();
}
break;
}
case NWPlugin::Function::NWPLUGIN_GET_CONNECTED_DURATION:
{
auto runtime_data = NW_data->getNWPluginData_static_runtime();
@@ -301,6 +311,13 @@ bool NWPluginCall(NWPlugin::Function Function, EventStruct *event, String& str)
if (success && (Function == NWPlugin::Function::NWPLUGIN_EXIT)) {
// Cache.clearNetworkSettings(networkIndex);
auto data = getNWPluginData_static_runtime(event->NetworkIndex);
if (data) {
delay(100); // Allow some time to process events
data->processEvent_and_clear();
}
}
#endif // ifdef ESP32
}
@@ -396,5 +413,16 @@ const NWPluginData_static_runtime* getDefaultRoute_NWPluginData_static_runtime()
return nullptr;
}
void processNetworkEvents()
{
START_TIMER;
for (networkIndex_t i = 0; i < NETWORK_MAX; ++i) {
EventStruct tmpEvent;
tmpEvent.NetworkIndex = i;
NWPluginCall(NWPlugin::Function::NWPLUGIN_PROCESS_EVENT, &tmpEvent);
}
STOP_TIMER(NWPLUGIN_PROCESS_NETWORK_EVENTS);
}
} // namespace net
} // namespace ESPEasy
+2
View File
@@ -48,5 +48,7 @@ NWPluginData_static_runtime* getWiFi_STA_NWPluginData_static_runtime();
NWPluginData_static_runtime* getNWPluginData_static_runtime(networkIndex_t index);
const NWPluginData_static_runtime* getDefaultRoute_NWPluginData_static_runtime();
void processNetworkEvents();
} // namespace net
} // namespace ESPEasy
@@ -29,7 +29,7 @@ namespace wifi {
# ifdef ESP32
static NWPluginData_static_runtime stats_and_cache(&NW_PLUGIN_INTERFACE);
# else
static NWPluginData_static_runtime stats_and_cache(false);
static NWPluginData_static_runtime stats_and_cache(false, F("AP"));
# endif // ifdef ESP32
static bool nw002_initialized{};
# ifdef ESP32
@@ -78,7 +78,7 @@ NW002_data_struct_WiFi_AP::~NW002_data_struct_WiFi_AP()
}
nw_event_id = 0;
# endif // ifdef ESP32
stats_and_cache.clear();
stats_and_cache.processEvent_and_clear();
}
void NW002_data_struct_WiFi_AP::webform_load(EventStruct *event) {}
@@ -109,6 +109,8 @@ bool NW002_data_struct_WiFi_AP::exit(EventStruct *event)
WiFi.softAPdisconnect();
# endif // ifdef ESP8266
stats_and_cache.processEvents();
return true;
}
@@ -33,7 +33,7 @@ NW003_data_struct_ETH_RMII::~NW003_data_struct_ETH_RMII()
Network.removeEvent(nw_event_id);
}
nw_event_id = 0;
stats_and_cache.clear();
stats_and_cache.processEvent_and_clear();
}
void NW003_data_struct_ETH_RMII::webform_load(EventStruct *event) {}
@@ -45,7 +45,7 @@ bool NW003_data_struct_ETH_RMII::webform_getPort(String& str) { return true;
bool NW003_data_struct_ETH_RMII::init(EventStruct *event) { return true; }
bool NW003_data_struct_ETH_RMII::exit(EventStruct *event) {
stats_and_cache.processEvents();
return true;
}
@@ -32,7 +32,7 @@ NW004_data_struct_ETH_SPI::~NW004_data_struct_ETH_SPI()
Network.removeEvent(nw_event_id);
}
nw_event_id = 0;
stats_and_cache.clear();
stats_and_cache.processEvent_and_clear();
}
void NW004_data_struct_ETH_SPI::webform_load(EventStruct *event) {}
@@ -30,7 +30,7 @@ namespace ESPEasy {
namespace net {
namespace ppp {
static NWPluginData_static_runtime stats_and_cache(&NW_PLUGIN_INTERFACE);
static NWPluginData_static_runtime stats_and_cache(&NW_PLUGIN_INTERFACE, F("PPP"));
// Keys as used in the Key-value-store
@@ -112,7 +112,7 @@ NW005_data_struct_PPP_modem::~NW005_data_struct_PPP_modem() {
Network.removeEvent(nw_event_id);
}
nw_event_id = 0;
stats_and_cache.clear();
stats_and_cache.processEvent_and_clear();
}
enum class NW005_modem_model {
@@ -153,6 +153,14 @@ ppp_modem_model_t to_ppp_modem_model_t(NW005_modem_model NW005_modemmodel)
return PPP_MODEM_GENERIC;
}
int doGetRSSI()
{
const int rssi_raw = NW_PLUGIN_INTERFACE.RSSI();
if (rssi_raw == 0) return -113;
if (rssi_raw == 99) return 0;
return map(rssi_raw, 2, 30, -109, -53);
}
String NW005_data_struct_PPP_modem::getRSSI() const
{
if (!_modem_task_data.modem_initialized) { return F("-"); }
@@ -967,7 +975,8 @@ bool NW005_data_struct_PPP_modem::record_stats()
if (_plugin_stats_array != nullptr) {
EventStruct tmpEvent;
size_t valueCount{};
tmpEvent.ParfN[valueCount++] = NW_PLUGIN_INTERFACE.RSSI();
const int rssi = doGetRSSI();
tmpEvent.ParfN[valueCount++] = rssi == 0 ? NAN : rssi;
tmpEvent.ParfN[valueCount++] = getBER_float();
bool trackPeaks = true;
bool onlyUpdateTimestampWhenSame = true;
@@ -34,7 +34,7 @@ namespace ESPEasy {
namespace net {
namespace wifi {
static NWPluginData_static_runtime stats_and_cache(&WiFi.STA);
static NWPluginData_static_runtime stats_and_cache(&WiFi.STA, F("WiFi"));
static wifi_event_sta_connected_t _wifi_event_sta_connected;
static WiFiDisconnectReason _wifi_disconnect_reason = WiFiDisconnectReason::WIFI_DISCONNECT_REASON_UNSPECIFIED;
@@ -56,7 +56,7 @@ ESPEasyWiFi_STA_EventHandler::~ESPEasyWiFi_STA_EventHandler()
}
nw_event_id = 0;
_ESPEasyWiFi_STA_EventHandler_initialized = false;
stats_and_cache.clear();
stats_and_cache.processEvent_and_clear();
}
bool ESPEasyWiFi_STA_EventHandler::initialized() {
@@ -24,7 +24,7 @@ namespace ESPEasy {
namespace net {
namespace wifi {
static NWPluginData_static_runtime stats_and_cache(true);
static NWPluginData_static_runtime stats_and_cache(true, F("WiFi"));
static WiFiDisconnectReason _wifi_disconnect_reason = WiFiDisconnectReason::WIFI_DISCONNECT_REASON_UNSPECIFIED;
static uint8_t _authmode{};
@@ -51,7 +51,7 @@ ESPEasyWiFi_STA_EventHandler::ESPEasyWiFi_STA_EventHandler(networkIndex_t networ
ESPEasyWiFi_STA_EventHandler::~ESPEasyWiFi_STA_EventHandler()
{
stats_and_cache.clear();
stats_and_cache.processEvent_and_clear();
}
bool ESPEasyWiFi_STA_EventHandler::initialized() {
+1
View File
@@ -228,6 +228,7 @@ const __FlashStringHelper* getMiscStatsName_F(TimingStatsElements stat) {
case TimingStatsElements::CPLUGIN_CALL_10PS: return F("CPlugin call 10 p/s");
case TimingStatsElements::NWPLUGIN_CALL_50PS: return F("NWPlugin call 50 p/s");
case TimingStatsElements::NWPLUGIN_CALL_10PS: return F("NWPlugin call 10 p/s");
case TimingStatsElements::NWPLUGIN_PROCESS_NETWORK_EVENTS: return F("NWPlugin process events");
case TimingStatsElements::SENSOR_SEND_TASK: return F("SensorSendTask()");
case TimingStatsElements::COMMAND_EXEC_INTERNAL: return F("Exec Internal Command");
case TimingStatsElements::COMMAND_DECODE_INTERNAL: return F("Decode Internal Command");
+1
View File
@@ -68,6 +68,7 @@ enum class TimingStatsElements {
CPLUGIN_CALL_50PS,
NWPLUGIN_CALL_10PS,
NWPLUGIN_CALL_50PS,
NWPLUGIN_PROCESS_NETWORK_EVENTS,
SENSOR_SEND_TASK,
SEND_DATA_STATS,
COMPUTE_FORMULA_STATS,
+1 -1
View File
@@ -107,7 +107,7 @@ void ESPEasy_loop()
}
setWebserverRunning(ESPEasy::net::NWPluginCall(NWPlugin::Function::NWPLUGIN_WEBSERVER_SHOULD_RUN));
// ESPEasy::net::processNetworkEvents();
#if FEATURE_CLEAR_I2C_STUCK
if (Settings.EnableClearHangingI2Cbus())
+3
View File
@@ -5,12 +5,14 @@ void LongTermOnOffTimer::clear()
_onTimer.clear();
_offTimer.clear();
_prevDuration = 0;
_changedSinceLastCheck = false;
resetCount();
}
bool LongTermOnOffTimer::setOn()
{
if (isOn()) { return false; }
_changedSinceLastCheck = true;
++_changeToOnCount;
if (isOff()) {
@@ -23,6 +25,7 @@ bool LongTermOnOffTimer::setOn()
bool LongTermOnOffTimer::setOff()
{
if (isOff()) { return false; }
_changedSinceLastCheck = true;
++_changeToOffCount;
if (isOn()) {
+13
View File
@@ -42,6 +42,17 @@ public:
size_t getChangeToOffCount() const { return _changeToOffCount; }
bool changedSinceLastCheck() const { return _changedSinceLastCheck; }
bool changedSinceLastCheck_and_clear()
{
if (_changedSinceLastCheck) {
_changedSinceLastCheck = false;
return true;
}
return false;
}
private:
LongTermTimer _onTimer;
@@ -51,4 +62,6 @@ private:
size_t _changeToOnCount{};
size_t _changeToOffCount{};
bool _changedSinceLastCheck{};
}; // class LongTermOnOffTimer
+3
View File
@@ -74,6 +74,9 @@ void run50TimesPerSecond() {
ESPEasy::net::NWPluginCall(NWPlugin::Function::NWPLUGIN_FIFTY_PER_SECOND, 0, dummy);
STOP_TIMER(NWPLUGIN_CALL_50PS);
}
{
ESPEasy::net::processNetworkEvents();
}
{
START_TIMER;
PluginCall(PLUGIN_FIFTY_PER_SECOND, 0, dummy);