mirror of
https://github.com/nymea/nymea-plugins.git
synced 2026-10-16 13:33:35 +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
|
||||
m_reconnectTimer.setInterval(5000);
|
||||
m_reconnectTimer.setSingleShot(false);
|
||||
@ -107,7 +119,7 @@ void EverestConnection::setMonitor(NetworkDeviceMonitor *monitor)
|
||||
connect(m_monitor, &NetworkDeviceMonitor::reachableChanged, this, &EverestConnection::onMonitorReachableChanged);
|
||||
}
|
||||
|
||||
if (m_monitor->reachable())
|
||||
if (m_monitor && m_monitor->reachable())
|
||||
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}::WebSockets)
|
||||
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