Merge PR #153: Webasto: Add support for the Webasto Unite wallbox
This commit is contained in:
commit
07e9b1cf97
@ -4,6 +4,7 @@ Connects nymea to Webasto wallboxes. Currently supported models:
|
|||||||
|
|
||||||
* Webasto Live
|
* Webasto Live
|
||||||
* Webasto NEXT
|
* Webasto NEXT
|
||||||
|
* Webasto Unite
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
|
|||||||
@ -35,12 +35,16 @@
|
|||||||
#include <types/param.h>
|
#include <types/param.h>
|
||||||
#include <hardware/electricity.h>
|
#include <hardware/electricity.h>
|
||||||
#include <network/networkdevicediscovery.h>
|
#include <network/networkdevicediscovery.h>
|
||||||
|
#include <network/networkaccessmanager.h>
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QNetworkInterface>
|
#include <QNetworkInterface>
|
||||||
|
#include <QNetworkRequest>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
|
||||||
|
#include "../vestel/evc04discovery.h"
|
||||||
|
|
||||||
IntegrationPluginWebasto::IntegrationPluginWebasto()
|
IntegrationPluginWebasto::IntegrationPluginWebasto()
|
||||||
{
|
{
|
||||||
@ -150,6 +154,38 @@ void IntegrationPluginWebasto::discoverThings(ThingDiscoveryInfo *info)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (info->thingClassId() == webastoUniteThingClassId) {
|
||||||
|
EVC04Discovery *discovery = new EVC04Discovery(hardwareManager()->networkDeviceDiscovery(), dcWebasto(), info);
|
||||||
|
connect(discovery, &EVC04Discovery::discoveryFinished, info, [=](){
|
||||||
|
foreach (const EVC04Discovery::Result &result, discovery->discoveryResults()) {
|
||||||
|
|
||||||
|
if (result.brand != "Webasto") {
|
||||||
|
qCDebug(dcWebasto()) << "Skipping Vestel wallbox without Webasto branding...";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
QString name = result.chargepointId;
|
||||||
|
QString description = result.brand + " " + result.model;
|
||||||
|
ThingDescriptor descriptor(webastoUniteThingClassId, name, description);
|
||||||
|
qCDebug(dcWebasto()) << "Discovered:" << descriptor.title() << descriptor.description();
|
||||||
|
|
||||||
|
// Check if we already have set up this device
|
||||||
|
Things existingThings = myThings().filterByParam(webastoUniteThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress());
|
||||||
|
if (existingThings.count() == 1) {
|
||||||
|
qCDebug(dcWebasto()) << "This wallbox already exists in the system:" << result.networkDeviceInfo;
|
||||||
|
descriptor.setThingId(existingThings.first()->id());
|
||||||
|
}
|
||||||
|
|
||||||
|
ParamList params;
|
||||||
|
params << Param(webastoUniteThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress());
|
||||||
|
descriptor.setParams(params);
|
||||||
|
info->addThingDescriptor(descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
info->finish(Thing::ThingErrorNoError);
|
||||||
|
});
|
||||||
|
discovery->startDiscovery();
|
||||||
|
|
||||||
|
}
|
||||||
Q_ASSERT_X(false, "discoverThings", QString("Unhandled thingClassId: %1").arg(info->thingClassId().toString()).toUtf8());
|
Q_ASSERT_X(false, "discoverThings", QString("Unhandled thingClassId: %1").arg(info->thingClassId().toString()).toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,6 +283,49 @@ void IntegrationPluginWebasto::setupThing(ThingSetupInfo *info)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (thing->thingClassId() == webastoUniteThingClassId) {
|
||||||
|
|
||||||
|
if (m_evc04Connections.contains(thing)) {
|
||||||
|
qCDebug(dcWebasto()) << "Reconfiguring existing thing" << thing->name();
|
||||||
|
m_evc04Connections.take(thing)->deleteLater();
|
||||||
|
|
||||||
|
if (m_monitors.contains(thing)) {
|
||||||
|
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MacAddress macAddress = MacAddress(thing->paramValue(webastoUniteThingMacAddressParamTypeId).toString());
|
||||||
|
if (!macAddress.isValid()) {
|
||||||
|
qCWarning(dcWebasto()) << "The configured mac address is not valid" << thing->params();
|
||||||
|
info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("The MAC address is not known. Please reconfigure the thing."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(macAddress);
|
||||||
|
m_monitors.insert(thing, monitor);
|
||||||
|
|
||||||
|
connect(info, &ThingSetupInfo::aborted, monitor, [=](){
|
||||||
|
if (m_monitors.contains(thing)) {
|
||||||
|
qCDebug(dcWebasto()) << "Unregistering monitor because setup has been aborted.";
|
||||||
|
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (monitor->reachable()) {
|
||||||
|
setupEVC04Connection(info);
|
||||||
|
} else {
|
||||||
|
qCDebug(dcWebasto()) << "Waiting for the network monitor to get reachable before continuing to set up the connection" << thing->name() << "...";
|
||||||
|
connect(monitor, &NetworkDeviceMonitor::reachableChanged, info, [=](bool reachable){
|
||||||
|
if (reachable) {
|
||||||
|
qCDebug(dcWebasto()) << "The monitor for thing setup" << thing->name() << "is now reachable. Continuing setup on" << monitor->networkDeviceInfo().address().toString();
|
||||||
|
setupEVC04Connection(info);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8());
|
Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8());
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -256,7 +335,7 @@ void IntegrationPluginWebasto::postSetupThing(Thing *thing)
|
|||||||
qCDebug(dcWebasto()) << "Post setup thing" << thing->name();
|
qCDebug(dcWebasto()) << "Post setup thing" << thing->name();
|
||||||
if (!m_pluginTimer) {
|
if (!m_pluginTimer) {
|
||||||
qCDebug(dcWebasto()) << "Setting up refresh timer for Webasto connections";
|
qCDebug(dcWebasto()) << "Setting up refresh timer for Webasto connections";
|
||||||
m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(1);
|
m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(2);
|
||||||
connect(m_pluginTimer, &PluginTimer::timeout, this, [this] {
|
connect(m_pluginTimer, &PluginTimer::timeout, this, [this] {
|
||||||
|
|
||||||
foreach(Webasto *connection, m_webastoLiveConnections) {
|
foreach(Webasto *connection, m_webastoLiveConnections) {
|
||||||
@ -270,6 +349,13 @@ void IntegrationPluginWebasto::postSetupThing(Thing *thing)
|
|||||||
webastoNext->update();
|
webastoNext->update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach(EVC04ModbusTcpConnection *connection, m_evc04Connections) {
|
||||||
|
qCDebug(dcWebasto()) << "Updating connection" << connection->modbusTcpMaster()->hostAddress().toString();
|
||||||
|
connection->update();
|
||||||
|
connection->setAliveRegister(1);
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
m_pluginTimer->start();
|
m_pluginTimer->start();
|
||||||
@ -306,6 +392,12 @@ void IntegrationPluginWebasto::thingRemoved(Thing *thing)
|
|||||||
connection->deleteLater();
|
connection->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (thing->thingClassId() == webastoUniteThingClassId && m_evc04Connections.contains(thing)) {
|
||||||
|
EVC04ModbusTcpConnection *connection = m_evc04Connections.take(thing);
|
||||||
|
delete connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Unregister related hardware resources
|
// Unregister related hardware resources
|
||||||
if (m_monitors.contains(thing))
|
if (m_monitors.contains(thing))
|
||||||
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
|
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
|
||||||
@ -424,6 +516,94 @@ void IntegrationPluginWebasto::executeAction(ThingActionInfo *info)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (info->thing()->thingClassId() == webastoUniteThingClassId) {
|
||||||
|
|
||||||
|
EVC04ModbusTcpConnection *evc04Connection = m_evc04Connections.value(info->thing());
|
||||||
|
|
||||||
|
if (info->action().actionTypeId() == webastoUnitePowerActionTypeId) {
|
||||||
|
bool power = info->action().paramValue(webastoUnitePowerActionPowerParamTypeId).toBool();
|
||||||
|
|
||||||
|
// If the car is *not* connected, writing a 0 to the charging current register will cause it to go to 6 A instead of 0
|
||||||
|
// Because of this, we we're not connected, we'll do nothing, but once it get's connected, we'll sync the state over (see below in cableStateChanged)
|
||||||
|
if (!power && evc04Connection->cableState() < EVC04ModbusTcpConnection::CableStateCableConnectedVehicleConnected) {
|
||||||
|
info->thing()->setStateValue(webastoUnitePowerStateTypeId, false);
|
||||||
|
info->finish(Thing::ThingErrorNoError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QModbusReply *reply = evc04Connection->setChargingCurrent(power ? info->thing()->stateValue(webastoUniteMaxChargingCurrentStateTypeId).toUInt() : 0);
|
||||||
|
connect(reply, &QModbusReply::finished, info, [info, reply, power](){
|
||||||
|
if (reply->error() == QModbusDevice::NoError) {
|
||||||
|
info->thing()->setStateValue(webastoUnitePowerStateTypeId, power);
|
||||||
|
info->finish(Thing::ThingErrorNoError);
|
||||||
|
} else {
|
||||||
|
qCWarning(dcWebasto()) << "Error setting power:" << reply->error() << reply->errorString();
|
||||||
|
info->finish(Thing::ThingErrorHardwareFailure);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (info->action().actionTypeId() == webastoUniteMaxChargingCurrentActionTypeId) {
|
||||||
|
int maxChargingCurrent = info->action().paramValue(webastoUniteMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toInt();
|
||||||
|
QModbusReply *reply = evc04Connection->setChargingCurrent(maxChargingCurrent);
|
||||||
|
connect(reply, &QModbusReply::finished, info, [info, reply, maxChargingCurrent](){
|
||||||
|
if (reply->error() == QModbusDevice::NoError) {
|
||||||
|
info->thing()->setStateValue(webastoUniteMaxChargingCurrentStateTypeId, maxChargingCurrent);
|
||||||
|
info->finish(Thing::ThingErrorNoError);
|
||||||
|
} else {
|
||||||
|
info->finish(Thing::ThingErrorHardwareFailure);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (info->action().actionTypeId() == webastoUniteDesiredPhaseCountActionTypeId) {
|
||||||
|
if (validTokenAvailable(thing)) {
|
||||||
|
executeWebastoUnitePhaseCountAction(info);
|
||||||
|
} else {
|
||||||
|
qCDebug(dcWebasto()) << "HTTP: Authentication required. Update access token for" << thing->name();
|
||||||
|
QNetworkReply *loginReply = requestWebstoUniteAccessToken(evc04Connection->modbusTcpMaster()->hostAddress());
|
||||||
|
connect(loginReply, &QNetworkReply::finished, evc04Connection, [=](){
|
||||||
|
if (loginReply->error() != QNetworkReply::NoError) {
|
||||||
|
info->finish(Thing::ThingErrorAuthenticationFailure);
|
||||||
|
qCWarning(dcWebasto()) << "HTTP: Authentication request failed for" << evc04Connection->modbusTcpMaster()->hostAddress() << loginReply->error() << loginReply->errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray response = loginReply->readAll();
|
||||||
|
|
||||||
|
QJsonParseError error;
|
||||||
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(response, &error);
|
||||||
|
if (error.error != QJsonParseError::NoError) {
|
||||||
|
info->finish(Thing::ThingErrorAuthenticationFailure);
|
||||||
|
qCWarning(dcWebasto()) << "HTTP: Authentication response JSON error:" << error.errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantMap responseMap = jsonDoc.toVariant().toMap();
|
||||||
|
QString accessToken = responseMap.value("access_token").toString();
|
||||||
|
QDateTime accessTokenExpireDateTime = QDateTime::fromString(responseMap.value("access_token_exp").toString(), Qt::ISODate);
|
||||||
|
|
||||||
|
QStringList tokenParts = accessToken.split('.');
|
||||||
|
if (tokenParts.count() != 3) {
|
||||||
|
qCWarning(dcWebasto()) << "HTTP: Could not read expiration timestamp. Invalid JWT token formatting. Does not contain 3 parts separated by dot.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(dcWebasto()) << "HTTP: Header" << QByteArray::fromBase64(tokenParts.at(0).toUtf8());
|
||||||
|
qCDebug(dcWebasto()) << "HTTP: Payload" << QByteArray::fromBase64(tokenParts.at(1).toUtf8());
|
||||||
|
qCDebug(dcWebasto()) << "HTTP: Signature" << tokenParts.at(2);
|
||||||
|
QJsonDocument payloadJsonDoc = QJsonDocument::fromJson(QByteArray::fromBase64(tokenParts.at(1).toUtf8()));
|
||||||
|
QVariantMap payloadMap = payloadJsonDoc.toVariant().toMap();
|
||||||
|
QDateTime expirationDateTime = QDateTime::fromString(payloadMap.value("access_token_exp").toString(), Qt::ISODate);
|
||||||
|
qCDebug(dcWebasto()) << "HTTP: Token payload:" << qUtf8Printable(payloadJsonDoc.toJson());
|
||||||
|
qCDebug(dcWebasto()) << "HTTP: Token expires:" << expirationDateTime.toString("dd.MM.yyyy hh:mm:ss");
|
||||||
|
qCDebug(dcWebasto()) << "HTTP: Authentication finished successfully. Token:" << accessToken << "Expires:" << accessTokenExpireDateTime.toString("dd.MM.yyyy hh:mm:ss");
|
||||||
|
m_webastoUniteTokens[thing] = QPair<QString, QDateTime>(accessToken, accessTokenExpireDateTime);
|
||||||
|
executeWebastoUnitePhaseCountAction(info);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Q_ASSERT_X(false, "executeAction", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8());
|
Q_ASSERT_X(false, "executeAction", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -984,3 +1164,319 @@ void IntegrationPluginWebasto::onReceivedRegister(Webasto::TqModbusRegister modb
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IntegrationPluginWebasto::setupEVC04Connection(ThingSetupInfo *info)
|
||||||
|
{
|
||||||
|
Thing *thing = info->thing();
|
||||||
|
|
||||||
|
QHostAddress address = m_monitors.value(thing)->networkDeviceInfo().address();
|
||||||
|
|
||||||
|
EVC04ModbusTcpConnection *evc04Connection = new EVC04ModbusTcpConnection(address, 502, 0xff, this);
|
||||||
|
connect(info, &ThingSetupInfo::aborted, evc04Connection, &EVC04ModbusTcpConnection::deleteLater);
|
||||||
|
|
||||||
|
// Reconnect on monitor reachable changed
|
||||||
|
NetworkDeviceMonitor *monitor = m_monitors.value(thing);
|
||||||
|
connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [=](bool reachable){
|
||||||
|
qCDebug(dcWebasto()) << "Network device monitor reachable changed for" << thing->name() << reachable;
|
||||||
|
if (!thing->setupComplete())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (reachable && !thing->stateValue("connected").toBool()) {
|
||||||
|
evc04Connection->modbusTcpMaster()->setHostAddress(monitor->networkDeviceInfo().address());
|
||||||
|
evc04Connection->connectDevice();
|
||||||
|
} else if (!reachable) {
|
||||||
|
// Note: We disable autoreconnect explicitly and we will
|
||||||
|
// connect the device once the monitor says it is reachable again
|
||||||
|
evc04Connection->disconnectDevice();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(evc04Connection, &EVC04ModbusTcpConnection::reachableChanged, thing, [thing, evc04Connection](bool reachable){
|
||||||
|
qCDebug(dcWebasto()) << "Reachable changed to" << reachable << "for" << thing;
|
||||||
|
if (reachable) {
|
||||||
|
evc04Connection->initialize();
|
||||||
|
} else {
|
||||||
|
thing->setStateValue(webastoUniteConnectedStateTypeId, false);
|
||||||
|
thing->setStateValue(webastoUniteCurrentPowerStateTypeId, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(evc04Connection, &EVC04ModbusTcpConnection::initializationFinished, thing, [=](bool success){
|
||||||
|
if (!thing->setupComplete())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
thing->setStateValue(webastoUniteConnectedStateTypeId, true);
|
||||||
|
} else {
|
||||||
|
thing->setStateValue(webastoUniteConnectedStateTypeId, false);
|
||||||
|
thing->setStateValue(webastoUniteCurrentPowerStateTypeId, 0);
|
||||||
|
|
||||||
|
// Try once to reconnect the device
|
||||||
|
evc04Connection->reconnectDevice();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(evc04Connection, &EVC04ModbusTcpConnection::initializationFinished, info, [=](bool success){
|
||||||
|
if (!success) {
|
||||||
|
qCWarning(dcWebasto()) << "Connection init finished with errors" << thing->name() << evc04Connection->modbusTcpMaster()->hostAddress().toString();
|
||||||
|
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(monitor);
|
||||||
|
evc04Connection->deleteLater();
|
||||||
|
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Error communicating with the wallbox."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(dcWebasto()) << "Connection init finished successfully" << evc04Connection;
|
||||||
|
|
||||||
|
m_evc04Connections.insert(thing, evc04Connection);
|
||||||
|
info->finish(Thing::ThingErrorNoError);
|
||||||
|
|
||||||
|
thing->setStateValue(webastoUniteConnectedStateTypeId, true);
|
||||||
|
thing->setStateValue(webastoUniteVersionStateTypeId, QString(QString::fromUtf16(evc04Connection->firmwareVersion().data(), evc04Connection->firmwareVersion().length()).toUtf8()).trimmed());
|
||||||
|
|
||||||
|
evc04Connection->update();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(evc04Connection, &EVC04ModbusTcpConnection::updateFinished, thing, [this, evc04Connection, thing](){
|
||||||
|
qCDebug(dcWebasto()) << "EVC04 update finished:" << thing->name() << evc04Connection;
|
||||||
|
qCDebug(dcWebasto()) << "Serial:" << QString(QString::fromUtf16(evc04Connection->serialNumber().data(), evc04Connection->serialNumber().length()).toUtf8()).trimmed();
|
||||||
|
qCDebug(dcWebasto()) << "ChargePoint ID:" << QString(QString::fromUtf16(evc04Connection->chargepointId().data(), evc04Connection->chargepointId().length()).toUtf8()).trimmed();
|
||||||
|
qCDebug(dcWebasto()) << "Brand:" << QString(QString::fromUtf16(evc04Connection->brand().data(), evc04Connection->brand().length()).toUtf8()).trimmed();
|
||||||
|
qCDebug(dcWebasto()) << "Model:" << QString(QString::fromUtf16(evc04Connection->model().data(), evc04Connection->model().length()).toUtf8()).trimmed();
|
||||||
|
|
||||||
|
updateEVC04MaxCurrent(thing);
|
||||||
|
|
||||||
|
// I've been observing the wallbox getting stuck on modbus. It is still functional, but modbus keeps on returning the same old values
|
||||||
|
// until the TCP connection is closed and reopened. Checking the wallbox time register to detect that and auto-reconnect.
|
||||||
|
if (m_lastWallboxTime.contains(thing) && m_lastWallboxTime[thing] == evc04Connection->time()) {
|
||||||
|
qCWarning(dcWebasto()) << "Wallbox seems stuck and returning outdated values. Reconnecting...";
|
||||||
|
evc04Connection->disconnectDevice();
|
||||||
|
QTimer::singleShot(1000, evc04Connection, &EVC04ModbusTcpConnection::reconnectDevice);
|
||||||
|
} else {
|
||||||
|
m_lastWallboxTime[thing] = evc04Connection->time();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(evc04Connection, &EVC04ModbusTcpConnection::chargepointStateChanged, thing, [thing](EVC04ModbusTcpConnection::ChargePointState chargePointState) {
|
||||||
|
qCDebug(dcWebasto()) << "Chargepoint state changed" << thing->name() << chargePointState;
|
||||||
|
// switch (chargePointState) {
|
||||||
|
// case EVC04ModbusTcpConnection::ChargePointStateAvailable:
|
||||||
|
// case EVC04ModbusTcpConnection::ChargePointStatePreparing:
|
||||||
|
// case EVC04ModbusTcpConnection::ChargePointStateReserved:
|
||||||
|
// case EVC04ModbusTcpConnection::ChargePointStateUnavailable:
|
||||||
|
// case EVC04ModbusTcpConnection::ChargePointStateFaulted:
|
||||||
|
// thing->setStateValue(evc04PluggedInStateTypeId, false);
|
||||||
|
// break;
|
||||||
|
// case EVC04ModbusTcpConnection::ChargePointStateCharging:
|
||||||
|
// case EVC04ModbusTcpConnection::ChargePointStateSuspendedEVSE:
|
||||||
|
// case EVC04ModbusTcpConnection::ChargePointStateSuspendedEV:
|
||||||
|
// case EVC04ModbusTcpConnection::ChargePointStateFinishing:
|
||||||
|
// thing->setStateValue(evc04PluggedInStateTypeId, true);
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
connect(evc04Connection, &EVC04ModbusTcpConnection::chargingStateChanged, thing, [thing](EVC04ModbusTcpConnection::ChargingState chargingState) {
|
||||||
|
qCDebug(dcWebasto()) << "Charging state changed:" << chargingState;
|
||||||
|
thing->setStateValue(webastoUniteChargingStateTypeId, chargingState == EVC04ModbusTcpConnection::ChargingStateCharging);
|
||||||
|
});
|
||||||
|
connect(evc04Connection, &EVC04ModbusTcpConnection::activePowerTotalChanged, thing, [thing](quint16 activePowerTotal) {
|
||||||
|
qCDebug(dcWebasto()) << "Total active power:" << activePowerTotal;
|
||||||
|
// The wallbox reports some 5-6W even when there's nothing connected. Let's hide that if we're not charging
|
||||||
|
if (thing->stateValue(webastoUniteChargingStateTypeId).toBool() == true) {
|
||||||
|
thing->setStateValue(webastoUniteCurrentPowerStateTypeId, activePowerTotal);
|
||||||
|
} else {
|
||||||
|
thing->setStateValue(webastoUniteCurrentPowerStateTypeId, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(evc04Connection, &EVC04ModbusTcpConnection::meterReadingChanged, thing, [thing](quint32 meterReading) {
|
||||||
|
qCDebug(dcWebasto()) << "Meter reading changed:" << meterReading;
|
||||||
|
thing->setStateValue(webastoUniteTotalEnergyConsumedStateTypeId, meterReading / 10.0);
|
||||||
|
});
|
||||||
|
connect(evc04Connection, &EVC04ModbusTcpConnection::sessionMaxCurrentChanged, thing, [](quint16 sessionMaxCurrent) {
|
||||||
|
// This mostly just reflects what we've been writing to cargingCurrent, so not of much use...
|
||||||
|
qCDebug(dcWebasto()) << "Session max current changed:" << sessionMaxCurrent;
|
||||||
|
});
|
||||||
|
connect(evc04Connection, &EVC04ModbusTcpConnection::cableMaxCurrentChanged, thing, [this, thing](quint16 cableMaxCurrent) {
|
||||||
|
qCDebug(dcWebasto()) << "Cable max current changed:" << cableMaxCurrent;
|
||||||
|
updateEVC04MaxCurrent(thing);
|
||||||
|
});
|
||||||
|
connect(evc04Connection, &EVC04ModbusTcpConnection::evseMinCurrentChanged, thing, [thing](quint16 evseMinCurrent) {
|
||||||
|
qCDebug(dcWebasto()) << "EVSE min current changed:" << evseMinCurrent;
|
||||||
|
thing->setStateMinValue(webastoUniteMaxChargingCurrentStateTypeId, evseMinCurrent);
|
||||||
|
});
|
||||||
|
connect(evc04Connection, &EVC04ModbusTcpConnection::evseMaxCurrentChanged, thing, [this, thing](quint16 evseMaxCurrent) {
|
||||||
|
qCDebug(dcWebasto()) << "EVSE max current changed:" << evseMaxCurrent;
|
||||||
|
updateEVC04MaxCurrent(thing);
|
||||||
|
});
|
||||||
|
connect(evc04Connection, &EVC04ModbusTcpConnection::sessionEnergyChanged, thing, [thing](quint32 sessionEnergy) {
|
||||||
|
qCDebug(dcWebasto()) << "Session energy changed:" << sessionEnergy;
|
||||||
|
thing->setStateValue(webastoUniteSessionEnergyStateTypeId, sessionEnergy / 1000.0);
|
||||||
|
});
|
||||||
|
connect(evc04Connection, &EVC04ModbusTcpConnection::chargingCurrentChanged, thing, [thing](quint16 chargingCurrent) {
|
||||||
|
qCDebug(dcWebasto()) << "Charging current changed:" << chargingCurrent;
|
||||||
|
if (chargingCurrent > 0) {
|
||||||
|
thing->setStateValue(webastoUnitePowerStateTypeId, true);
|
||||||
|
thing->setStateValue(webastoUniteMaxChargingCurrentStateTypeId, chargingCurrent);
|
||||||
|
} else {
|
||||||
|
thing->setStateValue(webastoUnitePowerStateTypeId, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(evc04Connection, &EVC04ModbusTcpConnection::numPhasesChanged, thing, [thing](EVC04ModbusTcpConnection::NumPhases numPhases) {
|
||||||
|
switch (numPhases) {
|
||||||
|
case EVC04ModbusTcpConnection::NumPhases1:
|
||||||
|
thing->setStateValue(webastoUnitePhaseCountStateTypeId, 1);
|
||||||
|
break;
|
||||||
|
case EVC04ModbusTcpConnection::NumPhases3:
|
||||||
|
thing->setStateValue(webastoUnitePhaseCountStateTypeId, 3);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(evc04Connection, &EVC04ModbusTcpConnection::cableStateChanged, thing, [evc04Connection, thing](EVC04ModbusTcpConnection::CableState cableState) {
|
||||||
|
switch (cableState) {
|
||||||
|
case EVC04ModbusTcpConnection::CableStateNotConnected:
|
||||||
|
case EVC04ModbusTcpConnection::CableStateCableConnectedVehicleNotConnected:
|
||||||
|
thing->setStateValue(webastoUnitePluggedInStateTypeId, false);
|
||||||
|
break;
|
||||||
|
case EVC04ModbusTcpConnection::CableStateCableConnectedVehicleConnected:
|
||||||
|
case EVC04ModbusTcpConnection::CableStateCableConnectedVehicleConnectedCableLocked:
|
||||||
|
thing->setStateValue(webastoUnitePluggedInStateTypeId, true);
|
||||||
|
|
||||||
|
// The car was plugged in, sync the power state now as the wallbox only allows to set that when the car is connected
|
||||||
|
if (thing->stateValue(webastoUnitePowerStateTypeId).toBool() == false) {
|
||||||
|
qCInfo(dcWebasto()) << "Car plugged in. Syncing cached power off state to wallbox";
|
||||||
|
evc04Connection->setChargingCurrent(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
evc04Connection->connectDevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IntegrationPluginWebasto::updateEVC04MaxCurrent(Thing *thing)
|
||||||
|
{
|
||||||
|
EVC04ModbusTcpConnection *connection = m_evc04Connections.value(thing);
|
||||||
|
quint16 wallboxMax = connection->maxChargePointPower() > 0 ? connection->maxChargePointPower() / 230 : 32;
|
||||||
|
quint16 evseMax = connection->evseMaxCurrent() > 0 ? connection->evseMaxCurrent() : wallboxMax;
|
||||||
|
quint16 cableMax = connection->cableMaxCurrent() > 0 ? connection->cableMaxCurrent() : wallboxMax;
|
||||||
|
|
||||||
|
quint8 overallMax = qMin(qMin(wallboxMax, evseMax), cableMax);
|
||||||
|
qCDebug(dcWebasto()) << "Adjusting max current: Wallbox max:" << wallboxMax << "EVSE max:" << evseMax << "cable max:" << cableMax << "Overall:" << overallMax;
|
||||||
|
thing->setStateMinMaxValues(webastoUniteMaxChargingCurrentStateTypeId, 6, overallMax);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IntegrationPluginWebasto::validTokenAvailable(Thing *thing)
|
||||||
|
{
|
||||||
|
if (m_webastoUniteTokens.contains(thing)) {
|
||||||
|
QPair<QString, QDateTime> tokenInfo = m_webastoUniteTokens.value(thing);
|
||||||
|
if (!tokenInfo.first.isEmpty() && tokenInfo.second > QDateTime::currentDateTimeUtc().addSecs(60)) {
|
||||||
|
qCDebug(dcWebasto()) << "HTTP: Valid access token found for" << thing->name();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
qCDebug(dcWebasto()) << "HTTP: Token need to be refreshed. The current token for" << thing->name() << "is expired:" << tokenInfo.second.toString("dd.MM.yyyy hh:mm:ss") << QDateTime::currentDateTimeUtc().toString();
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qCDebug(dcWebasto()) << "HTTP: Token need to be refreshed. There is no token for" << thing->name();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkReply *IntegrationPluginWebasto::requestWebstoUniteAccessToken(const QHostAddress &address)
|
||||||
|
{
|
||||||
|
// Note: these credentials are documented in the Websto Unite installation manual and also provided using QR code.
|
||||||
|
QVariantMap requestMap;
|
||||||
|
requestMap.insert("username", "admin");
|
||||||
|
requestMap.insert("password", "0#54&8eV%c+e2y(P2%h0");
|
||||||
|
|
||||||
|
QJsonDocument jsonDoc = QJsonDocument::fromVariant(requestMap);
|
||||||
|
|
||||||
|
QUrl url;
|
||||||
|
url.setScheme("https"); // we have to use ssl and ignore the endpoint error
|
||||||
|
url.setHost(address.toString());
|
||||||
|
url.setPath("/api/login");
|
||||||
|
|
||||||
|
QNetworkRequest request(url);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
qCDebug(dcWebasto()) << "HTTP: Requesting access token" << url.toString() << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Compact));;
|
||||||
|
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QJsonDocument::fromVariant(requestMap).toJson());
|
||||||
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||||
|
connect(reply, &QNetworkReply::sslErrors, this, [reply](const QList<QSslError> &){
|
||||||
|
// We ignore SSL errors in the LAN... quiet useless against MITM attacks
|
||||||
|
reply->ignoreSslErrors();
|
||||||
|
});
|
||||||
|
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkReply *IntegrationPluginWebasto::requestWebstoUnitePhaseCountChange(const QHostAddress &address, const QString &accessToken, uint desiredPhaseCount)
|
||||||
|
{
|
||||||
|
QVariantList settingsList;
|
||||||
|
QVariantMap settingMap;
|
||||||
|
settingMap.insert("fieldKey", "installationSettings.currentLimiterPhase");
|
||||||
|
settingMap.insert("value", QString("%1").arg(desiredPhaseCount == 3 ? 1 : 0));
|
||||||
|
settingsList.append(settingMap);
|
||||||
|
|
||||||
|
QJsonDocument jsonDoc = QJsonDocument::fromVariant(settingsList);
|
||||||
|
|
||||||
|
QUrl url;
|
||||||
|
url.setScheme("https"); // we have to use ssl and ignore the endpoint error
|
||||||
|
url.setHost(address.toString());
|
||||||
|
url.setPath("/api/configuration-updates");
|
||||||
|
|
||||||
|
QNetworkRequest request(url);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
request.setRawHeader("Authorization", "Bearer " + accessToken.toLocal8Bit());
|
||||||
|
qCDebug(dcWebasto()) << "HTTP: Requesting phase count change" << url.toString() << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Compact));
|
||||||
|
|
||||||
|
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, jsonDoc.toJson());
|
||||||
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||||
|
connect(reply, &QNetworkReply::sslErrors, this, [reply](const QList<QSslError> &){
|
||||||
|
// We ignore SSL errors in the LAN... quiet useless against MITM attacks
|
||||||
|
reply->ignoreSslErrors();
|
||||||
|
});
|
||||||
|
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IntegrationPluginWebasto::executeWebastoUnitePhaseCountAction(ThingActionInfo *info)
|
||||||
|
{
|
||||||
|
Thing *thing = info->thing();
|
||||||
|
Action action = info->action();
|
||||||
|
|
||||||
|
quint8 desiredPhaseCount = info->action().paramValue(webastoUniteDesiredPhaseCountActionDesiredPhaseCountParamTypeId).toUInt();
|
||||||
|
|
||||||
|
QNetworkReply *reply = requestWebstoUnitePhaseCountChange(m_evc04Connections.value(thing)->modbusTcpMaster()->hostAddress(), m_webastoUniteTokens.value(thing).first, desiredPhaseCount);
|
||||||
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||||
|
connect(reply, &QNetworkReply::finished, info, [=](){
|
||||||
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
|
qCWarning(dcWebasto()) << "HTTP: Error setting desired phase count:" << reply->error() << reply->errorString();
|
||||||
|
info->finish(Thing::ThingErrorHardwareFailure);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray response = reply->readAll();
|
||||||
|
|
||||||
|
QJsonParseError error;
|
||||||
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(response, &error);
|
||||||
|
if (error.error != QJsonParseError::NoError) {
|
||||||
|
info->finish(Thing::ThingErrorAuthenticationFailure);
|
||||||
|
qCWarning(dcWebasto()) << "HTTP: Set desired phase count response JSON error:" << error.errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(dcWebasto()) << "HTTP: Response:" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Compact));
|
||||||
|
|
||||||
|
QVariantMap responseMap = jsonDoc.toVariant().toMap();
|
||||||
|
if (responseMap.value("status").toString() != "SUCCESS") {
|
||||||
|
info->finish(Thing::ThingErrorHardwareFailure);
|
||||||
|
qCWarning(dcWebasto()) << "HTTP: Could not set desired phase count for" << thing->name();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(dcWebasto()) << "HTTP: Set webasto unite phase count to" << desiredPhaseCount << "finished successfully.";
|
||||||
|
thing->setStateValue(webastoUniteDesiredPhaseCountStateTypeId, desiredPhaseCount);
|
||||||
|
info->finish(Thing::ThingErrorNoError);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -37,11 +37,14 @@
|
|||||||
|
|
||||||
#include "webasto.h"
|
#include "webasto.h"
|
||||||
#include "webastonextmodbustcpconnection.h"
|
#include "webastonextmodbustcpconnection.h"
|
||||||
|
#include "evc04modbustcpconnection.h"
|
||||||
|
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QHostAddress>
|
#include <QHostAddress>
|
||||||
|
|
||||||
|
class QNetworkReply;
|
||||||
|
|
||||||
class IntegrationPluginWebasto : public IntegrationPlugin
|
class IntegrationPluginWebasto : public IntegrationPlugin
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -65,6 +68,7 @@ private:
|
|||||||
|
|
||||||
QHash<Thing *, Webasto *> m_webastoLiveConnections;
|
QHash<Thing *, Webasto *> m_webastoLiveConnections;
|
||||||
QHash<Thing *, WebastoNextModbusTcpConnection *> m_webastoNextConnections;
|
QHash<Thing *, WebastoNextModbusTcpConnection *> m_webastoNextConnections;
|
||||||
|
QHash<Thing *, EVC04ModbusTcpConnection *> m_evc04Connections;
|
||||||
QHash<Thing *, NetworkDeviceMonitor *> m_monitors;
|
QHash<Thing *, NetworkDeviceMonitor *> m_monitors;
|
||||||
|
|
||||||
void setupWebastoNextConnection(ThingSetupInfo *info);
|
void setupWebastoNextConnection(ThingSetupInfo *info);
|
||||||
@ -74,6 +78,15 @@ private:
|
|||||||
|
|
||||||
void executeWebastoNextPowerAction(ThingActionInfo *info, bool power);
|
void executeWebastoNextPowerAction(ThingActionInfo *info, bool power);
|
||||||
|
|
||||||
|
void setupEVC04Connection(ThingSetupInfo *info);
|
||||||
|
void updateEVC04MaxCurrent(Thing *thing);
|
||||||
|
QHash<Thing *, quint32> m_lastWallboxTime;
|
||||||
|
QHash<Thing *, QPair<QString, QDateTime>> m_webastoUniteTokens;
|
||||||
|
bool validTokenAvailable(Thing *thing);
|
||||||
|
QNetworkReply *requestWebstoUniteAccessToken(const QHostAddress &address);
|
||||||
|
QNetworkReply *requestWebstoUnitePhaseCountChange(const QHostAddress &address, const QString &accessToken, uint desiredPhaseCount);
|
||||||
|
void executeWebastoUnitePhaseCountAction(ThingActionInfo *info);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onConnectionChanged(bool connected);
|
void onConnectionChanged(bool connected);
|
||||||
void onWriteRequestExecuted(const QUuid &requestId, bool success);
|
void onWriteRequestExecuted(const QUuid &requestId, bool success);
|
||||||
|
|||||||
@ -431,6 +431,124 @@
|
|||||||
"defaultValue": ""
|
"defaultValue": ""
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "webastoUnite",
|
||||||
|
"displayName": "Webasto Unite",
|
||||||
|
"id": "f7598439-a794-44d4-ae51-47ab40189d61",
|
||||||
|
"createMethods": ["discovery", "user"],
|
||||||
|
"interfaces": ["evcharger", "smartmeterconsumer", "connectable"],
|
||||||
|
"paramTypes": [
|
||||||
|
{
|
||||||
|
"id": "99aedef2-1b23-4ab8-bee4-7e8b57b8fa18",
|
||||||
|
"name":"macAddress",
|
||||||
|
"displayName": "MAC address",
|
||||||
|
"type": "QString",
|
||||||
|
"inputType": "MacAddress",
|
||||||
|
"defaultValue": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateTypes": [
|
||||||
|
{
|
||||||
|
"id": "b670620e-c582-4e6e-8313-32a0f7d2c11c",
|
||||||
|
"name": "connected",
|
||||||
|
"displayName": "Connected",
|
||||||
|
"type": "bool",
|
||||||
|
"defaultValue": false,
|
||||||
|
"cached": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dfa057e0-4e25-48da-967f-2821356ad44f",
|
||||||
|
"name": "pluggedIn",
|
||||||
|
"displayName": "Plugged in",
|
||||||
|
"type": "bool",
|
||||||
|
"defaultValue": false,
|
||||||
|
"cached": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "c31ef8a1-4254-4507-bef5-1e959936bd3f",
|
||||||
|
"name": "charging",
|
||||||
|
"displayName": "Charging",
|
||||||
|
"type": "bool",
|
||||||
|
"defaultValue": false,
|
||||||
|
"cached": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "962762ac-3a94-44ac-b591-1060a68a1376",
|
||||||
|
"name": "phaseCount",
|
||||||
|
"displayName": "Connected phases",
|
||||||
|
"type": "uint",
|
||||||
|
"minValue": 1,
|
||||||
|
"maxValue": 3,
|
||||||
|
"defaultValue": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4cd2d119-0198-4f94-8476-56eebb0b6867",
|
||||||
|
"name": "desiredPhaseCount",
|
||||||
|
"displayName": "Desired phase count",
|
||||||
|
"displayNameAction": "Set desired phase count",
|
||||||
|
"type": "uint",
|
||||||
|
"minValue": 1,
|
||||||
|
"maxValue": 3,
|
||||||
|
"possibleValues": [1, 3],
|
||||||
|
"defaultValue": 3,
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "0d70ae14-15d7-4b1a-a621-2d0bc0bc28f1",
|
||||||
|
"name": "currentPower",
|
||||||
|
"displayName": "Active power",
|
||||||
|
"type": "double",
|
||||||
|
"unit": "Watt",
|
||||||
|
"defaultValue": 0,
|
||||||
|
"cached": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4d5fc8e4-e5f0-46b9-ba01-20e176312e05",
|
||||||
|
"name": "totalEnergyConsumed",
|
||||||
|
"displayName": "Total consumed energy",
|
||||||
|
"type": "double",
|
||||||
|
"unit": "KiloWattHour",
|
||||||
|
"defaultValue": 0.0,
|
||||||
|
"cached": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ebc0e32e-648e-468f-9566-585768a0d970",
|
||||||
|
"name": "power",
|
||||||
|
"displayName": "Charging enabled",
|
||||||
|
"displayNameAction": "Set charging enabled",
|
||||||
|
"type": "bool",
|
||||||
|
"defaultValue": false,
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "04aaf21a-9fd3-46f1-8fca-8cbed2117737",
|
||||||
|
"name": "maxChargingCurrent",
|
||||||
|
"displayName": "Maximum charging current",
|
||||||
|
"displayNameAction": "Set maximum charging current",
|
||||||
|
"type": "uint",
|
||||||
|
"unit": "Ampere",
|
||||||
|
"minValue": 6,
|
||||||
|
"maxValue": 32,
|
||||||
|
"defaultValue": 6,
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "106b2bd3-4bd5-4774-8571-d64a2d5bf78b",
|
||||||
|
"name": "sessionEnergy",
|
||||||
|
"displayName": "Session energy",
|
||||||
|
"type": "double",
|
||||||
|
"unit": "KiloWattHour",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ac1917a8-3445-4774-b0ec-042ebc7258aa",
|
||||||
|
"name": "version",
|
||||||
|
"displayName": "Firmware version",
|
||||||
|
"type": "QString",
|
||||||
|
"defaultValue": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,18 @@
|
|||||||
include(../plugins.pri)
|
include(../plugins.pri)
|
||||||
|
|
||||||
# Generate modbus connection
|
# Generate modbus connection
|
||||||
MODBUS_CONNECTIONS += webasto-next-registers.json
|
MODBUS_CONNECTIONS += webasto-next-registers.json ../vestel/evc04-registers.json
|
||||||
MODBUS_TOOLS_CONFIG += VERBOSE
|
MODBUS_TOOLS_CONFIG += VERBOSE
|
||||||
include(../modbus.pri)
|
include(../modbus.pri)
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
integrationpluginwebasto.cpp \
|
integrationpluginwebasto.cpp \
|
||||||
webasto.cpp \
|
webasto.cpp \
|
||||||
webastodiscovery.cpp
|
webastodiscovery.cpp \
|
||||||
|
../vestel/evc04discovery.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
integrationpluginwebasto.h \
|
integrationpluginwebasto.h \
|
||||||
webasto.h \
|
webasto.h \
|
||||||
webastodiscovery.h
|
webastodiscovery.h \
|
||||||
|
../vestel/evc04discovery.h
|
||||||
|
|||||||
@ -143,6 +143,21 @@ void WebastoDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDevice
|
|||||||
|
|
||||||
// All values good so far, let's assume this is a Webasto NEXT
|
// All values good so far, let's assume this is a Webasto NEXT
|
||||||
|
|
||||||
|
// Final check if there is a hostname available for this network device, if so it shouls contain the string "NEXT_".
|
||||||
|
// This is neccessary since Wallboxes from Vestel EVC04 aka. Webasto Unite wallboxes would also match
|
||||||
|
// the creteria up to here get detected as positiv Webasto NEXT.
|
||||||
|
|
||||||
|
// Example hostname: NEXT-WS10XXXX
|
||||||
|
|
||||||
|
if (!networkDeviceInfo.hostName().isEmpty() &&
|
||||||
|
(!networkDeviceInfo.hostName().contains("NEXT_") || networkDeviceInfo.hostName().contains("VESTEL"))) {
|
||||||
|
qCDebug(dcWebasto()) << "Discovery: network device has a hostname and it does match kriteria for Webasto next:" << networkDeviceInfo.hostName() << "on" << networkDeviceInfo.address().toString() << "Continue...";;
|
||||||
|
cleanupConnection(connection);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hostname verification also OK, let's assume this is a Webasto NEXT
|
||||||
|
|
||||||
Result result;
|
Result result;
|
||||||
result.productName = "Webasto NEXT";
|
result.productName = "Webasto NEXT";
|
||||||
result.type = TypeWebastoNext;
|
result.type = TypeWebastoNext;
|
||||||
|
|||||||
Reference in New Issue
Block a user