EVerest: Improve connection initialization

The initialization has now a retry logic if there are no data vailable yet.
Retry up to 4 times to create a localhost instance.
The max charging current will be ignored if a 0 value has been received.
everest-improve-init-handling
Simon Stürz 2025-10-02 12:17:38 +02:00
parent 062da8556d
commit 648f30c4ab
9 changed files with 176 additions and 127 deletions

View File

@ -92,13 +92,25 @@ void IntegrationPluginEverest::startMonitoringAutoThings()
client->disconnectFromServer();
client->deleteLater();
// Disable any auto setup retry logic...
m_autodetectCounter = m_autodetectCounterLimit;
}
});
connect(client, &EverestJsonRpcClient::connectionErrorOccurred, this, [client](){
qCDebug(dcEverest()) << "AutoSetup: The connection to" << client->serverUrl().toString() << "failed";
client->disconnectFromServer();
client->deleteLater();
connect(client, &EverestJsonRpcClient::connectionErrorOccurred, this, [this, client, url](){
m_autodetectCounter++;
if (m_autodetectCounter <= m_autodetectCounterLimit) {
qCDebug(dcEverest()) << "AutoSetup: The connection to" << client->serverUrl().toString() << "failed. Retry" << m_autodetectCounter << "/" << m_autodetectCounterLimit << "in 15 seconds...";
QTimer::singleShot(15000, client, [client, url](){
client->connectToServer(url);
});
} else {
qCDebug(dcEverest()) << "AutoSetup: The connection to" << client->serverUrl().toString() << "failed after" << m_autodetectCounterLimit << "retries. Stopping AutoSetup.";
client->disconnectFromServer();
client->deleteLater();
}
});
client->connectToServer(url);
@ -333,6 +345,8 @@ void IntegrationPluginEverest::discoverThings(ThingDiscoveryInfo *info)
jsonRpcDiscovery->start();
return;
}
info->finish(Thing::ThingErrorUnsupportedFeature);
}
void IntegrationPluginEverest::setupThing(ThingSetupInfo *info)
@ -374,10 +388,6 @@ void IntegrationPluginEverest::setupThing(ThingSetupInfo *info)
return;
} else if (thing->thingClassId() == everestConnectionThingClassId) {
QHostAddress address(thing->paramValue(everestConnectionThingAddressParamTypeId).toString());
MacAddress macAddress(thing->paramValue(everestConnectionThingMacAddressParamTypeId).toString());
QString hostName(thing->paramValue(everestConnectionThingHostNameParamTypeId).toString());
quint16 port = thing->paramValue(everestConnectionThingPortParamTypeId).toUInt();
EverestConnection *connection = nullptr;

View File

@ -59,6 +59,8 @@ public:
void executeAction(ThingActionInfo *info) override;
private:
int m_autodetectCounter = 0;
int m_autodetectCounterLimit = 4;
bool m_useMqtt = false;
QList<EverestMqttClient *> m_everstMqttClients;
QHash<Thing *, EverestMqttClient *> m_thingClients;

View File

@ -104,131 +104,110 @@ EverestJsonRpcReply *EverestEvse::setACChargingPhaseCount(int phaseCount)
void EverestEvse::initialize()
{
qCDebug(dcEverest()) << "Evse: Initializing data for" << m_thing->name();
qCDebug(dcEverest()) << "Evse: Starting to initialize the data for" << m_thing->name();
// Fetch all initial data for this device, once done we get notifications on data changes
EverestJsonRpcReply *reply = nullptr;
reply = m_client->evseGetInfo(m_index);
m_pendingInitReplies.append(reply);
EverestJsonRpcReply *reply = m_client->evseGetInfo(m_index);
connect(reply, &EverestJsonRpcReply::finished, reply, &EverestJsonRpcReply::deleteLater);
connect(reply, &EverestJsonRpcReply::finished, this, [this, reply](){
qCDebug(dcEverest()) << "Evse: Reply finished" << m_client->serverUrl().toString() << reply->method();
if (reply->error()) {
qCWarning(dcEverest()) << "Evse: JsonRpc reply finished with error" << reply->method() << reply->method() << reply->error();
// FIXME: check what we do if an init call failes. Do we stay disconnected and show an error or do we ignore it...
} else {
QVariantMap result = reply->response().value("result").toMap();
EverestJsonRpcClient::ResponseError error = EverestJsonRpcClient::parseResponseError(result.value("error").toString());
if (error) {
qCWarning(dcEverest()) << "Evse: Reply finished with an error" << reply->method() << error;
// FIXME: check what we do if an init call failes. Do we stay disconnected and show an error or do we ignore it...
} else {
m_evseInfo = EverestJsonRpcClient::parseEvseInfo(result.value("info").toMap());
}
m_client->disconnectFromServer();
return;
}
QVariantMap result = reply->response().value("result").toMap();
EverestJsonRpcClient::ResponseError error = EverestJsonRpcClient::parseResponseError(result.value("error").toString());
if (error) {
qCWarning(dcEverest()) << "Evse: Reply finished with an error" << reply->method() << error;
m_client->disconnectFromServer();
return;
}
// Check if we are done with the init process of this EVSE
evaluateInitFinished(reply);
});
// Store data, thy will be processed once all replies arrived
m_evseInfo = EverestJsonRpcClient::parseEvseInfo(result.value("info").toMap());
reply = m_client->evseGetHardwareCapabilities(m_index);
m_pendingInitReplies.append(reply);
connect(reply, &EverestJsonRpcReply::finished, reply, &EverestJsonRpcReply::deleteLater);
connect(reply, &EverestJsonRpcReply::finished, this, [this, reply](){
qCDebug(dcEverest()) << "Evse: Reply finished" << m_client->serverUrl().toString() << reply->method();
if (reply->error()) {
qCWarning(dcEverest()) << "Evse: JsonRpc reply finished with error" << reply->method() << reply->method() << reply->error();
// FIXME: check what we do if an init call failes. Do we stay disconnected and show an error or do we ignore it...
} else {
QVariantMap result = reply->response().value("result").toMap();
EverestJsonRpcClient::ResponseError error = EverestJsonRpcClient::parseResponseError(result.value("error").toString());
if (error) {
qCWarning(dcEverest()) << "Evse: Reply finished with an error" << reply->method() << error;
// FIXME: check what we do if an init call failes. Do we stay disconnected and show an error or do we ignore it...
} else {
// Store data, thy will be processed once all replies arrived
m_hardwareCapabilities = EverestJsonRpcClient::parseHardwareCapabilities(result.value("hardware_capabilities").toMap());
EverestJsonRpcReply *reply = m_client->evseGetHardwareCapabilities(m_index);
connect(reply, &EverestJsonRpcReply::finished, reply, &EverestJsonRpcReply::deleteLater);
connect(reply, &EverestJsonRpcReply::finished, this, [this, reply](){
qCDebug(dcEverest()) << "Evse: Reply finished" << m_client->serverUrl().toString() << reply->method();
if (reply->error()) {
qCWarning(dcEverest()) << "Evse: JsonRpc reply finished with error" << reply->method() << reply->method() << reply->error();
m_client->disconnectFromServer();
return;
}
}
// Check if we are done with the init process of this EVSE
evaluateInitFinished(reply);
});
reply = m_client->evseGetStatus(m_index);
m_pendingInitReplies.append(reply);
connect(reply, &EverestJsonRpcReply::finished, reply, &EverestJsonRpcReply::deleteLater);
connect(reply, &EverestJsonRpcReply::finished, this, [this, reply](){
qCDebug(dcEverest()) << "Evse: Reply finished" << m_client->serverUrl().toString() << reply->method();
if (reply->error()) {
qCWarning(dcEverest()) << "Evse: JsonRpc reply finished with error" << reply->method() << reply->method() << reply->error();
// FIXME: check what we do if an init call failes. Do we stay disconnected and show an error or do we ignore it...
} else {
QVariantMap result = reply->response().value("result").toMap();
EverestJsonRpcClient::ResponseError error = EverestJsonRpcClient::parseResponseError(result.value("error").toString());
if (error) {
qCWarning(dcEverest()) << "Evse: Reply finished with an error" << reply->method() << error;
// FIXME: check what we do if an init call failes. Do we stay disconnected and show an error or do we ignore it...
} else {
m_client->disconnectFromServer();
return;
}
// Store data, thy will be processed once all replies arrived
m_hardwareCapabilities = EverestJsonRpcClient::parseHardwareCapabilities(result.value("hardware_capabilities").toMap());
EverestJsonRpcReply *reply = m_client->evseGetStatus(m_index);
connect(reply, &EverestJsonRpcReply::finished, reply, &EverestJsonRpcReply::deleteLater);
connect(reply, &EverestJsonRpcReply::finished, this, [this, reply](){
qCDebug(dcEverest()) << "Evse: Reply finished" << m_client->serverUrl().toString() << reply->method();
if (reply->error()) {
qCWarning(dcEverest()) << "Evse: JsonRpc reply finished with error" << reply->method() << reply->method() << reply->error();
m_client->disconnectFromServer();
return;
}
QVariantMap result = reply->response().value("result").toMap();
EverestJsonRpcClient::ResponseError error = EverestJsonRpcClient::parseResponseError(result.value("error").toString());
if (error) {
qCWarning(dcEverest()) << "Evse: Reply finished with an error" << reply->method() << error;
m_client->disconnectFromServer();
return;
}
// Store data, thy will be processed once all replies arrived
m_evseStatus = EverestJsonRpcClient::parseEvseStatus(result.value("status").toMap());
}
}
// Check if we are done with the init process of this EVSE
evaluateInitFinished(reply);
EverestJsonRpcReply *reply = m_client->evseGetMeterData(m_index);
connect(reply, &EverestJsonRpcReply::finished, reply, &EverestJsonRpcReply::deleteLater);
connect(reply, &EverestJsonRpcReply::finished, this, [this, reply](){
qCDebug(dcEverest()) << "Evse: Reply finished" << m_client->serverUrl().toString() << reply->method();
if (reply->error()) {
qCWarning(dcEverest()) << "Evse: JsonRpc reply finished with error" << reply->method() << reply->method() << reply->error();
// FIXME: check what we do if an init call failes. Do we stay disconnected and show an error or do we ignore it...
}
QVariantMap result = reply->response().value("result").toMap();
EverestJsonRpcClient::ResponseError error = EverestJsonRpcClient::parseResponseError(result.value("error").toString());
if (error) {
if (error == EverestJsonRpcClient::ResponseErrorErrorNoDataAvailable) {
qCDebug(dcEverest()) << "Evse: There are no meter data available. Either there is no meter or the meter data are not available yet on EVSE side.";
} else {
// FIXME: check what we do if an init call failes. Do we stay disconnected and show an error or do we ignore it...
qCWarning(dcEverest()) << "Evse: Reply finished with an error" << reply->method() << error;
}
}
// Store data, thy will be processed once all replies arrived
m_meterData = EverestJsonRpcClient::parseMeterData(result.value("meter_data").toMap());
qCDebug(dcEverest()) << "Evse: The initialization of" << m_thing->name() << "has finished, the charger is now connected.";
m_initialized = true;
// Set all initial states
m_thing->setStateValue("connected", true);
// Process all data after beeing connected
processEvseStatus();
processHardwareCapabilities();
processMeterData();
});
});
});
});
reply = m_client->evseGetMeterData(m_index);
m_pendingInitReplies.append(reply);
connect(reply, &EverestJsonRpcReply::finished, reply, &EverestJsonRpcReply::deleteLater);
connect(reply, &EverestJsonRpcReply::finished, this, [this, reply](){
qCDebug(dcEverest()) << "Evse: Reply finished" << m_client->serverUrl().toString() << reply->method();
if (reply->error()) {
qCWarning(dcEverest()) << "Evse: JsonRpc reply finished with error" << reply->method() << reply->method() << reply->error();
// FIXME: check what we do if an init call failes. Do we stay disconnected and show an error or do we ignore it...
} else {
QVariantMap result = reply->response().value("result").toMap();
EverestJsonRpcClient::ResponseError error = EverestJsonRpcClient::parseResponseError(result.value("error").toString());
if (error) {
if (error == EverestJsonRpcClient::ResponseErrorErrorNoDataAvailable) {
qCDebug(dcEverest()) << "Evse: There are no meter data available. Either there is no meter or the meter data are not available yet on EVSE side.";
} else {
// FIXME: check what we do if an init call failes. Do we stay disconnected and show an error or do we ignore it...
qCWarning(dcEverest()) << "Evse: Reply finished with an error" << reply->method() << error;
}
} else {
// Store data, thy will be processed once all replies arrived
m_meterData = EverestJsonRpcClient::parseMeterData(result.value("meter_data").toMap());
}
}
// Check if we are done with the init process of this EVSE
evaluateInitFinished(reply);
});
}
void EverestEvse::evaluateInitFinished(EverestJsonRpcReply *reply)
{
if (m_initialized)
return;
m_pendingInitReplies.removeAll(reply);
if (m_pendingInitReplies.isEmpty()) {
qCDebug(dcEverest()) << "Evse: The initialization of" << m_thing->name() << "has finished, the charger is now connected.";
m_initialized = true;
// Set all initial states
m_thing->setStateValue("connected", true);
// Process all data after beeing conected
processEvseStatus();
processHardwareCapabilities();
processMeterData();
}
}
void EverestEvse::processEvseStatus()
@ -243,7 +222,9 @@ void EverestEvse::processEvseStatus()
m_thing->setStateValue(everestChargerAcPhaseCountStateTypeId, m_evseStatus.acChargeStatus.activePhaseCount);
m_thing->setStateValue(everestChargerAcDesiredPhaseCountStateTypeId, m_evseStatus.acChargeStatus.activePhaseCount);
m_thing->setStateValue(everestChargerAcMaxChargingCurrentStateTypeId, m_evseStatus.acChargeParameters.maxCurrent);
if (m_evseStatus.acChargeParameters.maxCurrent > 0)
m_thing->setStateValue(everestChargerAcMaxChargingCurrentStateTypeId, m_evseStatus.acChargeParameters.maxCurrent);
}
}

