Webasto: Add webasto NEXT support
parent
83c22e3b62
commit
28f280b15c
|
|
@ -58,9 +58,7 @@ ModbusTcpMaster::~ModbusTcpMaster()
|
|||
m_reconnectTimer->stop();
|
||||
}
|
||||
|
||||
if (m_modbusTcpClient) {
|
||||
disconnectDevice();
|
||||
}
|
||||
disconnectDevice();
|
||||
}
|
||||
|
||||
QHostAddress ModbusTcpMaster::hostAddress() const
|
||||
|
|
@ -78,19 +76,25 @@ void ModbusTcpMaster::setPort(uint port)
|
|||
m_port = port;
|
||||
}
|
||||
|
||||
QString ModbusTcpMaster::connectionUrl() const
|
||||
{
|
||||
return QString("%1:%2").arg(m_hostAddress.toString()).arg(m_port);
|
||||
}
|
||||
|
||||
void ModbusTcpMaster::setHostAddress(const QHostAddress &hostAddress)
|
||||
{
|
||||
m_hostAddress = hostAddress;
|
||||
}
|
||||
|
||||
bool ModbusTcpMaster::connectDevice() {
|
||||
bool ModbusTcpMaster::connectDevice()
|
||||
{
|
||||
// TCP connection to target device
|
||||
if (!m_modbusTcpClient)
|
||||
return false;
|
||||
|
||||
// Only connect if we are in the unconnected state
|
||||
if (m_modbusTcpClient->state() == QModbusDevice::UnconnectedState) {
|
||||
qCDebug(dcModbusTcpMaster()) << "Connecting modbus TCP client to" << QString("%1:%2").arg(m_hostAddress.toString()).arg(m_port);
|
||||
qCDebug(dcModbusTcpMaster()) << "Connecting modbus TCP client to" << connectionUrl();
|
||||
m_modbusTcpClient->setConnectionParameter(QModbusDevice::NetworkPortParameter, m_port);
|
||||
m_modbusTcpClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter, m_hostAddress.toString());
|
||||
m_modbusTcpClient->setTimeout(m_timeout);
|
||||
|
|
@ -98,9 +102,10 @@ bool ModbusTcpMaster::connectDevice() {
|
|||
return m_modbusTcpClient->connectDevice();
|
||||
} else if (m_modbusTcpClient->state() != QModbusDevice::ConnectedState) {
|
||||
// Restart the timer in case of connecting not finished yet or closing
|
||||
qCDebug(dcModbusTcpMaster()) << "Starting the reconnect mechanism timer";
|
||||
m_reconnectTimer->start();
|
||||
} else {
|
||||
qCWarning(dcModbusTcpMaster()) << "Connect modbus TCP device" << QString("%1:%2").arg(m_hostAddress.toString()).arg(m_port) << "called, but the socket is currently in the" << m_modbusTcpClient->state();
|
||||
qCWarning(dcModbusTcpMaster()) << "Connect modbus TCP device" << connectionUrl() << "called, but the socket is currently in the" << m_modbusTcpClient->state();
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
@ -108,9 +113,6 @@ bool ModbusTcpMaster::connectDevice() {
|
|||
|
||||
void ModbusTcpMaster::disconnectDevice()
|
||||
{
|
||||
if (!m_modbusTcpClient)
|
||||
return;
|
||||
|
||||
// Stop the reconnect timer since disconnect was explicitly called
|
||||
m_reconnectTimer->stop();
|
||||
m_modbusTcpClient->disconnectDevice();
|
||||
|
|
@ -118,10 +120,7 @@ void ModbusTcpMaster::disconnectDevice()
|
|||
|
||||
bool ModbusTcpMaster::reconnectDevice()
|
||||
{
|
||||
qCWarning(dcModbusTcpMaster()) << "Reconnecting modbus TCP device" << QString("%1:%2").arg(m_hostAddress.toString()).arg(m_port);
|
||||
if (!m_modbusTcpClient)
|
||||
return false;
|
||||
|
||||
qCWarning(dcModbusTcpMaster()) << "Reconnecting modbus TCP device" << connectionUrl();
|
||||
disconnectDevice();
|
||||
return connectDevice();
|
||||
}
|
||||
|
|
@ -165,10 +164,6 @@ QModbusDevice::Error ModbusTcpMaster::error() const
|
|||
|
||||
QUuid ModbusTcpMaster::readCoil(uint slaveAddress, uint registerAddress, uint size)
|
||||
{
|
||||
if (!m_modbusTcpClient) {
|
||||
return QUuid();
|
||||
}
|
||||
|
||||
QUuid requestId = QUuid::createUuid();
|
||||
QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::Coils, registerAddress, size);
|
||||
|
||||
|
|
@ -183,12 +178,12 @@ QUuid ModbusTcpMaster::readCoil(uint slaveAddress, uint registerAddress, uint si
|
|||
emit receivedCoil(reply->serverAddress(), modbusAddress, unit.values());
|
||||
} else {
|
||||
emit readRequestExecuted(requestId, false);
|
||||
qCWarning(dcModbusTcpMaster()) << "Read response error for device" << m_hostAddress.toString() << ":" << reply->error();
|
||||
qCWarning(dcModbusTcpMaster()) << "Read response error for device" << connectionUrl() << ":" << reply->error();
|
||||
}
|
||||
});
|
||||
|
||||
connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){
|
||||
qCWarning(dcModbusTcpMaster()) << "Modbus reply error for device" << m_hostAddress.toString() << ":" << error;
|
||||
qCWarning(dcModbusTcpMaster()) << "Modbus reply error for device" << connectionUrl() << ":" << error;
|
||||
emit readRequestError(requestId, reply->errorString());
|
||||
});
|
||||
|
||||
|
|
@ -198,7 +193,7 @@ QUuid ModbusTcpMaster::readCoil(uint slaveAddress, uint registerAddress, uint si
|
|||
return QUuid();
|
||||
}
|
||||
} else {
|
||||
qCWarning(dcModbusTcpMaster()) << "Read error for device" << m_hostAddress.toString() << ":" << m_modbusTcpClient->errorString();
|
||||
qCWarning(dcModbusTcpMaster()) << "Read error for device" << connectionUrl() << ":" << m_modbusTcpClient->errorString();
|
||||
return QUuid();
|
||||
}
|
||||
return requestId;
|
||||
|
|
@ -206,10 +201,6 @@ QUuid ModbusTcpMaster::readCoil(uint slaveAddress, uint registerAddress, uint si
|
|||
|
||||
QUuid ModbusTcpMaster::writeHoldingRegisters(uint slaveAddress, uint registerAddress, const QVector<quint16> &values)
|
||||
{
|
||||
if (!m_modbusTcpClient) {
|
||||
return QUuid();
|
||||
}
|
||||
|
||||
QUuid requestId = QUuid::createUuid();
|
||||
QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, registerAddress, values.length());
|
||||
request.setValues(values);
|
||||
|
|
@ -224,13 +215,13 @@ QUuid ModbusTcpMaster::writeHoldingRegisters(uint slaveAddress, uint registerAdd
|
|||
emit receivedHoldingRegister(reply->serverAddress(), modbusAddress, unit.values());
|
||||
} else {
|
||||
emit writeRequestExecuted(requestId, false);
|
||||
qCWarning(dcModbusTcpMaster()) << "Read response error for device" << m_hostAddress.toString() << ":" << reply->error();
|
||||
qCWarning(dcModbusTcpMaster()) << "Read response error for device" << connectionUrl() << ":" << reply->error();
|
||||
}
|
||||
reply->deleteLater();
|
||||
});
|
||||
|
||||
connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){
|
||||
qCWarning(dcModbusTcpMaster()) << "Modbus replay error for device" << m_hostAddress.toString() << ":" << error;
|
||||
qCWarning(dcModbusTcpMaster()) << "Modbus replay error for device" << connectionUrl() << ":" << error;
|
||||
emit writeRequestError(requestId, reply->errorString());
|
||||
});
|
||||
|
||||
|
|
@ -240,7 +231,7 @@ QUuid ModbusTcpMaster::writeHoldingRegisters(uint slaveAddress, uint registerAdd
|
|||
return QUuid();
|
||||
}
|
||||
} else {
|
||||
qCWarning(dcModbusTcpMaster()) << "Read error for device" << m_hostAddress.toString() << ":" << m_modbusTcpClient->errorString();
|
||||
qCWarning(dcModbusTcpMaster()) << "Read error for device" << connectionUrl() << ":" << m_modbusTcpClient->errorString();
|
||||
return QUuid();
|
||||
}
|
||||
return requestId;
|
||||
|
|
@ -268,9 +259,6 @@ QModbusReply *ModbusTcpMaster::sendWriteRequest(const QModbusDataUnit &write, in
|
|||
|
||||
QUuid ModbusTcpMaster::readDiscreteInput(uint slaveAddress, uint registerAddress, uint size)
|
||||
{
|
||||
if (!m_modbusTcpClient) {
|
||||
return QUuid();
|
||||
}
|
||||
QUuid requestId = QUuid::createUuid();
|
||||
|
||||
QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::DiscreteInputs, registerAddress, size);
|
||||
|
|
@ -286,12 +274,12 @@ QUuid ModbusTcpMaster::readDiscreteInput(uint slaveAddress, uint registerAddress
|
|||
emit receivedDiscreteInput(reply->serverAddress(), modbusAddress, unit.values());
|
||||
} else {
|
||||
emit readRequestExecuted(requestId, false);
|
||||
qCWarning(dcModbusTcpMaster()) << "Read response error for device" << m_hostAddress.toString() << ":" << reply->error();
|
||||
qCWarning(dcModbusTcpMaster()) << "Read response error for device" << connectionUrl() << ":" << reply->error();
|
||||
}
|
||||
});
|
||||
|
||||
connect(reply, &QModbusReply::errorOccurred, this, [requestId, reply, this] (QModbusDevice::Error error){
|
||||
qCWarning(dcModbusTcpMaster()) << "Modbus replay error for device" << m_hostAddress.toString() << ":" << error;
|
||||
qCWarning(dcModbusTcpMaster()) << "Modbus replay error for device" << connectionUrl() << ":" << error;
|
||||
emit readRequestError(requestId, reply->errorString());
|
||||
});
|
||||
|
||||
|
|
@ -301,7 +289,7 @@ QUuid ModbusTcpMaster::readDiscreteInput(uint slaveAddress, uint registerAddress
|
|||
return QUuid();
|
||||
}
|
||||
} else {
|
||||
qCWarning(dcModbusTcpMaster()) << "Read error for device" << m_hostAddress.toString() << ":" << m_modbusTcpClient->errorString();
|
||||
qCWarning(dcModbusTcpMaster()) << "Read error for device" << connectionUrl() << ":" << m_modbusTcpClient->errorString();
|
||||
return QUuid();
|
||||
}
|
||||
return requestId;
|
||||
|
|
@ -328,12 +316,12 @@ QUuid ModbusTcpMaster::readInputRegister(uint slaveAddress, uint registerAddress
|
|||
emit receivedInputRegister(reply->serverAddress(), modbusAddress, unit.values());
|
||||
} else {
|
||||
emit readRequestExecuted(requestId, false);
|
||||
qCWarning(dcModbusTcpMaster()) << "Read response error for device" << m_hostAddress.toString() << ":" << reply->error();
|
||||
qCWarning(dcModbusTcpMaster()) << "Read response error for device" << connectionUrl() << ":" << reply->error();
|
||||
}
|
||||
});
|
||||
|
||||
connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){
|
||||
qCWarning(dcModbusTcpMaster()) << "Modbus reply error for device" << m_hostAddress.toString() << ":" << error;
|
||||
qCWarning(dcModbusTcpMaster()) << "Modbus reply error for device" << connectionUrl() << ":" << error;
|
||||
emit readRequestError(requestId, reply->errorString());
|
||||
});
|
||||
|
||||
|
|
@ -344,7 +332,7 @@ QUuid ModbusTcpMaster::readInputRegister(uint slaveAddress, uint registerAddress
|
|||
return QUuid();
|
||||
}
|
||||
} else {
|
||||
qCWarning(dcModbusTcpMaster()) << "Read error for device" << m_hostAddress.toString() << ":" << m_modbusTcpClient->errorString();
|
||||
qCWarning(dcModbusTcpMaster()) << "Read error for device" << connectionUrl() << ":" << m_modbusTcpClient->errorString();
|
||||
return QUuid();
|
||||
}
|
||||
return requestId;
|
||||
|
|
@ -352,10 +340,6 @@ QUuid ModbusTcpMaster::readInputRegister(uint slaveAddress, uint registerAddress
|
|||
|
||||
QUuid ModbusTcpMaster::readHoldingRegister(uint slaveAddress, uint registerAddress, uint size)
|
||||
{
|
||||
if (!m_modbusTcpClient) {
|
||||
return QUuid();
|
||||
}
|
||||
|
||||
QUuid requestId = QUuid::createUuid();
|
||||
QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, registerAddress, size);
|
||||
|
||||
|
|
@ -372,7 +356,7 @@ QUuid ModbusTcpMaster::readHoldingRegister(uint slaveAddress, uint registerAddre
|
|||
|
||||
} else {
|
||||
emit writeRequestExecuted(requestId, false);
|
||||
qCWarning(dcModbusTcpMaster()) << "Read response error for device" << m_hostAddress.toString() << ":" << reply->error();
|
||||
qCWarning(dcModbusTcpMaster()) << "Read response error for device" << connectionUrl() << ":" << reply->error();
|
||||
emit readRequestError(requestId, reply->errorString());
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
|
@ -380,7 +364,7 @@ QUuid ModbusTcpMaster::readHoldingRegister(uint slaveAddress, uint registerAddre
|
|||
|
||||
connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){
|
||||
|
||||
qCWarning(dcModbusTcpMaster()) << "Modbus reply error for device" << m_hostAddress.toString() << ":" << error;
|
||||
qCWarning(dcModbusTcpMaster()) << "Modbus reply error for device" << connectionUrl() << ":" << error;
|
||||
emit readRequestError(requestId, reply->errorString());
|
||||
});
|
||||
|
||||
|
|
@ -390,7 +374,7 @@ QUuid ModbusTcpMaster::readHoldingRegister(uint slaveAddress, uint registerAddre
|
|||
return QUuid();
|
||||
}
|
||||
} else {
|
||||
qCWarning(dcModbusTcpMaster()) << "Read error for device" << m_hostAddress.toString() << ":" << m_modbusTcpClient->errorString();
|
||||
qCWarning(dcModbusTcpMaster()) << "Read error for device" << connectionUrl() << ":" << m_modbusTcpClient->errorString();
|
||||
return QUuid();
|
||||
}
|
||||
return requestId;
|
||||
|
|
@ -403,10 +387,6 @@ QUuid ModbusTcpMaster::writeCoil(uint slaveAddress, uint registerAddress, bool v
|
|||
|
||||
QUuid ModbusTcpMaster::writeCoils(uint slaveAddress, uint registerAddress, const QVector<quint16> &values)
|
||||
{
|
||||
if (!m_modbusTcpClient) {
|
||||
return QUuid();
|
||||
}
|
||||
|
||||
QUuid requestId = QUuid::createUuid();
|
||||
QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::Coils, registerAddress, values.length());
|
||||
request.setValues(values);
|
||||
|
|
@ -424,13 +404,13 @@ QUuid ModbusTcpMaster::writeCoils(uint slaveAddress, uint registerAddress, const
|
|||
|
||||
} else {
|
||||
emit writeRequestExecuted(requestId, false);
|
||||
qCWarning(dcModbusTcpMaster()) << "Write response error for device" << m_hostAddress.toString() << ":" << reply->error();
|
||||
qCWarning(dcModbusTcpMaster()) << "Write response error for device" << connectionUrl() << ":" << reply->error();
|
||||
}
|
||||
reply->deleteLater();
|
||||
});
|
||||
|
||||
connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){
|
||||
qCWarning(dcModbusTcpMaster()) << "Modbus reply error for device" << m_hostAddress.toString() << ":" << error;
|
||||
qCWarning(dcModbusTcpMaster()) << "Modbus reply error for device" << connectionUrl() << ":" << error;
|
||||
emit writeRequestError(requestId, reply->errorString());
|
||||
});
|
||||
|
||||
|
|
@ -440,7 +420,7 @@ QUuid ModbusTcpMaster::writeCoils(uint slaveAddress, uint registerAddress, const
|
|||
return QUuid();
|
||||
}
|
||||
} else {
|
||||
qCWarning(dcModbusTcpMaster()) << "Read error for device" << m_hostAddress.toString() << ":" << m_modbusTcpClient->errorString();
|
||||
qCWarning(dcModbusTcpMaster()) << "Read error for device" << connectionUrl() << ":" << m_modbusTcpClient->errorString();
|
||||
return QUuid();
|
||||
}
|
||||
return requestId;
|
||||
|
|
@ -453,7 +433,7 @@ QUuid ModbusTcpMaster::writeHoldingRegister(uint slaveAddress, uint registerAddr
|
|||
|
||||
void ModbusTcpMaster::onModbusErrorOccurred(QModbusDevice::Error error)
|
||||
{
|
||||
qCWarning(dcModbusTcpMaster()) << "An error occurred for device" << m_hostAddress.toString() << ":" << error;
|
||||
qCWarning(dcModbusTcpMaster()) << "An error occurred for device" << connectionUrl() << ":" << error;
|
||||
emit connectionErrorOccurred(error);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@ public:
|
|||
uint port() const;
|
||||
void setPort(uint port);
|
||||
|
||||
QString connectionUrl() const;
|
||||
|
||||
bool connected() const;
|
||||
|
||||
int numberOfRetries() const;
|
||||
|
|
|
|||
|
|
@ -335,7 +335,7 @@ def writeInitMethodImplementationTcp(fileDescriptor, className, registerDefiniti
|
|||
# First check if there are any init registers
|
||||
initRequired = False
|
||||
for registerDefinition in registerDefinitions:
|
||||
if registerDefinition['readSchedule'] == 'init':
|
||||
if 'readSchedule' in registerDefinition and registerDefinition['readSchedule'] == 'init':
|
||||
initRequired = True
|
||||
break
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,21 @@
|
|||
# Webasto
|
||||
|
||||
## Supported Things
|
||||
Connects nymea to Webasto wallboxes. Currently supported models:
|
||||
|
||||
* AC Wallbox Live
|
||||
* Webasto Live
|
||||
* Webasto NEXT
|
||||
|
||||
## Requirements
|
||||
|
||||
* The packages 'nymea-plugin-webasto' must be installed.
|
||||
* The modbus server must be enabled
|
||||
* The setting 'Modbus Slave Register Address Set' must be set to 'TQ-DM100'
|
||||
* The setting 'Modbus TCP Server Port Number' must be set to 502
|
||||
nymea uses the Modbus TCP connection to connect to the wallbox.
|
||||
|
||||
The modbus server must be enabled on the Wallbox.
|
||||
|
||||
For Webasto NEXT this can be done by enabling the `Home energy management` using the Webasto App.
|
||||
|
||||
For the Webasto Live the setting `Modbus Slave Register Address Set` must be set to `TQ-DM100`.
|
||||
|
||||
|
||||
## More
|
||||
|
||||
https://dealers.webasto.com/Sections/Public/Documents.aspx?SectionId=6&CategoryId=9&ProductTypeId=66&ProductId=630&ShowResult=true
|
||||
|
||||
https://charging.webasto.com/
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Copyright 2013 - 2023, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
|
|
@ -29,10 +29,12 @@
|
|||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "integrationpluginwebasto.h"
|
||||
#include "webastodiscovery.h"
|
||||
#include "plugininfo.h"
|
||||
|
||||
#include <network/networkdevicediscovery.h>
|
||||
#include <types/param.h>
|
||||
#include <hardware/electricity.h>
|
||||
#include <network/networkdevicediscovery.h>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QStringList>
|
||||
|
|
@ -51,14 +53,14 @@ void IntegrationPluginWebasto::init()
|
|||
|
||||
void IntegrationPluginWebasto::discoverThings(ThingDiscoveryInfo *info)
|
||||
{
|
||||
if (info->thingClassId() == webastoLiveThingClassId) {
|
||||
if (!hardwareManager()->networkDeviceDiscovery()->available()) {
|
||||
qCWarning(dcWebasto()) << "Failed to discover network devices. The network device discovery is not available.";
|
||||
info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("The discovery is not available."));
|
||||
return;
|
||||
}
|
||||
if (!hardwareManager()->networkDeviceDiscovery()->available()) {
|
||||
qCWarning(dcWebasto()) << "Failed to discover network devices. The network device discovery is not available.";
|
||||
info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("The discovery is not available."));
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(dcWebasto()) << "Discover things";
|
||||
if (info->thingClassId() == webastoLiveThingClassId) {
|
||||
qCInfo(dcWebasto()) << "Start discovering webasto live in the local network...";
|
||||
NetworkDeviceDiscoveryReply *discoveryReply = hardwareManager()->networkDeviceDiscovery()->discover();
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, discoveryReply, &NetworkDeviceDiscoveryReply::deleteLater);
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){
|
||||
|
|
@ -69,7 +71,7 @@ void IntegrationPluginWebasto::discoverThings(ThingDiscoveryInfo *info)
|
|||
if (!networkDeviceInfo.hostName().contains("webasto", Qt::CaseSensitivity::CaseInsensitive))
|
||||
continue;
|
||||
|
||||
QString title = "Wallbox ";
|
||||
QString title = "Webasto Live";
|
||||
if (networkDeviceInfo.hostName().isEmpty()) {
|
||||
title += networkDeviceInfo.address().toString();
|
||||
} else {
|
||||
|
|
@ -100,9 +102,55 @@ void IntegrationPluginWebasto::discoverThings(ThingDiscoveryInfo *info)
|
|||
}
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
});
|
||||
} else {
|
||||
Q_ASSERT_X(false, "discoverThings", QString("Unhandled thingClassId: %1").arg(info->thingClassId().toString()).toUtf8());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (info->thingClassId() == webastoNextThingClassId) {
|
||||
|
||||
qCInfo(dcWebasto()) << "Start discovering Webasto NEXT in the local network...";
|
||||
|
||||
// Create a discovery with the info as parent for auto deleting the object once the discovery info is done
|
||||
WebastoDiscovery *discovery = new WebastoDiscovery(hardwareManager()->networkDeviceDiscovery(), info);
|
||||
connect(discovery, &WebastoDiscovery::discoveryFinished, info, [=](){
|
||||
foreach (const WebastoDiscovery::Result &result, discovery->results()) {
|
||||
|
||||
QString title = "Webasto Next";
|
||||
if (!result.networkDeviceInfo.hostName().isEmpty()){
|
||||
title.append(" (" + result.networkDeviceInfo.hostName() + ")");
|
||||
}
|
||||
|
||||
QString description = result.networkDeviceInfo.address().toString();
|
||||
if (result.networkDeviceInfo.macAddressManufacturer().isEmpty()) {
|
||||
description += " " + result.networkDeviceInfo.macAddress();
|
||||
} else {
|
||||
description += " " + result.networkDeviceInfo.macAddress() + " (" + result.networkDeviceInfo.macAddressManufacturer() + ")";
|
||||
}
|
||||
|
||||
ThingDescriptor descriptor(webastoNextThingClassId, title, description);
|
||||
|
||||
// Check if we already have set up this device
|
||||
Things existingThings = myThings().filterByParam(webastoNextThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress());
|
||||
if (existingThings.count() == 1) {
|
||||
qCDebug(dcWebasto()) << "This thing already exists in the system." << existingThings.first() << result.networkDeviceInfo;
|
||||
descriptor.setThingId(existingThings.first()->id());
|
||||
}
|
||||
|
||||
ParamList params;
|
||||
params << Param(webastoNextThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress());
|
||||
descriptor.setParams(params);
|
||||
info->addThingDescriptor(descriptor);
|
||||
}
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
});
|
||||
|
||||
discovery->startDiscovery();
|
||||
return;
|
||||
}
|
||||
|
||||
Q_ASSERT_X(false, "discoverThings", QString("Unhandled thingClassId: %1").arg(info->thingClassId().toString()).toUtf8());
|
||||
}
|
||||
|
||||
void IntegrationPluginWebasto::setupThing(ThingSetupInfo *info)
|
||||
|
|
@ -112,14 +160,15 @@ void IntegrationPluginWebasto::setupThing(ThingSetupInfo *info)
|
|||
|
||||
if (thing->thingClassId() == webastoLiveThingClassId) {
|
||||
|
||||
if (m_webastoConnections.contains(thing)) {
|
||||
if (m_webastoLiveConnections.contains(thing)) {
|
||||
// Clean up after reconfiguration
|
||||
m_webastoConnections.take(thing)->deleteLater();
|
||||
m_webastoLiveConnections.take(thing)->deleteLater();
|
||||
}
|
||||
|
||||
QHostAddress address = QHostAddress(thing->paramValue(webastoLiveThingIpAddressParamTypeId).toString());
|
||||
Webasto *webasto = new Webasto(address, 502, thing);
|
||||
m_webastoConnections.insert(thing, webasto);
|
||||
connect(webasto, &Webasto::destroyed, this, [thing, this] {m_webastoConnections.remove(thing);});
|
||||
m_webastoLiveConnections.insert(thing, webasto);
|
||||
connect(webasto, &Webasto::destroyed, this, [thing, this] {m_webastoLiveConnections.remove(thing);});
|
||||
connect(webasto, &Webasto::connectionStateChanged, this, &IntegrationPluginWebasto::onConnectionChanged);
|
||||
connect(webasto, &Webasto::receivedRegister, this, &IntegrationPluginWebasto::onReceivedRegister);
|
||||
connect(webasto, &Webasto::writeRequestError, this, &IntegrationPluginWebasto::onWriteRequestError);
|
||||
|
|
@ -132,36 +181,142 @@ void IntegrationPluginWebasto::setupThing(ThingSetupInfo *info)
|
|||
if (connected)
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
});
|
||||
} else {
|
||||
Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (thing->thingClassId() == webastoNextThingClassId) {
|
||||
|
||||
// Handle reconfigure
|
||||
if (m_webastoNextConnections.contains(thing)) {
|
||||
qCDebug(dcWebasto()) << "Reconfiguring existing thing" << thing->name();
|
||||
m_webastoNextConnections.take(thing)->deleteLater();
|
||||
|
||||
if (m_monitors.contains(thing)) {
|
||||
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
|
||||
}
|
||||
}
|
||||
|
||||
MacAddress macAddress = MacAddress(thing->paramValue(webastoNextThingMacAddressParamTypeId).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;
|
||||
}
|
||||
|
||||
// Create the monitor
|
||||
NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(macAddress);
|
||||
m_monitors.insert(thing, monitor);
|
||||
|
||||
QHostAddress address = monitor->networkDeviceInfo().address();
|
||||
if (address.isNull()) {
|
||||
qCWarning(dcWebasto()) << "Cannot set up thing. The host address is not known yet. Maybe it will be available in the next run...";
|
||||
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
|
||||
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The host address is not known yet. Trying later again."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up in case the setup gets aborted
|
||||
connect(info, &ThingSetupInfo::aborted, monitor, [=](){
|
||||
if (m_monitors.contains(thing)) {
|
||||
qCDebug(dcWebasto()) << "Unregister monitor because setup has been aborted.";
|
||||
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
|
||||
}
|
||||
});
|
||||
|
||||
// If this is the first setup, the monitor must become reachable before we finish the setup
|
||||
if (info->isInitialSetup()) {
|
||||
// Wait for the monitor to be ready
|
||||
if (monitor->reachable()) {
|
||||
// Thing already reachable...let's continue with the setup
|
||||
setupWebastoNextConnection(info);
|
||||
} else {
|
||||
qCDebug(dcWebasto()) << "Waiting for the network monitor to get reachable before continue to set up the connection" << thing->name() << address.toString() << "...";
|
||||
connect(monitor, &NetworkDeviceMonitor::reachableChanged, info, [=](bool reachable){
|
||||
if (reachable) {
|
||||
qCDebug(dcWebasto()) << "The monitor for thing setup" << thing->name() << "is now reachable. Continue setup...";
|
||||
setupWebastoNextConnection(info);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Not the first setup, just add and let the monitor do the check reachable work
|
||||
setupWebastoNextConnection(info);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8());
|
||||
|
||||
}
|
||||
|
||||
void IntegrationPluginWebasto::postSetupThing(Thing *thing)
|
||||
{
|
||||
qCDebug(dcWebasto()) << "Post setup thing" << thing->name();
|
||||
if (!m_pluginTimer) {
|
||||
qCDebug(dcWebasto()) << "Setting up refresh timer for Webasto connections";
|
||||
m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(1);
|
||||
connect(m_pluginTimer, &PluginTimer::timeout, this, [this] {
|
||||
Q_FOREACH(Webasto *connection, m_webastoConnections) {
|
||||
if (connection->connected())
|
||||
|
||||
foreach(Webasto *connection, m_webastoLiveConnections) {
|
||||
if (connection->connected()) {
|
||||
update(connection);
|
||||
}
|
||||
}
|
||||
|
||||
foreach(WebastoNextModbusTcpConnection *webastoNext, m_webastoNextConnections) {
|
||||
if (webastoNext->reachable()) {
|
||||
webastoNext->update();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
m_pluginTimer->start();
|
||||
}
|
||||
|
||||
if (thing->thingClassId() == webastoLiveThingClassId) {
|
||||
Webasto *connection = m_webastoConnections.value(thing);
|
||||
if (!connection) {
|
||||
qCWarning(dcWebasto()) << "Can't find connection to thing";
|
||||
}
|
||||
Webasto *connection = m_webastoLiveConnections.value(thing);
|
||||
update(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
Q_ASSERT_X(false, "postSetupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8());
|
||||
if (thing->thingClassId() == webastoNextThingClassId) {
|
||||
WebastoNextModbusTcpConnection *connection = m_webastoNextConnections.value(thing);
|
||||
if (connection->reachable()) {
|
||||
thing->setStateValue(webastoNextConnectedStateTypeId, true);
|
||||
connection->update();
|
||||
} else {
|
||||
// We start the connection mechanism only if the monitor says the thing is reachable
|
||||
if (m_monitors.value(thing)->reachable()) {
|
||||
connection->connectDevice();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginWebasto::thingRemoved(Thing *thing)
|
||||
{
|
||||
qCDebug(dcWebasto()) << "Delete thing" << thing->name();
|
||||
|
||||
if (thing->thingClassId() == webastoNextThingClassId) {
|
||||
WebastoNextModbusTcpConnection *connection = m_webastoNextConnections.take(thing);
|
||||
connection->disconnectDevice();
|
||||
connection->deleteLater();
|
||||
}
|
||||
|
||||
// Unregister related hardware resources
|
||||
if (m_monitors.contains(thing))
|
||||
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
|
||||
|
||||
if (m_pluginTimer && myThings().isEmpty()) {
|
||||
hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer);
|
||||
m_pluginTimer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void IntegrationPluginWebasto::executeAction(ThingActionInfo *info)
|
||||
{
|
||||
Thing *thing = info->thing();
|
||||
|
|
@ -169,7 +324,7 @@ void IntegrationPluginWebasto::executeAction(ThingActionInfo *info)
|
|||
|
||||
if (thing->thingClassId() == webastoLiveThingClassId) {
|
||||
|
||||
Webasto *connection = m_webastoConnections.value(thing);
|
||||
Webasto *connection = m_webastoLiveConnections.value(thing);
|
||||
if (!connection) {
|
||||
qCWarning(dcWebasto()) << "Can't find connection to thing";
|
||||
return info->finish(Thing::ThingErrorHardwareNotAvailable);
|
||||
|
|
@ -200,20 +355,338 @@ void IntegrationPluginWebasto::executeAction(ThingActionInfo *info)
|
|||
} else {
|
||||
Q_ASSERT_X(false, "executeAction", QString("Unhandled actionTypeId: %1").arg(action.actionTypeId().toString()).toUtf8());
|
||||
}
|
||||
} else {
|
||||
Q_ASSERT_X(false, "executeAction", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (thing->thingClassId() == webastoNextThingClassId) {
|
||||
|
||||
WebastoNextModbusTcpConnection *connection = m_webastoNextConnections.value(thing);
|
||||
if (!connection) {
|
||||
qCWarning(dcWebasto()) << "Can't find modbus connection for" << thing;
|
||||
info->finish(Thing::ThingErrorHardwareNotAvailable);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!connection->reachable()) {
|
||||
qCWarning(dcWebasto()) << "Cannot execute action because the connection of" << thing << "is not reachable.";
|
||||
info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("The charging station is not reachable."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (action.actionTypeId() == webastoNextPowerActionTypeId) {
|
||||
bool power = action.paramValue(webastoNextPowerActionPowerParamTypeId).toBool();
|
||||
|
||||
// If this action was executed by the user, we start a new session, otherwise we assume it was a some charging logic
|
||||
// and we keep the current session.
|
||||
|
||||
if (power && action.triggeredBy() == Action::TriggeredByUser) {
|
||||
// First send 0 ChargingActionNoAction before sending 1 start session
|
||||
qCDebug(dcWebasto()) << "Enable charging action triggered by user. Restarting the session.";
|
||||
QModbusReply *reply = connection->setChargingAction(WebastoNextModbusTcpConnection::ChargingActionNoAction);
|
||||
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
|
||||
connect(reply, &QModbusReply::finished, info, [this, info, reply, power](){
|
||||
if (reply->error() == QModbusDevice::NoError) {
|
||||
info->thing()->setStateValue(webastoNextPowerStateTypeId, power);
|
||||
qCDebug(dcWebasto()) << "Restart charging session request finished successfully.";
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
} else {
|
||||
qCWarning(dcWebasto()) << "Restart charging session request finished with error:" << reply->errorString();
|
||||
info->finish(Thing::ThingErrorHardwareFailure);
|
||||
}
|
||||
|
||||
// Note: even if "NoAction" failed, we try to send the start charging action and report the error there just in case
|
||||
executeWebastoNextPowerAction(info, power);
|
||||
});
|
||||
} else {
|
||||
executeWebastoNextPowerAction(info, power);
|
||||
}
|
||||
} else if (action.actionTypeId() == webastoNextMaxChargingCurrentActionTypeId) {
|
||||
quint16 chargingCurrent = action.paramValue(webastoNextMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toUInt();
|
||||
qCDebug(dcWebasto()) << "Set max charging current of" << thing << "to" << chargingCurrent << "ampere";
|
||||
QModbusReply *reply = connection->setChargeCurrent(chargingCurrent);
|
||||
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
|
||||
connect(reply, &QModbusReply::finished, info, [info, reply, chargingCurrent](){
|
||||
if (reply->error() == QModbusDevice::NoError) {
|
||||
qCDebug(dcWebasto()) << "Set max charging current finished successfully.";
|
||||
info->thing()->setStateValue(webastoNextMaxChargingCurrentStateTypeId, chargingCurrent);
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
} else {
|
||||
qCWarning(dcWebasto()) << "Set max charging current request finished with error:" << reply->errorString();
|
||||
info->finish(Thing::ThingErrorHardwareFailure);
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
Q_ASSERT_X(false, "executeAction", QString("Unhandled actionTypeId: %1").arg(action.actionTypeId().toString()).toUtf8());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Q_ASSERT_X(false, "executeAction", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8());
|
||||
}
|
||||
|
||||
void IntegrationPluginWebasto::thingRemoved(Thing *thing)
|
||||
{
|
||||
qCDebug(dcWebasto()) << "Delete thing" << thing->name();
|
||||
if (thing->thingClassId() == webastoLiveThingClassId) {
|
||||
}
|
||||
|
||||
if (myThings().isEmpty()) {
|
||||
//Stop timer
|
||||
}
|
||||
void IntegrationPluginWebasto::setupWebastoNextConnection(ThingSetupInfo *info)
|
||||
{
|
||||
Thing *thing = info->thing();
|
||||
|
||||
QHostAddress address = m_monitors.value(thing)->networkDeviceInfo().address();
|
||||
uint port = thing->paramValue(webastoNextThingPortParamTypeId).toUInt();
|
||||
quint16 slaveId = thing->paramValue(webastoNextThingSlaveIdParamTypeId).toUInt();
|
||||
|
||||
qCDebug(dcWebasto()) << "Setting up webasto next connection on" << QString("%1:%2").arg(address.toString()).arg(port) << "slave ID:" << slaveId;
|
||||
WebastoNextModbusTcpConnection *webastoNextConnection = new WebastoNextModbusTcpConnection(address, port, slaveId, this);
|
||||
webastoNextConnection->modbusTcpMaster()->setTimeout(500);
|
||||
webastoNextConnection->modbusTcpMaster()->setNumberOfRetries(3);
|
||||
m_webastoNextConnections.insert(thing, webastoNextConnection);
|
||||
connect(info, &ThingSetupInfo::aborted, webastoNextConnection, [=](){
|
||||
webastoNextConnection->deleteLater();
|
||||
m_webastoNextConnections.remove(thing);
|
||||
});
|
||||
|
||||
// Reconnect on monitor reachable changed
|
||||
NetworkDeviceMonitor *monitor = m_monitors.value(thing);
|
||||
connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [=](bool reachable){
|
||||
|
||||
if (reachable) {
|
||||
qCDebug(dcWebasto()) << "Network device is now reachable for" << thing << monitor->networkDeviceInfo();
|
||||
} else {
|
||||
qCDebug(dcWebasto()) << "Network device not reachable any more" << thing;
|
||||
}
|
||||
|
||||
if (!thing->setupComplete())
|
||||
return;
|
||||
|
||||
if (reachable) {
|
||||
webastoNextConnection->modbusTcpMaster()->setHostAddress(monitor->networkDeviceInfo().address());
|
||||
webastoNextConnection->reconnectDevice();
|
||||
} else {
|
||||
// Note: We disable autoreconnect explicitly and we will
|
||||
// connect the device once the monitor says it is reachable again
|
||||
webastoNextConnection->disconnectDevice();
|
||||
}
|
||||
});
|
||||
|
||||
connect(webastoNextConnection, &WebastoNextModbusTcpConnection::reachableChanged, thing, [thing, webastoNextConnection, monitor](bool reachable){
|
||||
qCDebug(dcWebasto()) << "Reachable changed to" << reachable << "for" << thing;
|
||||
thing->setStateValue(webastoNextConnectedStateTypeId, reachable);
|
||||
if (reachable) {
|
||||
// Connected true will be set after successfull init
|
||||
webastoNextConnection->update();
|
||||
} else {
|
||||
thing->setStateValue(webastoNextCurrentPowerStateTypeId, 0);
|
||||
thing->setStateValue(webastoNextCurrentPowerPhaseAStateTypeId, 0);
|
||||
thing->setStateValue(webastoNextCurrentPowerPhaseBStateTypeId, 0);
|
||||
thing->setStateValue(webastoNextCurrentPowerPhaseCStateTypeId, 0);
|
||||
thing->setStateValue(webastoNextCurrentPhaseAStateTypeId, 0);
|
||||
thing->setStateValue(webastoNextCurrentPhaseBStateTypeId, 0);
|
||||
thing->setStateValue(webastoNextCurrentPhaseCStateTypeId, 0);
|
||||
|
||||
if (monitor->reachable()) {
|
||||
webastoNextConnection->reconnectDevice();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
connect(webastoNextConnection, &WebastoNextModbusTcpConnection::updateFinished, thing, [thing, webastoNextConnection](){
|
||||
|
||||
// Note: we get the update finished also if all calles failed...
|
||||
if (!webastoNextConnection->reachable()) {
|
||||
thing->setStateValue(webastoNextConnectedStateTypeId, false);
|
||||
return;
|
||||
}
|
||||
|
||||
thing->setStateValue(webastoNextConnectedStateTypeId, true);
|
||||
|
||||
qCDebug(dcWebasto()) << "Update finished" << webastoNextConnection;
|
||||
// States
|
||||
switch (webastoNextConnection->chargeState()) {
|
||||
case WebastoNextModbusTcpConnection::ChargeStateIdle:
|
||||
thing->setStateValue(webastoNextChargingStateTypeId, false);
|
||||
break;
|
||||
case WebastoNextModbusTcpConnection::ChargeStateCharging:
|
||||
thing->setStateValue(webastoNextChargingStateTypeId, true);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (webastoNextConnection->chargerState()) {
|
||||
case WebastoNextModbusTcpConnection::ChargerStateNoVehicle:
|
||||
thing->setStateValue(webastoNextChargingStateTypeId, false);
|
||||
thing->setStateValue(webastoNextPluggedInStateTypeId, false);
|
||||
break;
|
||||
case WebastoNextModbusTcpConnection::ChargerStateVehicleAttachedNoPermission:
|
||||
thing->setStateValue(webastoNextPluggedInStateTypeId, true);
|
||||
break;
|
||||
case WebastoNextModbusTcpConnection::ChargerStateCharging:
|
||||
thing->setStateValue(webastoNextChargingStateTypeId, true);
|
||||
thing->setStateValue(webastoNextPluggedInStateTypeId, true);
|
||||
break;
|
||||
case WebastoNextModbusTcpConnection::ChargerStateChargingPaused:
|
||||
thing->setStateValue(webastoNextPluggedInStateTypeId, true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Meter values
|
||||
thing->setStateValue(webastoNextCurrentPowerPhaseAStateTypeId, webastoNextConnection->activePowerL1());
|
||||
thing->setStateValue(webastoNextCurrentPowerPhaseBStateTypeId, webastoNextConnection->activePowerL2());
|
||||
thing->setStateValue(webastoNextCurrentPowerPhaseCStateTypeId, webastoNextConnection->activePowerL3());
|
||||
|
||||
double currentPhaseA = webastoNextConnection->currentL1() / 1000.0;
|
||||
double currentPhaseB = webastoNextConnection->currentL2() / 1000.0;
|
||||
double currentPhaseC = webastoNextConnection->currentL3() / 1000.0;
|
||||
thing->setStateValue(webastoNextCurrentPhaseAStateTypeId, currentPhaseA);
|
||||
thing->setStateValue(webastoNextCurrentPhaseBStateTypeId, currentPhaseB);
|
||||
thing->setStateValue(webastoNextCurrentPhaseCStateTypeId, currentPhaseC);
|
||||
|
||||
// Note: we do not use the active phase power, because we have sometimes a few watts on inactive phases
|
||||
Electricity::Phases phases = Electricity::PhaseNone;
|
||||
phases.setFlag(Electricity::PhaseA, currentPhaseA > 0);
|
||||
phases.setFlag(Electricity::PhaseB, currentPhaseB > 0);
|
||||
phases.setFlag(Electricity::PhaseC, currentPhaseC > 0);
|
||||
if (phases != Electricity::PhaseNone) {
|
||||
thing->setStateValue(webastoNextUsedPhasesStateTypeId, Electricity::convertPhasesToString(phases));
|
||||
thing->setStateValue(webastoNextPhaseCountStateTypeId, Electricity::getPhaseCount(phases));
|
||||
}
|
||||
|
||||
|
||||
thing->setStateValue(webastoNextCurrentPowerStateTypeId, webastoNextConnection->totalActivePower());
|
||||
|
||||
thing->setStateValue(webastoNextTotalEnergyConsumedStateTypeId, webastoNextConnection->energyConsumed() / 1000.0);
|
||||
thing->setStateValue(webastoNextSessionEnergyStateTypeId, webastoNextConnection->sessionEnergy() / 1000.0);
|
||||
|
||||
// Min / Max charging current^
|
||||
thing->setStateValue(webastoNextMinCurrentTotalStateTypeId, webastoNextConnection->minChargingCurrent());
|
||||
thing->setStateValue(webastoNextMaxCurrentTotalStateTypeId, webastoNextConnection->maxChargingCurrent());
|
||||
thing->setStateMinValue(webastoNextMaxChargingCurrentStateTypeId, webastoNextConnection->minChargingCurrent());
|
||||
thing->setStateMaxValue(webastoNextMaxChargingCurrentStateTypeId, webastoNextConnection->maxChargingCurrent());
|
||||
|
||||
thing->setStateValue(webastoNextMaxCurrentChargerStateTypeId, webastoNextConnection->maxChargingCurrentStation());
|
||||
thing->setStateValue(webastoNextMaxCurrentCableStateTypeId, webastoNextConnection->maxChargingCurrentCable());
|
||||
thing->setStateValue(webastoNextMaxCurrentElectricVehicleStateTypeId, webastoNextConnection->maxChargingCurrentEv());
|
||||
|
||||
if (webastoNextConnection->evseErrorCode() == 0) {
|
||||
thing->setStateValue(webastoNextErrorStateTypeId, "");
|
||||
} else {
|
||||
uint errorCode = webastoNextConnection->evseErrorCode() - 1;
|
||||
switch (errorCode) {
|
||||
case 1:
|
||||
// Note: also PB61 has the same mapping and the same reason for the error.
|
||||
// We inform only about the PB02 since it does not make any difference regarding the action
|
||||
thing->setStateValue(webastoNextErrorStateTypeId, "PB02 - PowerSwitch Failure");
|
||||
break;
|
||||
case 2:
|
||||
thing->setStateValue(webastoNextErrorStateTypeId, "PB07 - InternalError (Aux Voltage)");
|
||||
break;
|
||||
case 3:
|
||||
thing->setStateValue(webastoNextErrorStateTypeId, "PB09 - EV Communication Error");
|
||||
break;
|
||||
case 4:
|
||||
thing->setStateValue(webastoNextErrorStateTypeId, "PB17 - OverVoltage");
|
||||
break;
|
||||
case 5:
|
||||
thing->setStateValue(webastoNextErrorStateTypeId, "PB18 - UnderVoltage");
|
||||
break;
|
||||
case 6:
|
||||
thing->setStateValue(webastoNextErrorStateTypeId, "PB23 - OverCurrent Failure");
|
||||
break;
|
||||
case 7:
|
||||
thing->setStateValue(webastoNextErrorStateTypeId, "PB24 - OtherError");
|
||||
break;
|
||||
case 8:
|
||||
thing->setStateValue(webastoNextErrorStateTypeId, "PB27 - GroundFailure");
|
||||
break;
|
||||
case 9:
|
||||
thing->setStateValue(webastoNextErrorStateTypeId, "PB28 - InternalError (Selftest)");
|
||||
break;
|
||||
case 10:
|
||||
thing->setStateValue(webastoNextErrorStateTypeId, "PB29 - High Temperature");
|
||||
break;
|
||||
case 11:
|
||||
thing->setStateValue(webastoNextErrorStateTypeId, "PB52 - Proximity Pilot Error");
|
||||
break;
|
||||
case 12:
|
||||
thing->setStateValue(webastoNextErrorStateTypeId, "PB53 - Shutter Error");
|
||||
break;
|
||||
case 13:
|
||||
thing->setStateValue(webastoNextErrorStateTypeId, "PB57 - Error Three Phase Check");
|
||||
break;
|
||||
case 14:
|
||||
thing->setStateValue(webastoNextErrorStateTypeId, "PB59 - PWR internal error");
|
||||
break;
|
||||
case 15:
|
||||
thing->setStateValue(webastoNextErrorStateTypeId, "PB60 - EV Communication Error - Negative control pilot voltage");
|
||||
break;
|
||||
case 16:
|
||||
thing->setStateValue(webastoNextErrorStateTypeId, "PB62- DC residual current (Vehicle)");
|
||||
break;
|
||||
default:
|
||||
thing->setStateValue(webastoNextErrorStateTypeId, QString("Unknwon error code %1").arg(errorCode));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle life bit (keep alive mechanism if there is a HEMS activated)
|
||||
if (webastoNextConnection->lifeBit() == 0) {
|
||||
// Let's reset the life bit so the wallbox knows we are still here,
|
||||
// otherwise the wallbox goes into the failsave mode and limits the charging to the configured
|
||||
QModbusReply *reply = webastoNextConnection->setLifeBit(1);
|
||||
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
|
||||
connect(reply, &QModbusReply::finished, webastoNextConnection, [reply, webastoNextConnection](){
|
||||
if (reply->error() == QModbusDevice::NoError) {
|
||||
qCDebug(dcWebasto()) << "Resetted life bit watchdog on" << webastoNextConnection << "finished successfully";
|
||||
} else {
|
||||
qCWarning(dcWebasto()) << "Resetted life bit watchdog on" << webastoNextConnection << "finished with error:" << reply->errorString();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
connect(thing, &Thing::settingChanged, webastoNextConnection, [webastoNextConnection](const ParamTypeId ¶mTypeId, const QVariant &value){
|
||||
if (paramTypeId == webastoNextSettingsCommunicationTimeoutParamTypeId) {
|
||||
QModbusReply *reply = webastoNextConnection->setComTimeout(value.toUInt());
|
||||
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
|
||||
connect(reply, &QModbusReply::finished, webastoNextConnection, [reply, webastoNextConnection, value](){
|
||||
if (reply->error() == QModbusDevice::NoError) {
|
||||
qCDebug(dcWebasto()) << "Setting communication timout to" << value.toUInt() << "on" << webastoNextConnection << "finished successfully.";
|
||||
} else {
|
||||
qCWarning(dcWebasto()) << "Setting communication timout to" << value.toUInt() << "on" << webastoNextConnection << "finished with error:" << reply->errorString();
|
||||
if (webastoNextConnection->reachable()) {
|
||||
webastoNextConnection->updateComTimeout();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (paramTypeId == webastoNextSettingsSafeCurrentParamTypeId) {
|
||||
QModbusReply *reply = webastoNextConnection->setSafeCurrent(value.toUInt());
|
||||
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
|
||||
connect(reply, &QModbusReply::finished, webastoNextConnection, [reply, webastoNextConnection, value](){
|
||||
if (reply->error() == QModbusDevice::NoError) {
|
||||
qCDebug(dcWebasto()) << "Setting save current to" << value.toUInt() << "on" << webastoNextConnection << "finished successfully.";
|
||||
} else {
|
||||
qCWarning(dcWebasto()) << "Setting save current to" << value.toUInt() << "on" << webastoNextConnection << "finished with error:" << reply->errorString();
|
||||
if (webastoNextConnection->reachable()) {
|
||||
webastoNextConnection->updateSafeCurrent();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
qCWarning(dcWebasto()) << "Unhandled setting changed for" << webastoNextConnection;
|
||||
}
|
||||
});
|
||||
|
||||
connect(webastoNextConnection, &WebastoNextModbusTcpConnection::comTimeoutChanged, thing, [thing](quint16 comTimeout){
|
||||
thing->setSettingValue(webastoNextSettingsCommunicationTimeoutParamTypeId, comTimeout);
|
||||
});
|
||||
|
||||
connect(webastoNextConnection, &WebastoNextModbusTcpConnection::safeCurrentChanged, thing, [thing](quint16 safeCurrent){
|
||||
thing->setSettingValue(webastoNextSettingsSafeCurrentParamTypeId, safeCurrent);
|
||||
});
|
||||
|
||||
qCInfo(dcWebasto()) << "Setup finished successfully for Webasto NEXT" << thing << monitor;
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
}
|
||||
|
||||
void IntegrationPluginWebasto::update(Webasto *webasto)
|
||||
|
|
@ -258,10 +731,35 @@ void IntegrationPluginWebasto::evaluatePhaseCount(Thing *thing)
|
|||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginWebasto::executeWebastoNextPowerAction(ThingActionInfo *info, bool power)
|
||||
{
|
||||
qCDebug(dcWebasto()) << (power ? "Enabling": "Disabling") << "charging on" << info->thing();
|
||||
|
||||
WebastoNextModbusTcpConnection *connection = m_webastoNextConnections.value(info->thing());
|
||||
QModbusReply *reply = nullptr;
|
||||
if (power) {
|
||||
reply = connection->setChargingAction(WebastoNextModbusTcpConnection::ChargingActionStartSession);
|
||||
} else {
|
||||
reply = connection->setChargingAction(WebastoNextModbusTcpConnection::ChargingActionCancelSession);
|
||||
}
|
||||
|
||||
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
|
||||
connect(reply, &QModbusReply::finished, info, [info, reply, power](){
|
||||
if (reply->error() == QModbusDevice::NoError) {
|
||||
info->thing()->setStateValue(webastoNextPowerStateTypeId, power);
|
||||
qCDebug(dcWebasto()) << "Enabling/disabling charging request finished successfully.";
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
} else {
|
||||
qCWarning(dcWebasto()) << "Enabling/disabling charging request finished with error:" << reply->errorString();
|
||||
info->finish(Thing::ThingErrorHardwareFailure);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void IntegrationPluginWebasto::onConnectionChanged(bool connected)
|
||||
{
|
||||
Webasto *connection = static_cast<Webasto *>(sender());
|
||||
Thing *thing = m_webastoConnections.key(connection);
|
||||
Thing *thing = m_webastoLiveConnections.key(connection);
|
||||
if (!thing) {
|
||||
qCWarning(dcWebasto()) << "On connection changed, thing not found for connection";
|
||||
return;
|
||||
|
|
@ -290,7 +788,7 @@ void IntegrationPluginWebasto::onWriteRequestError(const QUuid &requestId, const
|
|||
void IntegrationPluginWebasto::onReceivedRegister(Webasto::TqModbusRegister modbusRegister, const QVector<quint16> &data)
|
||||
{
|
||||
Webasto *connection = static_cast<Webasto *>(sender());
|
||||
Thing *thing = m_webastoConnections.key(connection);
|
||||
Thing *thing = m_webastoLiveConnections.key(connection);
|
||||
if (!thing) {
|
||||
qCWarning(dcWebasto()) << "On basic information received, thing not found for connection";
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Copyright 2013 - 2023, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
|
|
@ -31,14 +31,16 @@
|
|||
#ifndef INTEGRATIONPLUGINWEBASTO_H
|
||||
#define INTEGRATIONPLUGINWEBASTO_H
|
||||
|
||||
#include <integrations/integrationplugin.h>
|
||||
#include <plugintimer.h>
|
||||
#include <integrations/integrationplugin.h>
|
||||
#include <network/networkdevicemonitor.h>
|
||||
|
||||
#include "webasto.h"
|
||||
#include "webastonextmodbustcpconnection.h"
|
||||
|
||||
#include <QUuid>
|
||||
#include <QObject>
|
||||
#include <QHostAddress>
|
||||
#include <QUuid>
|
||||
|
||||
class IntegrationPluginWebasto : public IntegrationPlugin
|
||||
{
|
||||
|
|
@ -53,17 +55,25 @@ public:
|
|||
void discoverThings(ThingDiscoveryInfo *info) override;
|
||||
void setupThing(ThingSetupInfo *info) override;
|
||||
void postSetupThing(Thing *thing) override;
|
||||
void executeAction(ThingActionInfo *info) override;
|
||||
void thingRemoved(Thing *thing) override;
|
||||
void executeAction(ThingActionInfo *info) override;
|
||||
|
||||
private:
|
||||
PluginTimer *m_pluginTimer = nullptr;
|
||||
QHash<Thing *, Webasto *> m_webastoConnections;
|
||||
|
||||
QHash<QUuid, ThingActionInfo *> m_asyncActions;
|
||||
|
||||
QHash<Thing *, Webasto *> m_webastoLiveConnections;
|
||||
QHash<Thing *, WebastoNextModbusTcpConnection *> m_webastoNextConnections;
|
||||
QHash<Thing *, NetworkDeviceMonitor *> m_monitors;
|
||||
|
||||
void setupWebastoNextConnection(ThingSetupInfo *info);
|
||||
|
||||
void update(Webasto *webasto);
|
||||
void evaluatePhaseCount(Thing *thing);
|
||||
|
||||
void executeWebastoNextPowerAction(ThingActionInfo *info, bool power);
|
||||
|
||||
private slots:
|
||||
void onConnectionChanged(bool connected);
|
||||
void onWriteRequestExecuted(const QUuid &requestId, bool success);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"thingClasses": [
|
||||
{
|
||||
"id": "48472124-3199-4827-990a-b72069bd5658",
|
||||
"displayName": "Live Wallbox",
|
||||
"displayName": "Webasto Live",
|
||||
"name": "webastoLive",
|
||||
"createMethods": ["discovery"],
|
||||
"interfaces": ["evcharger", "smartmeterconsumer", "connectable"],
|
||||
|
|
@ -35,7 +35,6 @@
|
|||
{
|
||||
"id": "7e6ed2b4-aa8a-4bf6-b20b-84ecc6cc1508",
|
||||
"displayName": "Connected",
|
||||
"displayNameEvent": "Connected changed",
|
||||
"name": "connected",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
|
|
@ -45,7 +44,6 @@
|
|||
"id": "b076353b-e911-444f-80ad-3f78c4075d1a",
|
||||
"name": "chargePointState",
|
||||
"displayName": "Charge point state",
|
||||
"displayNameEvent": "Charge point state changed",
|
||||
"type": "QString",
|
||||
"possibleValues": [
|
||||
"No vehicle attached",
|
||||
|
|
@ -65,7 +63,6 @@
|
|||
"id": "a1a452f9-de93-4c31-b71b-c74264f85a3e",
|
||||
"name": "cableState",
|
||||
"displayName": "Cable state",
|
||||
"displayNameEvent": "Cable state changed",
|
||||
"type": "QString",
|
||||
"possibleValues": [
|
||||
"No cable attached",
|
||||
|
|
@ -82,7 +79,6 @@
|
|||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"displayNameAction": "Start charging",
|
||||
"displayNameEvent": "Charging status changed",
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
|
|
@ -90,7 +86,6 @@
|
|||
"name": "maxChargingCurrent",
|
||||
"displayName": "Charging current",
|
||||
"displayNameAction": "Set charging current",
|
||||
"displayNameEvent": "Charging current changed",
|
||||
"type": "uint",
|
||||
"unit": "Ampere",
|
||||
"minValue": 6,
|
||||
|
|
@ -102,7 +97,6 @@
|
|||
"id": "0e15e78e-a233-4026-a0fd-f65edc824f1e",
|
||||
"name": "pluggedIn",
|
||||
"displayName": "Car plugged in",
|
||||
"displayNameEvent": "Car plugged in changed",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
|
|
@ -110,7 +104,6 @@
|
|||
"id": "8f35404d-8237-4ff8-8774-9ad10ceee5c3",
|
||||
"name": "charging",
|
||||
"displayName": "Charging",
|
||||
"displayNameEvent": "Charging changed",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
|
|
@ -118,7 +111,6 @@
|
|||
"id": "179e6136-2ac1-4247-b457-f804e2212293",
|
||||
"name": "phaseCount",
|
||||
"displayName": "Number of connected phases",
|
||||
"displayNameEvent": "Number of connected phases changed",
|
||||
"type": "uint",
|
||||
"minValue": 1,
|
||||
"maxValue": 3,
|
||||
|
|
@ -128,7 +120,6 @@
|
|||
"id": "2027fbb6-c9d2-4a75-bdd0-a3ad3785cdc6",
|
||||
"name": "currentPhase1",
|
||||
"displayName": "Current phase 1",
|
||||
"displayNameEvent": "Current phase 1 changed",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0.00
|
||||
|
|
@ -137,7 +128,6 @@
|
|||
"id": "1793f645-d7db-4e99-af92-3587aa3069f3",
|
||||
"name": "currentPhase2",
|
||||
"displayName": "Current phase 2",
|
||||
"displayNameEvent": "Current phase 2 changed",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0.00
|
||||
|
|
@ -146,7 +136,6 @@
|
|||
"id": "feb8c5da-91a7-45f9-acc3-c1b61478c3d2",
|
||||
"name": "currentPhase3",
|
||||
"displayName": "Current phase 3",
|
||||
"displayNameEvent": "Current phase 3 changed",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0.00
|
||||
|
|
@ -155,7 +144,6 @@
|
|||
"id": "b20a46ee-0f22-4096-a348-34e68e99e0be",
|
||||
"name": "currentPower",
|
||||
"displayName": "Current power consumption",
|
||||
"displayNameEvent": "Current power consumtion changed",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0.00
|
||||
|
|
@ -164,7 +152,6 @@
|
|||
"id": "80568c51-054c-4351-b9d2-e875fee4cc1f",
|
||||
"name": "totalEnergyConsumed",
|
||||
"displayName": "Total energy consumed",
|
||||
"displayNameEvent": "Total energy consumption changed",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0
|
||||
|
|
@ -173,7 +160,6 @@
|
|||
"id": "87c70567-794e-4af2-916c-b34cf864afcf",
|
||||
"name": "sessionTime",
|
||||
"displayName": "Session time",
|
||||
"displayNameEvent": "Session time changed",
|
||||
"type": "int",
|
||||
"unit": "Minutes",
|
||||
"defaultValue": 0
|
||||
|
|
@ -182,7 +168,6 @@
|
|||
"id": "b9b46920-55c1-4bfa-9200-acdc9c0a2471",
|
||||
"name": "sessionEnergy",
|
||||
"displayName": "Session energy",
|
||||
"displayNameEvent": "Session energy changed",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0
|
||||
|
|
@ -191,7 +176,6 @@
|
|||
"id": "56d31fd1-5cfb-42dd-8181-e6b0d0ca9c8a",
|
||||
"name": "error",
|
||||
"displayName": "Error ",
|
||||
"displayNameEvent": "Error changed",
|
||||
"type": "int",
|
||||
"defaultValue": 0
|
||||
},
|
||||
|
|
@ -199,7 +183,6 @@
|
|||
"id": "0e60b15d-2b0c-4672-960e-7c6ea67bf7ea",
|
||||
"name": "maxPossibleChargingCurrent",
|
||||
"displayName": "Maximum possible charging current",
|
||||
"displayNameEvent": "Maximum possible charging current changed",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 6.00
|
||||
|
|
@ -208,7 +191,242 @@
|
|||
"id": "48b62082-f286-433e-9cf8-2dcf6c0ea248",
|
||||
"name": "userId",
|
||||
"displayName": "User ID",
|
||||
"displayNameEvent": "User ID changed",
|
||||
"type": "QString",
|
||||
"defaultValue": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "1dddfbf4-a49d-4e28-8cbc-108547a369a2",
|
||||
"displayName": "Webasto NEXT",
|
||||
"name": "webastoNext",
|
||||
"createMethods": ["discovery"],
|
||||
"interfaces": ["evcharger", "smartmeterconsumer", "connectable"],
|
||||
"settingsTypes": [
|
||||
{
|
||||
"id": "5292e079-515c-47ae-9117-6a70d5c02566",
|
||||
"name": "safeCurrent",
|
||||
"displayName": "Maximum current on communication failure",
|
||||
"type": "uint",
|
||||
"defaultValue": "6",
|
||||
"minValue": 6,
|
||||
"maxValue": 16,
|
||||
"unit": "Ampere"
|
||||
},
|
||||
{
|
||||
"id": "20710f47-d585-40fa-a9bd-8b586711966e",
|
||||
"name": "communicationTimeout",
|
||||
"displayName": "Communication timeout",
|
||||
"type": "uint",
|
||||
"defaultValue": 60,
|
||||
"minValue": 1,
|
||||
"unit": "Seconds"
|
||||
}
|
||||
],
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "882b662f-ec7c-4134-be31-5d36567b9fc2",
|
||||
"name": "macAddress",
|
||||
"displayName": "MAC address",
|
||||
"type": "QString",
|
||||
"defaultValue": "",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"id": "be5a0c50-f3ba-4562-b6c0-a0208e2ab118",
|
||||
"name":"port",
|
||||
"displayName": "Port",
|
||||
"type": "uint",
|
||||
"defaultValue": 502
|
||||
},
|
||||
{
|
||||
"id": "bdb8a7bb-fcfd-4130-b860-ba3eaa3f9932",
|
||||
"name":"slaveId",
|
||||
"displayName": "Slave ID",
|
||||
"type": "uint",
|
||||
"defaultValue": 1
|
||||
}
|
||||
],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "291a55e5-2f63-42bc-b0aa-cf2079a19632",
|
||||
"displayName": "Connected",
|
||||
"name": "connected",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "de752511-b47d-4abc-980a-51c261a93a69",
|
||||
"name": "power",
|
||||
"displayName": "Charging enabled",
|
||||
"displayNameAction": "Enable charging",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"id": "62bfa06d-599c-4a3b-8f51-89e307a25ca6",
|
||||
"name": "maxChargingCurrent",
|
||||
"displayName": "Charging current",
|
||||
"displayNameAction": "Set charging current",
|
||||
"type": "uint",
|
||||
"unit": "Ampere",
|
||||
"minValue": 6,
|
||||
"maxValue": 32,
|
||||
"defaultValue": 6,
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"id": "e30dc786-6c01-4a86-9f72-8d32df00f528",
|
||||
"name": "pluggedIn",
|
||||
"displayName": "Car plugged in",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"suggestLogging": true
|
||||
},
|
||||
{
|
||||
"id": "c886d4a6-20fb-4aad-ad95-8b16aa6c8363",
|
||||
"name": "charging",
|
||||
"displayName": "Charging",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"suggestLogging": true
|
||||
},
|
||||
{
|
||||
"id": "ba17d0d4-bfed-4920-b85e-54b34200bfff",
|
||||
"name": "phaseCount",
|
||||
"displayName": "Number of connected phases",
|
||||
"type": "uint",
|
||||
"minValue": 1,
|
||||
"maxValue": 3,
|
||||
"defaultValue": 1
|
||||
},
|
||||
{
|
||||
"id": "d211886e-e755-4e7c-b95d-69e88e5be229",
|
||||
"name": "usedPhases",
|
||||
"displayName": "Phases used for charging",
|
||||
"type": "QString",
|
||||
"possibleValues" : ["", "A", "B", "C", "AB", "AC", "BC", "ABC" ],
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "584e1ae7-2844-44a9-a6f7-183ee0d595f1",
|
||||
"name": "currentPower",
|
||||
"displayName": "Charging power",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0.00
|
||||
},
|
||||
{
|
||||
"id": "9a858704-9525-4480-88ff-59ba0014daa1",
|
||||
"name": "totalEnergyConsumed",
|
||||
"displayName": "Total energy consumed",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "8fab231b-0270-4528-81b0-84c89b8ced1c",
|
||||
"name": "currentPhaseA",
|
||||
"displayName": "Current phase A",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0.00
|
||||
},
|
||||
{
|
||||
"id": "c4f4b78b-e220-4c49-9019-4d1dc0563f89",
|
||||
"name": "currentPhaseB",
|
||||
"displayName": "Current phase B",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0.00
|
||||
},
|
||||
{
|
||||
"id": "a8ef8fae-5ff4-4381-9341-cc8910d415f4",
|
||||
"name": "currentPhaseC",
|
||||
"displayName": "Current phase C",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0.00
|
||||
},
|
||||
{
|
||||
"id": "cb903571-9b0b-4a86-9840-112ec76088c5",
|
||||
"name": "currentPowerPhaseA",
|
||||
"displayName": "Current power phase A",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0.00
|
||||
},
|
||||
{
|
||||
"id": "d32c6b2c-0eae-4bbc-8d04-2a00a30de864",
|
||||
"name": "currentPowerPhaseB",
|
||||
"displayName": "Current power phase B",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0.00
|
||||
},
|
||||
{
|
||||
"id": "fc98ffe8-4824-4db5-96bb-62dfef6e0b34",
|
||||
"name": "currentPowerPhaseC",
|
||||
"displayName": "Current power phase C",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0.00
|
||||
},
|
||||
{
|
||||
"id": "52a7a45c-bdec-49ed-9a1b-4eebff5b1482",
|
||||
"name": "maxCurrentTotal",
|
||||
"displayName": "Maximum current overall",
|
||||
"type": "uint",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "f7d0f75f-5313-4d73-9420-eb776f9da3d5",
|
||||
"name": "minCurrentTotal",
|
||||
"displayName": "Minimum current overall",
|
||||
"type": "uint",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "e9b2a3c1-3a4b-4cb7-b253-ae9b4b8862f9",
|
||||
"name": "maxCurrentCharger",
|
||||
"displayName": "Maximum current charger",
|
||||
"type": "uint",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "d8c2c93f-2219-4b69-b7c0-c983c9d69232",
|
||||
"name": "maxCurrentCable",
|
||||
"displayName": "Maximum current cable",
|
||||
"type": "uint",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "3a2239fd-09c1-46ac-9dcb-5e08733f862c",
|
||||
"name": "maxCurrentElectricVehicle",
|
||||
"displayName": "Maximum current electric vehicle",
|
||||
"type": "uint",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "78e8262a-5d41-4749-ab8a-a50d5c661cbb",
|
||||
"name": "sessionEnergy",
|
||||
"displayName": "Session energy",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0.0,
|
||||
"suggestLogging": true
|
||||
},
|
||||
{
|
||||
"id": "54feab4b-3134-4968-bfec-d2d656cc4ad6",
|
||||
"name": "error",
|
||||
"displayName": "Error",
|
||||
"type": "QString",
|
||||
"defaultValue": ""
|
||||
}
|
||||
|
|
@ -218,4 +436,3 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,411 @@
|
|||
{
|
||||
"className": "WebastoNext",
|
||||
"protocol": "TCP",
|
||||
"endianness": "BigEndian",
|
||||
"errorLimitUntilNotReachable": 10,
|
||||
"checkReachableRegister": "totalActivePower",
|
||||
"enums": [
|
||||
{
|
||||
"name": "ChargerState",
|
||||
"values": [
|
||||
{
|
||||
"key": "NoVehicle",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"key": "VehicleAttachedNoPermission",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"key": "Charging",
|
||||
"value": 3
|
||||
},
|
||||
{
|
||||
"key": "ChargingPaused",
|
||||
"value": 4
|
||||
},
|
||||
{
|
||||
"key": "ChargingError",
|
||||
"value": 7
|
||||
},
|
||||
{
|
||||
"key": "ChargingStationReserved",
|
||||
"value": 8
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ChargeState",
|
||||
"values": [
|
||||
{
|
||||
"key": "Idle",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"key": "Charging",
|
||||
"value": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "EvseState",
|
||||
"values": [
|
||||
{
|
||||
"key": "Starting",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"key": "Running",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"key": "Error",
|
||||
"value": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "CableState",
|
||||
"values": [
|
||||
{
|
||||
"key": "NoCableAttached",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"key": "CableAttachedNoCar",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"key": "CableAttachedCarAttached",
|
||||
"value": 2
|
||||
},
|
||||
{
|
||||
"key": "CableAttachedCarAttachedLocked",
|
||||
"value": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ChargingAction",
|
||||
"values": [
|
||||
{
|
||||
"key": "NoAction",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"key": "StartSession",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"key": "CancelSession",
|
||||
"value": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"blocks": [
|
||||
{
|
||||
"id": "states",
|
||||
"readSchedule": "update",
|
||||
"registers": [
|
||||
{
|
||||
"id": "chargerState",
|
||||
"description": "State of the charging device",
|
||||
"address": 1000,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "holdingRegister",
|
||||
"enum": "ChargerState",
|
||||
"readSchedule": "update",
|
||||
"defaultValue": "ChargerStateNoVehicle",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "chargeState",
|
||||
"description": "Charge state",
|
||||
"address": 1001,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "holdingRegister",
|
||||
"enum": "ChargeState",
|
||||
"readSchedule": "update",
|
||||
"defaultValue": "ChargeStateIdle",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "evseState",
|
||||
"description": "EVSE state (state of charging station)",
|
||||
"address": 1002,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "holdingRegister",
|
||||
"enum": "EvseState",
|
||||
"readSchedule": "update",
|
||||
"defaultValue": "EvseStateStarting",
|
||||
"access": "RO"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"registers": [
|
||||
{
|
||||
"id": "cableState",
|
||||
"description": "Cable state",
|
||||
"address": 1004,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "holdingRegister",
|
||||
"enum": "CableState",
|
||||
"readSchedule": "update",
|
||||
"defaultValue": "CableStateNoCableAttached",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "evseErrorCode",
|
||||
"description": "ESVE Error codes, 0 = No error",
|
||||
"address": 1006,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "holdingRegister",
|
||||
"readSchedule": "update",
|
||||
"defaultValue": "CableStateNoCableAttached",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "currentL1",
|
||||
"description": "Charging current L1",
|
||||
"address": 1008,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"unit": "mA",
|
||||
"registerType": "holdingRegister",
|
||||
"readSchedule": "update",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "currentL2",
|
||||
"description": "Charging current L2",
|
||||
"address": 1010,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"unit": "mA",
|
||||
"registerType": "holdingRegister",
|
||||
"readSchedule": "update",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "currentL3",
|
||||
"description": "Charging current L3",
|
||||
"address": 1012,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"unit": "mA",
|
||||
"registerType": "holdingRegister",
|
||||
"readSchedule": "update",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "totalActivePower",
|
||||
"description": "Total active charging power",
|
||||
"address": 1020,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"unit": "W",
|
||||
"registerType": "holdingRegister",
|
||||
"readSchedule": "update",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "activePowerL1",
|
||||
"description": "Active power L1",
|
||||
"address": 1024,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"unit": "W",
|
||||
"registerType": "holdingRegister",
|
||||
"readSchedule": "update",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "activePowerL2",
|
||||
"description": "Active power L2",
|
||||
"address": 1028,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"unit": "W",
|
||||
"registerType": "holdingRegister",
|
||||
"readSchedule": "update",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "activePowerL3",
|
||||
"description": "Active power L3",
|
||||
"address": 1032,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"unit": "W",
|
||||
"registerType": "holdingRegister",
|
||||
"readSchedule": "update",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "energyConsumed",
|
||||
"description": "Energy meter reading of the charging station",
|
||||
"address": 1036,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"unit": "Wh",
|
||||
"registerType": "holdingRegister",
|
||||
"readSchedule": "update",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "maxChargingCurrent",
|
||||
"description": "The maximal charging current of the hardware (EVSE, Cable, EV)",
|
||||
"address": 1100,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"unit": "A",
|
||||
"registerType": "holdingRegister",
|
||||
"readSchedule": "update",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "minChargingCurrent",
|
||||
"description": "The minimal charging current of the hardware (EVSE, Cable, EV)",
|
||||
"address": 1102,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"unit": "A",
|
||||
"registerType": "holdingRegister",
|
||||
"readSchedule": "update",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "maxChargingCurrentStation",
|
||||
"description": "The maximal charging current of the station",
|
||||
"address": 1104,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"unit": "A",
|
||||
"registerType": "holdingRegister",
|
||||
"readSchedule": "update",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "maxChargingCurrentCable",
|
||||
"description": "The maximal charging current of the cable",
|
||||
"address": 1106,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"unit": "A",
|
||||
"registerType": "holdingRegister",
|
||||
"readSchedule": "update",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "maxChargingCurrentEv",
|
||||
"description": "The maximal charging current of the EV",
|
||||
"address": 1108,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"unit": "A",
|
||||
"registerType": "holdingRegister",
|
||||
"readSchedule": "update",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "sessionEnergy",
|
||||
"description": "Sum of charged energy for the last session",
|
||||
"address": 1502,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"unit": "Wh",
|
||||
"registerType": "holdingRegister",
|
||||
"readSchedule": "update",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "safeCurrent",
|
||||
"description": "Max. charge current under communication failure",
|
||||
"address": 2000,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"unit": "A",
|
||||
"readSchedule": "update",
|
||||
"registerType": "holdingRegister",
|
||||
"defaultValue": "0",
|
||||
"access": "RW"
|
||||
},
|
||||
{
|
||||
"id": "comTimeout",
|
||||
"description": "Communication timeout",
|
||||
"address": 2002,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"unit": "s",
|
||||
"readSchedule": "update",
|
||||
"registerType": "holdingRegister",
|
||||
"defaultValue": "0",
|
||||
"access": "RW"
|
||||
},
|
||||
{
|
||||
"id": "chargePower",
|
||||
"description": "Set the charge power",
|
||||
"address": 5000,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"unit": "W",
|
||||
"registerType": "holdingRegister",
|
||||
"defaultValue": "0",
|
||||
"access": "WO"
|
||||
},
|
||||
{
|
||||
"id": "chargeCurrent",
|
||||
"description": "Set the charge current",
|
||||
"address": 5004,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"unit": "A",
|
||||
"registerType": "holdingRegister",
|
||||
"defaultValue": "0",
|
||||
"access": "WO"
|
||||
},
|
||||
{
|
||||
"id": "chargingAction",
|
||||
"description": "Start / Cancel charging session",
|
||||
"address": 5006,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"enum": "ChargingAction",
|
||||
"registerType": "holdingRegister",
|
||||
"defaultValue": "0",
|
||||
"access": "WO"
|
||||
},
|
||||
{
|
||||
"id": "lifeBit",
|
||||
"description": "Life bit",
|
||||
"address": 6000,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"readSchedule": "update",
|
||||
"registerType": "holdingRegister",
|
||||
"defaultValue": "0",
|
||||
"access": "RW"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,10 +1,16 @@
|
|||
include(../plugins.pri)
|
||||
|
||||
# Generate modbus connection
|
||||
MODBUS_CONNECTIONS += webasto-next-registers.json
|
||||
MODBUS_TOOLS_CONFIG += VERBOSE
|
||||
include(../modbus.pri)
|
||||
|
||||
SOURCES += \
|
||||
integrationpluginwebasto.cpp \
|
||||
webasto.cpp
|
||||
webasto.cpp \
|
||||
webastodiscovery.cpp
|
||||
|
||||
HEADERS += \
|
||||
integrationpluginwebasto.h \
|
||||
webasto.h
|
||||
webasto.h \
|
||||
webastodiscovery.h
|
||||
|
|
|
|||
|
|
@ -0,0 +1,197 @@
|
|||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* 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 "webastodiscovery.h"
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
WebastoDiscovery::WebastoDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent)
|
||||
: QObject{parent},
|
||||
m_networkDeviceDiscovery{networkDeviceDiscovery}
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void WebastoDiscovery::startDiscovery()
|
||||
{
|
||||
// TODO: add parameter for searching WebastoNext or WebastoLive, for now the discovery searches only for WebastoNext
|
||||
|
||||
m_startDateTime = QDateTime::currentDateTime();
|
||||
|
||||
qCInfo(dcWebasto()) << "Discovery: Starting to search for WebastoNext wallboxes in the network...";
|
||||
NetworkDeviceDiscoveryReply *discoveryReply = m_networkDeviceDiscovery->discover();
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, &WebastoDiscovery::checkNetworkDevice);
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, discoveryReply, &NetworkDeviceDiscoveryReply::deleteLater);
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){
|
||||
qCDebug(dcWebasto()) << "Discovery: Network discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "network devices";
|
||||
// Give the last connections added right before the network discovery finished a chance to check the device...
|
||||
QTimer::singleShot(3000, this, [this](){
|
||||
qCDebug(dcWebasto()) << "Discovery: Grace period timer triggered.";
|
||||
finishDiscovery();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
QList<WebastoDiscovery::Result> WebastoDiscovery::results() const
|
||||
{
|
||||
return m_results;
|
||||
}
|
||||
|
||||
void WebastoDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo)
|
||||
{
|
||||
WebastoNextModbusTcpConnection *connection = new WebastoNextModbusTcpConnection(networkDeviceInfo.address(), 502, 1, this);
|
||||
m_connections.append(connection);
|
||||
|
||||
connect(connection, &WebastoNextModbusTcpConnection::reachableChanged, this, [=](bool reachable){
|
||||
if (!reachable) {
|
||||
// Disconnected ... done with this connection
|
||||
cleanupConnection(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
// Read some well known registers to verify if the register exist and make sense...
|
||||
QModbusReply *reply = connection->readCableState();
|
||||
connect(reply, &QModbusReply::finished, this, [=](){
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->error() != QModbusDevice::NoError) {
|
||||
// Something went wrong...probably not the device we are searching for
|
||||
cleanupConnection(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure this is a valid cable state
|
||||
const QModbusDataUnit unit = reply->result();
|
||||
quint16 rawValue = ModbusDataUtils::convertToUInt16(unit.values());
|
||||
QMetaEnum valueEnum = WebastoNextModbusTcpConnection::staticMetaObject.enumerator(WebastoNextModbusTcpConnection::staticMetaObject.indexOfEnumerator("CableState"));
|
||||
if (!valueEnum.valueToKey(rawValue)) {
|
||||
qCDebug(dcWebasto()) << "Discovery: invalid enum value for cable state on connection on" << networkDeviceInfo.address().toString() << "Continue...";;
|
||||
cleanupConnection(connection);
|
||||
}
|
||||
|
||||
QModbusReply *reply = connection->readChargerState();
|
||||
connect(reply, &QModbusReply::finished, this, [=](){
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->error() != QModbusDevice::NoError) {
|
||||
// Something went wrong...probably not the device we are searching for
|
||||
cleanupConnection(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure this is a valid charger state
|
||||
const QModbusDataUnit unit = reply->result();
|
||||
quint16 rawValue = ModbusDataUtils::convertToUInt16(unit.values());
|
||||
QMetaEnum valueEnum = WebastoNextModbusTcpConnection::staticMetaObject.enumerator(WebastoNextModbusTcpConnection::staticMetaObject.indexOfEnumerator("ChargerState"));
|
||||
if (!valueEnum.valueToKey(rawValue)) {
|
||||
qCDebug(dcWebasto()) << "Discovery: invalid enum value for charger state on connection on" << networkDeviceInfo.address().toString() << "Continue...";;
|
||||
cleanupConnection(connection);
|
||||
}
|
||||
|
||||
|
||||
// Read some registers distributed over the range...
|
||||
|
||||
QModbusReply *reply = connection->readTotalActivePower();
|
||||
connect(reply, &QModbusReply::finished, this, [=](){
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->error() != QModbusDevice::NoError) {
|
||||
// Something went wrong...probably not the device we are searching for
|
||||
cleanupConnection(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
QModbusReply *reply = connection->readSessionEnergy();
|
||||
connect(reply, &QModbusReply::finished, this, [=](){
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->error() != QModbusDevice::NoError) {
|
||||
// Something went wrong...probably not the device we are searching for
|
||||
cleanupConnection(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
// All values good so far, let's assume this is a Webasto NEXT
|
||||
|
||||
Result result;
|
||||
result.productName = "Webasto NEXT";
|
||||
result.type = TypeWebastoNext;
|
||||
result.networkDeviceInfo = networkDeviceInfo;
|
||||
m_results.append(result);
|
||||
|
||||
qCDebug(dcWebasto()) << "Discovery: --> Found" << result.productName << result.networkDeviceInfo;
|
||||
|
||||
// Done with this connection
|
||||
cleanupConnection(connection);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// If we get any error...skip this host...
|
||||
connect(connection->modbusTcpMaster(), &ModbusTcpMaster::connectionErrorOccurred, this, [=](QModbusDevice::Error error){
|
||||
if (error != QModbusDevice::NoError) {
|
||||
qCDebug(dcWebasto()) << "Discovery: Connection error on" << networkDeviceInfo.address().toString() << "Continue...";;
|
||||
cleanupConnection(connection);
|
||||
}
|
||||
});
|
||||
|
||||
// If check reachability failed...skip this host...
|
||||
connect(connection, &WebastoNextModbusTcpConnection::checkReachabilityFailed, this, [=](){
|
||||
qCDebug(dcWebasto()) << "Discovery: Check reachability failed on" << networkDeviceInfo.address().toString() << "Continue...";;
|
||||
cleanupConnection(connection);
|
||||
});
|
||||
|
||||
// Try to connect, maybe it works, maybe not...
|
||||
connection->connectDevice();
|
||||
}
|
||||
|
||||
void WebastoDiscovery::cleanupConnection(WebastoNextModbusTcpConnection *connection)
|
||||
{
|
||||
m_connections.removeAll(connection);
|
||||
connection->disconnectDevice();
|
||||
connection->deleteLater();
|
||||
}
|
||||
|
||||
void WebastoDiscovery::finishDiscovery()
|
||||
{
|
||||
qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startDateTime.toMSecsSinceEpoch();
|
||||
|
||||
// Cleanup any leftovers...we don't care any more
|
||||
foreach (WebastoNextModbusTcpConnection *connection, m_connections)
|
||||
cleanupConnection(connection);
|
||||
|
||||
qCInfo(dcWebasto()) << "Discovery: Finished the discovery process. Found" << m_results.count() << "Webasto NEXT wallboxes in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz");
|
||||
emit discoveryFinished();
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#ifndef WEBASTODISCOVERY_H
|
||||
#define WEBASTODISCOVERY_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <network/networkdevicediscovery.h>
|
||||
|
||||
#include "webastonextmodbustcpconnection.h"
|
||||
|
||||
class WebastoDiscovery : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Type {
|
||||
TypeWebastoLive,
|
||||
TypeWebastoNext
|
||||
};
|
||||
Q_ENUM(Type)
|
||||
|
||||
typedef struct Result {
|
||||
QString productName;
|
||||
Type type;
|
||||
NetworkDeviceInfo networkDeviceInfo;
|
||||
} Result;
|
||||
|
||||
explicit WebastoDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent = nullptr);
|
||||
|
||||
void startDiscovery();
|
||||
|
||||
QList<Result> results() const;
|
||||
|
||||
signals:
|
||||
void discoveryFinished();
|
||||
|
||||
private:
|
||||
NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr;
|
||||
|
||||
QList<WebastoNextModbusTcpConnection *> m_connections;
|
||||
|
||||
QList<Result> m_results;
|
||||
|
||||
QDateTime m_startDateTime;
|
||||
|
||||
void checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo);
|
||||
void cleanupConnection(WebastoNextModbusTcpConnection *connection);
|
||||
|
||||
void finishDiscovery();
|
||||
};
|
||||
|
||||
#endif // WEBASTODISCOVERY_H
|
||||
Loading…
Reference in New Issue