Mennekes: Add support for Mennekes ECU with firmware older than 5.22

This commit is contained in:
Simon Stürz 2023-11-10 17:05:24 +01:00
parent 88674987f3
commit de66bf8319
12 changed files with 903 additions and 127 deletions

View File

@ -165,8 +165,7 @@ def writeTcpHeaderFile():
writeLine(headerFile, ' void testReachability();')
writeLine(headerFile)
# Private members
writeLine(headerFile, 'private:')
writeLine(headerFile, ' /* Internals */')
writeLine(headerFile, ' ModbusTcpMaster *m_modbusTcpMaster = nullptr;')
writeLine(headerFile, ' ModbusDataUtils::ByteOrder m_endianness = ModbusDataUtils::ByteOrder%s;' % endianness)
writeLine(headerFile, ' ModbusDataUtils::ByteOrder m_stringEndianness = ModbusDataUtils::ByteOrder%s;' % stringEndianness)
@ -668,7 +667,7 @@ def writeRtuHeaderFile():
writeLine(headerFile)
# Private members
writeLine(headerFile, 'private:')
writeLine(headerFile, ' /* Internals */')
writeLine(headerFile, ' ModbusRtuMaster *m_modbusRtuMaster = nullptr;')
writeLine(headerFile, ' ModbusDataUtils::ByteOrder m_endianness = ModbusDataUtils::ByteOrder%s;' % endianness)
writeLine(headerFile, ' ModbusDataUtils::ByteOrder m_stringEndianness = ModbusDataUtils::ByteOrder%s;' % stringEndianness)

Binary file not shown.

View File

@ -18,6 +18,8 @@ nymea uses the Modbus RTU (AMTRON Compact 2.0s) or Modbus TCP (all other models)
For the Amtron Charge Control and Premium models, log in to the wallbox's web interface as operator. The login credentials can be obtained
from the user manual of the wallbox. Once logged in, navigate to the Load Management tab and set the Modbus TCP Server to On.
> Note: Amtron Charge Control and Premium models also support firmware version `5.12` since the latest version is not complient with the calibration law.
For the AMTRON Compact 2.0s, DIP switch 4 and 5 on Bank 51 must be set to ON in order to allow nymea controlling the wallbox. With this DIP switch configuration, the solar charging mode will merely control the LED display of the wallbox.
## More

View File

@ -134,7 +134,13 @@
"unit": "mA",
"defaultValue": "0",
"access": "RO"
},
}
]
},
{
"id": "consumptionsTotals",
"readSchedule": "update",
"registers": [
{
"id": "meterTotoalEnergy",
"address": 218,
@ -142,7 +148,7 @@
"type": "uint32",
"unit": "Wh",
"registerType": "holdingRegister",
"description": "Meter total energy",
"description": "Meter total energy (>= 5.22)",
"defaultValue": "0",
"access": "RO"
},
@ -152,7 +158,7 @@
"size": 2,
"type": "uint32",
"registerType": "holdingRegister",
"description": "Meter totoal power",
"description": "Meter total power (>= 5.22)",
"unit": "W",
"defaultValue": "0",
"access": "RO"
@ -163,7 +169,7 @@
"size": 2,
"type": "uint32",
"registerType": "holdingRegister",
"description": "Meter voltage L1",
"description": "Meter voltage L1 (>= 5.22)",
"unit": "V",
"defaultValue": "0",
"access": "RO"
@ -174,7 +180,7 @@
"size": 2,
"type": "uint32",
"registerType": "holdingRegister",
"description": "Meter voltage L2",
"description": "Meter voltage L2 (>= 5.22)",
"unit": "V",
"defaultValue": "0",
"access": "RO"
@ -185,7 +191,7 @@
"size": 2,
"type": "uint32",
"registerType": "holdingRegister",
"description": "Meter voltage L3",
"description": "Meter voltage L3 (>= 5.22)",
"unit": "V",
"defaultValue": "0",
"access": "RO"
@ -224,7 +230,7 @@
"type": "uint16",
"readSchedule": "update",
"registerType": "holdingRegister",
"description": "Charge Point availability",
"description": "Charge Point availability ( Version < 5.22: 0 available, 1 unavailable | Version >= 5.22 1 available, 0 unavailable )",
"defaultValue": 0,
"access": "RW"
},
@ -232,10 +238,10 @@
"id": "model",
"address": 142,
"size": 10,
"type": "string",
"readSchedule": "init",
"type": "string",
"registerType": "holdingRegister",
"description": "Device model",
"description": "Device model (>= 5.22)",
"access": "RO"
},
{
@ -269,7 +275,7 @@
"type": "uint16",
"readSchedule": "update",
"registerType": "holdingRegister",
"description": "Maximum current limit",
"description": "Maximum current limit (>= 5.22)",
"unit": "A",
"defaultValue": "0",
"access": "RO"
@ -281,7 +287,7 @@
"type": "uint32",
"readSchedule": "update",
"registerType": "holdingRegister",
"description": "Charged energy for current session",
"description": "Charged energy for current session (>= 5.22)",
"unit": "Wh",
"defaultValue": "0",
"access": "RO"

646
mennekes/amtronecu.cpp Normal file
View File

@ -0,0 +1,646 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 "amtronecu.h"
#include "extern-plugininfo.h"
Q_DECLARE_LOGGING_CATEGORY(dcAmtronECUModbusTcpConnection)
AmtronECU::AmtronECU(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent)
: AmtronECUModbusTcpConnection{hostAddress, port, slaveId, parent}
{
}
AmtronECU::AmtronECU(ModbusTcpMaster *modbusTcpMaster, quint16 slaveId, QObject *parent)
: AmtronECUModbusTcpConnection{modbusTcpMaster, slaveId, parent}
{
}
bool AmtronECU::initialize()
{
if (!m_reachable) {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Tried to initialize but the device is not to be reachable.";
return false;
}
if (m_initObject) {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Tried to initialize but the init process is already running.";
return false;
}
m_initializing = true;
// Parent object for the init process
m_initObject = new QObject(this);
QModbusReply *reply = nullptr;
// This is a 2 Step initialization. First read the firmware version, if the firmware version is smaller than 5.22 we are done
// otherwise read also the model since we use that for detecting the exact manufacturer.
// Some useres have to stay on 5.12 due to calibration law which is not available on 5.22
// Read Firmware version
qCDebug(dcAmtronECUModbusTcpConnection()) << "--> Read init \"Firmware version\" register:" << 100 << "size:" << 2;
reply = readFirmwareVersion();
if (!reply) {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Error occurred while reading \"Firmware version\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString();
finishInitialization(false);
return false;
}
if (reply->isFinished()) {
reply->deleteLater(); // Broadcast reply returns immediatly
return false;
}
m_pendingInitReplies.append(reply);
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, m_initObject, [this, reply](){
handleModbusError(reply->error());
m_pendingInitReplies.removeAll(reply);
if (reply->error() != QModbusDevice::NoError) {
finishInitialization(false);
return;
}
const QModbusDataUnit unit = reply->result();
qCDebug(dcAmtronECUModbusTcpConnection()) << "<-- Response from init \"Firmware version\" register" << 100 << "size:" << 2 << unit.values();
if (unit.values().size() == 2) {
processFirmwareVersionRegisterValues(unit.values());
} else {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Reading from \"Firmware version\" registers" << 100 << "size:" << 2 << "returned different size than requested. Ignoring incomplete data" << unit.values();
finishInitialization(false);
return;
}
// Make sure this is the correct firmware version format (0x-- 0x2e 0x-- 0x--)
if ((m_firmwareVersion & 0x00ff0000) != 0x002e0000) {
qCWarning(dcMennekes()) << "Invalid firmware version format. Could not determin the firmware format from uint32" << QByteArray::fromHex(QByteArray::number(m_firmwareVersion, 16));
m_detectedVersion = VersionUnknown;
finishInitialization(false);
return;
}
// If the firmware version is smaller than 5.22 (Ascii of the uint32 firmware version property),
// we are done since the model registers have been introduced in Version 5.22
if (m_firmwareVersion < 0x352e3232) {
qCDebug(dcMennekes()) << "Detected firmware version" << QByteArray::fromHex(QByteArray::number(m_firmwareVersion, 16)) << ". Some features are only available from version >= 5.22.";
m_detectedVersion = VersionOld;
} else {
qCDebug(dcMennekes()) << "Detected firmware version" << QByteArray::fromHex(QByteArray::number(m_firmwareVersion, 16)) << "All features available.";
m_detectedVersion = VersionNew;
// Read Device model (>= 5.22)
qCDebug(dcAmtronECUModbusTcpConnection()) << "--> Read init \"Device model (>= 5.22)\" register:" << 142 << "size:" << 10;
QModbusReply *reply = readModel();
if (!reply) {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Error occurred while reading \"Device model (>= 5.22)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString();
finishInitialization(false);
return;
}
if (reply->isFinished()) {
reply->deleteLater(); // Broadcast reply returns immediatly
return;
}
m_pendingInitReplies.append(reply);
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, m_initObject, [this, reply](){
handleModbusError(reply->error());
m_pendingInitReplies.removeAll(reply);
if (reply->error() != QModbusDevice::NoError) {
finishInitialization(false);
return;
}
const QModbusDataUnit unit = reply->result();
qCDebug(dcAmtronECUModbusTcpConnection()) << "<-- Response from init \"Device model (>= 5.22)\" register" << 142 << "size:" << 10 << unit.values();
if (unit.values().size() == 10) {
processModelRegisterValues(unit.values());
} else {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Reading from \"Device model (>= 5.22)\" registers" << 142 << "size:" << 10 << "returned different size than requested. Ignoring incomplete data" << unit.values();
}
verifyInitFinished();
});
connect(reply, &QModbusReply::errorOccurred, m_initObject, [this, reply] (QModbusDevice::Error error){
QModbusResponse response = reply->rawResult();
if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Device model (>= 5.22)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());
} else {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Device model (>= 5.22)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();
}
});
}
verifyInitFinished();
});
connect(reply, &QModbusReply::errorOccurred, m_initObject, [this, reply] (QModbusDevice::Error error){
QModbusResponse response = reply->rawResult();
if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Firmware version\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());
} else {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Firmware version\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();
}
});
return true;
}
bool AmtronECU::update()
{
if (!reachable()) {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Tried to update but the device is not to be reachable.";
return false;
}
if (!m_modbusTcpMaster->connected())
return false;
if (!m_pendingUpdateReplies.isEmpty()) {
qCDebug(dcAmtronECUModbusTcpConnection()) << "Tried to update but there are still some update replies pending. Waiting for them to be finished...";
return true;
}
// First update common registers
// Then update registers only available for > 5.22
QModbusReply *reply = nullptr;
// Read CP signal state
qCDebug(dcAmtronECUModbusTcpConnection()) << "--> Read \"CP signal state\" register:" << 122 << "size:" << 1;
reply = readCpSignalState();
if (!reply) {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Error occurred while reading \"CP signal state\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString();
return false;
}
if (reply->isFinished()) {
reply->deleteLater(); // Broadcast reply returns immediatly
return false;
}
m_pendingUpdateReplies.append(reply);
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
m_pendingUpdateReplies.removeAll(reply);
handleModbusError(reply->error());
if (reply->error() != QModbusDevice::NoError) {
verifyUpdateFinished();
return;
}
const QModbusDataUnit unit = reply->result();
qCDebug(dcAmtronECUModbusTcpConnection()) << "<-- Response from \"CP signal state\" register" << 122 << "size:" << 1 << unit.values();
if (unit.values().size() == 1) {
processCpSignalStateRegisterValues(unit.values());
} else {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Reading from \"CP signal state\" registers" << 122 << "size:" << 1 << "returned different size than requested. Ignoring incomplete data" << unit.values();
}
verifyUpdateFinished();
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
QModbusResponse response = reply->rawResult();
if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"CP signal state\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());
} else {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"CP signal state\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();
}
});
// Read Signalled current to EV
qCDebug(dcAmtronECUModbusTcpConnection()) << "--> Read \"Signalled current to EV\" register:" << 706 << "size:" << 1;
reply = readSignalledCurrent();
if (!reply) {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Error occurred while reading \"Signalled current to EV\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString();
return false;
}
if (reply->isFinished()) {
reply->deleteLater(); // Broadcast reply returns immediatly
return false;
}
m_pendingUpdateReplies.append(reply);
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
m_pendingUpdateReplies.removeAll(reply);
handleModbusError(reply->error());
if (reply->error() != QModbusDevice::NoError) {
verifyUpdateFinished();
return;
}
const QModbusDataUnit unit = reply->result();
qCDebug(dcAmtronECUModbusTcpConnection()) << "<-- Response from \"Signalled current to EV\" register" << 706 << "size:" << 1 << unit.values();
if (unit.values().size() == 1) {
processSignalledCurrentRegisterValues(unit.values());
} else {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Reading from \"Signalled current to EV\" registers" << 706 << "size:" << 1 << "returned different size than requested. Ignoring incomplete data" << unit.values();
}
verifyUpdateFinished();
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
QModbusResponse response = reply->rawResult();
if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Signalled current to EV\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());
} else {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Signalled current to EV\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();
}
});
// Read Minimum current limit
qCDebug(dcAmtronECUModbusTcpConnection()) << "--> Read \"Minimum current limit\" register:" << 712 << "size:" << 1;
reply = readMinCurrentLimit();
if (!reply) {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Error occurred while reading \"Minimum current limit\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString();
return false;
}
if (reply->isFinished()) {
reply->deleteLater(); // Broadcast reply returns immediatly
return false;
}
m_pendingUpdateReplies.append(reply);
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
m_pendingUpdateReplies.removeAll(reply);
handleModbusError(reply->error());
if (reply->error() != QModbusDevice::NoError) {
verifyUpdateFinished();
return;
}
const QModbusDataUnit unit = reply->result();
qCDebug(dcAmtronECUModbusTcpConnection()) << "<-- Response from \"Minimum current limit\" register" << 712 << "size:" << 1 << unit.values();
if (unit.values().size() == 1) {
processMinCurrentLimitRegisterValues(unit.values());
} else {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Reading from \"Minimum current limit\" registers" << 712 << "size:" << 1 << "returned different size than requested. Ignoring incomplete data" << unit.values();
}
verifyUpdateFinished();
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
QModbusResponse response = reply->rawResult();
if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Minimum current limit\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());
} else {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Minimum current limit\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();
}
});
// Read consumptions
reply = readBlockConsumptions();
qCDebug(dcAmtronECUModbusTcpConnection()) << "--> Read block \"consumptions\" registers from:" << 200 << "size:" << 18;
if (!reply) {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Error occurred while reading block \"consumptions\" registers";
return false;
}
if (reply->isFinished()) {
reply->deleteLater(); // Broadcast reply returns immediatly
return false;
}
m_pendingUpdateReplies.append(reply);
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
m_pendingUpdateReplies.removeAll(reply);
handleModbusError(reply->error());
if (reply->error() != QModbusDevice::NoError) {
verifyUpdateFinished();
return;
}
const QModbusDataUnit unit = reply->result();
const QVector<quint16> blockValues = unit.values();
qCDebug(dcAmtronECUModbusTcpConnection()) << "<-- Response from reading block \"consumptions\" register" << 200 << "size:" << 18 << blockValues;
if (blockValues.size() == 18) {
processMeterEnergyL1RegisterValues(blockValues.mid(0, 2));
processMeterEnergyL2RegisterValues(blockValues.mid(2, 2));
processMeterEnergyL3RegisterValues(blockValues.mid(4, 2));
processMeterPowerL1RegisterValues(blockValues.mid(6, 2));
processMeterPowerL2RegisterValues(blockValues.mid(8, 2));
processMeterPowerL3RegisterValues(blockValues.mid(10, 2));
processMeterCurrentL1RegisterValues(blockValues.mid(12, 2));
processMeterCurrentL2RegisterValues(blockValues.mid(14, 2));
processMeterCurrentL3RegisterValues(blockValues.mid(16, 2));
} else {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Reading from \"consumptions\" block registers" << 200 << "size:" << 18 << "returned different size than requested. Ignoring incomplete data" << blockValues;
}
verifyUpdateFinished();
});
connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error){
QModbusResponse response = reply->rawResult();
if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while updating block \"consumptions\" registers" << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());
} else {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while updating block \"consumptions\" registers" << error << reply->errorString();
}
});
// Read Charge Point availability ( Version < 5.22: 0 available, 1 unavailable | Version >= 5.22 1 available, 0 unavailable )
qCDebug(dcAmtronECUModbusTcpConnection()) << "--> Read \"Charge Point availability ( Version < 5.22: 0 available, 1 unavailable | Version >= 5.22 1 available, 0 unavailable )\" register:" << 124 << "size:" << 1;
reply = readCpAvailability();
if (!reply) {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Error occurred while reading \"Charge Point availability ( Version < 5.22: 0 available, 1 unavailable | Version >= 5.22 1 available, 0 unavailable )\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString();
return false;
}
if (reply->isFinished()) {
reply->deleteLater(); // Broadcast reply returns immediatly
return false;
}
m_pendingUpdateReplies.append(reply);
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
m_pendingUpdateReplies.removeAll(reply);
handleModbusError(reply->error());
if (reply->error() != QModbusDevice::NoError) {
verifyUpdateFinished();
return;
}
const QModbusDataUnit unit = reply->result();
qCDebug(dcAmtronECUModbusTcpConnection()) << "<-- Response from \"Charge Point availability ( Version < 5.22: 0 available, 1 unavailable | Version >= 5.22 1 available, 0 unavailable )\" register" << 124 << "size:" << 1 << unit.values();
if (unit.values().size() == 1) {
processCpAvailabilityRegisterValues(unit.values());
} else {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Reading from \"Charge Point availability ( Version < 5.22: 0 available, 1 unavailable | Version >= 5.22 1 available, 0 unavailable )\" registers" << 124 << "size:" << 1 << "returned different size than requested. Ignoring incomplete data" << unit.values();
}
verifyUpdateFinished();
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
QModbusResponse response = reply->rawResult();
if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Charge Point availability ( Version < 5.22: 0 available, 1 unavailable | Version >= 5.22 1 available, 0 unavailable )\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());
} else {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Charge Point availability ( Version < 5.22: 0 available, 1 unavailable | Version >= 5.22 1 available, 0 unavailable )\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();
}
});
// Read HEMS current limit
qCDebug(dcAmtronECUModbusTcpConnection()) << "--> Read \"HEMS current limit\" register:" << 1000 << "size:" << 1;
reply = readHemsCurrentLimit();
if (!reply) {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Error occurred while reading \"HEMS current limit\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString();
return false;
}
if (reply->isFinished()) {
reply->deleteLater(); // Broadcast reply returns immediatly
return false;
}
m_pendingUpdateReplies.append(reply);
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
m_pendingUpdateReplies.removeAll(reply);
handleModbusError(reply->error());
if (reply->error() != QModbusDevice::NoError) {
verifyUpdateFinished();
return;
}
const QModbusDataUnit unit = reply->result();
qCDebug(dcAmtronECUModbusTcpConnection()) << "<-- Response from \"HEMS current limit\" register" << 1000 << "size:" << 1 << unit.values();
if (unit.values().size() == 1) {
processHemsCurrentLimitRegisterValues(unit.values());
} else {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Reading from \"HEMS current limit\" registers" << 1000 << "size:" << 1 << "returned different size than requested. Ignoring incomplete data" << unit.values();
}
verifyUpdateFinished();
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
QModbusResponse response = reply->rawResult();
if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"HEMS current limit\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());
} else {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"HEMS current limit\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();
}
});
// Only available on >= 5.22
if (m_detectedVersion == VersionNew) {
// Read Maximum current limit (>= 5.22)
qCDebug(dcAmtronECUModbusTcpConnection()) << "--> Read \"Maximum current limit (>= 5.22)\" register:" << 715 << "size:" << 1;
reply = readMaxCurrentLimit();
if (!reply) {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Error occurred while reading \"Maximum current limit (>= 5.22)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString();
return false;
}
if (reply->isFinished()) {
reply->deleteLater(); // Broadcast reply returns immediatly
return false;
}
m_pendingUpdateReplies.append(reply);
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
m_pendingUpdateReplies.removeAll(reply);
handleModbusError(reply->error());
if (reply->error() != QModbusDevice::NoError) {
verifyUpdateFinished();
return;
}
const QModbusDataUnit unit = reply->result();
qCDebug(dcAmtronECUModbusTcpConnection()) << "<-- Response from \"Maximum current limit (>= 5.22)\" register" << 715 << "size:" << 1 << unit.values();
if (unit.values().size() == 1) {
processMaxCurrentLimitRegisterValues(unit.values());
} else {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Reading from \"Maximum current limit (>= 5.22)\" registers" << 715 << "size:" << 1 << "returned different size than requested. Ignoring incomplete data" << unit.values();
}
verifyUpdateFinished();
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
QModbusResponse response = reply->rawResult();
if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Maximum current limit (>= 5.22)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());
} else {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Maximum current limit (>= 5.22)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();
}
});
// Read Charged energy for current session (>= 5.22)
qCDebug(dcAmtronECUModbusTcpConnection()) << "--> Read \"Charged energy for current session (>= 5.22)\" register:" << 716 << "size:" << 2;
reply = readChargedEnergy();
if (!reply) {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Error occurred while reading \"Charged energy for current session (>= 5.22)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString();
return false;
}
if (reply->isFinished()) {
reply->deleteLater(); // Broadcast reply returns immediatly
return false;
}
m_pendingUpdateReplies.append(reply);
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
m_pendingUpdateReplies.removeAll(reply);
handleModbusError(reply->error());
if (reply->error() != QModbusDevice::NoError) {
verifyUpdateFinished();
return;
}
const QModbusDataUnit unit = reply->result();
qCDebug(dcAmtronECUModbusTcpConnection()) << "<-- Response from \"Charged energy for current session (>= 5.22)\" register" << 716 << "size:" << 2 << unit.values();
if (unit.values().size() == 2) {
processChargedEnergyRegisterValues(unit.values());
} else {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Reading from \"Charged energy for current session (>= 5.22)\" registers" << 716 << "size:" << 2 << "returned different size than requested. Ignoring incomplete data" << unit.values();
}
verifyUpdateFinished();
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
QModbusResponse response = reply->rawResult();
if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Charged energy for current session (>= 5.22)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());
} else {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Charged energy for current session (>= 5.22)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();
}
});
// Read consumptionsTotals
reply = readBlockConsumptionsTotals();
qCDebug(dcAmtronECUModbusTcpConnection()) << "--> Read block \"consumptionsTotals\" registers from:" << 218 << "size:" << 10;
if (!reply) {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Error occurred while reading block \"consumptionsTotals\" registers";
return false;
}
if (reply->isFinished()) {
reply->deleteLater(); // Broadcast reply returns immediatly
return false;
}
m_pendingUpdateReplies.append(reply);
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [this, reply](){
m_pendingUpdateReplies.removeAll(reply);
handleModbusError(reply->error());
if (reply->error() != QModbusDevice::NoError) {
verifyUpdateFinished();
return;
}
const QModbusDataUnit unit = reply->result();
const QVector<quint16> blockValues = unit.values();
qCDebug(dcAmtronECUModbusTcpConnection()) << "<-- Response from reading block \"consumptionsTotals\" register" << 218 << "size:" << 10 << blockValues;
if (blockValues.size() == 10) {
processMeterTotoalEnergyRegisterValues(blockValues.mid(0, 2));
processMeterTotalPowerRegisterValues(blockValues.mid(2, 2));
processMeterVoltageL1RegisterValues(blockValues.mid(4, 2));
processMeterVoltageL2RegisterValues(blockValues.mid(6, 2));
processMeterVoltageL3RegisterValues(blockValues.mid(8, 2));
} else {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Reading from \"consumptionsTotals\" block registers" << 218 << "size:" << 10 << "returned different size than requested. Ignoring incomplete data" << blockValues;
}
verifyUpdateFinished();
});
connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error){
QModbusResponse response = reply->rawResult();
if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while updating block \"consumptionsTotals\" registers" << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());
} else {
qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while updating block \"consumptionsTotals\" registers" << error << reply->errorString();
}
});
}
return true;
}
AmtronECU::Version AmtronECU::detectedVersion() const
{
return m_detectedVersion;
}
QDebug operator<<(QDebug debug, AmtronECU *amtronECU)
{
debug.nospace().noquote() << "AmtronECU(" << amtronECU->modbusTcpMaster()->hostAddress().toString() << ":" << amtronECU->modbusTcpMaster()->port() << ")" << "\n";
debug.nospace().noquote() << " - holding 100 | Firmware version: " << amtronECU->firmwareVersion() << "\n";
debug.nospace().noquote() << " - holding 122 | CP signal state: " << amtronECU->cpSignalState() << "\n";
debug.nospace().noquote() << " - holding 124 | Charge Point availability ( Version < 5.22: 0 available, 1 unavailable | Version >= 5.22 1 available, 0 unavailable ): " << amtronECU->cpAvailability() << "\n";
debug.nospace().noquote() << " - holding 200 | Meter energy L1: " << amtronECU->meterEnergyL1() << " [Wh]" << "\n";
debug.nospace().noquote() << " - holding 202 | Meter energy L2: " << amtronECU->meterEnergyL2() << " [Wh]" << "\n";
debug.nospace().noquote() << " - holding 204 | Meter energy L3: " << amtronECU->meterEnergyL3() << " [Wh]" << "\n";
debug.nospace().noquote() << " - holding 206 | Meter power L1: " << amtronECU->meterPowerL1() << " [W]" << "\n";
debug.nospace().noquote() << " - holding 208 | Meter power L2: " << amtronECU->meterPowerL2() << " [W]" << "\n";
debug.nospace().noquote() << " - holding 210 | Meter power L3: " << amtronECU->meterPowerL3() << " [W]" << "\n";
debug.nospace().noquote() << " - holding 212 | Meter current L1: " << amtronECU->meterCurrentL1() << " [mA]" << "\n";
debug.nospace().noquote() << " - holding 214 | Meter current L2: " << amtronECU->meterCurrentL2() << " [mA]" << "\n";
debug.nospace().noquote() << " - holding 216 | Meter current L3: " << amtronECU->meterCurrentL3() << " [mA]" << "\n";
debug.nospace().noquote() << " - holding 706 | Signalled current to EV: " << amtronECU->signalledCurrent() << " [A]" << "\n";
debug.nospace().noquote() << " - holding 712 | Minimum current limit: " << amtronECU->minCurrentLimit() << " [A]" << "\n";
debug.nospace().noquote() << " - holding 1000 | HEMS current limit: " << amtronECU->hemsCurrentLimit() << " [A]" << "\n";
if (amtronECU->detectedVersion() == AmtronECU::VersionNew) {
debug.nospace().noquote() << " - holding 142 | Device model (>= 5.22): " << amtronECU->model() << "\n";
debug.nospace().noquote() << " - holding 218 | Meter total energy (>= 5.22): " << amtronECU->meterTotoalEnergy() << " [Wh]" << "\n";
debug.nospace().noquote() << " - holding 220 | Meter total power (>= 5.22): " << amtronECU->meterTotalPower() << " [W]" << "\n";
debug.nospace().noquote() << " - holding 222 | Meter voltage L1 (>= 5.22): " << amtronECU->meterVoltageL1() << " [V]" << "\n";
debug.nospace().noquote() << " - holding 224 | Meter voltage L2 (>= 5.22): " << amtronECU->meterVoltageL2() << " [V]" << "\n";
debug.nospace().noquote() << " - holding 226 | Meter voltage L3 (>= 5.22): " << amtronECU->meterVoltageL3() << " [V]" << "\n";
debug.nospace().noquote() << " - holding 715 | Maximum current limit (>= 5.22): " << amtronECU->maxCurrentLimit() << " [A]" << "\n";
debug.nospace().noquote() << " - holding 716 | Charged energy for current session (>= 5.22): " << amtronECU->chargedEnergy() << " [Wh]" << "\n";
}
return debug.quote().space();
}

64
mennekes/amtronecu.h Normal file
View File

@ -0,0 +1,64 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 AMTRONECU_H
#define AMTRONECU_H
#include <QObject>
#include "amtronecumodbustcpconnection.h"
class AmtronECU : public AmtronECUModbusTcpConnection
{
Q_OBJECT
public:
enum Version {
VersionUnknown,
VersionOld, // < 5.22
VersionNew // >= 5.22
};
Q_ENUM(Version)
explicit AmtronECU(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent = nullptr);
explicit AmtronECU(ModbusTcpMaster *modbusTcpMaster, quint16 slaveId, QObject *parent = nullptr);
bool initialize() override;
bool update() override;
Version detectedVersion() const;
private:
Version m_detectedVersion = VersionUnknown;
};
QDebug operator<<(QDebug debug, AmtronECU *amtronECU);
#endif // AMTRONECU_H

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2022, nymea GmbH
* Copyright 2013 - 2023, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
@ -46,6 +46,8 @@ AmtronECUDiscovery::AmtronECUDiscovery(NetworkDeviceDiscovery *networkDeviceDisc
void AmtronECUDiscovery::startDiscovery()
{
qCInfo(dcMennekes()) << "Discovery: Searching for AMTRON wallboxes in the network...";
m_startDateTime = QDateTime::currentDateTime();
NetworkDeviceDiscoveryReply *discoveryReply = m_networkDeviceDiscovery->discover();
connect(discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, &AmtronECUDiscovery::checkNetworkDevice);
@ -66,35 +68,50 @@ void AmtronECUDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDevi
{
int port = 502;
int slaveId = 0xff;
qCDebug(dcMennekes()) << "Checking network device:" << networkDeviceInfo << "Port:" << port << "Slave ID:" << slaveId;
qCDebug(dcMennekes()) << "Discovery: Checking network device:" << networkDeviceInfo << "Port:" << port << "Slave ID:" << slaveId;
AmtronECUModbusTcpConnection *connection = new AmtronECUModbusTcpConnection(networkDeviceInfo.address(), port, slaveId, this);
AmtronECU *connection = new AmtronECU(networkDeviceInfo.address(), port, slaveId, this);
m_connections.append(connection);
connect(connection, &AmtronECUModbusTcpConnection::reachableChanged, this, [=](bool reachable){
connect(connection, &AmtronECU::reachableChanged, this, [=](bool reachable){
if (!reachable) {
cleanupConnection(connection);
return;
}
connect(connection, &AmtronECUModbusTcpConnection::initializationFinished, this, [=](bool success){
connect(connection, &AmtronECU::initializationFinished, this, [=](bool success){
if (!success) {
qCDebug(dcMennekes()) << "Discovery: Initialization failed on" << networkDeviceInfo.address().toString();
cleanupConnection(connection);
return;
}
if (connection->firmwareVersion() == 0 || connection->model().isEmpty()) {
qCDebug(dcMennekes()) << "Firmware version or model invalid. Skipping" << networkDeviceInfo.address();
cleanupConnection(connection);
return;
}
Result result;
result.firmwareVersion = connection->firmwareVersion();
result.detectedVersion = connection->detectedVersion();
result.firmwareVersion = QString::fromUtf8(QByteArray::fromHex(QByteArray::number(connection->firmwareVersion(), 16)));
result.model = connection->model();
result.networkDeviceInfo = networkDeviceInfo;
m_discoveryResults.append(result);
qCDebug(dcMennekes()) << "Discovery: Found wallbox with firmware version:" << result.firmwareVersion << result.networkDeviceInfo;
// Note: the model is only known in firmware >= 5.22
// Some useres have to stay on 5.12 due to calibration law which is not available on 5.22
switch(connection->detectedVersion()) {
case AmtronECU::VersionOld:
qCDebug(dcMennekes()) << "Discovery: Found wallbox with old firmware version:" << result.firmwareVersion << result.networkDeviceInfo;
m_discoveryResults.append(result);
break;
case AmtronECU::VersionNew:
if (connection->model().isEmpty()) {
qCDebug(dcMennekes()) << "Discovery: Firmware version is >= 5.22 but the model could not be fetched. Skipping" << networkDeviceInfo.address();
break;
}
qCDebug(dcMennekes()) << "Discovery: Found wallbox with new firmware version:" << result.model << result.firmwareVersion << result.networkDeviceInfo;
m_discoveryResults.append(result);
break;
case AmtronECU::VersionUnknown:
qCDebug(dcMennekes()) << "Discovery: Firmware version or model invalid. Skipping" << networkDeviceInfo.address();
break;
}
cleanupConnection(connection);
});
@ -105,7 +122,7 @@ void AmtronECUDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDevi
}
});
connect(connection, &AmtronECUModbusTcpConnection::checkReachabilityFailed, this, [=](){
connect(connection, &AmtronECU::checkReachabilityFailed, this, [=](){
qCDebug(dcMennekes()) << "Discovery: Checking reachability failed on" << networkDeviceInfo.address().toString();
cleanupConnection(connection);
});
@ -113,7 +130,7 @@ void AmtronECUDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDevi
connection->connectDevice();
}
void AmtronECUDiscovery::cleanupConnection(AmtronECUModbusTcpConnection *connection)
void AmtronECUDiscovery::cleanupConnection(AmtronECU *connection)
{
m_connections.removeAll(connection);
connection->disconnectDevice();
@ -125,11 +142,11 @@ void AmtronECUDiscovery::finishDiscovery()
qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startDateTime.toMSecsSinceEpoch();
// Cleanup any leftovers...we don't care any more
foreach (AmtronECUModbusTcpConnection *connection, m_connections)
foreach (AmtronECU *connection, m_connections)
cleanupConnection(connection);
qCInfo(dcMennekes()) << "Discovery: Finished the discovery process. Found" << m_discoveryResults.count()
<< "AMTRON wallboxes in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz");
<< "AMTRON ECU wallboxes in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz");
m_gracePeriodTimer.stop();
emit discoveryFinished();

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2022, nymea GmbH
* Copyright 2013 - 2023, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
@ -36,7 +36,7 @@
#include <network/networkdevicediscovery.h>
#include "amtronecumodbustcpconnection.h"
#include "amtronecu.h"
class AmtronECUDiscovery : public QObject
{
@ -44,6 +44,7 @@ class AmtronECUDiscovery : public QObject
public:
explicit AmtronECUDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent = nullptr);
struct Result {
AmtronECU::Version detectedVersion;
QString firmwareVersion;
QString model;
NetworkDeviceInfo networkDeviceInfo;
@ -62,12 +63,12 @@ private:
QTimer m_gracePeriodTimer;
QDateTime m_startDateTime;
QList<AmtronECUModbusTcpConnection *> m_connections;
QList<AmtronECU *> m_connections;
QList<Result> m_discoveryResults;
void checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo);
void cleanupConnection(AmtronECUModbusTcpConnection *connection);
void cleanupConnection(AmtronECU *connection);
void finishDiscovery();
};

View File

@ -66,14 +66,13 @@ void IntegrationPluginMennekes::discoverThings(ThingDiscoveryInfo *info)
QString name = "AMTRON Charge Control/Professional";
QString description = result.model.isEmpty() ? result.networkDeviceInfo.address().toString() :
result.model + " (" + result.networkDeviceInfo.address().toString() + ")";
result.model + " (" + result.networkDeviceInfo.address().toString() + ")";
if (result.model.startsWith("CC")) {
name = "AMTRON Charge Control";
} else if (result.model.startsWith("P")) {
name = "AMTRON Professional";
} else {
qCWarning(dcMennekes()) << "Unknown Amtron model:" << result.model;
continue;
}
ThingDescriptor descriptor(amtronECUThingClassId, name, description);
qCDebug(dcMennekes()) << "Discovered:" << descriptor.title() << descriptor.description();
@ -289,7 +288,7 @@ void IntegrationPluginMennekes::postSetupThing(Thing *thing)
qCDebug(dcMennekes()) << "Starting plugin timer...";
m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(2);
connect(m_pluginTimer, &PluginTimer::timeout, this, [this] {
foreach(AmtronECUModbusTcpConnection *connection, m_amtronECUConnections) {
foreach(AmtronECU *connection, m_amtronECUConnections) {
qCDebug(dcMennekes()) << "Updating connection" << connection->modbusTcpMaster()->hostAddress().toString();
connection->update();
}
@ -311,8 +310,7 @@ void IntegrationPluginMennekes::postSetupThing(Thing *thing)
void IntegrationPluginMennekes::executeAction(ThingActionInfo *info)
{
if (info->thing()->thingClassId() == amtronECUThingClassId) {
AmtronECUModbusTcpConnection *amtronECUConnection = m_amtronECUConnections.value(info->thing());
AmtronECU *amtronECUConnection = m_amtronECUConnections.value(info->thing());
if (info->action().actionTypeId() == amtronECUPowerActionTypeId) {
bool power = info->action().paramValue(amtronECUPowerActionPowerParamTypeId).toBool();
@ -458,7 +456,7 @@ void IntegrationPluginMennekes::executeAction(ThingActionInfo *info)
void IntegrationPluginMennekes::thingRemoved(Thing *thing)
{
if (thing->thingClassId() == amtronECUThingClassId && m_amtronECUConnections.contains(thing)) {
AmtronECUModbusTcpConnection *connection = m_amtronECUConnections.take(thing);
AmtronECU *connection = m_amtronECUConnections.take(thing);
delete connection;
}
@ -484,7 +482,7 @@ void IntegrationPluginMennekes::thingRemoved(Thing *thing)
void IntegrationPluginMennekes::updateECUPhaseCount(Thing *thing)
{
AmtronECUModbusTcpConnection *amtronECUConnection = m_amtronECUConnections.value(thing);
AmtronECU *amtronECUConnection = m_amtronECUConnections.value(thing);
int phaseCount = 0;
qCDebug(dcMennekes()) << "Phases: L1" << amtronECUConnection->meterCurrentL1() << "L2" << amtronECUConnection->meterCurrentL2() << "L3" << amtronECUConnection->meterCurrentL3();
// the current idles on some 5 - 10 mA when not charging...
@ -501,17 +499,27 @@ void IntegrationPluginMennekes::updateECUPhaseCount(Thing *thing)
phaseCount++;
}
qCDebug(dcMennekes()) << "Actively charging phases:" << phaseCount;
if (phaseCount == 0) {
if (amtronECUConnection->meterVoltageL1() > 0) {
phaseCount++;
if (amtronECUConnection->detectedVersion() == AmtronECU::VersionNew) {
// Only available since 5.22
if (phaseCount == 0) {
if (amtronECUConnection->meterVoltageL1() > 0) {
phaseCount++;
}
if (amtronECUConnection->meterVoltageL2() > 0) {
phaseCount++;
}
if (amtronECUConnection->meterVoltageL3() > 0) {
phaseCount++;
}
qCDebug(dcMennekes()) << "Connected phases:" << phaseCount;
}
if (amtronECUConnection->meterVoltageL2() > 0) {
phaseCount++;
} else if (amtronECUConnection->detectedVersion() == AmtronECU::VersionOld) {
if (phaseCount == 0) {
qCDebug(dcMennekes()) << "Could not detect phases in use. Default to 1 phase.";
phaseCount = 1;
}
if (amtronECUConnection->meterVoltageL3() > 0) {
phaseCount++;
}
qCDebug(dcMennekes()) << "Connected phases:" << phaseCount;
}
thing->setStateValue(amtronECUPhaseCountStateTypeId, phaseCount);
@ -536,7 +544,7 @@ void IntegrationPluginMennekes::setupAmtronECUConnection(ThingSetupInfo *info)
QHostAddress address = m_monitors.value(thing)->networkDeviceInfo().address();
qCDebug(dcMennekes()) << "Setting up amtron wallbox on" << address.toString();
AmtronECUModbusTcpConnection *amtronECUConnection = new AmtronECUModbusTcpConnection(address, 502, 0xff, this);
AmtronECU *amtronECUConnection = new AmtronECU(address, 502, 0xff, this);
connect(info, &ThingSetupInfo::aborted, amtronECUConnection, &ModbusTcpMaster::deleteLater);
// Reconnect on monitor reachable changed
@ -556,7 +564,7 @@ void IntegrationPluginMennekes::setupAmtronECUConnection(ThingSetupInfo *info)
}
});
connect(amtronECUConnection, &AmtronECUModbusTcpConnection::reachableChanged, thing, [thing, amtronECUConnection](bool reachable){
connect(amtronECUConnection, &AmtronECU::reachableChanged, thing, [thing, amtronECUConnection](bool reachable){
qCDebug(dcMennekes()) << "Reachable changed to" << reachable << "for" << thing;
if (reachable) {
amtronECUConnection->initialize();
@ -565,7 +573,7 @@ void IntegrationPluginMennekes::setupAmtronECUConnection(ThingSetupInfo *info)
}
});
connect(amtronECUConnection, &AmtronECUModbusTcpConnection::initializationFinished, thing, [=](bool success){
connect(amtronECUConnection, &AmtronECU::initializationFinished, thing, [=](bool success){
if (!thing->setupComplete())
return;
@ -578,7 +586,8 @@ void IntegrationPluginMennekes::setupAmtronECUConnection(ThingSetupInfo *info)
}
});
connect(amtronECUConnection, &AmtronECUModbusTcpConnection::initializationFinished, info, [=](bool success){
connect(amtronECUConnection, &AmtronECU::initializationFinished, info, [=](bool success){
if (!success) {
qCWarning(dcMennekes()) << "Connection init finished with errors" << thing->name() << amtronECUConnection->modbusTcpMaster()->hostAddress().toString();
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(monitor);
@ -589,44 +598,80 @@ void IntegrationPluginMennekes::setupAmtronECUConnection(ThingSetupInfo *info)
qCDebug(dcMennekes()) << "Connection init finished successfully" << amtronECUConnection;
QString minimumVersion = "5.22";
if (!ensureAmtronECUVersion(amtronECUConnection, minimumVersion)) {
qCWarning(dcMennekes()) << "Firmware version too old:" << QByteArray::fromHex(QByteArray::number(amtronECUConnection->firmwareVersion(), 16)) << "Minimum required:" << minimumVersion;
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(monitor);
amtronECUConnection->deleteLater();
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The firmware version of this wallbox is too old. Please upgrade the firmware to at least version 5.22."));
return;
}
m_amtronECUConnections.insert(thing, amtronECUConnection);
info->finish(Thing::ThingErrorNoError);
thing->setStateValue(amtronECUConnectedStateTypeId, true);
thing->setStateValue(amtronECUFirmwareVersionStateTypeId, QString::fromUtf8(QByteArray::fromHex(QByteArray::number(amtronECUConnection->firmwareVersion(), 16))));
amtronECUConnection->update();
});
connect(amtronECUConnection, &AmtronECUModbusTcpConnection::updateFinished, thing, [this, amtronECUConnection, thing](){
connect(amtronECUConnection, &AmtronECU::updateFinished, thing, [this, amtronECUConnection, thing](){
qCDebug(dcMennekes()) << "Amtron ECU update finished:" << thing->name() << amtronECUConnection;
updateECUPhaseCount(thing);
// Firmware >= 5.12
if (amtronECUConnection->cpSignalState() != AmtronECU::CPSignalStateE) {
// State E (Off): don't update as the wallbox goes to this state for a few seconds regardless of the actual plugged in state.
qCDebug(dcMennekes()) << "CP signal state changed" << amtronECUConnection->cpSignalState();
thing->setStateValue(amtronECUPluggedInStateTypeId, amtronECUConnection->cpSignalState() >= AmtronECU::CPSignalStateB);
}
thing->setStateMinValue(amtronECUMaxChargingCurrentStateTypeId, amtronECUConnection->minCurrentLimit());
qCDebug(dcMennekes()) << "HEMS current limit:" << amtronECUConnection->hemsCurrentLimit();
if (amtronECUConnection->hemsCurrentLimit() == 0) {
thing->setStateValue(amtronECUPowerStateTypeId, false);
} else {
thing->setStateValue(amtronECUPowerStateTypeId, true);
thing->setStateValue(amtronECUMaxChargingCurrentStateTypeId, amtronECUConnection->hemsCurrentLimit());
}
if (amtronECUConnection->detectedVersion() == AmtronECU::VersionOld) {
// Note: version < 5.22 has no totals, we need to sum them up
int totalPower = 0;
if (amtronECUConnection->meterPowerL1() != 0xffffffff) {
totalPower += amtronECUConnection->meterPowerL1();
}
if (amtronECUConnection->meterPowerL2() != 0xffffffff) {
totalPower += amtronECUConnection->meterPowerL2();
}
if (amtronECUConnection->meterPowerL3() != 0xffffffff) {
totalPower += amtronECUConnection->meterPowerL3();
}
thing->setStateValue(amtronECUCurrentPowerStateTypeId, totalPower);
double totalEnergy = 0; // Wh
if (amtronECUConnection->meterEnergyL1() != 0xffffffff) {
totalEnergy += amtronECUConnection->meterEnergyL1();
}
if (amtronECUConnection->meterEnergyL2() != 0xffffffff) {
totalEnergy += amtronECUConnection->meterEnergyL2();
}
if (amtronECUConnection->meterEnergyL3() != 0xffffffff) {
totalEnergy += amtronECUConnection->meterEnergyL3();
}
totalEnergy /= 1000.0; // Convert Wh to kWh
thing->setStateValue(amtronECUTotalEnergyConsumedStateTypeId, qRound(totalEnergy * 100.0) / 100.0); // rounded to 2 as it changes on every update
}
});
connect(amtronECUConnection, &AmtronECUModbusTcpConnection::cpSignalStateChanged, thing, [thing](AmtronECUModbusTcpConnection::CPSignalState cpSignalState) {
qCDebug(dcMennekes()) << "CP signal state changed" << cpSignalState;
if (cpSignalState == AmtronECUModbusTcpConnection::CPSignalStateE) {
// State E (Off): don't update as the wallbox goes to this state for a few seconds regardless of the actual plugged in state.
return;
}
thing->setStateValue(amtronECUPluggedInStateTypeId, cpSignalState >= AmtronECUModbusTcpConnection::CPSignalStateB);
});
connect(amtronECUConnection, &AmtronECUModbusTcpConnection::signalledCurrentChanged, thing, [](quint16 signalledCurrent) {
connect(amtronECUConnection, &AmtronECU::signalledCurrentChanged, thing, [](quint16 signalledCurrent) {
qCDebug(dcMennekes()) << "Signalled current changed:" << signalledCurrent;
});
connect(amtronECUConnection, &AmtronECUModbusTcpConnection::minCurrentLimitChanged, thing, [thing](quint16 minCurrentLimit) {
qCDebug(dcMennekes()) << "min current limit changed:" << minCurrentLimit;
thing->setStateMinValue(amtronECUMaxChargingCurrentStateTypeId, minCurrentLimit);
});
connect(amtronECUConnection, &AmtronECUModbusTcpConnection::maxCurrentLimitChanged, thing, [this, thing](quint16 maxCurrentLimit) {
// From here only for firmware version >= 5.22, otherwise these signal will never be emitted, but if the user updates to 5.22, they start working
connect(amtronECUConnection, &AmtronECU::maxCurrentLimitChanged, thing, [this, thing](quint16 maxCurrentLimit) {
qCDebug(dcMennekes()) << "max current limit changed:" << maxCurrentLimit;
// If the vehicle or cable are not capable of reporting the maximum, this will be 0
// We'll reset to the max defined in the json file in that case
@ -635,25 +680,19 @@ void IntegrationPluginMennekes::setupAmtronECUConnection(ThingSetupInfo *info)
}
thing->setStateMaxValue(amtronECUMaxChargingCurrentStateTypeId, maxCurrentLimit);
});
connect(amtronECUConnection, &AmtronECUModbusTcpConnection::hemsCurrentLimitChanged, thing, [thing](quint16 hemsCurrentLimit) {
qCDebug(dcMennekes()) << "HEMS current limit changed:" << hemsCurrentLimit;
if (hemsCurrentLimit == 0) {
thing->setStateValue(amtronECUPowerStateTypeId, false);
} else {
thing->setStateValue(amtronECUPowerStateTypeId, true);
thing->setStateValue(amtronECUMaxChargingCurrentStateTypeId, hemsCurrentLimit);
}
});
connect(amtronECUConnection, &AmtronECUModbusTcpConnection::meterTotoalEnergyChanged, thing, [thing](quint32 meterTotalEnergy) {
connect(amtronECUConnection, &AmtronECU::meterTotoalEnergyChanged, thing, [thing](quint32 meterTotalEnergy) {
qCDebug(dcMennekes()) << "meter total energy changed:" << meterTotalEnergy;
thing->setStateValue(amtronECUTotalEnergyConsumedStateTypeId, qRound(meterTotalEnergy / 10.0) / 100.0); // rounded to 2 as it changes on every update
});
connect(amtronECUConnection, &AmtronECUModbusTcpConnection::meterTotalPowerChanged, thing, [thing](quint32 meterTotalPower) {
connect(amtronECUConnection, &AmtronECU::meterTotalPowerChanged, thing, [thing](quint32 meterTotalPower) {
qCDebug(dcMennekes()) << "meter total power changed:" << meterTotalPower;
thing->setStateValue(amtronECUCurrentPowerStateTypeId, meterTotalPower);
thing->setStateValue(amtronECUChargingStateTypeId, meterTotalPower > 0);
});
connect(amtronECUConnection, &AmtronECUModbusTcpConnection::chargedEnergyChanged, thing, [thing](quint32 chargedEnergy) {
connect(amtronECUConnection, &AmtronECU::chargedEnergyChanged, thing, [thing](quint32 chargedEnergy) {
qCDebug(dcMennekes()) << "charged energy changed:" << chargedEnergy;
thing->setStateValue(amtronECUSessionEnergyStateTypeId, qRound(chargedEnergy / 10.0) / 100.0); // rounded to 2 as it changes on every update
});
@ -857,31 +896,31 @@ void IntegrationPluginMennekes::setupAmtronCompact20Connection(ThingSetupInfo *i
connect(compact20Connection, &AmtronCompact20ModbusRtuConnection::cpSignalStateChanged, thing, [thing](AmtronCompact20ModbusRtuConnection::CPSignalState cpSignalState){
qCDebug(dcMennekes()) << "CP signal state changed:" << thing->name() << cpSignalState;
// Note: using EVSE state register instead
// switch (cpSignalState) {
// case AmtronCompact20ModbusRtuConnection::CPSignalStateA1:
// case AmtronCompact20ModbusRtuConnection::CPSignalStateA2:
// thing->setStateValue(amtronCompact20PluggedInStateTypeId, false);
// thing->setStateValue(amtronCompact20ChargingStateTypeId, false);
// break;
// case AmtronCompact20ModbusRtuConnection::CPSignalStateB1:
// case AmtronCompact20ModbusRtuConnection::CPSignalStateB2:
// thing->setStateValue(amtronCompact20PluggedInStateTypeId, true);
// thing->setStateValue(amtronCompact20ChargingStateTypeId, false);
// break;
// case AmtronCompact20ModbusRtuConnection::CPSignalStateC1:
// case AmtronCompact20ModbusRtuConnection::CPSignalStateC2:
// case AmtronCompact20ModbusRtuConnection::CPSignalStateD1:
// case AmtronCompact20ModbusRtuConnection::CPSignalStateD2:
// thing->setStateValue(amtronCompact20PluggedInStateTypeId, true);
// thing->setStateValue(amtronCompact20ChargingStateTypeId, true);
// break;
// case AmtronCompact20ModbusRtuConnection::CPSignalStateE:
// case AmtronCompact20ModbusRtuConnection::CPSignalStateF:
// qCWarning(dcMennekes()) << "Wallbox in Error state!";
// thing->setStateValue(amtronCompact20PluggedInStateTypeId, false);
// thing->setStateValue(amtronCompact20ChargingStateTypeId, false);
// break;
// }
// switch (cpSignalState) {
// case AmtronCompact20ModbusRtuConnection::CPSignalStateA1:
// case AmtronCompact20ModbusRtuConnection::CPSignalStateA2:
// thing->setStateValue(amtronCompact20PluggedInStateTypeId, false);
// thing->setStateValue(amtronCompact20ChargingStateTypeId, false);
// break;
// case AmtronCompact20ModbusRtuConnection::CPSignalStateB1:
// case AmtronCompact20ModbusRtuConnection::CPSignalStateB2:
// thing->setStateValue(amtronCompact20PluggedInStateTypeId, true);
// thing->setStateValue(amtronCompact20ChargingStateTypeId, false);
// break;
// case AmtronCompact20ModbusRtuConnection::CPSignalStateC1:
// case AmtronCompact20ModbusRtuConnection::CPSignalStateC2:
// case AmtronCompact20ModbusRtuConnection::CPSignalStateD1:
// case AmtronCompact20ModbusRtuConnection::CPSignalStateD2:
// thing->setStateValue(amtronCompact20PluggedInStateTypeId, true);
// thing->setStateValue(amtronCompact20ChargingStateTypeId, true);
// break;
// case AmtronCompact20ModbusRtuConnection::CPSignalStateE:
// case AmtronCompact20ModbusRtuConnection::CPSignalStateF:
// qCWarning(dcMennekes()) << "Wallbox in Error state!";
// thing->setStateValue(amtronCompact20PluggedInStateTypeId, false);
// thing->setStateValue(amtronCompact20ChargingStateTypeId, false);
// break;
// }
});
connect(compact20Connection, &AmtronCompact20ModbusRtuConnection::evseStateChanged, thing, [thing](AmtronCompact20ModbusRtuConnection::EvseState evseState){
@ -951,9 +990,3 @@ void IntegrationPluginMennekes::setupAmtronCompact20Connection(ThingSetupInfo *i
});
}
bool IntegrationPluginMennekes::ensureAmtronECUVersion(AmtronECUModbusTcpConnection *connection, const QString &version)
{
QByteArray deviceVersion = QByteArray::fromHex(QByteArray::number(connection->firmwareVersion(), 16));
return deviceVersion >= version;
}

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2022, nymea GmbH
* Copyright 2013 - 2023, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
@ -37,7 +37,7 @@
#include "extern-plugininfo.h"
#include "amtronecumodbustcpconnection.h"
#include "amtronecu.h"
#include "amtronhcc3modbustcpconnection.h"
#include "amtroncompact20modbusrtuconnection.h"
@ -66,10 +66,8 @@ private:
void setupAmtronHCC3Connection(ThingSetupInfo *info);
void setupAmtronCompact20Connection(ThingSetupInfo *info);
bool ensureAmtronECUVersion(AmtronECUModbusTcpConnection *connection, const QString &version);
PluginTimer *m_pluginTimer = nullptr;
QHash<Thing *, AmtronECUModbusTcpConnection *> m_amtronECUConnections;
QHash<Thing *, AmtronECU *> m_amtronECUConnections;
QHash<Thing *, AmtronHCC3ModbusTcpConnection *> m_amtronHCC3Connections;
QHash<Thing *, AmtronCompact20ModbusRtuConnection *> m_amtronCompact20Connections;
QHash<Thing *, NetworkDeviceMonitor *> m_monitors;

View File

@ -104,6 +104,14 @@
"type": "double",
"unit": "KiloWattHour",
"defaultValue": 0
},
{
"id": "00813b6e-bd93-4728-a11a-aac6298503e6",
"name": "firmwareVersion",
"displayName": "Firmware version",
"type": "QString",
"defaultValue": "",
"cached": true
}
]
},

View File

@ -10,12 +10,14 @@ include(../modbus.pri)
HEADERS += \
amtroncompact20discovery.h \
amtronecu.h \
amtronecudiscovery.h \
amtronhcc3discovery.h \
integrationpluginmennekes.h
SOURCES += \
amtroncompact20discovery.cpp \
amtronecu.cpp \
amtronecudiscovery.cpp \
amtronhcc3discovery.cpp \
integrationpluginmennekes.cpp