577 lines
28 KiB
C++
577 lines
28 KiB
C++
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
*
|
|
* Copyright 2013 - 2023, nymea GmbH
|
|
* Contact: contact@nymea.io
|
|
*
|
|
* This file is part of nymea.
|
|
* This project including source code and documentation is protected by
|
|
* copyright law, and remains the property of nymea GmbH. All rights, including
|
|
* reproduction, publication, editing and translation, are reserved. The use of
|
|
* this project is subject to the terms of a license agreement to be concluded
|
|
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
|
* under https://nymea.io/license
|
|
*
|
|
* GNU Lesser General Public License Usage
|
|
* Alternatively, this project may be redistributed and/or modified under the
|
|
* terms of the GNU Lesser General Public License as published by the Free
|
|
* Software Foundation; version 3. This project is distributed in the hope that
|
|
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
|
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this project. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
* For any further details and any questions please contact us under
|
|
* contact@nymea.io or see our FAQ/Licensing Information on
|
|
* https://nymea.io/license/faq
|
|
*
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
|
|
#include "integrationplugineasee.h"
|
|
#include "plugininfo.h"
|
|
#include "signalrconnection.h"
|
|
|
|
#include <network/networkaccessmanager.h>
|
|
|
|
#include <QNetworkRequest>
|
|
#include <QNetworkReply>
|
|
#include <QJsonDocument>
|
|
#include <QWebSocket>
|
|
|
|
QString apiEndpoint = "https://api.easee.com/api";
|
|
QString streamEndpoint = "http://streams.easee.com/hubs/chargers";
|
|
|
|
IntegrationPluginEasee::IntegrationPluginEasee()
|
|
{
|
|
|
|
}
|
|
|
|
IntegrationPluginEasee::~IntegrationPluginEasee()
|
|
{
|
|
}
|
|
|
|
void IntegrationPluginEasee::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret)
|
|
{
|
|
QNetworkRequest request(QUrl(QString("%1/%2").arg(apiEndpoint).arg("accounts/login")));
|
|
request.setRawHeader("accept", "application/json");
|
|
request.setRawHeader("content-type", "application/*+json");
|
|
QVariantMap body;
|
|
body.insert("userName", username);
|
|
body.insert("password", secret);
|
|
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QJsonDocument::fromVariant(body).toJson(QJsonDocument::Compact));
|
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
|
connect(reply, &QNetworkReply::finished, info, [this, info, reply, username, secret](){
|
|
qCDebug(dcEasee) << "auth reply finished" << reply->error();
|
|
|
|
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."));
|
|
return;
|
|
}
|
|
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."));
|
|
return;
|
|
}
|
|
|
|
QByteArray data = reply->readAll();
|
|
QJsonParseError error;
|
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
|
if (error.error != QJsonParseError::NoError) {
|
|
qCWarning(dcEasee) << "Unable to parse json:" << error.errorString() << data;
|
|
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Unable to process the response from easee. Please try again later."));
|
|
return;
|
|
}
|
|
|
|
QVariantMap map = jsonDoc.toVariant().toMap();
|
|
QByteArray accessToken = map.value("accessToken").toByteArray();
|
|
int expiresIn = map.value("expiresIn").toInt();
|
|
QByteArray refreshToken = map.value("refreshToken").toByteArray();
|
|
|
|
pluginStorage()->beginGroup(info->thingId().toString());
|
|
pluginStorage()->setValue("accessToken", accessToken);
|
|
pluginStorage()->setValue("expiry", QDateTime::currentDateTime().addSecs(expiresIn));
|
|
pluginStorage()->setValue("refreshToken", refreshToken);
|
|
|
|
// FIXME: the refresh_token api call seems to not work... So we'll store user/pass in the config for now
|
|
pluginStorage()->setValue("username", username);
|
|
pluginStorage()->setValue("password", secret);
|
|
|
|
pluginStorage()->endGroup();
|
|
|
|
info->finish(Thing::ThingErrorNoError);
|
|
});
|
|
}
|
|
|
|
void IntegrationPluginEasee::setupThing(ThingSetupInfo *info)
|
|
{
|
|
Thing *thing = info->thing();
|
|
|
|
if (thing->thingClassId() == accountThingClassId) {
|
|
pluginStorage()->beginGroup(info->thing()->id().toString());
|
|
QByteArray accessToken = pluginStorage()->value("accessToken").toByteArray();
|
|
QByteArray refreshToken = pluginStorage()->value("refreshToken").toByteArray();
|
|
QDateTime expiry = pluginStorage()->value("expiry").toDateTime();
|
|
pluginStorage()->endGroup();
|
|
|
|
if (expiry < QDateTime::currentDateTime()) {
|
|
QNetworkReply *reply = this->refreshToken(thing);
|
|
connect(reply, &QNetworkReply::finished, info, [=](){
|
|
setupThing(info);
|
|
});
|
|
return;
|
|
}
|
|
|
|
QNetworkRequest request = createRequest(thing, "accounts/profile");
|
|
QNetworkReply *reply = hardwareManager()->networkManager()->get(request);
|
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
|
connect(reply, &QNetworkReply::finished, thing, [this, thing, reply](){
|
|
qCDebug(dcEasee) << "profile info finished" << reply->error();
|
|
if (reply->error() != QNetworkReply::NoError) {
|
|
qCWarning(dcEasee) << "Unable to contact easee server...";
|
|
thing->setStateValue(accountConnectedStateTypeId, false);
|
|
thing->setStateValue(accountLoggedInStateTypeId, false);
|
|
return;
|
|
}
|
|
|
|
QByteArray data = reply->readAll();
|
|
QJsonParseError error;
|
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
|
if (error.error != QJsonParseError::NoError) {
|
|
qCWarning(dcEasee) << "Unable to parse json:" << error.errorString() << data;
|
|
thing->setStateValue(accountConnectedStateTypeId, false);
|
|
thing->setStateValue(accountLoggedInStateTypeId, false);
|
|
return;
|
|
}
|
|
|
|
thing->setStateValue(accountConnectedStateTypeId, true);
|
|
thing->setStateValue(accountLoggedInStateTypeId, true);
|
|
|
|
QVariantMap map = jsonDoc.toVariant().toMap();
|
|
qCDebug(dcEasee) << "Profile reply:" << data;
|
|
|
|
refreshProducts(thing);
|
|
|
|
});
|
|
}
|
|
|
|
if (thing->thingClassId() == chargerThingClassId) {
|
|
// 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);
|
|
|
|
}
|
|
|
|
void IntegrationPluginEasee::postSetupThing(Thing *thing)
|
|
{
|
|
if (!m_timer) {
|
|
m_timer = hardwareManager()->pluginTimerManager()->registerTimer(60);
|
|
connect(m_timer, &PluginTimer::timeout, [this](){
|
|
foreach (Thing *t, myThings()) {
|
|
if (t->thingClassId() == accountThingClassId) {
|
|
|
|
// Refreshing the token if it is about to expire
|
|
pluginStorage()->beginGroup(t->id().toString());
|
|
QByteArray accessToken = pluginStorage()->value("accessToken").toByteArray();
|
|
QByteArray refreshToken = pluginStorage()->value("refreshToken").toByteArray();
|
|
QDateTime expiry = pluginStorage()->value("expiry").toDateTime();
|
|
pluginStorage()->endGroup();
|
|
if (expiry < QDateTime::currentDateTime().addSecs(120)) {
|
|
this->refreshToken(t);
|
|
}
|
|
|
|
// Refreshing the products
|
|
refreshProducts(t);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
if (thing->thingClassId() == accountThingClassId) {
|
|
pluginStorage()->beginGroup(thing->id().toString());
|
|
QByteArray accessToken = pluginStorage()->value("accessToken").toByteArray();
|
|
QDateTime expiry = pluginStorage()->value("expiry").toDateTime();
|
|
pluginStorage()->endGroup();
|
|
|
|
qCDebug(dcEasee()) << "Access token:" << accessToken;
|
|
qCDebug(dcEasee()) << "Token expiry:" << expiry;
|
|
|
|
SignalRConnection *signalR = new SignalRConnection(QUrl(streamEndpoint), accessToken, hardwareManager()->networkManager(), thing);
|
|
m_signalRConnections.insert(thing, signalR);
|
|
|
|
connect(signalR, &SignalRConnection::connectionStateChanged, thing, [=](bool connected){
|
|
foreach (Thing *charger, myThings().filterByParentId(thing->id())) {
|
|
if (connected) {
|
|
signalR->subscribe(charger->paramValue(chargerThingIdParamTypeId).toString());
|
|
} else {
|
|
charger->setStateValue(chargerConnectedStateTypeId, false);
|
|
}
|
|
}
|
|
});
|
|
|
|
connect(signalR, &SignalRConnection::dataReceived, thing, [=](const QVariantMap &data){
|
|
if (data.value("target").toString() != "ProductUpdate") {
|
|
qCWarning(dcEasee()) << "Unhandled SignalR notification:" << data;
|
|
return;
|
|
}
|
|
|
|
foreach (const QVariant &argumentVariant, data.value("arguments").toList()) {
|
|
QVariantMap arg = argumentVariant.toMap();
|
|
QString chargerId = arg.value("mid").toString();
|
|
ObservationPoint dataId = static_cast<ObservationPoint>(arg.value("id").toUInt());
|
|
QVariant value = arg.value("value");
|
|
Thing *charger = myThings().filterByParentId(thing->id()).findByParams({Param(chargerThingIdParamTypeId, chargerId)});
|
|
if (!charger) {
|
|
qCWarning(dcEasee()) << "Cannot find charger" << chargerId;
|
|
continue;
|
|
}
|
|
qCDebug(dcEasee()) << "SignalR data point:" << dataId << value;
|
|
|
|
switch (dataId) {
|
|
case ObservationPointTotalPower:
|
|
charger->setStateValue(chargerCurrentPowerStateTypeId, value.toDouble() * 1000);
|
|
break;
|
|
case ObservationPointSessionEnergy:
|
|
charger->setStateValue(chargerSessionEnergyStateTypeId, value.toDouble());
|
|
break;
|
|
case ObservationPointLifetimeEnergy:
|
|
charger->setStateValue(chargerTotalEnergyConsumedStateTypeId, value.toDouble());
|
|
break;
|
|
case ObservationPointWiFiRSSI:
|
|
charger->setStateValue(chargerSignalStrengthStateTypeId, qMin(100, qMax(0, ((value.toInt() + 100) * 2))));
|
|
break;
|
|
case ObservationPointPilotMode: {
|
|
QString mode = value.toString();
|
|
qCDebug(dcEasee()) << "CP mode:" << mode;
|
|
if (mode == "A") {
|
|
charger->setStateValue(chargerPluggedInStateTypeId, false);
|
|
} else if (mode == "B" || mode == "C") {
|
|
charger->setStateValue(chargerPluggedInStateTypeId, true);
|
|
}
|
|
break;
|
|
}
|
|
case ObservationPointOutputPhase:
|
|
charger->setStateValue(chargerPhaseCountStateTypeId, value.toUInt() == 30 ? 3 : 1);
|
|
break;
|
|
case ObservationPointChargerOpMode:
|
|
// 2: charging disabled, 3: enabled and charging, 4: enabled but not charging
|
|
charger->setStateValue(chargerChargingStateTypeId, value.toUInt() == 3);
|
|
charger->setStateValue(chargerPowerStateTypeId, value.toUInt() >= 3);
|
|
break;
|
|
case ObservationPointDynamicChargerCurrent:
|
|
// 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;
|
|
case ObservationPointMaxChargerCurrent:
|
|
m_wallboxMax[chargerId] = value.toUInt();
|
|
charger->setStateMinMaxValues(chargerMaxChargingCurrentStateTypeId, 6, qMin(m_wallboxMax.value(chargerId), m_cableRating.value(chargerId, 32)));
|
|
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:
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
void IntegrationPluginEasee::thingRemoved(Thing *thing)
|
|
{
|
|
pluginStorage()->beginGroup(thing->id().toString());
|
|
pluginStorage()->remove("");
|
|
pluginStorage()->endGroup();
|
|
|
|
if (myThings().isEmpty()) {
|
|
hardwareManager()->pluginTimerManager()->unregisterTimer(m_timer);
|
|
m_timer = nullptr;
|
|
}
|
|
|
|
if (thing->thingClassId() == chargerThingClassId) {
|
|
m_desiredMax.take(thing);
|
|
}
|
|
}
|
|
|
|
void IntegrationPluginEasee::executeAction(ThingActionInfo *info)
|
|
{
|
|
Thing *thing = info->thing();
|
|
if (thing->thingClassId() == chargerThingClassId) {
|
|
Thing *parentThing = myThings().findById(thing->parentId());
|
|
QString chargerId = thing->paramValue(chargerThingIdParamTypeId).toString();
|
|
if (info->action().actionTypeId() == chargerPowerActionTypeId) {
|
|
bool power = info->action().paramValue(chargerPowerActionPowerParamTypeId).toBool();
|
|
QString actionPath = power ? "start_charging" : "pause_charging";
|
|
QNetworkRequest request = createRequest(parentThing, QString("chargers/%1/commands/%2").arg(chargerId).arg(actionPath));
|
|
qCDebug(dcEasee()) << "Setting power:" << request.url().toString();
|
|
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QByteArray());
|
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
|
connect(reply, &QNetworkReply::finished, info, [=](){
|
|
qCDebug(dcEasee()) << "Reply" << reply->error();
|
|
if (reply->error() == QNetworkReply::NoError) {
|
|
info->thing()->setStateValue(chargerPowerStateTypeId, power);
|
|
}
|
|
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;
|
|
}
|
|
if (info->action().actionTypeId() == chargerMaxChargingCurrentActionTypeId) {
|
|
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));
|
|
QVariantMap data;
|
|
data.insert("dynamicChargerCurrent", maxChargingCurrent);
|
|
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);
|
|
connect(reply, &QNetworkReply::finished, info, [reply, info, maxChargingCurrent](){
|
|
qCDebug(dcEasee()) << "Set dynamicaChargerCurrent reply" << reply->error();
|
|
if (reply->error() == QNetworkReply::NoError) {
|
|
info->thing()->setStateValue(chargerMaxChargingCurrentStateTypeId, maxChargingCurrent);
|
|
}
|
|
info->finish(reply->error() == QNetworkReply::NoError ? Thing::ThingErrorNoError : Thing::ThingErrorHardwareFailure);
|
|
});
|
|
return;
|
|
}
|
|
if (info->action().actionTypeId() == chargerDesiredPhaseCountActionTypeId) {
|
|
uint desiredPhaseCount = info->action().paramValue(chargerDesiredPhaseCountActionDesiredPhaseCountParamTypeId).toUInt();
|
|
bool wasOn = thing->stateValue(chargerPowerStateTypeId).toBool();
|
|
bool oldMaxCurrent = m_desiredMax.value(info->thing());
|
|
if (desiredPhaseCount == thing->stateValue(chargerPhaseCountStateTypeId)) {
|
|
qCInfo(dcEasee()) << "effective phases already equals desired ones...";
|
|
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, info, [=](){
|
|
|
|
QNetworkRequest request = createRequest(parentThing, QString("chargers/%1/settings").arg(chargerId));
|
|
|
|
QVariantMap data;
|
|
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;
|
|
}
|
|
|
|
}
|
|
info->finish(Thing::ThingErrorNoError);
|
|
}
|
|
|
|
QNetworkRequest IntegrationPluginEasee::createRequest(Thing *thing, const QString &endpoint)
|
|
{
|
|
pluginStorage()->beginGroup(thing->id().toString());
|
|
QByteArray accessToken = pluginStorage()->value("accessToken").toByteArray();
|
|
pluginStorage()->endGroup();
|
|
QNetworkRequest request(QUrl(QString("%1/%2").arg(apiEndpoint).arg(endpoint)));
|
|
request.setRawHeader("Authorization", "Bearer " + accessToken);
|
|
request.setRawHeader("accept", "application/json");
|
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/*+json");
|
|
return request;
|
|
}
|
|
|
|
QNetworkReply *IntegrationPluginEasee::refreshToken(Thing *thing)
|
|
{
|
|
pluginStorage()->beginGroup(thing->id().toString());
|
|
QByteArray refreshToken = pluginStorage()->value("refreshToken").toByteArray();
|
|
QByteArray accessToken = pluginStorage()->value("accessToken").toByteArray();
|
|
QString username = pluginStorage()->value("username").toString();
|
|
QString password = pluginStorage()->value("password").toString();
|
|
pluginStorage()->endGroup();
|
|
|
|
|
|
// 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("%1/%2").arg(apiEndpoint).arg("accounts/refresh_token")));
|
|
// request.setRawHeader("accept", "application/json");
|
|
// request.setRawHeader("content-type", "application/*+json");
|
|
// QVariantMap body;
|
|
// body.insert("refreshToken", refreshToken);
|
|
// body.insert("accessToken", accessToken);
|
|
|
|
QNetworkRequest request(QUrl(QString("%1/%2").arg(apiEndpoint).arg("accounts/login")));
|
|
request.setRawHeader("accept", "application/json");
|
|
request.setRawHeader("content-type", "application/*+json");
|
|
QVariantMap body;
|
|
body.insert("userName", username);
|
|
body.insert("password", password);
|
|
|
|
qCDebug(dcEasee()) << "Fetching:" << request.url().toString();
|
|
|
|
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QJsonDocument::fromVariant(body).toJson(QJsonDocument::Compact));
|
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
|
connect(reply, &QNetworkReply::finished, thing, [=](){
|
|
qCDebug(dcEasee) << "Token refresh finished" << reply->error();
|
|
if (reply->error() != QNetworkReply::NoError) {
|
|
qCWarning(dcEasee) << "Unable to contact easee server...";
|
|
return;
|
|
}
|
|
|
|
QByteArray data = reply->readAll();
|
|
QJsonParseError error;
|
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
|
if (error.error != QJsonParseError::NoError) {
|
|
qCWarning(dcEasee) << "Unable to parse json:" << error.errorString() << data;
|
|
return;
|
|
}
|
|
|
|
QVariantMap map = jsonDoc.toVariant().toMap();
|
|
qCDebug(dcEasee) << "Token refresh reply:" << data;
|
|
|
|
QByteArray accessToken = map.value("accessToken").toByteArray();
|
|
int expiresIn = map.value("expiresIn").toInt();
|
|
QByteArray refreshToken = map.value("refreshToken").toByteArray();
|
|
|
|
pluginStorage()->beginGroup(thing->id().toString());
|
|
pluginStorage()->setValue("accessToken", accessToken);
|
|
pluginStorage()->setValue("expiry", QDateTime::currentDateTime().addSecs(expiresIn));
|
|
pluginStorage()->setValue("refreshToken", refreshToken);
|
|
pluginStorage()->endGroup();
|
|
|
|
if (m_signalRConnections.contains(thing)) {
|
|
m_signalRConnections.value(thing)->updateToken(accessToken);
|
|
}
|
|
|
|
});
|
|
|
|
return reply;
|
|
}
|
|
|
|
void IntegrationPluginEasee::refreshProducts(Thing *account)
|
|
{
|
|
QNetworkRequest request = createRequest(account, "accounts/products");
|
|
QNetworkReply *reply = hardwareManager()->networkManager()->get(request);
|
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
|
connect(reply, &QNetworkReply::finished, account, [this, account, reply](){
|
|
if (reply->error() != QNetworkReply::NoError) {
|
|
qCWarning(dcEasee) << "Unable to refresh products:" << reply->error() << reply->errorString();
|
|
return;
|
|
}
|
|
|
|
QByteArray data = reply->readAll();
|
|
QJsonParseError error;
|
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
|
if (error.error != QJsonParseError::NoError) {
|
|
qCWarning(dcEasee) << "Unable to parse json for products:" << error.errorString() << data;
|
|
return;
|
|
}
|
|
|
|
QVariantList list = jsonDoc.toVariant().toList();
|
|
qCDebug(dcEasee) << "Products reply:" << qUtf8Printable(data);
|
|
|
|
foreach (const QVariant &siteVariant, list) {
|
|
QVariantMap site = siteVariant.toMap();
|
|
foreach (const QVariant &circuitVariant, site.value("circuits").toList()) {
|
|
QVariantMap circuit = circuitVariant.toMap();
|
|
// double maxChartingCurrentLimit = circuit.value("ratedCurrent").toDouble();
|
|
uint circuitId = circuit.value("id").toUInt();
|
|
uint siteId = circuit.value("siteId").toUInt();
|
|
foreach (const QVariant &chargerVariant, circuit.value("chargers").toList()) {
|
|
QVariantMap charger = chargerVariant.toMap();
|
|
QString id = charger.value("id").toString();
|
|
QString name = charger.value("name").toString();
|
|
|
|
ParamList params{Param(chargerThingIdParamTypeId, id)};
|
|
Thing *existingThing = myThings().filterByParentId(account->id()).findByParams(params);
|
|
if (!existingThing) {
|
|
ThingDescriptor descriptor(chargerThingClassId, name, QString(), account->id());
|
|
descriptor.setParams(params);
|
|
emit autoThingsAppeared({descriptor});
|
|
}
|
|
m_siteIds[id] = siteId;
|
|
m_circuitIds[id] = circuitId;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
void IntegrationPluginEasee::refreshCurrentState(Thing *charger)
|
|
{
|
|
Thing *parentThing = myThings().findById(charger->parentId());
|
|
QString chargerId = charger->paramValue(chargerThingIdParamTypeId).toString();
|
|
QNetworkRequest request = createRequest(parentThing, QString("chargers/%1/state").arg(chargerId));
|
|
|
|
QNetworkReply *reply = hardwareManager()->networkManager()->get(request);
|
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
|
connect(reply, &QNetworkReply::finished, charger, [charger, reply](){
|
|
if (reply->error() != QNetworkReply::NoError) {
|
|
qCWarning(dcEasee) << "Unable to fetch charger state:" << reply->error() << reply->errorString();
|
|
return;
|
|
}
|
|
|
|
QByteArray data = reply->readAll();
|
|
QJsonParseError error;
|
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
|
if (error.error != QJsonParseError::NoError) {
|
|
qCWarning(dcEasee) << "Unable to parse json for charger state:" << error.errorString() << data;
|
|
return;
|
|
}
|
|
|
|
QVariantMap map = jsonDoc.toVariant().toMap();
|
|
qCDebug(dcEasee) << "Charger state reply:" << qUtf8Printable(jsonDoc.toJson());
|
|
charger->setStateValue(chargerConnectedStateTypeId, map.value("isOnline").toBool());
|
|
});
|
|
|
|
|
|
}
|
|
|