View File

@ -59,10 +59,7 @@ private:
EverestJsonRpcClient::HardwareCapabilities m_hardwareCapabilities;
EverestJsonRpcClient::MeterData m_meterData;
QVector<EverestJsonRpcReply *> m_pendingInitReplies;
void initialize();
void evaluateInitFinished(EverestJsonRpcReply *reply);
void processEvseStatus();
void processHardwareCapabilities();

View File

@ -128,9 +128,10 @@ EverestJsonRpcClient::EverestJsonRpcClient(QObject *parent)
m_available = false;
emit availableChanged(m_available);
}
emit connectionErrorOccurred();
}
});
}
QUrl EverestJsonRpcClient::serverUrl()
@ -168,7 +169,7 @@ EverestJsonRpcReply *EverestJsonRpcClient::evseGetInfo(int evseIndex)
QVariantMap params;
params.insert("evse_index", evseIndex);
EverestJsonRpcReply *reply = createReply("EVSE.GetInfo", params);
EverestJsonRpcReply *reply = createReply("EVSE.GetInfo", params, true);
qCDebug(dcEverest()) << "Calling" << reply->method() << params;
sendRequest(reply);
return reply;
@ -179,7 +180,7 @@ EverestJsonRpcReply *EverestJsonRpcClient::evseGetStatus(int evseIndex)
QVariantMap params;
params.insert("evse_index", evseIndex);
EverestJsonRpcReply *reply = createReply("EVSE.GetStatus", params);
EverestJsonRpcReply *reply = createReply("EVSE.GetStatus", params, true);
qCDebug(dcEverest()) << "Calling" << reply->method() << params;
sendRequest(reply);
return reply;
@ -190,7 +191,7 @@ EverestJsonRpcReply *EverestJsonRpcClient::evseGetHardwareCapabilities(int evseI
QVariantMap params;
params.insert("evse_index", evseIndex);
EverestJsonRpcReply *reply = createReply("EVSE.GetHardwareCapabilities", params);
EverestJsonRpcReply *reply = createReply("EVSE.GetHardwareCapabilities", params, true);
qCDebug(dcEverest()) << "Calling" << reply->method() << params;
sendRequest(reply);
return reply;
@ -201,7 +202,8 @@ EverestJsonRpcReply *EverestJsonRpcClient::evseGetMeterData(int evseIndex)
QVariantMap params;
params.insert("evse_index", evseIndex);
EverestJsonRpcReply *reply = createReply("EVSE.GetMeterData", params);
// FIXME: do not retry...
EverestJsonRpcReply *reply = createReply("EVSE.GetMeterData", params, true);
qCDebug(dcEverest()) << "Calling" << reply->method() << params;
sendRequest(reply);
return reply;
@ -461,6 +463,24 @@ void EverestJsonRpcClient::processDataPacket(const QByteArray &data)
if (reply) {
reply->setResponse(dataMap);
if (reply->retry()) {
QVariantMap result = reply->response().value("result").toMap();
EverestJsonRpcClient::ResponseError error = EverestJsonRpcClient::parseResponseError(result.value("error").toString());
if (error == EverestJsonRpcClient::ResponseErrorErrorNoDataAvailable) {
reply->m_retryCount++;
if (reply->retryCount() <= reply->retryLimit()) {
qCDebug(dcEverest()) << "Reply for" << reply->method() << "has no data available yet. Retry" << reply->retryCount() << "/" << reply->retryLimit();
reply->m_commandId = getNextCommandId();
QTimer::singleShot(2000, this, [this, reply](){ sendRequest(reply); });
// Retry scheduled, we are done with this packet
return;
} else {
qCWarning(dcEverest()) << "Reply for" << reply->method() << "has still no data available. Retry limit of" << reply->retryLimit() << "reached. Finish reply with error.";
}
}
}
// Verify if we received a json rpc error
if (dataMap.contains("error")) {
reply->finishReply(EverestJsonRpcReply::ErrorJsonRpcError);
@ -499,12 +519,16 @@ void EverestJsonRpcClient::processDataPacket(const QByteArray &data)
}
}
EverestJsonRpcReply *EverestJsonRpcClient::createReply(QString method, QVariantMap params)
EverestJsonRpcReply *EverestJsonRpcClient::createReply(QString method, QVariantMap params, bool retry)
{
int commandId = m_commandId;
m_commandId += 1;
EverestJsonRpcReply *reply = new EverestJsonRpcReply(getNextCommandId(), method, params, this);
reply->m_retry = retry;
return reply;
}
return new EverestJsonRpcReply(commandId, method, params, this);
int EverestJsonRpcClient::getNextCommandId()
{
return m_commandId++;
}
EverestJsonRpcReply *EverestJsonRpcClient::apiHello()

View File

@ -272,7 +272,7 @@ private:
EverestJsonRpcInterface *m_interface = nullptr;
QHash<int, EverestJsonRpcReply *> m_replies;
EverestJsonRpcReply *createReply(QString method, QVariantMap params = QVariantMap());
EverestJsonRpcReply *createReply(QString method, QVariantMap params = QVariantMap(), bool retry = false);
// Init infos
QString m_apiVersion;
@ -281,6 +281,8 @@ private:
bool m_authenticationRequired = false;
QList<EVSEInfo> m_evseInfos;
int getNextCommandId();
// API calls
EverestJsonRpcReply *apiHello();
EverestJsonRpcReply *chargePointGetEVSEInfos();

View File

@ -39,8 +39,13 @@ EverestJsonRpcInterface::EverestJsonRpcInterface(QObject *parent)
connect(m_webSocket, &QWebSocket::disconnected, this, &EverestJsonRpcInterface::onDisconnected);
connect(m_webSocket, &QWebSocket::textMessageReceived, this, &EverestJsonRpcInterface::onTextMessageReceived);
connect(m_webSocket, &QWebSocket::binaryMessageReceived, this, &EverestJsonRpcInterface::onBinaryMessageReceived);
connect(m_webSocket, &QWebSocket::stateChanged, this, &EverestJsonRpcInterface::onStateChanged);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
connect(m_webSocket, &QWebSocket::errorOccurred, this, &EverestJsonRpcInterface::onError);
#else
connect(m_webSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError)));
connect(m_webSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onStateChanged(QAbstractSocket::SocketState)));
#endif
}
EverestJsonRpcInterface::~EverestJsonRpcInterface()
@ -91,6 +96,9 @@ void EverestJsonRpcInterface::onDisconnected()
void EverestJsonRpcInterface::onError(QAbstractSocket::SocketError error)
{
qCDebug(dcEverest()) << "Socket error occurred" << error << m_webSocket->errorString();
if (error == QAbstractSocket::ConnectionRefusedError)
emit connectedChanged(false);
}
void EverestJsonRpcInterface::onStateChanged(QAbstractSocket::SocketState state)

View File

@ -76,6 +76,21 @@ QVariantMap EverestJsonRpcReply::requestMap()
return request;
}
bool EverestJsonRpcReply::retry() const
{
return m_retry;
}
int EverestJsonRpcReply::retryCount() const
{
return m_retryCount;
}
int EverestJsonRpcReply::retryLimit() const
{
return m_retryLimit;
}
QVariantMap EverestJsonRpcReply::response() const
{
return m_response;

View File

@ -58,6 +58,12 @@ public:
QVariantMap params() const;
QVariantMap requestMap();
// Retry logic, as of now only for init requests and if
// they return ResponseErrorErrorNoDataAvailable
bool retry() const;
int retryCount() const;
int retryLimit() const;
// Response
QVariantMap response() const;
@ -75,6 +81,10 @@ private:
QTimer m_timer;
Error m_error = ErrorNoError;
bool m_retry = false;
int m_retryCount = 0;
int m_retryLimit = 5;
void setResponse(const QVariantMap &response);
void startWaiting();
void finishReply(Error error = ErrorNoError);