mirror of
https://github.com/nymea/nymea-plugins.git
synced 2026-10-21 14:23:24 +02:00
EVerest: Fix initial connection race condition and implement automatic reconnect mechanism
This commit is contained in:
parent
073ff88f32
commit
44158b8aec
@ -48,6 +48,18 @@ EverestConnection::EverestConnection(quint16 port, QObject *parent)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Connection refused or failed init does not change availability, so availableChanged alone never triggers a retry
|
||||||
|
connect(m_client, &EverestJsonRpcClient::connectionErrorOccurred, this, [this](){
|
||||||
|
if (!m_running || m_reconnectTimer.isActive())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_monitor && !m_monitor->reachable())
|
||||||
|
return;
|
||||||
|
|
||||||
|
qCDebug(dcEverest()) << "Connection error occurred. Starting reconnect timer for JsonRpc client to" << m_client->serverUrl().toString();
|
||||||
|
m_reconnectTimer.start();
|
||||||
|
});
|
||||||
|
|
||||||
// Reconnect timer in case the network device is reachable but the websocketserver not yet
|
// Reconnect timer in case the network device is reachable but the websocketserver not yet
|
||||||
m_reconnectTimer.setInterval(5000);
|
m_reconnectTimer.setInterval(5000);
|
||||||
m_reconnectTimer.setSingleShot(false);
|
m_reconnectTimer.setSingleShot(false);
|
||||||
@ -107,7 +119,7 @@ void EverestConnection::setMonitor(NetworkDeviceMonitor *monitor)
|
|||||||
connect(m_monitor, &NetworkDeviceMonitor::reachableChanged, this, &EverestConnection::onMonitorReachableChanged);
|
connect(m_monitor, &NetworkDeviceMonitor::reachableChanged, this, &EverestConnection::onMonitorReachableChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_monitor->reachable())
|
if (m_monitor && m_monitor->reachable())
|
||||||
onMonitorReachableChanged(true);
|
onMonitorReachableChanged(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,3 +14,21 @@ target_link_libraries(tst_rfidproviderclient PRIVATE
|
|||||||
Qt${NYMEA_TEST_QT_MAJOR_VERSION}::Test Qt${NYMEA_TEST_QT_MAJOR_VERSION}::Network
|
Qt${NYMEA_TEST_QT_MAJOR_VERSION}::Test Qt${NYMEA_TEST_QT_MAJOR_VERSION}::Network
|
||||||
Qt${NYMEA_TEST_QT_MAJOR_VERSION}::WebSockets)
|
Qt${NYMEA_TEST_QT_MAJOR_VERSION}::WebSockets)
|
||||||
add_test(NAME everest-rfid COMMAND ${DBUS_RUN_SESSION} -- $<TARGET_FILE:tst_rfidproviderclient>)
|
add_test(NAME everest-rfid COMMAND ${DBUS_RUN_SESSION} -- $<TARGET_FILE:tst_rfidproviderclient>)
|
||||||
|
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(NYMEA REQUIRED IMPORTED_TARGET nymea)
|
||||||
|
find_program(NYMEA_PLUGININFO_COMPILER nymea-plugininfocompiler REQUIRED)
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/plugininfo.h ${CMAKE_CURRENT_BINARY_DIR}/extern-plugininfo.h
|
||||||
|
COMMAND ${NYMEA_PLUGININFO_COMPILER} ${CMAKE_CURRENT_SOURCE_DIR}/../integrationplugineverest.json
|
||||||
|
-o ${CMAKE_CURRENT_BINARY_DIR}/plugininfo.h -e ${CMAKE_CURRENT_BINARY_DIR}/extern-plugininfo.h
|
||||||
|
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../integrationplugineverest.json)
|
||||||
|
add_executable(tst_everestconnection tst_everestconnection.cpp
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/plugininfo.h
|
||||||
|
../jsonrpc/everestconnection.cpp ../jsonrpc/everestevse.cpp ../jsonrpc/everestjsonrpcclient.cpp
|
||||||
|
../jsonrpc/everestjsonrpcinterface.cpp ../jsonrpc/everestjsonrpcreply.cpp)
|
||||||
|
target_include_directories(tst_everestconnection PRIVATE .. ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
target_link_libraries(tst_everestconnection PRIVATE PkgConfig::NYMEA
|
||||||
|
Qt${NYMEA_TEST_QT_MAJOR_VERSION}::Core Qt${NYMEA_TEST_QT_MAJOR_VERSION}::Test
|
||||||
|
Qt${NYMEA_TEST_QT_MAJOR_VERSION}::Network Qt${NYMEA_TEST_QT_MAJOR_VERSION}::WebSockets)
|
||||||
|
add_test(NAME everest-connection COMMAND $<TARGET_FILE:tst_everestconnection>)
|
||||||
|
|||||||
131
everest/tests/tst_everestconnection.cpp
Normal file
131
everest/tests/tst_everestconnection.cpp
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#include <QtTest>
|
||||||
|
#include <QTcpServer>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QWebSocket>
|
||||||
|
#include <QWebSocketServer>
|
||||||
|
|
||||||
|
#include "plugininfo.h"
|
||||||
|
#include "loggingcategories.h"
|
||||||
|
#include "jsonrpc/everestconnection.h"
|
||||||
|
#include "jsonrpc/everestjsonrpcclient.h"
|
||||||
|
|
||||||
|
NYMEA_LOGGING_CATEGORY(dcEverestTraffic, "EverestTraffic")
|
||||||
|
|
||||||
|
class FakeMonitor : public NetworkDeviceMonitor
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit FakeMonitor(QObject *parent = nullptr) : NetworkDeviceMonitor(parent)
|
||||||
|
{
|
||||||
|
m_info.setAddress(QHostAddress::LocalHost);
|
||||||
|
}
|
||||||
|
|
||||||
|
MacAddress macAddress() const override { return MacAddress(); }
|
||||||
|
QString hostName() const override { return QString(); }
|
||||||
|
QHostAddress address() const override { return QHostAddress::LocalHost; }
|
||||||
|
NetworkDeviceInfo::MonitorMode monitorMode() const override { return NetworkDeviceInfo::MonitorModeIp; }
|
||||||
|
NetworkDeviceInfo networkDeviceInfo() const override { return m_info; }
|
||||||
|
bool reachable() const override { return true; }
|
||||||
|
QDateTime lastSeen() const override { return QDateTime::currentDateTime(); }
|
||||||
|
uint pingRetries() const override { return 0; }
|
||||||
|
void setPingRetries(uint) override { }
|
||||||
|
|
||||||
|
private:
|
||||||
|
NetworkDeviceInfo m_info;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Minimal RpcApi answering the init sequence of EverestJsonRpcClient
|
||||||
|
class FakeRpcApi : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
QWebSocketServer server{"fake-everest", QWebSocketServer::NonSecureMode};
|
||||||
|
int connectionCount = 0;
|
||||||
|
|
||||||
|
bool listen(quint16 port)
|
||||||
|
{
|
||||||
|
connect(&server, &QWebSocketServer::newConnection, this, [this](){
|
||||||
|
QWebSocket *socket = server.nextPendingConnection();
|
||||||
|
connectionCount++;
|
||||||
|
connect(socket, &QWebSocket::disconnected, socket, &QWebSocket::deleteLater);
|
||||||
|
connect(socket, &QWebSocket::textMessageReceived, socket, [socket](const QString &message){
|
||||||
|
QVariantMap request = QJsonDocument::fromJson(message.toUtf8()).toVariant().toMap();
|
||||||
|
QVariantMap result;
|
||||||
|
const QString method = request.value("method").toString();
|
||||||
|
if (method == "API.Hello") {
|
||||||
|
result = {{"api_version", "1.0"}, {"everest_version", "test"}, {"authentication_required", false}, {"charger_info", QVariantMap()}};
|
||||||
|
} else if (method == "ChargePoint.GetEVSEInfos") {
|
||||||
|
result = {{"error", "NoError"}, {"infos", QVariantList()}};
|
||||||
|
} else {
|
||||||
|
result = {{"error", "NoError"}};
|
||||||
|
}
|
||||||
|
QVariantMap response = {{"jsonrpc", "2.0"}, {"id", request.value("id")}, {"result", result}};
|
||||||
|
socket->sendTextMessage(QString::fromUtf8(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Compact)));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return server.listen(QHostAddress::LocalHost, port);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class TestEverestConnection : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
static quint16 freePort()
|
||||||
|
{
|
||||||
|
QTcpServer probe;
|
||||||
|
probe.listen(QHostAddress::LocalHost, 0);
|
||||||
|
return probe.serverPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void reconnectAfterConnectionRefused()
|
||||||
|
{
|
||||||
|
const quint16 port = freePort();
|
||||||
|
QVERIFY(port != 0);
|
||||||
|
|
||||||
|
FakeMonitor monitor;
|
||||||
|
EverestConnection connection(port);
|
||||||
|
connection.setMonitor(&monitor);
|
||||||
|
QSignalSpy errorSpy(connection.client(), &EverestJsonRpcClient::connectionErrorOccurred);
|
||||||
|
QSignalSpy availableSpy(&connection, &EverestConnection::availableChanged);
|
||||||
|
|
||||||
|
// Server not up yet, like EVerest still booting
|
||||||
|
connection.start();
|
||||||
|
QTRY_VERIFY_WITH_TIMEOUT(errorSpy.count() >= 1, 2000);
|
||||||
|
QVERIFY(!connection.available());
|
||||||
|
|
||||||
|
FakeRpcApi api;
|
||||||
|
QVERIFY(api.listen(port));
|
||||||
|
|
||||||
|
QTRY_VERIFY_WITH_TIMEOUT(connection.available(), 8000);
|
||||||
|
QCOMPARE(availableSpy.count(), 1);
|
||||||
|
QCOMPARE(availableSpy.first().first().toBool(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void noReconnectWhenStopped()
|
||||||
|
{
|
||||||
|
const quint16 port = freePort();
|
||||||
|
QVERIFY(port != 0);
|
||||||
|
|
||||||
|
FakeMonitor monitor;
|
||||||
|
EverestConnection connection(port);
|
||||||
|
connection.setMonitor(&monitor);
|
||||||
|
QSignalSpy errorSpy(connection.client(), &EverestJsonRpcClient::connectionErrorOccurred);
|
||||||
|
|
||||||
|
connection.start();
|
||||||
|
QTRY_VERIFY_WITH_TIMEOUT(errorSpy.count() >= 1, 2000);
|
||||||
|
connection.stop();
|
||||||
|
|
||||||
|
FakeRpcApi api;
|
||||||
|
QVERIFY(api.listen(port));
|
||||||
|
|
||||||
|
QTest::qWait(6000);
|
||||||
|
QVERIFY(!connection.available());
|
||||||
|
QCOMPARE(api.connectionCount, 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
QTEST_GUILESS_MAIN(TestEverestConnection)
|
||||||
|
#include "tst_everestconnection.moc"
|
||||||
Loading…
x
Reference in New Issue
Block a user