v2c: connexion TCP persistante — fin des rafales SYN
Diagnostic tcpdump : le plugin ouvrait/fermait la socket Modbus TCP à chaque cycle de poll, générant 6-7 paquets SYN consécutifs qui saturaient l'ESP32 WiFi de la borne (timeouts en boucle). mbpoll avec une connexion persistante = 0 % d'échec. Corrections : - connectDevice() : guard "si déjà connecté → no-op" (élimine le warning "already in ConnectedState" et les FIN/RESET parasites). - onConnectionStateChanged(true) : appelle initialize() directement au lieu de setReachable(true) — évite la ré-entrée initialize() via reachableChanged. - initialize() : setReachable(true) uniquement au succès, après lecture MinIntensity/MaxIntensity ; guard || m_updating ajouté. - update() : guard || m_initializing ajouté (pas de poll concurrent avec init). - Timer : if (!master->update()) master->connectDevice() — le socket reste ouvert tant que la connexion TCP est active ; connectDevice() n'est appelé que quand le TCP est réellement coupé. - initializationFinished(thing) : master->update() immédiat après reconnect. - reachableChanged handler : suppression de l'appel à initialize() (déplacé). Résultat attendu tcpdump : 1 seul SYN à l'établissement, puis uniquement des échanges Modbus (P. length 12/13) toutes les 30 s, aucun FIN/RESET. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
189d58d9ce
commit
186f195900
@ -67,18 +67,19 @@ void IntegrationPluginV2c::setupThing(ThingSetupInfo *info)
|
||||
|
||||
connect(info, &ThingSetupInfo::aborted, master, &TrydanModbusTcpMaster::deleteLater);
|
||||
|
||||
// Reachable → initialize (reads MinIntensity, MaxIntensity).
|
||||
// Lost → set disconnected state with timestamp for SAV visibility.
|
||||
// Reachability is driven by Modbus poll results (not TCP state directly).
|
||||
// initialize() is now called from TrydanModbusTcpMaster::onConnectionStateChanged(true)
|
||||
// so we no longer need to call it here — this handler only updates the UI.
|
||||
connect(master, &TrydanModbusMaster::reachableChanged, thing,
|
||||
[this, thing, master](bool reachable) {
|
||||
if (reachable) {
|
||||
master->initialize();
|
||||
} else {
|
||||
[this, thing](bool reachable) {
|
||||
if (!reachable) {
|
||||
setDisconnectedState(thing, QStringLiteral("timeout Modbus — vérifier signal WiFi de la borne"));
|
||||
}
|
||||
// reachable=true: UI cleared by next successful poll (updateThingStates).
|
||||
});
|
||||
|
||||
// After init, expose the configured current range and finish setup.
|
||||
// After init (first time or reconnect): expose current range and kick an immediate poll
|
||||
// so states are refreshed without waiting up to 30 s for the next timer tick.
|
||||
connect(master, &TrydanModbusMaster::initializationFinished, thing,
|
||||
[this, thing, master](bool success) {
|
||||
if (!success) {
|
||||
@ -89,6 +90,9 @@ void IntegrationPluginV2c::setupThing(ThingSetupInfo *info)
|
||||
thing->setStateMinMaxValues(trydanMaxChargingCurrentStateTypeId,
|
||||
master->minIntensity(),
|
||||
master->maxIntensity());
|
||||
// Immediate first poll. For setup the info handler below also calls update();
|
||||
// the m_updating guard makes the second call a no-op.
|
||||
master->update();
|
||||
});
|
||||
|
||||
connect(master, &TrydanModbusMaster::initializationFinished, info,
|
||||
@ -124,12 +128,12 @@ void IntegrationPluginV2c::postSetupThing(Thing *thing)
|
||||
m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(30);
|
||||
connect(m_pluginTimer, &PluginTimer::timeout, this, [this]() {
|
||||
for (TrydanModbusTcpMaster *master : m_tcpMasters) {
|
||||
if (master->reachable()) {
|
||||
master->update();
|
||||
} else {
|
||||
// TCP connection may have dropped (charger WiFi glitch).
|
||||
// connectDevice() is idempotent on an already-connecting socket;
|
||||
// the 30s timer provides the reconnect backoff — no busy-loop.
|
||||
// update() returns false only when the TCP socket is not connected
|
||||
// (or a poll/init is already running). connectDevice() is a no-op
|
||||
// if the socket is already connected — it will NOT close and reopen
|
||||
// the connection, avoiding the SYN bursts that saturate the ESP32 WiFi.
|
||||
// Normal path: TCP stays open, only Modbus frames exchanged every 30 s.
|
||||
if (!master->update()) {
|
||||
master->connectDevice();
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,6 +44,11 @@ void TrydanModbusTcpMaster::setHostAddress(const QHostAddress &address)
|
||||
|
||||
void TrydanModbusTcpMaster::connectDevice()
|
||||
{
|
||||
// Guard: calling connectDevice() on an already-connected QModbusTcpClient
|
||||
// logs a warning AND closes+reopens the TCP socket, generating a SYN burst
|
||||
// that saturates the Trydan ESP32 WiFi stack. Skip if already connected.
|
||||
if (m_modbusTcpMaster->connected())
|
||||
return;
|
||||
m_modbusTcpMaster->connectDevice();
|
||||
}
|
||||
|
||||
@ -55,7 +60,10 @@ void TrydanModbusTcpMaster::disconnectDevice()
|
||||
|
||||
bool TrydanModbusTcpMaster::initialize()
|
||||
{
|
||||
if (!m_modbusTcpMaster->connected() || m_initializing)
|
||||
// Also guard against an ongoing poll: init and update share the same sequential
|
||||
// read pipeline; running both concurrently would interleave FC3 transactions
|
||||
// and corrupt both sequences.
|
||||
if (!m_modbusTcpMaster->connected() || m_initializing || m_updating)
|
||||
return false;
|
||||
|
||||
m_initializing = true;
|
||||
@ -70,6 +78,12 @@ bool TrydanModbusTcpMaster::initialize()
|
||||
// on a spurious 0 and caps to 32 on a spurious 65535.
|
||||
m_minIntensity = qBound(6, m_minIntensity, 32);
|
||||
m_maxIntensity = qBound(6, m_maxIntensity, 32);
|
||||
// TCP is live and init succeeded — mark reachable so the plugin can
|
||||
// update states. This is the ONLY place setReachable(true) is called
|
||||
// from the connection path; onConnectionStateChanged(true) no longer
|
||||
// does it directly, avoiding a re-entrant initialize() call via the
|
||||
// reachableChanged signal.
|
||||
setReachable(true);
|
||||
}
|
||||
emit initializationFinished(ok);
|
||||
});
|
||||
@ -78,7 +92,8 @@ bool TrydanModbusTcpMaster::initialize()
|
||||
|
||||
bool TrydanModbusTcpMaster::update()
|
||||
{
|
||||
if (!m_modbusTcpMaster->connected() || m_updating)
|
||||
// Guard against concurrent init: both use the same sequential FC3 pipeline.
|
||||
if (!m_modbusTcpMaster->connected() || m_updating || m_initializing)
|
||||
return false;
|
||||
|
||||
m_updating = true;
|
||||
@ -261,9 +276,15 @@ void TrydanModbusTcpMaster::onConnectionStateChanged(bool connected)
|
||||
{
|
||||
if (connected) {
|
||||
m_errorCount = 0;
|
||||
// Signal reachability so the plugin calls initialize().
|
||||
setReachable(true);
|
||||
// Start init directly — do NOT call setReachable(true) here.
|
||||
// setReachable(true) is deferred to initialize()'s success callback so the
|
||||
// plugin's reachableChanged handler can no longer trigger a second initialize()
|
||||
// call via signal re-entry. The connection sequence is:
|
||||
// TCP up → initialize() → setReachable(true) → reachableChanged(true) → UI update
|
||||
initialize();
|
||||
} else {
|
||||
// TCP actually dropped: abort any in-flight sequence and go offline.
|
||||
// The 30s poll timer will call connectDevice() → new TCP handshake.
|
||||
m_updating = false;
|
||||
m_initializing = false;
|
||||
setReachable(false);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user