New Plugin: Vestel
This commit is contained in:
parent
2d9d4aea42
commit
7d940e5ede
12
debian/control
vendored
12
debian/control
vendored
@ -258,6 +258,17 @@ Description: nymea integration plugin for PhoenixConnect wallboxes
|
||||
This package contains the nymea integration plugin for wallboxes made
|
||||
by PhonenixConnect and rebranded as Wallbe, Compleo and Scapo.
|
||||
|
||||
|
||||
Package: nymea-plugin-vestel
|
||||
Architecture: any
|
||||
Section: libs
|
||||
Depends: ${shlibs:Depends},
|
||||
${misc:Depends},
|
||||
Description: nymea integration plugin for Vestel wallboxes
|
||||
This package contains the nymea integration plugin for wallboxes made
|
||||
by Vestel.
|
||||
|
||||
|
||||
Package: nymea-plugin-wattsonic
|
||||
Architecture: any
|
||||
Section: libs
|
||||
@ -267,6 +278,7 @@ Description: nymea integration plugin for Wattsonic hybrid inverters
|
||||
This package contains the nymea integration plugin for hybrid inverters made
|
||||
by Wattsonic.
|
||||
|
||||
|
||||
Package: nymea-plugin-webasto
|
||||
Architecture: any
|
||||
Section: libs
|
||||
|
||||
5
debian/nymea-plugin-vestel.install.in
vendored
Normal file
5
debian/nymea-plugin-vestel.install.in
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginvestel.so
|
||||
vestel/translations/*qm usr/share/nymea/translations/
|
||||
|
||||
|
||||
|
||||
@ -146,6 +146,16 @@ QString ModbusDataUtils::convertToString(const QVector<quint16> ®isters, Byte
|
||||
return QString::fromUtf8(bytes).trimmed();
|
||||
}
|
||||
|
||||
QByteArray ModbusDataUtils::convertToByteArray(const QVector<quint16> ®isters)
|
||||
{
|
||||
QByteArray bytes;
|
||||
QDataStream stream(&bytes, QIODevice::WriteOnly);
|
||||
for (int i = 0; i < registers.count(); i++) {
|
||||
stream << registers.at(i);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
float ModbusDataUtils::convertToFloat32(const QVector<quint16> ®isters, ByteOrder byteOrder)
|
||||
{
|
||||
Q_ASSERT_X(registers.count() == 2, "ModbusDataUtils", "invalid raw data size for converting value to float32");
|
||||
|
||||
@ -90,6 +90,7 @@ public:
|
||||
static quint64 convertToUInt64(const QVector<quint16> ®isters, ByteOrder byteOrder = ByteOrderLittleEndian);
|
||||
static qint64 convertToInt64(const QVector<quint16> ®isters, ByteOrder byteOrder = ByteOrderLittleEndian);
|
||||
static QString convertToString(const QVector<quint16> ®isters, ByteOrder characterByteOrder = ByteOrderLittleEndian);
|
||||
static QByteArray convertToByteArray(const QVector<quint16> ®isters);
|
||||
static float convertToFloat32(const QVector<quint16> ®isters, ByteOrder byteOrder = ByteOrderLittleEndian);
|
||||
static double convertToFloat64(const QVector<quint16> ®isters, ByteOrder byteOrder = ByteOrderLittleEndian);
|
||||
|
||||
|
||||
@ -219,6 +219,12 @@ def getCppDataType(registerDefinition, rawType = False):
|
||||
if registerDefinition['type'] == 'string':
|
||||
return 'QString'
|
||||
|
||||
if registerDefinition['type'] == 'bytearray':
|
||||
return 'QByteArray'
|
||||
|
||||
if registerDefinition['type'] == 'raw':
|
||||
return 'QVector<quint16>'
|
||||
|
||||
|
||||
def getConversionToValueMethod(registerDefinition):
|
||||
# Handle enums
|
||||
@ -335,6 +341,10 @@ def getValueConversionMethod(registerDefinition):
|
||||
return ('ModbusDataUtils::convertToFloat64(values, m_endianness)')
|
||||
elif registerDefinition['type'] == 'string':
|
||||
return ('ModbusDataUtils::convertToString(values, m_stringEndianness)')
|
||||
elif registerDefinition['type'] == 'bytearray':
|
||||
return ('ModbusDataUtils::convertToByteArray(values)')
|
||||
elif registerDefinition['type'] == 'raw':
|
||||
return ('values')
|
||||
|
||||
|
||||
def writeBlockGetMethodDeclarations(fileDescriptor, registerDefinitions):
|
||||
|
||||
@ -25,6 +25,7 @@ PLUGIN_DIRS = \
|
||||
stiebeleltron \
|
||||
sunspec \
|
||||
unipi \
|
||||
vestel \
|
||||
wattsonic \
|
||||
webasto \
|
||||
|
||||
|
||||
9
vestel/README.md
Normal file
9
vestel/README.md
Normal file
@ -0,0 +1,9 @@
|
||||
# Vestel
|
||||
|
||||
Connects nymea to Vestel wallboxes. Currently supported models are:
|
||||
|
||||
* EVC04
|
||||
|
||||
# Requirements
|
||||
|
||||
nymea uses the Modbus TCP connection to connect to the wallbox. Please make sure that the wallbox is connected to the same network as the nymea system and that the Local Load Management Option is set to Modbus TCP in the wallboxes web interface.
|
||||
362
vestel/evc04-registers.json
Normal file
362
vestel/evc04-registers.json
Normal file
@ -0,0 +1,362 @@
|
||||
{
|
||||
"className": "EVC04",
|
||||
"protocol": "TCP",
|
||||
"endianness": "BigEndian",
|
||||
"errorLimitUntilNotReachable": 20,
|
||||
"checkReachableRegister": "chargepointState",
|
||||
"enums": [
|
||||
{
|
||||
"name": "ChargePointState",
|
||||
"values": [
|
||||
{
|
||||
"key": "Available",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"key": "Preparing",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"key": "Charging",
|
||||
"value": 2
|
||||
},
|
||||
{
|
||||
"key": "SuspendedEVSE",
|
||||
"value": 3
|
||||
},
|
||||
{
|
||||
"key": "SuspendedEV",
|
||||
"value": 4
|
||||
},
|
||||
{
|
||||
"key": "Finishing",
|
||||
"value": 5
|
||||
},
|
||||
{
|
||||
"key": "Reserved",
|
||||
"value": 6
|
||||
},
|
||||
{
|
||||
"key": "Unavailable",
|
||||
"value": 7
|
||||
},
|
||||
{
|
||||
"key": "Faulted",
|
||||
"value": 8
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ChargingState",
|
||||
"values": [
|
||||
{
|
||||
"key": "NotCharging",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"key": "Charging",
|
||||
"value": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "NumPhases",
|
||||
"values": [
|
||||
{
|
||||
"key": "1",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"key": "3",
|
||||
"value": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "CableState",
|
||||
"values": [
|
||||
{
|
||||
"key": "NotConnected",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"key": "CableConnectedVehicleNotConnected",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"key": "CableConnectedVehicleConnected",
|
||||
"value": 2
|
||||
},
|
||||
{
|
||||
"key": "CableConnectedVehicleConnectedCableLocked",
|
||||
"value": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"registers": [
|
||||
{
|
||||
"id": "serialNumber",
|
||||
"address": 100,
|
||||
"size": 25,
|
||||
"type": "raw",
|
||||
"readSchedule": "init",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Serial number",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "chargepointId",
|
||||
"address": 130,
|
||||
"size": 50,
|
||||
"type": "raw",
|
||||
"readSchedule": "init",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Chargepoint ID",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "brand",
|
||||
"address": 190,
|
||||
"size": 10,
|
||||
"type": "raw",
|
||||
"readSchedule": "init",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Brand",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "model",
|
||||
"address": 210,
|
||||
"size": 5,
|
||||
"type": "raw",
|
||||
"readSchedule": "init",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Model",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "firmwareVersion",
|
||||
"address": 230,
|
||||
"size": 50,
|
||||
"type": "raw",
|
||||
"readSchedule": "init",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Firmware version",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "time",
|
||||
"address": 294,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"readSchedule": "update",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Wallbox time",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "maxChargePointPower",
|
||||
"address": 400,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"readSchedule": "init",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Max power of Chargepoint",
|
||||
"unit": "W",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "numPhases",
|
||||
"address": 404,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"readSchedule": "update",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Number of Phases",
|
||||
"enum": "NumPhases",
|
||||
"defaultValue": "NumPhases1",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "chargepointState",
|
||||
"address": 1000,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"readSchedule": "update",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Chargepoint State",
|
||||
"enum": "ChargePointState",
|
||||
"defaultValue": "ChargePointStateAvailable",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "chargingState",
|
||||
"address": 1001,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"readSchedule": "update",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Charging State",
|
||||
"enum": "ChargingState",
|
||||
"defaultValue": "ChargingStateNotCharging",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "cableState",
|
||||
"address": 1004,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"readSchedule": "update",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Cable state",
|
||||
"enum": "CableState",
|
||||
"defaultValue": "CableStateNotConnected",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "activePowerTotal",
|
||||
"address": 1020,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"readSchedule": "update",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Active Power Total",
|
||||
"unit": "W",
|
||||
"defaultValue": 0,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "meterReading",
|
||||
"address": 1036,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"readSchedule": "update",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Meter reading",
|
||||
"unit": "1/10 kWh",
|
||||
"defaultValue": "ChargingStateNotCharging",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "sessionMaxCurrent",
|
||||
"address": 1100,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"readSchedule": "update",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Session max current",
|
||||
"unit": "A",
|
||||
"defaultValue": 0,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "evseMinCurrent",
|
||||
"address": 1102,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"readSchedule": "update",
|
||||
"registerType": "inputRegister",
|
||||
"description": "EVSE min current",
|
||||
"unit": "A",
|
||||
"defaultValue": 6,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "evseMaxCurrent",
|
||||
"address": 1104,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"readSchedule": "update",
|
||||
"registerType": "inputRegister",
|
||||
"description": "EVSE max current",
|
||||
"unit": "A",
|
||||
"defaultValue": 32,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "cableMaxCurrent",
|
||||
"address": 1106,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"readSchedule": "update",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Cable max current",
|
||||
"unit": "A",
|
||||
"defaultValue": 32,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "sessionEnergy",
|
||||
"address": 1502,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"readSchedule": "update",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Session energy",
|
||||
"unit": "Wh",
|
||||
"defaultValue": 0,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "sessionDuration",
|
||||
"address": 1508,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"readSchedule": "update",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Session duration",
|
||||
"unit": "s",
|
||||
"defaultValue": 0,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "failsafeCurrent",
|
||||
"address": 2000,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"readSchedule": "update",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Failsafe current",
|
||||
"unit": "A",
|
||||
"defaultValue": 6,
|
||||
"access": "RW"
|
||||
},
|
||||
{
|
||||
"id": "failsafeTimeout",
|
||||
"address": 2002,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"readSchedule": "update",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Failsafe timeout",
|
||||
"unit": "s",
|
||||
"defaultValue": 20,
|
||||
"access": "RW"
|
||||
},
|
||||
{
|
||||
"id": "chargingCurrent",
|
||||
"address": 5004,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"readSchedule": "update",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Dynamic charging current",
|
||||
"unit": "A",
|
||||
"defaultValue": 6,
|
||||
"access": "RW"
|
||||
},
|
||||
{
|
||||
"id": "aliveRegister",
|
||||
"address": 6000,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"readSchedule": "update",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Alive register",
|
||||
"defaultValue": 0,
|
||||
"access": "RW"
|
||||
}
|
||||
]
|
||||
}
|
||||
136
vestel/evc04discovery.cpp
Normal file
136
vestel/evc04discovery.cpp
Normal file
@ -0,0 +1,136 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* 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 "evc04discovery.h"
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
EVC04Discovery::EVC04Discovery(NetworkDeviceDiscovery *networkDeviceDiscovery, const QLoggingCategory &dc, QObject *parent) :
|
||||
QObject{parent},
|
||||
m_networkDeviceDiscovery{networkDeviceDiscovery},
|
||||
m_dc{dc.categoryName()}
|
||||
{
|
||||
m_gracePeriodTimer.setSingleShot(true);
|
||||
m_gracePeriodTimer.setInterval(3000);
|
||||
connect(&m_gracePeriodTimer, &QTimer::timeout, this, [this](){
|
||||
qCDebug(m_dc) << "Discovery: Grace period timer triggered.";
|
||||
finishDiscovery();
|
||||
});
|
||||
}
|
||||
|
||||
void EVC04Discovery::startDiscovery()
|
||||
{
|
||||
qCInfo(m_dc()) << "Discovery: Searching for Vestel EVC04 wallboxes in the network...";
|
||||
NetworkDeviceDiscoveryReply *discoveryReply = m_networkDeviceDiscovery->discover();
|
||||
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, &EVC04Discovery::checkNetworkDevice);
|
||||
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){
|
||||
qCDebug(m_dc()) << "Discovery: Network discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "network devices";
|
||||
m_gracePeriodTimer.start();
|
||||
discoveryReply->deleteLater();
|
||||
});
|
||||
}
|
||||
|
||||
QList<EVC04Discovery::Result> EVC04Discovery::discoveryResults() const
|
||||
{
|
||||
return m_discoveryResults;
|
||||
}
|
||||
|
||||
void EVC04Discovery::checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo)
|
||||
{
|
||||
int port = 502;
|
||||
int slaveId = 0xff;
|
||||
qCDebug(m_dc()) << "Checking network device:" << networkDeviceInfo << "Port:" << port << "Slave ID:" << slaveId;
|
||||
|
||||
EVC04ModbusTcpConnection *connection = new EVC04ModbusTcpConnection(networkDeviceInfo.address(), port, slaveId, this);
|
||||
m_connections.append(connection);
|
||||
|
||||
connect(connection, &EVC04ModbusTcpConnection::reachableChanged, this, [=](bool reachable){
|
||||
if (!reachable) {
|
||||
cleanupConnection(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
connect(connection, &EVC04ModbusTcpConnection::initializationFinished, this, [=](bool success){
|
||||
qCDebug(m_dc()) << "Discovered device on" << networkDeviceInfo.address() << connection->brand() << connection->model() << connection->firmwareVersion();
|
||||
qCDebug(m_dc()) << connection;
|
||||
if (!success) {
|
||||
qCDebug(m_dc()) << "Discovery: Initialization failed on" << networkDeviceInfo.address().toString();
|
||||
cleanupConnection(connection);
|
||||
return;
|
||||
}
|
||||
Result result;
|
||||
result.chargepointId = QString(QString::fromUtf16(connection->chargepointId().data(), connection->chargepointId().length()).toUtf8()).trimmed();
|
||||
result.brand = QString(QString::fromUtf16(connection->brand().data(), connection->brand().length()).toUtf8()).trimmed();
|
||||
result.model = QString(QString::fromUtf16(connection->model().data(), connection->model().length()).toUtf8()).trimmed();
|
||||
result.firmwareVersion = QString(QString::fromUtf16(connection->firmwareVersion().data(), connection->firmwareVersion().length()).toUtf8()).trimmed();
|
||||
result.networkDeviceInfo = networkDeviceInfo;
|
||||
m_discoveryResults.append(result);
|
||||
|
||||
qCDebug(m_dc()) << "Discovery: Found wallbox with firmware version:" << result.firmwareVersion << result.networkDeviceInfo;
|
||||
|
||||
cleanupConnection(connection);
|
||||
});
|
||||
|
||||
if (!connection->initialize()) {
|
||||
qCDebug(m_dc()) << "Discovery: Unable to initialize connection on" << networkDeviceInfo.address().toString();
|
||||
cleanupConnection(connection);
|
||||
}
|
||||
});
|
||||
|
||||
connect(connection, &EVC04ModbusTcpConnection::checkReachabilityFailed, this, [=](){
|
||||
qCDebug(m_dc()) << "Discovery: Checking reachability failed on" << networkDeviceInfo.address().toString();
|
||||
cleanupConnection(connection);
|
||||
});
|
||||
|
||||
connection->connectDevice();
|
||||
}
|
||||
|
||||
void EVC04Discovery::cleanupConnection(EVC04ModbusTcpConnection *connection)
|
||||
{
|
||||
m_connections.removeAll(connection);
|
||||
connection->disconnectDevice();
|
||||
connection->deleteLater();
|
||||
}
|
||||
|
||||
void EVC04Discovery::finishDiscovery()
|
||||
{
|
||||
qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startDateTime.toMSecsSinceEpoch();
|
||||
|
||||
// Cleanup any leftovers...we don't care any more
|
||||
foreach (EVC04ModbusTcpConnection *connection, m_connections)
|
||||
cleanupConnection(connection);
|
||||
|
||||
qCInfo(m_dc()) << "Discovery: Finished the discovery process. Found" << m_discoveryResults.count()
|
||||
<< "Vestel EVC04 wallboxes in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz");
|
||||
m_gracePeriodTimer.stop();
|
||||
|
||||
emit discoveryFinished();
|
||||
}
|
||||
79
vestel/evc04discovery.h
Normal file
79
vestel/evc04discovery.h
Normal file
@ -0,0 +1,79 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2022, 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 AMTRONECUDISCOVERY_H
|
||||
#define AMTRONECUDISCOVERY_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
#include <network/networkdevicediscovery.h>
|
||||
|
||||
#include "evc04modbustcpconnection.h"
|
||||
|
||||
class EVC04Discovery : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit EVC04Discovery(NetworkDeviceDiscovery *networkDeviceDiscovery, const QLoggingCategory &dc, QObject *parent = nullptr);
|
||||
struct Result {
|
||||
QString chargepointId;
|
||||
QString firmwareVersion;
|
||||
QString brand;
|
||||
QString model;
|
||||
NetworkDeviceInfo networkDeviceInfo;
|
||||
};
|
||||
|
||||
void startDiscovery();
|
||||
|
||||
QList<Result> discoveryResults() const;
|
||||
|
||||
signals:
|
||||
void discoveryFinished();
|
||||
|
||||
private:
|
||||
NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr;
|
||||
|
||||
QLoggingCategory m_dc;
|
||||
QTimer m_gracePeriodTimer;
|
||||
QDateTime m_startDateTime;
|
||||
|
||||
QList<EVC04ModbusTcpConnection*> m_connections;
|
||||
|
||||
QList<Result> m_discoveryResults;
|
||||
|
||||
void checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo);
|
||||
void cleanupConnection(EVC04ModbusTcpConnection *connection);
|
||||
|
||||
void finishDiscovery();
|
||||
};
|
||||
|
||||
#endif // AMTRONECUDISCOVERY_H
|
||||
405
vestel/integrationpluginvestel.cpp
Normal file
405
vestel/integrationpluginvestel.cpp
Normal file
@ -0,0 +1,405 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2022, 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 "integrationpluginvestel.h"
|
||||
#include "plugininfo.h"
|
||||
#include "evc04discovery.h"
|
||||
|
||||
#include <network/networkdevicediscovery.h>
|
||||
#include <hardwaremanager.h>
|
||||
|
||||
IntegrationPluginVestel::IntegrationPluginVestel()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void IntegrationPluginVestel::discoverThings(ThingDiscoveryInfo *info)
|
||||
{
|
||||
if (!hardwareManager()->networkDeviceDiscovery()->available()) {
|
||||
qCWarning(dcVestel()) << "The network discovery is not available on this platform.";
|
||||
info->finish(Thing::ThingErrorUnsupportedFeature, QT_TR_NOOP("Unable to discover devices in the network. The system may not be installed correctly."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (info->thingClassId() == evc04ThingClassId) {
|
||||
EVC04Discovery *discovery = new EVC04Discovery(hardwareManager()->networkDeviceDiscovery(), dcVestel(), info);
|
||||
connect(discovery, &EVC04Discovery::discoveryFinished, info, [=](){
|
||||
foreach (const EVC04Discovery::Result &result, discovery->discoveryResults()) {
|
||||
|
||||
QString name = result.chargepointId;
|
||||
QString description = result.brand + " " + result.model;
|
||||
ThingDescriptor descriptor(evc04ThingClassId, name, description);
|
||||
qCDebug(dcVestel()) << "Discovered:" << descriptor.title() << descriptor.description();
|
||||
|
||||
// Check if we already have set up this device
|
||||
Things existingThings = myThings().filterByParam(evc04ThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress());
|
||||
if (existingThings.count() == 1) {
|
||||
qCDebug(dcVestel()) << "This wallbox already exists in the system:" << result.networkDeviceInfo;
|
||||
descriptor.setThingId(existingThings.first()->id());
|
||||
}
|
||||
|
||||
ParamList params;
|
||||
params << Param(evc04ThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress());
|
||||
descriptor.setParams(params);
|
||||
info->addThingDescriptor(descriptor);
|
||||
}
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
});
|
||||
discovery->startDiscovery();
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginVestel::setupThing(ThingSetupInfo *info)
|
||||
{
|
||||
Thing *thing = info->thing();
|
||||
qCDebug(dcVestel()) << "Setup" << thing << thing->params();
|
||||
|
||||
if (thing->thingClassId() == evc04ThingClassId) {
|
||||
|
||||
if (m_evc04Connections.contains(thing)) {
|
||||
qCDebug(dcVestel()) << "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(evc04ThingMacAddressParamTypeId).toString());
|
||||
if (!macAddress.isValid()) {
|
||||
qCWarning(dcVestel()) << "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(dcVestel()) << "Unregistering monitor because setup has been aborted.";
|
||||
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
|
||||
}
|
||||
});
|
||||
|
||||
if (monitor->reachable()) {
|
||||
setupEVC04Connection(info);
|
||||
} else {
|
||||
qCDebug(dcVestel()) << "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(dcVestel()) << "The monitor for thing setup" << thing->name() << "is now reachable. Continuing setup on" << monitor->networkDeviceInfo().address().toString();
|
||||
setupEVC04Connection(info);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginVestel::postSetupThing(Thing *thing)
|
||||
{
|
||||
Q_UNUSED(thing)
|
||||
if (!m_pluginTimer) {
|
||||
qCDebug(dcVestel()) << "Starting plugin timer...";
|
||||
m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(2);
|
||||
connect(m_pluginTimer, &PluginTimer::timeout, this, [this] {
|
||||
foreach(EVC04ModbusTcpConnection *connection, m_evc04Connections) {
|
||||
qCDebug(dcVestel()) << "Updating connection" << connection->modbusTcpMaster()->hostAddress().toString();
|
||||
connection->update();
|
||||
connection->setAliveRegister(1);
|
||||
}
|
||||
});
|
||||
|
||||
m_pluginTimer->start();
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginVestel::executeAction(ThingActionInfo *info)
|
||||
{
|
||||
if (info->thing()->thingClassId() == evc04ThingClassId) {
|
||||
EVC04ModbusTcpConnection *evc04Connection = m_evc04Connections.value(info->thing());
|
||||
|
||||
if (info->action().actionTypeId() == evc04PowerActionTypeId) {
|
||||
bool power = info->action().paramValue(evc04PowerActionPowerParamTypeId).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(evc04PowerStateTypeId, false);
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
return;
|
||||
}
|
||||
|
||||
QModbusReply *reply = evc04Connection->setChargingCurrent(power ? info->thing()->stateValue(evc04MaxChargingCurrentStateTypeId).toUInt() : 0);
|
||||
connect(reply, &QModbusReply::finished, info, [info, reply, power](){
|
||||
if (reply->error() == QModbusDevice::NoError) {
|
||||
info->thing()->setStateValue(evc04PowerStateTypeId, power);
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
} else {
|
||||
qCWarning(dcVestel()) << "Error setting power:" << reply->error() << reply->errorString();
|
||||
info->finish(Thing::ThingErrorHardwareFailure);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (info->action().actionTypeId() == evc04MaxChargingCurrentActionTypeId) {
|
||||
int maxChargingCurrent = info->action().paramValue(evc04MaxChargingCurrentActionMaxChargingCurrentParamTypeId).toInt();
|
||||
QModbusReply *reply = evc04Connection->setChargingCurrent(maxChargingCurrent);
|
||||
connect(reply, &QModbusReply::finished, info, [info, reply, maxChargingCurrent](){
|
||||
if (reply->error() == QModbusDevice::NoError) {
|
||||
info->thing()->setStateValue(evc04MaxChargingCurrentStateTypeId, maxChargingCurrent);
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
} else {
|
||||
info->finish(Thing::ThingErrorHardwareFailure);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginVestel::thingRemoved(Thing *thing)
|
||||
{
|
||||
if (thing->thingClassId() == evc04ThingClassId && m_evc04Connections.contains(thing)) {
|
||||
EVC04ModbusTcpConnection *connection = m_evc04Connections.take(thing);
|
||||
delete connection;
|
||||
}
|
||||
|
||||
// Unregister related hardware resources
|
||||
if (m_monitors.contains(thing))
|
||||
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
|
||||
|
||||
if (myThings().isEmpty() && m_pluginTimer) {
|
||||
hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer);
|
||||
m_pluginTimer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginVestel::setupEVC04Connection(ThingSetupInfo *info)
|
||||
{
|
||||
Thing *thing = info->thing();
|
||||
|
||||
QHostAddress address = m_monitors.value(thing)->networkDeviceInfo().address();
|
||||
|
||||
qCDebug(dcVestel()) << "Setting up EVC04 wallbox on" << address.toString();
|
||||
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(dcVestel()) << "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(dcVestel()) << "Reachable changed to" << reachable << "for" << thing;
|
||||
if (reachable) {
|
||||
evc04Connection->initialize();
|
||||
} else {
|
||||
thing->setStateValue(evc04ConnectedStateTypeId, false);
|
||||
thing->setStateValue(evc04CurrentPowerStateTypeId, 0);
|
||||
}
|
||||
});
|
||||
|
||||
connect(evc04Connection, &EVC04ModbusTcpConnection::initializationFinished, thing, [=](bool success){
|
||||
if (!thing->setupComplete())
|
||||
return;
|
||||
|
||||
if (success) {
|
||||
thing->setStateValue(evc04ConnectedStateTypeId, true);
|
||||
} else {
|
||||
thing->setStateValue(evc04ConnectedStateTypeId, false);
|
||||
thing->setStateValue(evc04CurrentPowerStateTypeId, 0);
|
||||
|
||||
// Try once to reconnect the device
|
||||
evc04Connection->reconnectDevice();
|
||||
}
|
||||
});
|
||||
|
||||
connect(evc04Connection, &EVC04ModbusTcpConnection::initializationFinished, info, [=](bool success){
|
||||
if (!success) {
|
||||
qCWarning(dcVestel()) << "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(dcVestel()) << "Connection init finished successfully" << evc04Connection;
|
||||
|
||||
m_evc04Connections.insert(thing, evc04Connection);
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
|
||||
thing->setStateValue(evc04ConnectedStateTypeId, true);
|
||||
thing->setStateValue(evc04VersionStateTypeId, QString(QString::fromUtf16(evc04Connection->firmwareVersion().data(), evc04Connection->firmwareVersion().length()).toUtf8()).trimmed());
|
||||
|
||||
evc04Connection->update();
|
||||
});
|
||||
|
||||
connect(evc04Connection, &EVC04ModbusTcpConnection::updateFinished, thing, [this, evc04Connection, thing](){
|
||||
qCDebug(dcVestel()) << "EVC04 update finished:" << thing->name() << evc04Connection;
|
||||
|
||||
qCDebug(dcVestel()) << "Serial:" << QString(QString::fromUtf16(evc04Connection->serialNumber().data(), evc04Connection->serialNumber().length()).toUtf8()).trimmed();
|
||||
qCDebug(dcVestel()) << "ChargePoint ID:" << QString(QString::fromUtf16(evc04Connection->chargepointId().data(), evc04Connection->chargepointId().length()).toUtf8()).trimmed();
|
||||
qCDebug(dcVestel()) << "Brand:" << QString(QString::fromUtf16(evc04Connection->brand().data(), evc04Connection->brand().length()).toUtf8()).trimmed();
|
||||
qCDebug(dcVestel()) << "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[thing] == evc04Connection->time()) {
|
||||
qCWarning(dcVestel()) << "Wallbox seems stuck and returning outdated values. Reconnecting...";
|
||||
evc04Connection->reconnectDevice();
|
||||
}
|
||||
m_lastWallboxTime[thing] = evc04Connection->time();
|
||||
});
|
||||
|
||||
connect(evc04Connection, &EVC04ModbusTcpConnection::chargepointStateChanged, thing, [thing](EVC04ModbusTcpConnection::ChargePointState chargePointState) {
|
||||
qCDebug(dcVestel()) << "Chargepoint state changed" << 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(dcVestel()) << "Charging state changed:" << chargingState;
|
||||
thing->setStateValue(evc04ChargingStateTypeId, chargingState == EVC04ModbusTcpConnection::ChargingStateCharging);
|
||||
});
|
||||
connect(evc04Connection, &EVC04ModbusTcpConnection::activePowerTotalChanged, thing, [thing](quint16 activePowerTotal) {
|
||||
qCDebug(dcVestel()) << "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(evc04ChargingStateTypeId).toBool() == true) {
|
||||
thing->setStateValue(evc04CurrentPowerStateTypeId, activePowerTotal);
|
||||
} else {
|
||||
thing->setStateValue(evc04CurrentPowerStateTypeId, 0);
|
||||
}
|
||||
});
|
||||
connect(evc04Connection, &EVC04ModbusTcpConnection::meterReadingChanged, thing, [thing](quint32 meterReading) {
|
||||
qCDebug(dcVestel()) << "Meter reading changed:" << meterReading;
|
||||
thing->setStateValue(evc04TotalEnergyConsumedStateTypeId, 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(dcVestel()) << "Session max current changed:" << sessionMaxCurrent;
|
||||
});
|
||||
connect(evc04Connection, &EVC04ModbusTcpConnection::cableMaxCurrentChanged, thing, [this, thing](quint16 cableMaxCurrent) {
|
||||
qCDebug(dcVestel()) << "Cable max current changed:" << cableMaxCurrent;
|
||||
updateEVC04MaxCurrent(thing);
|
||||
});
|
||||
connect(evc04Connection, &EVC04ModbusTcpConnection::evseMinCurrentChanged, thing, [thing](quint16 evseMinCurrent) {
|
||||
qCDebug(dcVestel()) << "EVSE min current changed:" << evseMinCurrent;
|
||||
thing->setStateMinValue(evc04MaxChargingCurrentStateTypeId, evseMinCurrent);
|
||||
});
|
||||
connect(evc04Connection, &EVC04ModbusTcpConnection::evseMaxCurrentChanged, thing, [this, thing](quint16 evseMaxCurrent) {
|
||||
qCDebug(dcVestel()) << "EVSE max current changed:" << evseMaxCurrent;
|
||||
updateEVC04MaxCurrent(thing);
|
||||
});
|
||||
connect(evc04Connection, &EVC04ModbusTcpConnection::sessionEnergyChanged, thing, [thing](quint32 sessionEnergy) {
|
||||
qCDebug(dcVestel()) << "Session energy changed:" << sessionEnergy;
|
||||
thing->setStateValue(evc04SessionEnergyStateTypeId, sessionEnergy / 1000.0);
|
||||
});
|
||||
connect(evc04Connection, &EVC04ModbusTcpConnection::chargingCurrentChanged, thing, [thing](quint16 chargingCurrent) {
|
||||
qCDebug(dcVestel()) << "Charging current changed:" << chargingCurrent;
|
||||
if (chargingCurrent > 0) {
|
||||
thing->setStateValue(evc04PowerStateTypeId, true);
|
||||
thing->setStateValue(evc04MaxChargingCurrentStateTypeId, chargingCurrent);
|
||||
} else {
|
||||
thing->setStateValue(evc04PowerStateTypeId, false);
|
||||
}
|
||||
});
|
||||
connect(evc04Connection, &EVC04ModbusTcpConnection::numPhasesChanged, thing, [thing](EVC04ModbusTcpConnection::NumPhases numPhases) {
|
||||
switch (numPhases) {
|
||||
case EVC04ModbusTcpConnection::NumPhases1:
|
||||
thing->setStateValue(evc04PhaseCountStateTypeId, 1);
|
||||
break;
|
||||
case EVC04ModbusTcpConnection::NumPhases3:
|
||||
thing->setStateValue(evc04PhaseCountStateTypeId, 3);
|
||||
break;
|
||||
}
|
||||
});
|
||||
connect(evc04Connection, &EVC04ModbusTcpConnection::cableStateChanged, thing, [evc04Connection, thing](EVC04ModbusTcpConnection::CableState cableState) {
|
||||
switch (cableState) {
|
||||
case EVC04ModbusTcpConnection::CableStateNotConnected:
|
||||
case EVC04ModbusTcpConnection::CableStateCableConnectedVehicleNotConnected:
|
||||
thing->setStateValue(evc04PluggedInStateTypeId, false);
|
||||
break;
|
||||
case EVC04ModbusTcpConnection::CableStateCableConnectedVehicleConnected:
|
||||
case EVC04ModbusTcpConnection::CableStateCableConnectedVehicleConnectedCableLocked:
|
||||
thing->setStateValue(evc04PluggedInStateTypeId, 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(evc04PowerStateTypeId).toBool() == false) {
|
||||
qCInfo(dcVestel()) << "Car plugged in. Syncing cached power off state to wallbox";
|
||||
evc04Connection->setChargingCurrent(0);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
evc04Connection->connectDevice();
|
||||
}
|
||||
|
||||
void IntegrationPluginVestel::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(dcVestel()) << "Adjusting max current: Wallbox max:" << wallboxMax << "EVSE max:" << evseMax << "cable max:" << cableMax << "Overall:" << overallMax;
|
||||
thing->setStateMinMaxValues(evc04MaxChargingCurrentStateTypeId, 6, overallMax);
|
||||
}
|
||||
72
vestel/integrationpluginvestel.h
Normal file
72
vestel/integrationpluginvestel.h
Normal file
@ -0,0 +1,72 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* 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 INTEGRATIONPLUGINVESTEL_H
|
||||
#define INTEGRATIONPLUGINVESTEL_H
|
||||
|
||||
#include <plugintimer.h>
|
||||
#include <integrations/integrationplugin.h>
|
||||
#include <network/networkdevicemonitor.h>
|
||||
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
#include "evc04modbustcpconnection.h"
|
||||
|
||||
class IntegrationPluginVestel: public IntegrationPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginvestel.json")
|
||||
Q_INTERFACES(IntegrationPlugin)
|
||||
|
||||
public:
|
||||
explicit IntegrationPluginVestel();
|
||||
|
||||
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;
|
||||
|
||||
private:
|
||||
void setupEVC04Connection(ThingSetupInfo *info);
|
||||
|
||||
void updateEVC04MaxCurrent(Thing *thing);
|
||||
|
||||
PluginTimer *m_pluginTimer = nullptr;
|
||||
QHash<Thing *, EVC04ModbusTcpConnection *> m_evc04Connections;
|
||||
QHash<Thing *, NetworkDeviceMonitor *> m_monitors;
|
||||
|
||||
QHash<Thing *, quint32> m_lastWallboxTime;
|
||||
|
||||
};
|
||||
|
||||
#endif // INTEGRATIONPLUGINVESTEL_H
|
||||
|
||||
120
vestel/integrationpluginvestel.json
Normal file
120
vestel/integrationpluginvestel.json
Normal file
@ -0,0 +1,120 @@
|
||||
{
|
||||
"name": "vestel",
|
||||
"displayName": "Vestel",
|
||||
"id": "d7d8c2d5-e85d-4a9b-9113-61b43d8bf8ea",
|
||||
"vendors": [
|
||||
{
|
||||
"name": "vestel",
|
||||
"displayName": "Vestel",
|
||||
"id": "9a8441f9-f71a-4e3d-a2a0-2f912fa6f047",
|
||||
"thingClasses": [
|
||||
{
|
||||
"name": "evc04",
|
||||
"displayName": "EVC04",
|
||||
"id": "396de19c-ef2b-4f32-b551-9d0d153304a4",
|
||||
"createMethods": ["discovery", "user"],
|
||||
"interfaces": ["evcharger", "smartmeterconsumer", "connectable"],
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "afdc55b7-b509-4d0c-ab79-176f2edf8488",
|
||||
"name":"macAddress",
|
||||
"displayName": "MAC address",
|
||||
"type": "QString",
|
||||
"inputType": "MacAddress",
|
||||
"defaultValue": ""
|
||||
}
|
||||
],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "ababb315-4c90-4d7b-8336-de0f22630c32",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "cef29b4b-11e5-4342-9fae-0ac90a70082a",
|
||||
"name": "pluggedIn",
|
||||
"displayName": "Plugged in",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "c1be88ee-776f-4d05-8dba-ca8086181086",
|
||||
"name": "charging",
|
||||
"displayName": "Charging",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "73a165bc-23b9-487e-9758-505bcc6cf10c",
|
||||
"name": "phaseCount",
|
||||
"displayName": "Connected phases",
|
||||
"type": "uint",
|
||||
"minValue": 1,
|
||||
"maxValue": 3,
|
||||
"defaultValue": 1
|
||||
},
|
||||
{
|
||||
"id": "4646932a-a1f4-4b21-919e-a8c393f4e801",
|
||||
"name": "currentPower",
|
||||
"displayName": "Active power",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "dc6eea53-0ae1-4786-b1c2-f1d5a14be9b0",
|
||||
"name": "totalEnergyConsumed",
|
||||
"displayName": "Total consumed energy",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0.0,
|
||||
"cached": true
|
||||
},
|
||||
{
|
||||
"id": "639f28f3-4956-4d7c-9d72-cbbe8eda0d7a",
|
||||
"name": "power",
|
||||
"displayName": "Charging enabled",
|
||||
"displayNameAction": "Set charging enabled",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"id": "7f619704-36d8-4898-9625-484f96949e1c",
|
||||
"name": "maxChargingCurrent",
|
||||
"displayName": "Maximum charging current",
|
||||
"displayNameAction": "Set maximum charging current",
|
||||
"type": "uint",
|
||||
"unit": "Ampere",
|
||||
"minValue": 6,
|
||||
"maxValue": 32,
|
||||
"defaultValue": 6,
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"id": "b37996f6-5721-419e-b6cc-83a21e945a08",
|
||||
"name": "sessionEnergy",
|
||||
"displayName": "Session energy",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "0b4d26c3-558a-4b75-92c8-a2b07606e762",
|
||||
"name": "version",
|
||||
"displayName": "Firmware version",
|
||||
"type": "QString",
|
||||
"defaultValue": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
13
vestel/meta.json
Normal file
13
vestel/meta.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"title": "Bestel",
|
||||
"tagline": "Connect Vestel wallboxes to nymea.",
|
||||
"icon": "vestel.png",
|
||||
"stability": "consumer",
|
||||
"offline": true,
|
||||
"technologies": [
|
||||
"network"
|
||||
],
|
||||
"categories": [
|
||||
"energy"
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,123 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1">
|
||||
<context>
|
||||
<name>IntegrationPluginVestel</name>
|
||||
<message>
|
||||
<location filename="../integrationpluginvestel.cpp" line="47"/>
|
||||
<source>Unable to discover devices in the network. The system may not be installed correctly.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../integrationpluginvestel.cpp" line="100"/>
|
||||
<source>The MAC address is not known. Please reconfigure the thing.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../integrationpluginvestel.cpp" line="111"/>
|
||||
<source>The host address is not known yet. Trying later again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../integrationpluginvestel.cpp" line="294"/>
|
||||
<source>Error communicating with the wallbox.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>vestel</name>
|
||||
<message>
|
||||
<location filename="../../../build/nymea-plugins-modbus-Desktop-Debug/vestel/plugininfo.h" line="42"/>
|
||||
<source>Active power</source>
|
||||
<extracomment>The name of the StateType ({4646932a-a1f4-4b21-919e-a8c393f4e801}) of ThingClass evc04</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build/nymea-plugins-modbus-Desktop-Debug/vestel/plugininfo.h" line="45"/>
|
||||
<source>Charging</source>
|
||||
<extracomment>The name of the StateType ({c1be88ee-776f-4d05-8dba-ca8086181086}) of ThingClass evc04</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build/nymea-plugins-modbus-Desktop-Debug/vestel/plugininfo.h" line="48"/>
|
||||
<location filename="../../../build/nymea-plugins-modbus-Desktop-Debug/vestel/plugininfo.h" line="51"/>
|
||||
<source>Charging enabled</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: evc04, ActionType: power, ID: {639f28f3-4956-4d7c-9d72-cbbe8eda0d7a})
|
||||
----------
|
||||
The name of the StateType ({639f28f3-4956-4d7c-9d72-cbbe8eda0d7a}) of ThingClass evc04</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build/nymea-plugins-modbus-Desktop-Debug/vestel/plugininfo.h" line="54"/>
|
||||
<source>Connected</source>
|
||||
<extracomment>The name of the StateType ({ababb315-4c90-4d7b-8336-de0f22630c32}) of ThingClass evc04</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build/nymea-plugins-modbus-Desktop-Debug/vestel/plugininfo.h" line="57"/>
|
||||
<source>Connected phases</source>
|
||||
<extracomment>The name of the StateType ({73a165bc-23b9-487e-9758-505bcc6cf10c}) of ThingClass evc04</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build/nymea-plugins-modbus-Desktop-Debug/vestel/plugininfo.h" line="60"/>
|
||||
<source>EVC04</source>
|
||||
<extracomment>The name of the ThingClass ({396de19c-ef2b-4f32-b551-9d0d153304a4})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build/nymea-plugins-modbus-Desktop-Debug/vestel/plugininfo.h" line="63"/>
|
||||
<source>MAC address</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: evc04, Type: thing, ID: {afdc55b7-b509-4d0c-ab79-176f2edf8488})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build/nymea-plugins-modbus-Desktop-Debug/vestel/plugininfo.h" line="66"/>
|
||||
<location filename="../../../build/nymea-plugins-modbus-Desktop-Debug/vestel/plugininfo.h" line="69"/>
|
||||
<source>Maximum charging current</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: evc04, ActionType: maxChargingCurrent, ID: {7f619704-36d8-4898-9625-484f96949e1c})
|
||||
----------
|
||||
The name of the StateType ({7f619704-36d8-4898-9625-484f96949e1c}) of ThingClass evc04</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build/nymea-plugins-modbus-Desktop-Debug/vestel/plugininfo.h" line="72"/>
|
||||
<source>Plugged in</source>
|
||||
<extracomment>The name of the StateType ({cef29b4b-11e5-4342-9fae-0ac90a70082a}) of ThingClass evc04</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build/nymea-plugins-modbus-Desktop-Debug/vestel/plugininfo.h" line="75"/>
|
||||
<source>Session energy</source>
|
||||
<extracomment>The name of the StateType ({b37996f6-5721-419e-b6cc-83a21e945a08}) of ThingClass evc04</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build/nymea-plugins-modbus-Desktop-Debug/vestel/plugininfo.h" line="78"/>
|
||||
<source>Set charging enabled</source>
|
||||
<extracomment>The name of the ActionType ({639f28f3-4956-4d7c-9d72-cbbe8eda0d7a}) of ThingClass evc04</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build/nymea-plugins-modbus-Desktop-Debug/vestel/plugininfo.h" line="81"/>
|
||||
<source>Set maximum charging current</source>
|
||||
<extracomment>The name of the ActionType ({7f619704-36d8-4898-9625-484f96949e1c}) of ThingClass evc04</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build/nymea-plugins-modbus-Desktop-Debug/vestel/plugininfo.h" line="84"/>
|
||||
<source>Total consumed energy</source>
|
||||
<extracomment>The name of the StateType ({dc6eea53-0ae1-4786-b1c2-f1d5a14be9b0}) of ThingClass evc04</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build/nymea-plugins-modbus-Desktop-Debug/vestel/plugininfo.h" line="87"/>
|
||||
<location filename="../../../build/nymea-plugins-modbus-Desktop-Debug/vestel/plugininfo.h" line="90"/>
|
||||
<source>Vestel</source>
|
||||
<extracomment>The name of the vendor ({9a8441f9-f71a-4e3d-a2a0-2f912fa6f047})
|
||||
----------
|
||||
The name of the plugin vestel ({d7d8c2d5-e85d-4a9b-9113-61b43d8bf8ea})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
BIN
vestel/vestel.png
Normal file
BIN
vestel/vestel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
15
vestel/vestel.pro
Normal file
15
vestel/vestel.pro
Normal file
@ -0,0 +1,15 @@
|
||||
include(../plugins.pri)
|
||||
|
||||
# Generate modbus connection
|
||||
MODBUS_CONNECTIONS += evc04-registers.json
|
||||
|
||||
#MODBUS_TOOLS_CONFIG += VERBOSE
|
||||
include(../modbus.pri)
|
||||
|
||||
HEADERS += \
|
||||
evc04discovery.h \
|
||||
integrationpluginvestel.h
|
||||
|
||||
SOURCES += \
|
||||
evc04discovery.cpp \
|
||||
integrationpluginvestel.cpp
|
||||
Reference in New Issue
Block a user