Easee: Various fixes after beta testing
parent
17489941cf
commit
0f79c1d3a1
|
|
@ -40,6 +40,9 @@
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QWebSocket>
|
#include <QWebSocket>
|
||||||
|
|
||||||
|
QString apiEndpoint = "https://api.easee.com/api";
|
||||||
|
QString streamEndpoint = "http://streams.easee.com/hubs/chargers";
|
||||||
|
|
||||||
IntegrationPluginEasee::IntegrationPluginEasee()
|
IntegrationPluginEasee::IntegrationPluginEasee()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
@ -51,7 +54,7 @@ IntegrationPluginEasee::~IntegrationPluginEasee()
|
||||||
|
|
||||||
void IntegrationPluginEasee::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret)
|
void IntegrationPluginEasee::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret)
|
||||||
{
|
{
|
||||||
QNetworkRequest request(QUrl(QString("https://api.easee.cloud/api/accounts/login")));
|
QNetworkRequest request(QUrl(QString("%1/%2").arg(apiEndpoint).arg("accounts/login")));
|
||||||
request.setRawHeader("accept", "application/json");
|
request.setRawHeader("accept", "application/json");
|
||||||
request.setRawHeader("content-type", "application/*+json");
|
request.setRawHeader("content-type", "application/*+json");
|
||||||
QVariantMap body;
|
QVariantMap body;
|
||||||
|
|
@ -63,10 +66,12 @@ void IntegrationPluginEasee::confirmPairing(ThingPairingInfo *info, const QStrin
|
||||||
qCDebug(dcEasee) << "auth reply finished" << reply->error();
|
qCDebug(dcEasee) << "auth reply finished" << reply->error();
|
||||||
|
|
||||||
if (reply->error() == QNetworkReply::ProtocolInvalidOperationError) {
|
if (reply->error() == QNetworkReply::ProtocolInvalidOperationError) {
|
||||||
|
qCWarning(dcEasee) << "Authentication failed. Looks like a wrong password";
|
||||||
info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Authentication failed. Please try again."));
|
info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Authentication failed. Please try again."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
|
qCWarning(dcEasee) << "Unable to connect to the Easee server";
|
||||||
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Unable to contact the easee server. Please try again later."));
|
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Unable to contact the easee server. Please try again later."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -153,10 +158,11 @@ void IntegrationPluginEasee::setupThing(ThingSetupInfo *info)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thing->thingClassId() == chargerThingClassId) {
|
if (thing->thingClassId() == chargerThingClassId) {
|
||||||
refreshCurrentState(thing);
|
// We'll need a cache of the maxChargingCurrent as sometimes need that before a executeAction is finished
|
||||||
|
// initializing it to make sure there's always a value in it.
|
||||||
|
m_desiredMax[info->thing()] = thing->stateValue(chargerMaxChargingCurrentStateTypeId).toUInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
info->finish(Thing::ThingErrorNoError);
|
info->finish(Thing::ThingErrorNoError);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -181,16 +187,6 @@ void IntegrationPluginEasee::postSetupThing(Thing *thing)
|
||||||
|
|
||||||
// Refreshing the products
|
// Refreshing the products
|
||||||
refreshProducts(t);
|
refreshProducts(t);
|
||||||
|
|
||||||
if (!m_signalRConnections.value(t)->connected()) {
|
|
||||||
// If the SignalR connection fails for whatever reason, let's poll
|
|
||||||
foreach (Thing *child, myThings().filterByParentId(t->id())) {
|
|
||||||
refreshCurrentState(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (t->thingClassId() == chargerThingClassId) {
|
|
||||||
// We'll be using the SignalR connection instead for updates.
|
|
||||||
//refreshCurrentState(t);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -206,14 +202,15 @@ void IntegrationPluginEasee::postSetupThing(Thing *thing)
|
||||||
qCDebug(dcEasee()) << "Access token:" << accessToken;
|
qCDebug(dcEasee()) << "Access token:" << accessToken;
|
||||||
qCDebug(dcEasee()) << "Token expiry:" << expiry;
|
qCDebug(dcEasee()) << "Token expiry:" << expiry;
|
||||||
|
|
||||||
SignalRConnection *signalR = new SignalRConnection(QUrl("http://streams.easee.com/hubs/chargers"), accessToken, hardwareManager()->networkManager(), thing);
|
SignalRConnection *signalR = new SignalRConnection(QUrl(streamEndpoint), accessToken, hardwareManager()->networkManager(), thing);
|
||||||
m_signalRConnections.insert(thing, signalR);
|
m_signalRConnections.insert(thing, signalR);
|
||||||
|
|
||||||
connect(signalR, &SignalRConnection::connectionStateChanged, thing, [=](bool connected){
|
connect(signalR, &SignalRConnection::connectionStateChanged, thing, [=](bool connected){
|
||||||
foreach (Thing *charger, myThings().filterByParentId(thing->id())) {
|
foreach (Thing *charger, myThings().filterByParentId(thing->id())) {
|
||||||
charger->setStateValue(chargerConnectedStateTypeId, true);
|
|
||||||
if (connected) {
|
if (connected) {
|
||||||
signalR->subscribe(charger->paramValue(chargerThingIdParamTypeId).toString());
|
signalR->subscribe(charger->paramValue(chargerThingIdParamTypeId).toString());
|
||||||
|
} else {
|
||||||
|
charger->setStateValue(chargerConnectedStateTypeId, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -256,12 +253,11 @@ void IntegrationPluginEasee::postSetupThing(Thing *thing)
|
||||||
charger->setStateValue(chargerPluggedInStateTypeId, false);
|
charger->setStateValue(chargerPluggedInStateTypeId, false);
|
||||||
} else if (mode == "B" || mode == "C") {
|
} else if (mode == "B" || mode == "C") {
|
||||||
charger->setStateValue(chargerPluggedInStateTypeId, true);
|
charger->setStateValue(chargerPluggedInStateTypeId, true);
|
||||||
} else {
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ObservationPointOutputPhase:
|
case ObservationPointOutputPhase:
|
||||||
charger->setStateValue(chargerPhaseCountStateTypeId, value.toUInt() > 10 ? 3 : 1);
|
charger->setStateValue(chargerPhaseCountStateTypeId, value.toUInt() == 30 ? 3 : 1);
|
||||||
break;
|
break;
|
||||||
case ObservationPointChargerOpMode:
|
case ObservationPointChargerOpMode:
|
||||||
// 2: charging disabled, 3: enabled and charging, 4: enabled but not charging
|
// 2: charging disabled, 3: enabled and charging, 4: enabled but not charging
|
||||||
|
|
@ -269,11 +265,25 @@ void IntegrationPluginEasee::postSetupThing(Thing *thing)
|
||||||
charger->setStateValue(chargerPowerStateTypeId, value.toUInt() >= 3);
|
charger->setStateValue(chargerPowerStateTypeId, value.toUInt() >= 3);
|
||||||
break;
|
break;
|
||||||
case ObservationPointDynamicChargerCurrent:
|
case ObservationPointDynamicChargerCurrent:
|
||||||
charger->setStateValue(chargerMaxChargingCurrentStateTypeId, value.toUInt());
|
// May give us 0 when pausing charging etc, ignoring that.
|
||||||
|
if (value.toUInt() > 0) {
|
||||||
|
charger->setStateValue(chargerMaxChargingCurrentStateTypeId, value.toUInt());
|
||||||
|
// Updating the desired value when it is changed by the wallbox (e.g. through app)
|
||||||
|
m_desiredMax[charger] = value.toUInt();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case ObservationPointMaxChargerCurrent:
|
case ObservationPointMaxChargerCurrent:
|
||||||
charger->setStateMaxValue(chargerMaxChargingCurrentStateTypeId, value.toUInt());
|
m_wallboxMax[chargerId] = value.toUInt();
|
||||||
|
charger->setStateMinMaxValues(chargerMaxChargingCurrentStateTypeId, 6, qMin(m_wallboxMax.value(chargerId), m_cableRating.value(chargerId, 32)));
|
||||||
break;
|
break;
|
||||||
|
case ObservationPointCableRating:
|
||||||
|
m_cableRating[chargerId] = value.toUInt();
|
||||||
|
charger->setStateMinMaxValues(chargerMaxChargingCurrentStateTypeId, 6, qMin(m_wallboxMax.value(chargerId, 32), value.toUInt()));
|
||||||
|
break;
|
||||||
|
case ObservationPointConnectedToCloud:
|
||||||
|
charger->setStateValue(chargerConnectedStateTypeId, value.toString() == "True" || value.toString() == "1");
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|
@ -293,6 +303,10 @@ void IntegrationPluginEasee::thingRemoved(Thing *thing)
|
||||||
hardwareManager()->pluginTimerManager()->unregisterTimer(m_timer);
|
hardwareManager()->pluginTimerManager()->unregisterTimer(m_timer);
|
||||||
m_timer = nullptr;
|
m_timer = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (thing->thingClassId() == chargerThingClassId) {
|
||||||
|
m_desiredMax.take(thing);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IntegrationPluginEasee::executeAction(ThingActionInfo *info)
|
void IntegrationPluginEasee::executeAction(ThingActionInfo *info)
|
||||||
|
|
@ -303,22 +317,34 @@ void IntegrationPluginEasee::executeAction(ThingActionInfo *info)
|
||||||
QString chargerId = thing->paramValue(chargerThingIdParamTypeId).toString();
|
QString chargerId = thing->paramValue(chargerThingIdParamTypeId).toString();
|
||||||
if (info->action().actionTypeId() == chargerPowerActionTypeId) {
|
if (info->action().actionTypeId() == chargerPowerActionTypeId) {
|
||||||
bool power = info->action().paramValue(chargerPowerActionPowerParamTypeId).toBool();
|
bool power = info->action().paramValue(chargerPowerActionPowerParamTypeId).toBool();
|
||||||
QString actionPath = power ? "start_charging" : "stop_charging";
|
QString actionPath = power ? "start_charging" : "pause_charging";
|
||||||
QNetworkRequest request = createRequest(parentThing, QString("chargers/%1/commands/%2").arg(chargerId).arg(actionPath));
|
QNetworkRequest request = createRequest(parentThing, QString("chargers/%1/commands/%2").arg(chargerId).arg(actionPath));
|
||||||
qCDebug(dcEasee()) << "Setting power:" << request.url().toString();
|
qCDebug(dcEasee()) << "Setting power:" << request.url().toString();
|
||||||
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QByteArray());
|
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QByteArray());
|
||||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||||
connect(reply, &QNetworkReply::finished, info, [reply, info, power](){
|
connect(reply, &QNetworkReply::finished, info, [=](){
|
||||||
qCDebug(dcEasee()) << "Reply" << reply->error();
|
qCDebug(dcEasee()) << "Reply" << reply->error();
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
info->thing()->setStateValue(chargerPowerStateTypeId, power);
|
info->thing()->setStateValue(chargerPowerStateTypeId, power);
|
||||||
}
|
}
|
||||||
info->finish(reply->error() == QNetworkReply::NoError ? Thing::ThingErrorNoError : Thing::ThingErrorHardwareFailure);
|
info->finish(reply->error() == QNetworkReply::NoError ? Thing::ThingErrorNoError : Thing::ThingErrorHardwareFailure);
|
||||||
|
|
||||||
|
// resume/start charging will for some reason reset the dynamicChargerCurrent... We'll have to re-write ours.
|
||||||
|
if (power) {
|
||||||
|
uint maxChargingCurrent = m_desiredMax[info->thing()];
|
||||||
|
QVariantMap data;
|
||||||
|
data.insert("dynamicChargerCurrent", maxChargingCurrent);
|
||||||
|
QNetworkRequest request = createRequest(parentThing, QString("chargers/%1/settings").arg(chargerId));
|
||||||
|
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QJsonDocument::fromVariant(data).toJson(QJsonDocument::Compact));
|
||||||
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (info->action().actionTypeId() == chargerMaxChargingCurrentActionTypeId) {
|
if (info->action().actionTypeId() == chargerMaxChargingCurrentActionTypeId) {
|
||||||
uint maxChargingCurrent = info->action().paramValue(chargerMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toUInt();
|
uint maxChargingCurrent = info->action().paramValue(chargerMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toUInt();
|
||||||
|
// We'll need this for resume_charging as that call will for some reason reset it to the max of 32A, so we'll need to immediately write this one again.
|
||||||
|
m_desiredMax[info->thing()] = maxChargingCurrent;
|
||||||
QNetworkRequest request = createRequest(parentThing, QString("chargers/%1/settings").arg(chargerId));
|
QNetworkRequest request = createRequest(parentThing, QString("chargers/%1/settings").arg(chargerId));
|
||||||
QVariantMap data;
|
QVariantMap data;
|
||||||
data.insert("dynamicChargerCurrent", maxChargingCurrent);
|
data.insert("dynamicChargerCurrent", maxChargingCurrent);
|
||||||
|
|
@ -326,7 +352,7 @@ void IntegrationPluginEasee::executeAction(ThingActionInfo *info)
|
||||||
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QJsonDocument::fromVariant(data).toJson(QJsonDocument::Compact));
|
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QJsonDocument::fromVariant(data).toJson(QJsonDocument::Compact));
|
||||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||||
connect(reply, &QNetworkReply::finished, info, [reply, info, maxChargingCurrent](){
|
connect(reply, &QNetworkReply::finished, info, [reply, info, maxChargingCurrent](){
|
||||||
qCDebug(dcEasee()) << "Reply" << reply->error();
|
qCDebug(dcEasee()) << "Set dynamicaChargerCurrent reply" << reply->error();
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
info->thing()->setStateValue(chargerMaxChargingCurrentStateTypeId, maxChargingCurrent);
|
info->thing()->setStateValue(chargerMaxChargingCurrentStateTypeId, maxChargingCurrent);
|
||||||
}
|
}
|
||||||
|
|
@ -335,20 +361,54 @@ void IntegrationPluginEasee::executeAction(ThingActionInfo *info)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (info->action().actionTypeId() == chargerDesiredPhaseCountActionTypeId) {
|
if (info->action().actionTypeId() == chargerDesiredPhaseCountActionTypeId) {
|
||||||
uint desiredPhaseCount = info->action().paramValue(chargerMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toUInt();
|
uint desiredPhaseCount = info->action().paramValue(chargerDesiredPhaseCountActionDesiredPhaseCountParamTypeId).toUInt();
|
||||||
QNetworkRequest request = createRequest(parentThing, QString("chargers/%1/settings").arg(chargerId));
|
bool wasOn = thing->stateValue(chargerPowerStateTypeId).toBool();
|
||||||
QVariantMap data;
|
bool oldMaxCurrent = m_desiredMax.value(info->thing());
|
||||||
data.insert("lockToSinglePhaseCharging", desiredPhaseCount == 1);
|
if (desiredPhaseCount == thing->stateValue(chargerPhaseCountStateTypeId)) {
|
||||||
qCDebug(dcEasee()) << "Setting single phase charging:" << request.url().toString() << QJsonDocument::fromVariant(data).toJson();
|
qCInfo(dcEasee()) << "effective phases already equals desired ones...";
|
||||||
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QJsonDocument::fromVariant(data).toJson(QJsonDocument::Compact));
|
info->finish(Thing::ThingErrorNoError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
qCDebug(dcEasee()) << "Pausing charging";
|
||||||
|
QNetworkRequest request = createRequest(parentThing, QString("chargers/%1/commands/pause_charging").arg(chargerId));
|
||||||
|
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QByteArray());
|
||||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||||
connect(reply, &QNetworkReply::finished, info, [reply, info, desiredPhaseCount](){
|
connect(reply, &QNetworkReply::finished, info, [=](){
|
||||||
qCDebug(dcEasee()) << "Reply" << reply->error();
|
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
QNetworkRequest request = createRequest(parentThing, QString("chargers/%1/settings").arg(chargerId));
|
||||||
info->thing()->setStateValue(chargerDesiredPhaseCountStateTypeId, desiredPhaseCount);
|
|
||||||
}
|
QVariantMap data;
|
||||||
info->finish(reply->error() == QNetworkReply::NoError ? Thing::ThingErrorNoError : Thing::ThingErrorHardwareFailure);
|
data.insert("phaseMode", desiredPhaseCount == 1 ? PhaseModeLockedTo1Phase : PhaseModeLockedTo3Phase);
|
||||||
|
qCDebug(dcEasee()) << "Setting single phase charging:" << request.url().toString() << QJsonDocument::fromVariant(data).toJson();
|
||||||
|
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QJsonDocument::fromVariant(data).toJson(QJsonDocument::Compact));
|
||||||
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||||
|
connect(reply, &QNetworkReply::finished, info, [=](){
|
||||||
|
qCDebug(dcEasee()) << "Set phaseMode reply" << reply->error();
|
||||||
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
|
info->thing()->setStateValue(chargerDesiredPhaseCountStateTypeId, desiredPhaseCount);
|
||||||
|
}
|
||||||
|
info->finish(reply->error() == QNetworkReply::NoError ? Thing::ThingErrorNoError : Thing::ThingErrorHardwareFailure);
|
||||||
|
|
||||||
|
if (wasOn) {
|
||||||
|
qCDebug(dcEasee()) << "Resuming charging";
|
||||||
|
QNetworkRequest request = createRequest(parentThing, QString("chargers/%1/commands/resume_charging").arg(chargerId));
|
||||||
|
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QByteArray());
|
||||||
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||||
|
connect(reply, &QNetworkReply::finished, info, [=](){
|
||||||
|
qCDebug(dcEasee()) << "Restoring max charger current";
|
||||||
|
QNetworkRequest request = createRequest(parentThing, QString("chargers/%1/settings").arg(chargerId));
|
||||||
|
QVariantMap data;
|
||||||
|
data.insert("dynamicChargerCurrent", oldMaxCurrent);
|
||||||
|
qCDebug(dcEasee()) << "Setting max current:" << request.url().toString() << QJsonDocument::fromVariant(data).toJson();
|
||||||
|
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QJsonDocument::fromVariant(data).toJson(QJsonDocument::Compact));
|
||||||
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -361,7 +421,7 @@ QNetworkRequest IntegrationPluginEasee::createRequest(Thing *thing, const QStrin
|
||||||
pluginStorage()->beginGroup(thing->id().toString());
|
pluginStorage()->beginGroup(thing->id().toString());
|
||||||
QByteArray accessToken = pluginStorage()->value("accessToken").toByteArray();
|
QByteArray accessToken = pluginStorage()->value("accessToken").toByteArray();
|
||||||
pluginStorage()->endGroup();
|
pluginStorage()->endGroup();
|
||||||
QNetworkRequest request(QUrl(QString("https://api.easee.cloud/api/%1").arg(endpoint)));
|
QNetworkRequest request(QUrl(QString("%1/%2").arg(apiEndpoint).arg(endpoint)));
|
||||||
request.setRawHeader("Authorization", "Bearer " + accessToken);
|
request.setRawHeader("Authorization", "Bearer " + accessToken);
|
||||||
request.setRawHeader("accept", "application/json");
|
request.setRawHeader("accept", "application/json");
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/*+json");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/*+json");
|
||||||
|
|
@ -379,20 +439,21 @@ QNetworkReply *IntegrationPluginEasee::refreshToken(Thing *thing)
|
||||||
|
|
||||||
|
|
||||||
// FIXME: Ideally we should use the refresh_token API and not store user/pass in the config, but it seems to not work
|
// FIXME: Ideally we should use the refresh_token API and not store user/pass in the config, but it seems to not work
|
||||||
// QNetworkRequest request(QUrl(QString("https://api.easee.cloud/api/accounts/refresh_token")));
|
// QNetworkRequest request(QUrl(QString("%1/%2").arg(apiEndpoint).arg("accounts/refresh_token")));
|
||||||
// request.setRawHeader("accept", "application/json");
|
// request.setRawHeader("accept", "application/json");
|
||||||
// request.setRawHeader("content-type", "application/*+json");
|
// request.setRawHeader("content-type", "application/*+json");
|
||||||
// QVariantMap body;
|
// QVariantMap body;
|
||||||
// body.insert("refreshToken", refreshToken);
|
// body.insert("refreshToken", refreshToken);
|
||||||
// body.insert("accessToken", accessToken);
|
// body.insert("accessToken", accessToken);
|
||||||
|
|
||||||
QNetworkRequest request(QUrl(QString("https://api.easee.cloud/api/accounts/login")));
|
QNetworkRequest request(QUrl(QString("%1/%2").arg(apiEndpoint).arg("accounts/login")));
|
||||||
request.setRawHeader("accept", "application/json");
|
request.setRawHeader("accept", "application/json");
|
||||||
request.setRawHeader("content-type", "application/*+json");
|
request.setRawHeader("content-type", "application/*+json");
|
||||||
QVariantMap body;
|
QVariantMap body;
|
||||||
body.insert("userName", username);
|
body.insert("userName", username);
|
||||||
body.insert("password", password);
|
body.insert("password", password);
|
||||||
|
|
||||||
|
qCDebug(dcEasee()) << "Fetching:" << request.url().toString();
|
||||||
|
|
||||||
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QJsonDocument::fromVariant(body).toJson(QJsonDocument::Compact));
|
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QJsonDocument::fromVariant(body).toJson(QJsonDocument::Compact));
|
||||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||||
|
|
@ -424,7 +485,9 @@ QNetworkReply *IntegrationPluginEasee::refreshToken(Thing *thing)
|
||||||
pluginStorage()->setValue("refreshToken", refreshToken);
|
pluginStorage()->setValue("refreshToken", refreshToken);
|
||||||
pluginStorage()->endGroup();
|
pluginStorage()->endGroup();
|
||||||
|
|
||||||
m_signalRConnections.value(thing)->updateToken(accessToken);
|
if (m_signalRConnections.contains(thing)) {
|
||||||
|
m_signalRConnections.value(thing)->updateToken(accessToken);
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -506,24 +569,6 @@ void IntegrationPluginEasee::refreshCurrentState(Thing *charger)
|
||||||
QVariantMap map = jsonDoc.toVariant().toMap();
|
QVariantMap map = jsonDoc.toVariant().toMap();
|
||||||
qCDebug(dcEasee) << "Charger state reply:" << qUtf8Printable(jsonDoc.toJson());
|
qCDebug(dcEasee) << "Charger state reply:" << qUtf8Printable(jsonDoc.toJson());
|
||||||
charger->setStateValue(chargerConnectedStateTypeId, map.value("isOnline").toBool());
|
charger->setStateValue(chargerConnectedStateTypeId, map.value("isOnline").toBool());
|
||||||
charger->setStateValue(chargerSignalStrengthStateTypeId, qMax(0, qMin(100, (map.value("wiFiRSSI").toInt() + 100) * 2)));
|
|
||||||
charger->setStateValue(chargerCurrentPowerStateTypeId, map.value("totalPower").toDouble() * 1000);
|
|
||||||
charger->setStateValue(chargerPhaseCountStateTypeId, map.value("outputPhase").toUInt() > 10 ? 3 : 1);
|
|
||||||
charger->setStateValue(chargerChargingStateTypeId, map.value("chargerOpMode").toUInt() == 3);
|
|
||||||
|
|
||||||
// 1: unplugged, 2: charging disabled, 3: enabled and charging, 4: enabled but not charging
|
|
||||||
uint chargerOpMode = map.value("chargerOpMode").toUInt();
|
|
||||||
charger->setStateValue(chargerPluggedInStateTypeId, chargerOpMode >= 2);
|
|
||||||
charger->setStateValue(chargerChargingStateTypeId, chargerOpMode == 3);
|
|
||||||
charger->setStateValue(chargerPowerStateTypeId, chargerOpMode >= 3);
|
|
||||||
|
|
||||||
charger->setStateValue(chargerMaxChargingCurrentStateTypeId, map.value("dynamicChargerCurrent").toUInt());
|
|
||||||
charger->setStateMaxValue(chargerMaxChargingCurrentStateTypeId, 6); // Fixme: where to get this from?
|
|
||||||
charger->setStateMaxValue(chargerMaxChargingCurrentStateTypeId, 32); // Fixme: where to get this from?
|
|
||||||
|
|
||||||
charger->setStateValue(chargerTotalEnergyConsumedStateTypeId, map.value("lifetimeEnergy").toDouble());
|
|
||||||
charger->setStateValue(chargerSessionEnergyStateTypeId, map.value("sessionEnergy").toDouble());
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -212,10 +212,20 @@ public:
|
||||||
ObservationPointLTERSRQ = 222,
|
ObservationPointLTERSRQ = 222,
|
||||||
ObservationPointEQAvailableCurrentP1 = 230,
|
ObservationPointEQAvailableCurrentP1 = 230,
|
||||||
ObservationPointEQAvailableCurrentP2 = 231,
|
ObservationPointEQAvailableCurrentP2 = 231,
|
||||||
ObservationPointEQAvailableCurrentP3 = 232
|
ObservationPointEQAvailableCurrentP3 = 232,
|
||||||
|
ObservationPointConnectedToCloud = 250
|
||||||
};
|
};
|
||||||
Q_ENUM(ObservationPoint)
|
Q_ENUM(ObservationPoint)
|
||||||
|
|
||||||
|
|
||||||
|
enum PhaseMode {
|
||||||
|
PhaseModeIgnore = 0,
|
||||||
|
PhaseModeLockedTo1Phase = 1,
|
||||||
|
PhaseModeAuto = 2,
|
||||||
|
PhaseModeLockedTo3Phase = 3
|
||||||
|
};
|
||||||
|
Q_ENUM(PhaseMode)
|
||||||
|
|
||||||
explicit IntegrationPluginEasee();
|
explicit IntegrationPluginEasee();
|
||||||
~IntegrationPluginEasee();
|
~IntegrationPluginEasee();
|
||||||
|
|
||||||
|
|
@ -237,6 +247,10 @@ private:
|
||||||
QHash<QString, uint> m_siteIds; // chargerId, siteId
|
QHash<QString, uint> m_siteIds; // chargerId, siteId
|
||||||
|
|
||||||
PluginTimer *m_timer = nullptr;
|
PluginTimer *m_timer = nullptr;
|
||||||
|
|
||||||
|
QHash<QString,uint> m_cableRating;
|
||||||
|
QHash<QString,uint> m_wallboxMax;
|
||||||
|
QHash<Thing*,uint> m_desiredMax;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // INTEGRATIONPLUGINEASEE_H
|
#endif // INTEGRATIONPLUGINEASEE_H
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,13 @@ SignalRConnection::SignalRConnection(const QUrl &url, const QByteArray &accessTo
|
||||||
m_socket = new QWebSocket();
|
m_socket = new QWebSocket();
|
||||||
typedef void (QWebSocket:: *errorSignal)(QAbstractSocket::SocketError);
|
typedef void (QWebSocket:: *errorSignal)(QAbstractSocket::SocketError);
|
||||||
connect(m_socket, static_cast<errorSignal>(&QWebSocket::error), this, [](QAbstractSocket::SocketError error){
|
connect(m_socket, static_cast<errorSignal>(&QWebSocket::error), this, [](QAbstractSocket::SocketError error){
|
||||||
qCWarning(dcEasee) << "Error in websocket:" << error;
|
qCWarning(dcEasee) << "SingalR: Error in websocket:" << error;
|
||||||
});
|
});
|
||||||
connect(m_socket, &QWebSocket::stateChanged, this, [=](QAbstractSocket::SocketState state){
|
connect(m_socket, &QWebSocket::stateChanged, this, [=](QAbstractSocket::SocketState state){
|
||||||
qCDebug(dcEasee) << "Websocket state changed" << state;
|
qCDebug(dcEasee) << "SingalR: Websocket state changed" << state;
|
||||||
|
|
||||||
if (state == QAbstractSocket::ConnectedState) {
|
if (state == QAbstractSocket::ConnectedState) {
|
||||||
qCDebug(dcEasee) << "Websocket connected";
|
qCDebug(dcEasee) << "SingalR: Websocket connected";
|
||||||
|
|
||||||
QVariantMap handshake;
|
QVariantMap handshake;
|
||||||
handshake.insert("protocol", "json");
|
handshake.insert("protocol", "json");
|
||||||
|
|
@ -32,7 +32,9 @@ SignalRConnection::SignalRConnection(const QUrl &url, const QByteArray &accessTo
|
||||||
QByteArray data = encode(handshake);
|
QByteArray data = encode(handshake);
|
||||||
qCDebug(dcEasee) << "Sending handshake" << data;
|
qCDebug(dcEasee) << "Sending handshake" << data;
|
||||||
m_socket->sendTextMessage(data);
|
m_socket->sendTextMessage(data);
|
||||||
|
m_watchdog->start();
|
||||||
} else if (QAbstractSocket::UnconnectedState) {
|
} else if (QAbstractSocket::UnconnectedState) {
|
||||||
|
m_watchdog->stop();
|
||||||
QTimer::singleShot(5000, this, [=](){
|
QTimer::singleShot(5000, this, [=](){
|
||||||
connectToHost();
|
connectToHost();
|
||||||
});
|
});
|
||||||
|
|
@ -40,9 +42,11 @@ SignalRConnection::SignalRConnection(const QUrl &url, const QByteArray &accessTo
|
||||||
|
|
||||||
});
|
});
|
||||||
connect(m_socket, &QWebSocket::binaryMessageReceived, this, [](const QByteArray &message){
|
connect(m_socket, &QWebSocket::binaryMessageReceived, this, [](const QByteArray &message){
|
||||||
qCDebug(dcEasee) << "Binary message received" << message;
|
qCDebug(dcEasee) << "SingalR: Binary message received" << message;
|
||||||
});
|
});
|
||||||
connect(m_socket, &QWebSocket::textMessageReceived, this, [=](const QString &message){
|
connect(m_socket, &QWebSocket::textMessageReceived, this, [=](const QString &message){
|
||||||
|
qCDebug(dcEasee) << "SingalR: Text message received" << message;
|
||||||
|
|
||||||
QStringList messages = message.split(QByteArray::fromHex("1E"));
|
QStringList messages = message.split(QByteArray::fromHex("1E"));
|
||||||
|
|
||||||
foreach (const QString &msg, messages) {
|
foreach (const QString &msg, messages) {
|
||||||
|
|
@ -54,13 +58,13 @@ SignalRConnection::SignalRConnection(const QUrl &url, const QByteArray &accessTo
|
||||||
QJsonParseError error;
|
QJsonParseError error;
|
||||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(msg.toUtf8(), &error);
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(msg.toUtf8(), &error);
|
||||||
if (error.error != QJsonParseError::NoError) {
|
if (error.error != QJsonParseError::NoError) {
|
||||||
qCWarning(dcEasee()) << "Unable to parse message from SignalR socket" << error.errorString() << msg;
|
qCWarning(dcEasee()) << "SingalR: Unable to parse message from SignalR socket" << error.errorString() << msg;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_waitingForHandshakeReply && jsonDoc.toVariant().toMap().isEmpty()) {
|
if (m_waitingForHandshakeReply && jsonDoc.toVariant().toMap().isEmpty()) {
|
||||||
m_waitingForHandshakeReply = false;
|
m_waitingForHandshakeReply = false;
|
||||||
qCDebug(dcEasee()) << "Handshake reply received.";
|
qCDebug(dcEasee()) << "SingalR: Handshake reply received.";
|
||||||
emit connectionStateChanged(true);
|
emit connectionStateChanged(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -72,17 +76,27 @@ SignalRConnection::SignalRConnection(const QUrl &url, const QByteArray &accessTo
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
// Silencing acks to our requests
|
// Silencing acks to our requests
|
||||||
qCDebug(dcEasee()) << "Message ACK received:" << map;
|
qCDebug(dcEasee()) << "SingalR: Message ACK received:" << map;
|
||||||
|
break;
|
||||||
case 6:
|
case 6:
|
||||||
// Silencing pings
|
// Resetting watchdog
|
||||||
|
m_watchdog->start();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
qCWarning(dcEasee()) << "Unhandled signalr message type" << map;
|
qCWarning(dcEasee()) << "SingalR: Unhandled SingalR message type" << map;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
connectToHost();
|
connectToHost();
|
||||||
|
|
||||||
|
m_watchdog = new QTimer(this);
|
||||||
|
m_watchdog->setInterval(30000);
|
||||||
|
connect(m_watchdog, &QTimer::timeout, this, [=](){
|
||||||
|
qCWarning(dcEasee()) << "SingalR: Watchdog triggered! Reconnecting web socket stream...";
|
||||||
|
m_socket->close();
|
||||||
|
connectToHost();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void SignalRConnection::subscribe(const QString &chargerId)
|
void SignalRConnection::subscribe(const QString &chargerId)
|
||||||
|
|
@ -92,7 +106,7 @@ void SignalRConnection::subscribe(const QString &chargerId)
|
||||||
map.insert("invocationId", QUuid::createUuid());
|
map.insert("invocationId", QUuid::createUuid());
|
||||||
map.insert("target", "SubscribeWithCurrentState");
|
map.insert("target", "SubscribeWithCurrentState");
|
||||||
map.insert("arguments", QVariantList{chargerId, true});
|
map.insert("arguments", QVariantList{chargerId, true});
|
||||||
qCDebug(dcEasee) << "subscribing to" << chargerId;
|
qCDebug(dcEasee) << "SingalR: subscribing to" << chargerId;
|
||||||
m_socket->sendTextMessage(encode(map));
|
m_socket->sendTextMessage(encode(map));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,6 +118,8 @@ bool SignalRConnection::connected() const
|
||||||
void SignalRConnection::updateToken(const QByteArray &accessToken)
|
void SignalRConnection::updateToken(const QByteArray &accessToken)
|
||||||
{
|
{
|
||||||
m_accessToken = accessToken;
|
m_accessToken = accessToken;
|
||||||
|
m_socket->close();
|
||||||
|
connectToHost();
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray SignalRConnection::encode(const QVariantMap &message)
|
QByteArray SignalRConnection::encode(const QVariantMap &message)
|
||||||
|
|
@ -118,19 +134,21 @@ void SignalRConnection::connectToHost()
|
||||||
negotiationUrl.setPath(negotiationUrl.path() + "/negotiate");
|
negotiationUrl.setPath(negotiationUrl.path() + "/negotiate");
|
||||||
QNetworkRequest negotiateRequest(negotiationUrl);
|
QNetworkRequest negotiateRequest(negotiationUrl);
|
||||||
negotiateRequest.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
negotiateRequest.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||||
qCDebug(dcEasee()) << "Negotiating:" << negotiationUrl << negotiateRequest.rawHeader("Authorization");
|
qCDebug(dcEasee()) << "SingalR: Negotiating:" << negotiationUrl << negotiateRequest.rawHeader("Authorization");
|
||||||
QNetworkReply *negotiantionReply = m_nam->post(negotiateRequest, QByteArray());
|
QNetworkReply *negotiantionReply = m_nam->post(negotiateRequest, QByteArray());
|
||||||
connect(negotiantionReply, &QNetworkReply::finished, this, [=](){
|
connect(negotiantionReply, &QNetworkReply::finished, this, [=](){
|
||||||
if (negotiantionReply->error() != QNetworkReply::NoError) {
|
if (negotiantionReply->error() != QNetworkReply::NoError) {
|
||||||
qCWarning(dcEasee()) << "Unable to neotiate SignalR channel:" << negotiantionReply->error();
|
qCWarning(dcEasee()) << "SingalR: Unable to neotiate SignalR channel:" << negotiantionReply->error();
|
||||||
|
QTimer::singleShot(5000, this, [=](){connectToHost();});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QByteArray data = negotiantionReply->readAll();
|
QByteArray data = negotiantionReply->readAll();
|
||||||
qCDebug(dcEasee) << "Negotiation reply" << data;
|
qCDebug(dcEasee) << "SingalR: Negotiation reply" << data;
|
||||||
QJsonParseError error;
|
QJsonParseError error;
|
||||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
||||||
if (error.error != QJsonParseError::NoError) {
|
if (error.error != QJsonParseError::NoError) {
|
||||||
qCWarning(dcEasee()) << "Unable to parse json from negoatiate endpoint" << error.errorString() << data;
|
qCWarning(dcEasee()) << "SingalR: Unable to parse json from negoatiate endpoint" << error.errorString() << data;
|
||||||
|
QTimer::singleShot(5000, this, [=](){connectToHost();});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -145,7 +163,7 @@ void SignalRConnection::connectToHost()
|
||||||
wsUrl.setQuery(query);
|
wsUrl.setQuery(query);
|
||||||
QNetworkRequest request(wsUrl);
|
QNetworkRequest request(wsUrl);
|
||||||
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
request.setRawHeader("Authorization", "Bearer " + m_accessToken);
|
||||||
qCDebug(dcEasee()) << "Connecting websocket:" << wsUrl.toString();
|
qCDebug(dcEasee()) << "SingalR: Connecting websocket:" << wsUrl.toString();
|
||||||
m_waitingForHandshakeReply = true;
|
m_waitingForHandshakeReply = true;
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5,6,0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5,6,0)
|
||||||
m_socket->open(request);
|
m_socket->open(request);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
#include <QWebSocket>
|
#include <QWebSocket>
|
||||||
|
#include <QTimer>
|
||||||
#include <network/networkaccessmanager.h>
|
#include <network/networkaccessmanager.h>
|
||||||
|
|
||||||
class SignalRConnection : public QObject
|
class SignalRConnection : public QObject
|
||||||
|
|
@ -32,6 +33,7 @@ private:
|
||||||
QByteArray m_accessToken;
|
QByteArray m_accessToken;
|
||||||
NetworkAccessManager *m_nam = nullptr;
|
NetworkAccessManager *m_nam = nullptr;
|
||||||
QWebSocket *m_socket = nullptr;
|
QWebSocket *m_socket = nullptr;
|
||||||
|
QTimer *m_watchdog = nullptr;
|
||||||
|
|
||||||
bool m_waitingForHandshakeReply = false;
|
bool m_waitingForHandshakeReply = false;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue