From 5879dd68c21fc34d2f7f9f71799b47aef6d9571d Mon Sep 17 00:00:00 2001 From: Orestes Sanchez Benavente Date: Tue, 28 Apr 2026 01:05:48 +0200 Subject: [PATCH 01/12] Allow reconnection on TCP failure. --- drivers.xml | 2 +- drivers/auxiliary/snapcap.cpp | 304 ++++++++++++++++++++++++++++------ drivers/auxiliary/snapcap.h | 31 ++++ 3 files changed, 285 insertions(+), 52 deletions(-) diff --git a/drivers.xml b/drivers.xml index 963b855341..08927da823 100644 --- a/drivers.xml +++ b/drivers.xml @@ -837,7 +837,7 @@ indi_snapcap - 1.3 + 1.5 indi_Excalibur diff --git a/drivers/auxiliary/snapcap.cpp b/drivers/auxiliary/snapcap.cpp index a0e0ca753b..53f76cfeda 100644 --- a/drivers/auxiliary/snapcap.cpp +++ b/drivers/auxiliary/snapcap.cpp @@ -30,11 +30,24 @@ #include "connectionplugins/connectionserial.h" #include "connectionplugins/connectiontcp.h" +#include #include +#include #include #include #include #include +#include + +namespace +{ +uint64_t monotonicMs() +{ + return std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()) + .count(); +} +} // We declare an auto pointer to SnapCap. std::unique_ptr snapcap(new SnapCap()); @@ -45,7 +58,7 @@ std::unique_ptr snapcap(new SnapCap()); SnapCap::SnapCap() : LightBoxInterface(this), DustCapInterface(this) { - setVersion(1, 4); + setVersion(1, 5); } SnapCap::~SnapCap() @@ -73,6 +86,13 @@ bool SnapCap::initProperties() ForceSP[1].fill("ON", "On", ISS_OFF); ForceSP.fill(getDeviceName(), "FORCE", "Force movement", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE); + ReconnectNP[0].fill("RETRIES", "Retries", "%.f", MIN_RECONNECT_RETRIES, MAX_RECONNECT_RETRIES, 1, + DEFAULT_MAX_CONSECUTIVE_FAILURES); + ReconnectNP[1].fill("DELAY_MS", "Delay (ms)", "%.f", MIN_RECONNECT_DELAY_MS, MAX_RECONNECT_DELAY_SETTING_MS, 100, + DEFAULT_RECONNECT_BASE_DELAY_MS); + ReconnectNP.fill(getDeviceName(), "RECONNECT_POLICY", "Reconnect", CONNECTION_TAB, IP_RW, 60, IPS_IDLE); + ReconnectNP.load(); + DI::initProperties(MAIN_CONTROL_TAB, CAN_ABORT); LI::initProperties(MAIN_CONTROL_TAB, CAN_DIM); @@ -113,6 +133,7 @@ bool SnapCap::initProperties() void SnapCap::ISGetProperties(const char *dev) { INDI::DefaultDevice::ISGetProperties(dev); + defineProperty(ReconnectNP); // Get Light box properties LI::ISGetProperties(dev); @@ -124,12 +145,11 @@ bool SnapCap::updateProperties() DI::updateProperties(); + if (hasLight) + LI::updateProperties(); + if (isConnected()) { - if (hasLight) - { - LI::updateProperties(); - } defineProperty(StatusTP); defineProperty(FirmwareTP); defineProperty(ForceSP); @@ -138,13 +158,11 @@ bool SnapCap::updateProperties() } else { - if (hasLight) - { - LI::updateProperties(); - } deleteProperty(StatusTP); deleteProperty(FirmwareTP); deleteProperty(ForceSP); + PortFD = -1; + resetReconnectState(); } return true; @@ -184,14 +202,46 @@ bool SnapCap::callHandshake() PortFD = tcpConnection->getPortFD(); } + resetReconnectState(); return Handshake(); } +void SnapCap::resetReconnectState() +{ + connectionFailureCount = 0; + reconnectPending = false; + reconnectAttemptCount = 0; + nextReconnectAttemptMs = 0; +} + bool SnapCap::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) { if (!dev || strcmp(dev, getDeviceName())) return false; + if (ReconnectNP.isNameMatch(name)) + { + ReconnectNP.update(values, names, n); + + int retries = static_cast(ReconnectNP[0].getValue()); + int delayMs = static_cast(ReconnectNP[1].getValue()); + + retries = std::clamp(retries, MIN_RECONNECT_RETRIES, MAX_RECONNECT_RETRIES); + delayMs = std::clamp(delayMs, MIN_RECONNECT_DELAY_MS, MAX_RECONNECT_DELAY_SETTING_MS); + + ReconnectNP[0].setValue(retries); + ReconnectNP[1].setValue(delayMs); + ReconnectNP.setState(IPS_OK); + ReconnectNP.apply(); + saveConfig(ReconnectNP); + + // Apply new timing immediately for any pending reconnect cycle. + if (reconnectPending) + nextReconnectAttemptMs = monotonicMs(); + + return true; + } + if (LI::processNumber(dev, name, values, names, n)) return true; @@ -240,6 +290,7 @@ bool SnapCap::ISSnoopDevice(XMLEle *root) bool SnapCap::saveConfigItems(FILE *fp) { INDI::DefaultDevice::saveConfigItems(fp); + ReconnectNP.save(fp); return LI::saveConfigItems(fp); } @@ -254,12 +305,155 @@ bool SnapCap::ping() return found; } +bool SnapCap::isConnectionValid() +{ + if (PortFD < 0) + { + return false; + } + + // For TCP connections, check if socket is still open by attempting a non-blocking check + if (getActiveConnection() == tcpConnection) + { + // Check if file descriptor is still valid using fstat + struct stat sb; + if (fstat(PortFD, &sb) < 0) + { + LOGF_WARN("TCP connection appears to be broken (fstat failed): %s", strerror(errno)); + return false; + } + } + + return true; +} + +bool SnapCap::attemptReconnect() +{ + LOGF_INFO("Attempting to reconnect to %s...", getDeviceName()); + + // Disconnect first + if (PortFD >= 0) + { + if (getActiveConnection() == serialConnection) + serialConnection->disconnect(); + else if (getActiveConnection() == tcpConnection) + tcpConnection->disconnect(); + } + + PortFD = -1; + + if (getActiveConnection() == serialConnection) + { + if (!serialConnection->connect()) + { + LOGF_ERROR("Failed to reconnect serial connection"); + return false; + } + PortFD = serialConnection->getPortFD(); + } + else if (getActiveConnection() == tcpConnection) + { + if (!tcpConnection->connect()) + { + LOGF_ERROR("Failed to reconnect TCP connection"); + return false; + } + PortFD = tcpConnection->getPortFD(); + } + + char response[SNAP_RES]; + if (!sendCommand(">V000", response)) + { + LOGF_ERROR("Device ping failed after reconnect"); + return false; + } + + resetReconnectState(); + LOGF_INFO("Successfully reconnected to %s", getDeviceName()); + return true; +} + +void SnapCap::scheduleReconnect(const char *reason) +{ + if (!reconnectPending) + { + LOGF_WARN("Scheduling non-blocking reconnect: %s", reason); + reconnectPending = true; + reconnectAttemptCount = 0; + } + + if (nextReconnectAttemptMs == 0) + nextReconnectAttemptMs = monotonicMs(); +} + +void SnapCap::processPendingReconnect() +{ + if (!reconnectPending) + return; + + const uint64_t nowMs = monotonicMs(); + if (nowMs < nextReconnectAttemptMs) + return; + + if (attemptReconnect()) + { + getStartupData(); + return; + } + + reconnectAttemptCount++; + const int backoffMs = computeReconnectDelayMs(); + nextReconnectAttemptMs = nowMs + static_cast(backoffMs); + LOGF_WARN("Reconnect attempt %u failed, next retry in %d ms", reconnectAttemptCount, backoffMs); +} + +int SnapCap::computeReconnectDelayMs() const +{ + int delayMs = reconnectBaseDelayMs(); + + // Use a capped exponential backoff: base * 2^attempt. + for (uint8_t i = 0; i < reconnectAttemptCount && delayMs < reconnectMaxDelayMs() / 2; i++) + delayMs *= 2; + + delayMs = std::min(delayMs, reconnectMaxDelayMs()); + + + return delayMs; +} + +int SnapCap::maxConsecutiveFailures() const +{ + return std::clamp(static_cast(ReconnectNP[0].getValue()), MIN_RECONNECT_RETRIES, MAX_RECONNECT_RETRIES); +} + +int SnapCap::reconnectBaseDelayMs() const +{ + return std::clamp(static_cast(ReconnectNP[1].getValue()), MIN_RECONNECT_DELAY_MS, + MAX_RECONNECT_DELAY_SETTING_MS); +} + +int SnapCap::reconnectMaxDelayMs() const +{ + return MAX_RECONNECT_DELAY_MS; +} + bool SnapCap::sendCommand(const char *command, char *response) { int nbytes_written = 0, nbytes_read = 0, rc = -1; char errstr[MAXRBUF]; - tcflush(PortFD, TCIOFLUSH); + if (!isConnectionValid()) + { + LOGF_WARN("Connection appears invalid before sending command: %s", command); + return false; + } + + // Flush any pending data, but handle errors gracefully + if (tcflush(PortFD, TCIOFLUSH) < 0) + { + LOGF_WARN("tcflush failed: %s", strerror(errno)); + // Don't return false here, tcflush failure isn't always fatal + } LOGF_DEBUG("CMD (%s)", command); @@ -269,14 +463,25 @@ bool SnapCap::sendCommand(const char *command, char *response) if ((rc = tty_write(PortFD, buffer, SNAP_CMD, &nbytes_written)) != TTY_OK) { tty_error_msg(rc, errstr, MAXRBUF); - LOGF_ERROR("%s error: %s.", command, errstr); + LOGF_ERROR("Write failed for command '%s': %s", command, errstr); + connectionFailureCount++; return false; } if ((rc = tty_nread_section(PortFD, response, SNAP_RES, '\n', SNAP_TIMEOUT, &nbytes_read)) != TTY_OK) { tty_error_msg(rc, errstr, MAXRBUF); - LOGF_ERROR("%s: %s.", command, errstr); + LOGF_ERROR("Read failed for command '%s': %s", command, errstr); + connectionFailureCount++; + return false; + } + + // Success - reset failure counter + connectionFailureCount = 0; + + if (nbytes_read < 3) + { + LOGF_ERROR("Response too short for command '%s': got %d bytes", command, nbytes_read); return false; } @@ -286,6 +491,23 @@ bool SnapCap::sendCommand(const char *command, char *response) return true; } +bool SnapCap::sendCommandWithRetry(const char *command, char *response) +{ + if (!sendCommand(command, response)) + { + maybeScheduleReconnect(command); + return false; + } + + return true; +} + +void SnapCap::maybeScheduleReconnect(const char *reason) +{ + if (connectionFailureCount >= maxConsecutiveFailures()) + scheduleReconnect(reason); +} + bool SnapCap::getStartupData() { bool rc1 = getFirmwareVersion(); @@ -303,15 +525,11 @@ IPState SnapCap::ParkCap() return IPS_BUSY; } - char command[SNAP_CMD]; char response[SNAP_RES]; - if (ForceSP[1].getState() == ISS_ON) - strncpy(command, ">c000", SNAP_CMD); // Force close command - else - strncpy(command, ">C000", SNAP_CMD); + const char *command = (ForceSP[1].getState() == ISS_ON) ? ">c000" : ">C000"; - if (!sendCommand(command, response)) + if (!sendCommandWithRetry(command, response)) return IPS_ALERT; if (strcmp(response, "*C000") == 0 || strcmp(response, "*c000") == 0) @@ -333,15 +551,11 @@ IPState SnapCap::UnParkCap() return IPS_BUSY; } - char command[SNAP_CMD]; char response[SNAP_RES]; - if (ForceSP[1].getState() == ISS_ON) - strncpy(command, ">o000", SNAP_CMD); // Force open command - else - strncpy(command, ">O000", SNAP_CMD); + const char *command = (ForceSP[1].getState() == ISS_ON) ? ">o000" : ">O000"; - if (!sendCommand(command, response)) + if (!sendCommandWithRetry(command, response)) return IPS_ALERT; if (strcmp(response, "*O000") == 0 || strcmp(response, "*o000") == 0) @@ -365,7 +579,7 @@ IPState SnapCap::AbortCap() char response[SNAP_RES]; - if (!sendCommand(">A000", response)) + if (!sendCommandWithRetry(">A000", response)) return IPS_ALERT; if (strcmp(response, "*A000") == 0) @@ -380,30 +594,18 @@ IPState SnapCap::AbortCap() bool SnapCap::EnableLightBox(bool enable) { - char command[SNAP_CMD]; char response[SNAP_RES]; if (isSimulation()) return true; - if (enable) - strncpy(command, ">L000", SNAP_CMD); - else - strncpy(command, ">D000", SNAP_CMD); + const char *command = enable ? ">L000" : ">D000"; + const char *expectedResponse = enable ? "*L000" : "*D000"; - if (!sendCommand(command, response)) + if (!sendCommandWithRetry(command, response)) return false; - char expectedResponse[SNAP_RES]; - if (enable) - snprintf(expectedResponse, SNAP_RES, "*L000"); - else - snprintf(expectedResponse, SNAP_RES, "*D000"); - - if (strcmp(response, expectedResponse) == 0) - return true; - - return false; + return strcmp(response, expectedResponse) == 0; } bool SnapCap::getStatus() @@ -428,17 +630,14 @@ bool SnapCap::getStatus() { response[2] = '0'; // Parked/Closed - if (ParkCapSP[CAP_PARK].getState() == ISS_ON) - response[4] = '2'; - else - response[4] = '1'; + response[4] = (ParkCapSP[CAP_PARK].getState() == ISS_ON) ? '2' : '1'; } response[3] = (LightSP[FLAT_LIGHT_ON].getState() == ISS_ON) ? '1' : '0'; } else { - if (!sendCommand(">S000", response)) + if (!sendCommandWithRetry(">S000", response)) return false; } @@ -572,7 +771,7 @@ bool SnapCap::getFirmwareVersion() char response[SNAP_RES]; - if (!sendCommand(">V000", response)) + if (!sendCommandWithRetry(">V000", response)) return false; char versionString[4] = { 0 }; @@ -585,10 +784,13 @@ bool SnapCap::getFirmwareVersion() void SnapCap::TimerHit() { - if (!isConnected()) + if (!isConnected() && !reconnectPending) return; - getStatus(); + processPendingReconnect(); + + if (isConnected() && !reconnectPending) + getStatus(); SetTimer(getCurrentPollingPeriod()); } @@ -602,7 +804,7 @@ bool SnapCap::getBrightness() char response[SNAP_RES]; - if (!sendCommand(">J000", response)) + if (!sendCommandWithRetry(">J000", response)) return false; int brightnessValue = 0; @@ -638,7 +840,7 @@ bool SnapCap::SetLightBoxBrightness(uint16_t value) snprintf(command, SNAP_CMD, ">B%03d", value); - if (!sendCommand(command, response)) + if (!sendCommandWithRetry(command, response)) return false; int brightnessValue = 0; diff --git a/drivers/auxiliary/snapcap.h b/drivers/auxiliary/snapcap.h index 06b9ed170b..614ff9333f 100644 --- a/drivers/auxiliary/snapcap.h +++ b/drivers/auxiliary/snapcap.h @@ -32,6 +32,8 @@ #include "indilightboxinterface.h" #include "indidustcapinterface.h" +#include + namespace Connection { class Serial; @@ -90,6 +92,12 @@ class SnapCap : public INDI::DefaultDevice, public INDI::LightBoxInterface, publ virtual bool SetLightBoxBrightness(uint16_t value) override; virtual bool EnableLightBox(bool enable) override; + // Retry/reconnect interface for subclasses. + bool sendCommandWithRetry(const char *command, char *response); + virtual int maxConsecutiveFailures() const; + virtual int reconnectBaseDelayMs() const; + virtual int reconnectMaxDelayMs() const; + private: bool getStartupData(); bool ping(); @@ -100,6 +108,13 @@ class SnapCap : public INDI::DefaultDevice, public INDI::LightBoxInterface, publ bool Handshake(); bool sendCommand(const char *command, char *response); + void maybeScheduleReconnect(const char *reason); + bool isConnectionValid(); + bool attemptReconnect(); + void scheduleReconnect(const char *reason); + void processPendingReconnect(); + void resetReconnectState(); + int computeReconnectDelayMs() const; // Status INDI::PropertyText StatusTP{3}; @@ -110,6 +125,9 @@ class SnapCap : public INDI::DefaultDevice, public INDI::LightBoxInterface, publ // Force open & close INDI::PropertySwitch ForceSP{2}; + // Reconnect policy + INDI::PropertyNumber ReconnectNP{2}; + int PortFD{ -1 }; bool hasLight{ true }; uint8_t simulationWorkCounter{ 0 }; @@ -122,6 +140,19 @@ class SnapCap : public INDI::DefaultDevice, public INDI::LightBoxInterface, publ Connection::Serial *serialConnection = nullptr; Connection::TCP *tcpConnection = nullptr; + // Connection reliability tracking + int connectionFailureCount{ 0 }; + static constexpr int DEFAULT_MAX_CONSECUTIVE_FAILURES = 2; + static constexpr int DEFAULT_RECONNECT_BASE_DELAY_MS = 500; + static constexpr int MIN_RECONNECT_RETRIES = 1; + static constexpr int MAX_RECONNECT_RETRIES = 10; + static constexpr int MIN_RECONNECT_DELAY_MS = 100; + static constexpr int MAX_RECONNECT_DELAY_SETTING_MS = 5000; + static constexpr int MAX_RECONNECT_DELAY_MS = 8000; + bool reconnectPending{ false }; + uint64_t nextReconnectAttemptMs{ 0 }; + uint8_t reconnectAttemptCount{ 0 }; + private: bool callHandshake(); uint8_t dustcapConnection = CONNECTION_SERIAL | CONNECTION_TCP; From b7cfe9310890383c542e47f14b17c1bc2dd2e041 Mon Sep 17 00:00:00 2001 From: Orestes Sanchez Benavente Date: Tue, 28 Apr 2026 01:51:42 +0200 Subject: [PATCH 02/12] Move retries code to a subclass. --- drivers/auxiliary/snapcap.cpp | 179 +++++++++++++++++++--------------- drivers/auxiliary/snapcap.h | 27 +++-- 2 files changed, 119 insertions(+), 87 deletions(-) diff --git a/drivers/auxiliary/snapcap.cpp b/drivers/auxiliary/snapcap.cpp index 53f76cfeda..30ec27b72b 100644 --- a/drivers/auxiliary/snapcap.cpp +++ b/drivers/auxiliary/snapcap.cpp @@ -49,6 +49,90 @@ uint64_t monotonicMs() } } +class SnapCapReconnect final : public SnapCap::ReconnectInterface +{ + public: + explicit SnapCapReconnect(SnapCap &owner) : m_Owner(owner) {} + + bool sendCommand(const char *command, char *response) override + { + if (!m_Owner.sendCommand(command, response)) + { + m_ConnectionFailureCount++; + if (m_ConnectionFailureCount >= m_Owner.maxConsecutiveFailures()) + schedule(command); + return false; + } + + m_ConnectionFailureCount = 0; + return true; + } + + void process() override + { + if (!m_ReconnectPending) + return; + + const uint64_t nowMs = monotonicMs(); + if (nowMs < m_NextReconnectAttemptMs) + return; + + if (m_Owner.attemptReconnect()) + { + reset(); + m_Owner.getStartupData(); + return; + } + + m_ReconnectAttemptCount++; + const int backoffMs = m_Owner.computeReconnectDelayMs(m_ReconnectAttemptCount); + m_NextReconnectAttemptMs = nowMs + static_cast(backoffMs); + INDI::Logger::getInstance().print(m_Owner.getDeviceName(), INDI::Logger::DBG_WARNING, __FILE__, __LINE__, + "Reconnect attempt %u failed, next retry in %d ms", + m_ReconnectAttemptCount, backoffMs); + } + + void reset() override + { + m_ConnectionFailureCount = 0; + m_ReconnectPending = false; + m_ReconnectAttemptCount = 0; + m_NextReconnectAttemptMs = 0; + } + + void rescheduleNow() override + { + if (m_ReconnectPending) + m_NextReconnectAttemptMs = monotonicMs(); + } + + bool isPending() const override + { + return m_ReconnectPending; + } + + private: + void schedule(const char *reason) + { + if (!m_ReconnectPending) + { + INDI::Logger::getInstance().print(m_Owner.getDeviceName(), INDI::Logger::DBG_WARNING, __FILE__, + __LINE__, "Scheduling non-blocking reconnect: %s", reason); + m_ReconnectPending = true; + m_ReconnectAttemptCount = 0; + } + + if (m_NextReconnectAttemptMs == 0) + m_NextReconnectAttemptMs = monotonicMs(); + } + + SnapCap &m_Owner; + int m_ConnectionFailureCount{ 0 }; + bool m_ReconnectPending{ false }; + uint64_t m_NextReconnectAttemptMs{ 0 }; + uint8_t m_ReconnectAttemptCount{ 0 }; +}; + // We declare an auto pointer to SnapCap. std::unique_ptr snapcap(new SnapCap()); @@ -56,7 +140,7 @@ std::unique_ptr snapcap(new SnapCap()); #define SNAP_RES 8 // Includes terminating null #define SNAP_TIMEOUT 3 -SnapCap::SnapCap() : LightBoxInterface(this), DustCapInterface(this) +SnapCap::SnapCap() : LightBoxInterface(this), DustCapInterface(this), reconnect(std::make_unique(*this)) { setVersion(1, 5); } @@ -162,7 +246,7 @@ bool SnapCap::updateProperties() deleteProperty(FirmwareTP); deleteProperty(ForceSP); PortFD = -1; - resetReconnectState(); + reconnect->reset(); } return true; @@ -202,18 +286,10 @@ bool SnapCap::callHandshake() PortFD = tcpConnection->getPortFD(); } - resetReconnectState(); + reconnect->reset(); return Handshake(); } -void SnapCap::resetReconnectState() -{ - connectionFailureCount = 0; - reconnectPending = false; - reconnectAttemptCount = 0; - nextReconnectAttemptMs = 0; -} - bool SnapCap::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) { if (!dev || strcmp(dev, getDeviceName())) @@ -236,8 +312,7 @@ bool SnapCap::ISNewNumber(const char *dev, const char *name, double values[], ch saveConfig(ReconnectNP); // Apply new timing immediately for any pending reconnect cycle. - if (reconnectPending) - nextReconnectAttemptMs = monotonicMs(); + reconnect->rescheduleNow(); return true; } @@ -335,27 +410,27 @@ bool SnapCap::attemptReconnect() if (PortFD >= 0) { if (getActiveConnection() == serialConnection) - serialConnection->disconnect(); + serialConnection->Disconnect(); else if (getActiveConnection() == tcpConnection) - tcpConnection->disconnect(); + tcpConnection->Disconnect(); } PortFD = -1; if (getActiveConnection() == serialConnection) { - if (!serialConnection->connect()) + if (!serialConnection->Connect()) { - LOGF_ERROR("Failed to reconnect serial connection"); + LOG_ERROR("Failed to reconnect serial connection"); return false; } PortFD = serialConnection->getPortFD(); } else if (getActiveConnection() == tcpConnection) { - if (!tcpConnection->connect()) + if (!tcpConnection->Connect()) { - LOGF_ERROR("Failed to reconnect TCP connection"); + LOG_ERROR("Failed to reconnect TCP connection"); return false; } PortFD = tcpConnection->getPortFD(); @@ -364,55 +439,20 @@ bool SnapCap::attemptReconnect() char response[SNAP_RES]; if (!sendCommand(">V000", response)) { - LOGF_ERROR("Device ping failed after reconnect"); + LOG_ERROR("Device ping failed after reconnect"); return false; } - resetReconnectState(); LOGF_INFO("Successfully reconnected to %s", getDeviceName()); return true; } -void SnapCap::scheduleReconnect(const char *reason) -{ - if (!reconnectPending) - { - LOGF_WARN("Scheduling non-blocking reconnect: %s", reason); - reconnectPending = true; - reconnectAttemptCount = 0; - } - - if (nextReconnectAttemptMs == 0) - nextReconnectAttemptMs = monotonicMs(); -} - -void SnapCap::processPendingReconnect() -{ - if (!reconnectPending) - return; - - const uint64_t nowMs = monotonicMs(); - if (nowMs < nextReconnectAttemptMs) - return; - - if (attemptReconnect()) - { - getStartupData(); - return; - } - - reconnectAttemptCount++; - const int backoffMs = computeReconnectDelayMs(); - nextReconnectAttemptMs = nowMs + static_cast(backoffMs); - LOGF_WARN("Reconnect attempt %u failed, next retry in %d ms", reconnectAttemptCount, backoffMs); -} - -int SnapCap::computeReconnectDelayMs() const +int SnapCap::computeReconnectDelayMs(uint8_t attemptCount) const { int delayMs = reconnectBaseDelayMs(); // Use a capped exponential backoff: base * 2^attempt. - for (uint8_t i = 0; i < reconnectAttemptCount && delayMs < reconnectMaxDelayMs() / 2; i++) + for (uint8_t i = 0; i < attemptCount && delayMs < reconnectMaxDelayMs() / 2; i++) delayMs *= 2; delayMs = std::min(delayMs, reconnectMaxDelayMs()); @@ -464,7 +504,6 @@ bool SnapCap::sendCommand(const char *command, char *response) { tty_error_msg(rc, errstr, MAXRBUF); LOGF_ERROR("Write failed for command '%s': %s", command, errstr); - connectionFailureCount++; return false; } @@ -472,13 +511,9 @@ bool SnapCap::sendCommand(const char *command, char *response) { tty_error_msg(rc, errstr, MAXRBUF); LOGF_ERROR("Read failed for command '%s': %s", command, errstr); - connectionFailureCount++; return false; } - // Success - reset failure counter - connectionFailureCount = 0; - if (nbytes_read < 3) { LOGF_ERROR("Response too short for command '%s': got %d bytes", command, nbytes_read); @@ -493,19 +528,7 @@ bool SnapCap::sendCommand(const char *command, char *response) bool SnapCap::sendCommandWithRetry(const char *command, char *response) { - if (!sendCommand(command, response)) - { - maybeScheduleReconnect(command); - return false; - } - - return true; -} - -void SnapCap::maybeScheduleReconnect(const char *reason) -{ - if (connectionFailureCount >= maxConsecutiveFailures()) - scheduleReconnect(reason); + return reconnect->sendCommand(command, response); } bool SnapCap::getStartupData() @@ -784,12 +807,12 @@ bool SnapCap::getFirmwareVersion() void SnapCap::TimerHit() { - if (!isConnected() && !reconnectPending) + if (!isConnected() && !reconnect->isPending()) return; - processPendingReconnect(); + reconnect->process(); - if (isConnected() && !reconnectPending) + if (isConnected() && !reconnect->isPending()) getStatus(); SetTimer(getCurrentPollingPeriod()); diff --git a/drivers/auxiliary/snapcap.h b/drivers/auxiliary/snapcap.h index 614ff9333f..5f18027a88 100644 --- a/drivers/auxiliary/snapcap.h +++ b/drivers/auxiliary/snapcap.h @@ -33,6 +33,9 @@ #include "indidustcapinterface.h" #include +#include + +class SnapCapReconnect; namespace Connection { @@ -80,6 +83,17 @@ class SnapCap : public INDI::DefaultDevice, public INDI::LightBoxInterface, publ protected: const char *getDefaultName() override; + class ReconnectInterface + { + public: + virtual ~ReconnectInterface() = default; + virtual bool sendCommand(const char *command, char *response) = 0; + virtual void process() = 0; + virtual void reset() = 0; + virtual void rescheduleNow() = 0; + virtual bool isPending() const = 0; + }; + virtual bool saveConfigItems(FILE *fp) override; void TimerHit() override; @@ -99,6 +113,8 @@ class SnapCap : public INDI::DefaultDevice, public INDI::LightBoxInterface, publ virtual int reconnectMaxDelayMs() const; private: + friend class SnapCapReconnect; + bool getStartupData(); bool ping(); bool getStatus(); @@ -108,13 +124,9 @@ class SnapCap : public INDI::DefaultDevice, public INDI::LightBoxInterface, publ bool Handshake(); bool sendCommand(const char *command, char *response); - void maybeScheduleReconnect(const char *reason); bool isConnectionValid(); bool attemptReconnect(); - void scheduleReconnect(const char *reason); - void processPendingReconnect(); - void resetReconnectState(); - int computeReconnectDelayMs() const; + int computeReconnectDelayMs(uint8_t attemptCount) const; // Status INDI::PropertyText StatusTP{3}; @@ -139,9 +151,9 @@ class SnapCap : public INDI::DefaultDevice, public INDI::LightBoxInterface, publ Connection::Serial *serialConnection = nullptr; Connection::TCP *tcpConnection = nullptr; + std::unique_ptr reconnect; // Connection reliability tracking - int connectionFailureCount{ 0 }; static constexpr int DEFAULT_MAX_CONSECUTIVE_FAILURES = 2; static constexpr int DEFAULT_RECONNECT_BASE_DELAY_MS = 500; static constexpr int MIN_RECONNECT_RETRIES = 1; @@ -149,9 +161,6 @@ class SnapCap : public INDI::DefaultDevice, public INDI::LightBoxInterface, publ static constexpr int MIN_RECONNECT_DELAY_MS = 100; static constexpr int MAX_RECONNECT_DELAY_SETTING_MS = 5000; static constexpr int MAX_RECONNECT_DELAY_MS = 8000; - bool reconnectPending{ false }; - uint64_t nextReconnectAttemptMs{ 0 }; - uint8_t reconnectAttemptCount{ 0 }; private: bool callHandshake(); From d62404d691c53da82bedcdfba8aa226729f54f58 Mon Sep 17 00:00:00 2001 From: Orestes Sanchez Benavente Date: Wed, 29 Apr 2026 00:39:17 +0200 Subject: [PATCH 03/12] fix: only flush on serial connections --- drivers/auxiliary/snapcap.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/auxiliary/snapcap.cpp b/drivers/auxiliary/snapcap.cpp index 30ec27b72b..8d7f6f24ee 100644 --- a/drivers/auxiliary/snapcap.cpp +++ b/drivers/auxiliary/snapcap.cpp @@ -488,12 +488,9 @@ bool SnapCap::sendCommand(const char *command, char *response) return false; } - // Flush any pending data, but handle errors gracefully - if (tcflush(PortFD, TCIOFLUSH) < 0) - { - LOGF_WARN("tcflush failed: %s", strerror(errno)); - // Don't return false here, tcflush failure isn't always fatal - } + // Flush any pending data — only valid for serial TTY file descriptors, not TCP sockets. + if (isatty(PortFD)) + tcflush(PortFD, TCIOFLUSH); LOGF_DEBUG("CMD (%s)", command); From c8a551cba66803e7ecdf9fd74cd18d36f6ba0d5a Mon Sep 17 00:00:00 2001 From: Orestes Sanchez Benavente Date: Wed, 29 Apr 2026 01:24:55 +0200 Subject: [PATCH 04/12] Set appropiate connection status on failure and recovery. --- drivers/auxiliary/snapcap.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/auxiliary/snapcap.cpp b/drivers/auxiliary/snapcap.cpp index 8d7f6f24ee..0e356dbe09 100644 --- a/drivers/auxiliary/snapcap.cpp +++ b/drivers/auxiliary/snapcap.cpp @@ -422,6 +422,7 @@ bool SnapCap::attemptReconnect() if (!serialConnection->Connect()) { LOG_ERROR("Failed to reconnect serial connection"); + setConnected(false, IPS_ALERT); return false; } PortFD = serialConnection->getPortFD(); @@ -431,6 +432,7 @@ bool SnapCap::attemptReconnect() if (!tcpConnection->Connect()) { LOG_ERROR("Failed to reconnect TCP connection"); + setConnected(false, IPS_ALERT); return false; } PortFD = tcpConnection->getPortFD(); @@ -440,10 +442,12 @@ bool SnapCap::attemptReconnect() if (!sendCommand(">V000", response)) { LOG_ERROR("Device ping failed after reconnect"); + setConnected(false, IPS_ALERT); return false; } LOGF_INFO("Successfully reconnected to %s", getDeviceName()); + setConnected(true, IPS_OK); return true; } From 0b41bf1be6c60d658e9d65618e5b3d98d7b89bd7 Mon Sep 17 00:00:00 2001 From: Orestes Sanchez Benavente Date: Wed, 29 Apr 2026 01:37:48 +0200 Subject: [PATCH 05/12] Set disconnected on failures. --- drivers/auxiliary/snapcap.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/auxiliary/snapcap.cpp b/drivers/auxiliary/snapcap.cpp index 0e356dbe09..eb8e6c3c4d 100644 --- a/drivers/auxiliary/snapcap.cpp +++ b/drivers/auxiliary/snapcap.cpp @@ -59,6 +59,7 @@ class SnapCapReconnect final : public SnapCap::ReconnectInterface if (!m_Owner.sendCommand(command, response)) { m_ConnectionFailureCount++; + m_Owner.setConnected(false, IPS_ALERT); if (m_ConnectionFailureCount >= m_Owner.maxConsecutiveFailures()) schedule(command); return false; @@ -409,10 +410,14 @@ bool SnapCap::attemptReconnect() // Disconnect first if (PortFD >= 0) { - if (getActiveConnection() == serialConnection) + if (getActiveConnection() == serialConnection) { serialConnection->Disconnect(); - else if (getActiveConnection() == tcpConnection) + setConnected(false, IPS_ALERT); + } + else if (getActiveConnection() == tcpConnection) { tcpConnection->Disconnect(); + setConnected(false, IPS_ALERT); + } } PortFD = -1; From b2d596e8d954e5162cc0e467602efbbe70cd24c5 Mon Sep 17 00:00:00 2001 From: Orestes Sanchez Benavente Date: Wed, 29 Apr 2026 01:44:06 +0200 Subject: [PATCH 06/12] Set connect when connection is restablished. --- drivers/auxiliary/snapcap.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/auxiliary/snapcap.cpp b/drivers/auxiliary/snapcap.cpp index eb8e6c3c4d..a66f82e22f 100644 --- a/drivers/auxiliary/snapcap.cpp +++ b/drivers/auxiliary/snapcap.cpp @@ -82,6 +82,7 @@ class SnapCapReconnect final : public SnapCap::ReconnectInterface { reset(); m_Owner.getStartupData(); + m_Owner.setConnected(true, IPS_OK); return; } From dfe381eb82587dcab452615ea653b8b040a8d4c7 Mon Sep 17 00:00:00 2001 From: Orestes Sanchez Benavente Date: Wed, 29 Apr 2026 01:47:35 +0200 Subject: [PATCH 07/12] Set connected on errors and on recovery. --- drivers/auxiliary/snapcap.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/auxiliary/snapcap.cpp b/drivers/auxiliary/snapcap.cpp index a66f82e22f..20e5e93565 100644 --- a/drivers/auxiliary/snapcap.cpp +++ b/drivers/auxiliary/snapcap.cpp @@ -59,7 +59,7 @@ class SnapCapReconnect final : public SnapCap::ReconnectInterface if (!m_Owner.sendCommand(command, response)) { m_ConnectionFailureCount++; - m_Owner.setConnected(false, IPS_ALERT); + m_Owner.setConnected(true, IPS_ALERT); if (m_ConnectionFailureCount >= m_Owner.maxConsecutiveFailures()) schedule(command); return false; @@ -428,7 +428,7 @@ bool SnapCap::attemptReconnect() if (!serialConnection->Connect()) { LOG_ERROR("Failed to reconnect serial connection"); - setConnected(false, IPS_ALERT); + setConnected(true, IPS_ALERT); return false; } PortFD = serialConnection->getPortFD(); @@ -438,7 +438,7 @@ bool SnapCap::attemptReconnect() if (!tcpConnection->Connect()) { LOG_ERROR("Failed to reconnect TCP connection"); - setConnected(false, IPS_ALERT); + setConnected(true, IPS_ALERT); return false; } PortFD = tcpConnection->getPortFD(); @@ -448,7 +448,7 @@ bool SnapCap::attemptReconnect() if (!sendCommand(">V000", response)) { LOG_ERROR("Device ping failed after reconnect"); - setConnected(false, IPS_ALERT); + setConnected(true, IPS_ALERT); return false; } From 44952276bd096efb40026b3fe70de1928a6431b8 Mon Sep 17 00:00:00 2001 From: Orestes Sanchez Benavente Date: Wed, 29 Apr 2026 01:51:12 +0200 Subject: [PATCH 08/12] Remove status update --- drivers/auxiliary/snapcap.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/auxiliary/snapcap.cpp b/drivers/auxiliary/snapcap.cpp index 20e5e93565..3f20bd33bb 100644 --- a/drivers/auxiliary/snapcap.cpp +++ b/drivers/auxiliary/snapcap.cpp @@ -59,7 +59,7 @@ class SnapCapReconnect final : public SnapCap::ReconnectInterface if (!m_Owner.sendCommand(command, response)) { m_ConnectionFailureCount++; - m_Owner.setConnected(true, IPS_ALERT); + // m_Owner.setConnected(true, IPS_ALERT); if (m_ConnectionFailureCount >= m_Owner.maxConsecutiveFailures()) schedule(command); return false; @@ -82,7 +82,7 @@ class SnapCapReconnect final : public SnapCap::ReconnectInterface { reset(); m_Owner.getStartupData(); - m_Owner.setConnected(true, IPS_OK); + // m_Owner.setConnected(true, IPS_OK); return; } From fec9b8ad52a23a9739ff003b76f9a418f701427c Mon Sep 17 00:00:00 2001 From: Orestes Sanchez Benavente Date: Wed, 29 Apr 2026 02:00:02 +0200 Subject: [PATCH 09/12] Remove obsolete comments. --- drivers/auxiliary/snapcap.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/auxiliary/snapcap.cpp b/drivers/auxiliary/snapcap.cpp index 3f20bd33bb..c665f42888 100644 --- a/drivers/auxiliary/snapcap.cpp +++ b/drivers/auxiliary/snapcap.cpp @@ -59,7 +59,6 @@ class SnapCapReconnect final : public SnapCap::ReconnectInterface if (!m_Owner.sendCommand(command, response)) { m_ConnectionFailureCount++; - // m_Owner.setConnected(true, IPS_ALERT); if (m_ConnectionFailureCount >= m_Owner.maxConsecutiveFailures()) schedule(command); return false; @@ -82,7 +81,6 @@ class SnapCapReconnect final : public SnapCap::ReconnectInterface { reset(); m_Owner.getStartupData(); - // m_Owner.setConnected(true, IPS_OK); return; } From eac14ab90f4357e92e1e3eba3cb04a13c89dab16 Mon Sep 17 00:00:00 2001 From: Orestes Sanchez Benavente Date: Wed, 29 Apr 2026 21:27:21 +0200 Subject: [PATCH 10/12] Rename properties to better match their meaning. --- drivers/auxiliary/snapcap.cpp | 35 +++++++++++++++++++++++------------ drivers/auxiliary/snapcap.h | 4 ++-- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/drivers/auxiliary/snapcap.cpp b/drivers/auxiliary/snapcap.cpp index c665f42888..fe275ac934 100644 --- a/drivers/auxiliary/snapcap.cpp +++ b/drivers/auxiliary/snapcap.cpp @@ -87,9 +87,10 @@ class SnapCapReconnect final : public SnapCap::ReconnectInterface m_ReconnectAttemptCount++; const int backoffMs = m_Owner.computeReconnectDelayMs(m_ReconnectAttemptCount); m_NextReconnectAttemptMs = nowMs + static_cast(backoffMs); - INDI::Logger::getInstance().print(m_Owner.getDeviceName(), INDI::Logger::DBG_WARNING, __FILE__, __LINE__, - "Reconnect attempt %u failed, next retry in %d ms", - m_ReconnectAttemptCount, backoffMs); + INDI::Logger::getInstance().print( + m_Owner.getDeviceName(), INDI::Logger::DBG_WARNING, __FILE__, __LINE__, + "Reconnect attempt %u failed, next retry in %d ms (base delay %d ms, exponential backoff)", + m_ReconnectAttemptCount, backoffMs, m_Owner.reconnectBaseDelayMs()); } void reset() override @@ -116,8 +117,10 @@ class SnapCapReconnect final : public SnapCap::ReconnectInterface { if (!m_ReconnectPending) { - INDI::Logger::getInstance().print(m_Owner.getDeviceName(), INDI::Logger::DBG_WARNING, __FILE__, - __LINE__, "Scheduling non-blocking reconnect: %s", reason); + INDI::Logger::getInstance().print( + m_Owner.getDeviceName(), INDI::Logger::DBG_WARNING, __FILE__, __LINE__, + "Scheduling non-blocking reconnect after %d consecutive command failures (reason: %s)", + m_Owner.maxConsecutiveFailures(), reason); m_ReconnectPending = true; m_ReconnectAttemptCount = 0; } @@ -170,11 +173,13 @@ bool SnapCap::initProperties() ForceSP[1].fill("ON", "On", ISS_OFF); ForceSP.fill(getDeviceName(), "FORCE", "Force movement", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE); - ReconnectNP[0].fill("RETRIES", "Retries", "%.f", MIN_RECONNECT_RETRIES, MAX_RECONNECT_RETRIES, 1, + ReconnectNP[0].fill("FAILURES_BEFORE_RECONNECT", "Failures Before Reconnect", "%.f", + MIN_FAILURES_BEFORE_RECONNECT, MAX_FAILURES_BEFORE_RECONNECT, 1, DEFAULT_MAX_CONSECUTIVE_FAILURES); - ReconnectNP[1].fill("DELAY_MS", "Delay (ms)", "%.f", MIN_RECONNECT_DELAY_MS, MAX_RECONNECT_DELAY_SETTING_MS, 100, + ReconnectNP[1].fill("DELAY_MS", "Backoff Base Delay (ms)", "%.f", MIN_RECONNECT_DELAY_MS, + MAX_RECONNECT_DELAY_SETTING_MS, 100, DEFAULT_RECONNECT_BASE_DELAY_MS); - ReconnectNP.fill(getDeviceName(), "RECONNECT_POLICY", "Reconnect", CONNECTION_TAB, IP_RW, 60, IPS_IDLE); + ReconnectNP.fill(getDeviceName(), "RECONNECT_POLICY", "Reconnect Policy", CONNECTION_TAB, IP_RW, 60, IPS_IDLE); ReconnectNP.load(); DI::initProperties(MAIN_CONTROL_TAB, CAN_ABORT); @@ -299,18 +304,23 @@ bool SnapCap::ISNewNumber(const char *dev, const char *name, double values[], ch { ReconnectNP.update(values, names, n); - int retries = static_cast(ReconnectNP[0].getValue()); + int failuresBeforeReconnect = static_cast(ReconnectNP[0].getValue()); int delayMs = static_cast(ReconnectNP[1].getValue()); - retries = std::clamp(retries, MIN_RECONNECT_RETRIES, MAX_RECONNECT_RETRIES); + failuresBeforeReconnect = + std::clamp(failuresBeforeReconnect, MIN_FAILURES_BEFORE_RECONNECT, MAX_FAILURES_BEFORE_RECONNECT); delayMs = std::clamp(delayMs, MIN_RECONNECT_DELAY_MS, MAX_RECONNECT_DELAY_SETTING_MS); - ReconnectNP[0].setValue(retries); + ReconnectNP[0].setValue(failuresBeforeReconnect); ReconnectNP[1].setValue(delayMs); ReconnectNP.setState(IPS_OK); ReconnectNP.apply(); saveConfig(ReconnectNP); + LOGF_INFO( + "Reconnect policy updated: reconnect after %d consecutive command failures; base backoff delay %d ms", + failuresBeforeReconnect, delayMs); + // Apply new timing immediately for any pending reconnect cycle. reconnect->rescheduleNow(); @@ -471,7 +481,8 @@ int SnapCap::computeReconnectDelayMs(uint8_t attemptCount) const int SnapCap::maxConsecutiveFailures() const { - return std::clamp(static_cast(ReconnectNP[0].getValue()), MIN_RECONNECT_RETRIES, MAX_RECONNECT_RETRIES); + return std::clamp(static_cast(ReconnectNP[0].getValue()), MIN_FAILURES_BEFORE_RECONNECT, + MAX_FAILURES_BEFORE_RECONNECT); } int SnapCap::reconnectBaseDelayMs() const diff --git a/drivers/auxiliary/snapcap.h b/drivers/auxiliary/snapcap.h index 5f18027a88..a79f20b960 100644 --- a/drivers/auxiliary/snapcap.h +++ b/drivers/auxiliary/snapcap.h @@ -156,8 +156,8 @@ class SnapCap : public INDI::DefaultDevice, public INDI::LightBoxInterface, publ // Connection reliability tracking static constexpr int DEFAULT_MAX_CONSECUTIVE_FAILURES = 2; static constexpr int DEFAULT_RECONNECT_BASE_DELAY_MS = 500; - static constexpr int MIN_RECONNECT_RETRIES = 1; - static constexpr int MAX_RECONNECT_RETRIES = 10; + static constexpr int MIN_FAILURES_BEFORE_RECONNECT = 1; + static constexpr int MAX_FAILURES_BEFORE_RECONNECT = 10; static constexpr int MIN_RECONNECT_DELAY_MS = 100; static constexpr int MAX_RECONNECT_DELAY_SETTING_MS = 5000; static constexpr int MAX_RECONNECT_DELAY_MS = 8000; From 18af0eaae71709ecf02b098082cfaeca4c911b07 Mon Sep 17 00:00:00 2001 From: Orestes Sanchez Benavente Date: Wed, 29 Apr 2026 22:04:27 +0200 Subject: [PATCH 11/12] Update labels --- drivers/auxiliary/snapcap.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/auxiliary/snapcap.cpp b/drivers/auxiliary/snapcap.cpp index fe275ac934..8d10248470 100644 --- a/drivers/auxiliary/snapcap.cpp +++ b/drivers/auxiliary/snapcap.cpp @@ -173,10 +173,10 @@ bool SnapCap::initProperties() ForceSP[1].fill("ON", "On", ISS_OFF); ForceSP.fill(getDeviceName(), "FORCE", "Force movement", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 0, IPS_IDLE); - ReconnectNP[0].fill("FAILURES_BEFORE_RECONNECT", "Failures Before Reconnect", "%.f", + ReconnectNP[0].fill("FAILURES_BEFORE_RECONNECT", "Max Failures", "%.f", MIN_FAILURES_BEFORE_RECONNECT, MAX_FAILURES_BEFORE_RECONNECT, 1, DEFAULT_MAX_CONSECUTIVE_FAILURES); - ReconnectNP[1].fill("DELAY_MS", "Backoff Base Delay (ms)", "%.f", MIN_RECONNECT_DELAY_MS, + ReconnectNP[1].fill("DELAY_MS", "Base delay (ms)", "%.f", MIN_RECONNECT_DELAY_MS, MAX_RECONNECT_DELAY_SETTING_MS, 100, DEFAULT_RECONNECT_BASE_DELAY_MS); ReconnectNP.fill(getDeviceName(), "RECONNECT_POLICY", "Reconnect Policy", CONNECTION_TAB, IP_RW, 60, IPS_IDLE); From 3e26cb4ccc40e9b4b520ea45f231d2b280523e28 Mon Sep 17 00:00:00 2001 From: Orestes Sanchez Benavente Date: Thu, 30 Apr 2026 01:02:03 +0200 Subject: [PATCH 12/12] Remove unused variable. --- libs/indibase/indidriver.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libs/indibase/indidriver.c b/libs/indibase/indidriver.c index 3270318035..bbc68e5ad3 100644 --- a/libs/indibase/indidriver.c +++ b/libs/indibase/indidriver.c @@ -822,8 +822,7 @@ int IUGetConfigOnSwitchName(const char *dev, const char *property, char *name, s if ((property && !strcmp(property, rname)) || property == NULL) { XMLEle *oneSwitch = NULL; - int currentIndex = 0; - for (oneSwitch = nextXMLEle(root, 1); oneSwitch != NULL; oneSwitch = nextXMLEle(root, 0), currentIndex++) + for (oneSwitch = nextXMLEle(root, 1); oneSwitch != NULL; oneSwitch = nextXMLEle(root, 0)) { ISState s = ISS_OFF; if (crackISState(pcdataXMLEle(oneSwitch), &s) == 0 && s == ISS_ON)