diff --git a/debian/control b/debian/control index 8738471..0ab3eb2 100644 --- a/debian/control +++ b/debian/control @@ -122,6 +122,14 @@ Description: nymea integration plugin for M-TEC heat pumps This package contains the nymea integration plugin for M-TEC heat pumps. +Package: nymea-plugin-schrack +Architecture: any +Depends: ${shlibs:Depends}, + ${misc:Depends}, +Description: nymea integration plugin for Schrack wallboxes + This package contains the nymea integration plugin for Schrack wallboxes. + + Package: nymea-plugin-sunspec Architecture: any Depends: ${shlibs:Depends}, diff --git a/debian/nymea-plugin-schrack.install.in b/debian/nymea-plugin-schrack.install.in new file mode 100644 index 0000000..e713ba5 --- /dev/null +++ b/debian/nymea-plugin-schrack.install.in @@ -0,0 +1,2 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginschrack.so +schrack/translations/*qm usr/share/nymea/translations/ diff --git a/nymea-plugins-modbus.pro b/nymea-plugins-modbus.pro index 717131d..9f6bdcb 100644 --- a/nymea-plugins-modbus.pro +++ b/nymea-plugins-modbus.pro @@ -14,6 +14,7 @@ PLUGIN_DIRS = \ modbuscommander \ mtec \ mypv \ + schrack \ sunspec \ unipi \ wallbe \ diff --git a/plugins.pri b/plugins.pri index f180f53..ee37061 100644 --- a/plugins.pri +++ b/plugins.pri @@ -15,3 +15,5 @@ isEmpty(PLUGIN_PRI) { # message("Using $$PLUGIN_PRI") include($$PLUGIN_PRI) } + +top_srcdir=$${PWD} diff --git a/schrack/cion-registers.json b/schrack/cion-registers.json new file mode 100644 index 0000000..66d9221 --- /dev/null +++ b/schrack/cion-registers.json @@ -0,0 +1,132 @@ +{ + "protocol": "RTU", + "endianness": "BigEndian", + "blocks": [ + { + "id": "e3", + "readSchedule": "update", + "registers": [ + { + "id": "currentChargingCurrentE3", + "address": 126, + "size": 1, + "type": "uint16", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "Current charging Ampere", + "unit": "A", + "defaultValue": 6, + "access": "R" + }, + { + "id": "maxChargingCurrentE3", + "address": 127, + "size": 1, + "type": "uint16", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "Maximum charging current of connected cable", + "unit": "A", + "defaultValue": 32, + "access": "R" + } + ] + } + ], + "registers": [ + { + "id": "chargingEnabled", + "address": 100, + "size": 1, + "type": "uint16", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "Charging enabled", + "defaultValue": 0, + "access": "RW" + }, + { + "id": "chargingCurrentSetpoint", + "address": 101, + "size": 1, + "type": "uint16", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "Allowed charging current", + "unit": "A", + "defaultValue": 6, + "access": "RW" + }, + { + "id": "statusBits", + "address": 121, + "size": 1, + "type": "uint16", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "Maximum charging current", + "defaultValue": 0, + "access": "R" + }, + { + "id": "chargingDuration", + "address": 151, + "size": 2, + "type": "uint32", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "Charging duration", + "unit": "ms", + "defaultValue": 6, + "access": "R" + }, + { + "id": "pluggedInDuration", + "address": 153, + "size": 2, + "type": "uint32", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "Plugged in duration", + "unit": "ms", + "defaultValue": 6, + "access": "R" + }, + { + "id": "u1Voltage", + "address": 167, + "size": 1, + "type": "uint16", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "U1 voltage", + "unit": "V", + "defaultValue": 32, + "access": "R" + }, + { + "id": "gridVoltage", + "address": 302, + "size": 1, + "type": "uint16", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "Voltage of the power supply grid", + "unit": "V", + "defaultValue": 0, + "access": "R" + }, + { + "id": "minChargingCurrent", + "address": 507, + "size": 1, + "type": "uint16", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "Minimum charging current", + "unit": "A", + "defaultValue": 6, + "access": "R" + } + ] +} diff --git a/schrack/cion-registers.pdf b/schrack/cion-registers.pdf new file mode 100644 index 0000000..0b42698 Binary files /dev/null and b/schrack/cion-registers.pdf differ diff --git a/schrack/cionmodbusrtuconnection.cpp b/schrack/cionmodbusrtuconnection.cpp new file mode 100644 index 0000000..2384519 --- /dev/null +++ b/schrack/cionmodbusrtuconnection.cpp @@ -0,0 +1,469 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, nymea GmbH +* Contact: contact@nymea.io +* +* This fileDescriptor 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 . +* +* 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 "cionmodbusrtuconnection.h" +#include "loggingcategories.h" + +NYMEA_LOGGING_CATEGORY(dcCionModbusRtuConnection, "CionModbusRtuConnection") + +CionModbusRtuConnection::CionModbusRtuConnection(ModbusRtuMaster *modbusRtuMaster, quint16 slaveId, QObject *parent) : + QObject(parent), + m_modbusRtuMaster(modbusRtuMaster), + m_slaveId(slaveId) +{ + +} + +ModbusRtuMaster *CionModbusRtuConnection::modbusRtuMaster() const +{ + return m_modbusRtuMaster; +} +quint16 CionModbusRtuConnection::slaveId() const +{ + return m_slaveId; +} +quint16 CionModbusRtuConnection::chargingEnabled() const +{ + return m_chargingEnabled; +} + +ModbusRtuReply *CionModbusRtuConnection::setChargingEnabled(quint16 chargingEnabled) +{ + QVector values = ModbusDataUtils::convertFromUInt16(chargingEnabled); + qCDebug(dcCionModbusRtuConnection()) << "--> Write \"Charging enabled\" register:" << 100 << "size:" << 1 << values; + return m_modbusRtuMaster->writeHoldingRegisters(m_slaveId, 100, values); +} + +quint16 CionModbusRtuConnection::chargingCurrentSetpoint() const +{ + return m_chargingCurrentSetpoint; +} + +ModbusRtuReply *CionModbusRtuConnection::setChargingCurrentSetpoint(quint16 chargingCurrentSetpoint) +{ + QVector values = ModbusDataUtils::convertFromUInt16(chargingCurrentSetpoint); + qCDebug(dcCionModbusRtuConnection()) << "--> Write \"Allowed charging current\" register:" << 101 << "size:" << 1 << values; + return m_modbusRtuMaster->writeHoldingRegisters(m_slaveId, 101, values); +} + +quint16 CionModbusRtuConnection::statusBits() const +{ + return m_statusBits; +} + +quint32 CionModbusRtuConnection::chargingDuration() const +{ + return m_chargingDuration; +} + +quint32 CionModbusRtuConnection::pluggedInDuration() const +{ + return m_pluggedInDuration; +} + +quint16 CionModbusRtuConnection::u1Voltage() const +{ + return m_u1Voltage; +} + +quint16 CionModbusRtuConnection::gridVoltage() const +{ + return m_gridVoltage; +} + +quint16 CionModbusRtuConnection::minChargingCurrent() const +{ + return m_minChargingCurrent; +} + +quint16 CionModbusRtuConnection::currentChargingCurrentE3() const +{ + return m_currentChargingCurrentE3; +} + +quint16 CionModbusRtuConnection::maxChargingCurrentE3() const +{ + return m_maxChargingCurrentE3; +} + +void CionModbusRtuConnection::initialize() +{ + // No init registers defined. Nothing to be done and we are finished. + emit initializationFinished(); +} + +void CionModbusRtuConnection::update() +{ + updateChargingEnabled(); + updateChargingCurrentSetpoint(); + updateStatusBits(); + updateChargingDuration(); + updatePluggedInDuration(); + updateU1Voltage(); + updateGridVoltage(); + updateMinChargingCurrent(); + updateE3Block(); +} + +void CionModbusRtuConnection::updateChargingEnabled() +{ + // Update registers from Charging enabled + qCDebug(dcCionModbusRtuConnection()) << "--> Read \"Charging enabled\" register:" << 100 << "size:" << 1; + ModbusRtuReply *reply = readChargingEnabled(); + if (reply) { + if (!reply->isFinished()) { + connect(reply, &ModbusRtuReply::finished, this, [this, reply](){ + if (reply->error() == ModbusRtuReply::NoError) { + QVector values = reply->result(); + qCDebug(dcCionModbusRtuConnection()) << "<-- Response from \"Charging enabled\" register" << 100 << "size:" << 1 << values; + quint16 receivedChargingEnabled = ModbusDataUtils::convertToUInt16(values); + if (m_chargingEnabled != receivedChargingEnabled) { + m_chargingEnabled = receivedChargingEnabled; + emit chargingEnabledChanged(m_chargingEnabled); + } + } + }); + + connect(reply, &ModbusRtuReply::errorOccurred, this, [reply] (ModbusRtuReply::Error error){ + qCWarning(dcCionModbusRtuConnection()) << "ModbusRtu reply error occurred while updating \"Charging enabled\" registers" << error << reply->errorString(); + emit reply->finished(); + }); + } + } else { + qCWarning(dcCionModbusRtuConnection()) << "Error occurred while reading \"Charging enabled\" registers"; + } +} + +void CionModbusRtuConnection::updateChargingCurrentSetpoint() +{ + // Update registers from Allowed charging current + qCDebug(dcCionModbusRtuConnection()) << "--> Read \"Allowed charging current\" register:" << 101 << "size:" << 1; + ModbusRtuReply *reply = readChargingCurrentSetpoint(); + if (reply) { + if (!reply->isFinished()) { + connect(reply, &ModbusRtuReply::finished, this, [this, reply](){ + if (reply->error() == ModbusRtuReply::NoError) { + QVector values = reply->result(); + qCDebug(dcCionModbusRtuConnection()) << "<-- Response from \"Allowed charging current\" register" << 101 << "size:" << 1 << values; + quint16 receivedChargingCurrentSetpoint = ModbusDataUtils::convertToUInt16(values); + if (m_chargingCurrentSetpoint != receivedChargingCurrentSetpoint) { + m_chargingCurrentSetpoint = receivedChargingCurrentSetpoint; + emit chargingCurrentSetpointChanged(m_chargingCurrentSetpoint); + } + } + }); + + connect(reply, &ModbusRtuReply::errorOccurred, this, [reply] (ModbusRtuReply::Error error){ + qCWarning(dcCionModbusRtuConnection()) << "ModbusRtu reply error occurred while updating \"Allowed charging current\" registers" << error << reply->errorString(); + emit reply->finished(); + }); + } + } else { + qCWarning(dcCionModbusRtuConnection()) << "Error occurred while reading \"Allowed charging current\" registers"; + } +} + +void CionModbusRtuConnection::updateStatusBits() +{ + // Update registers from Maximum charging current + qCDebug(dcCionModbusRtuConnection()) << "--> Read \"Maximum charging current\" register:" << 121 << "size:" << 1; + ModbusRtuReply *reply = readStatusBits(); + if (reply) { + if (!reply->isFinished()) { + connect(reply, &ModbusRtuReply::finished, this, [this, reply](){ + if (reply->error() == ModbusRtuReply::NoError) { + QVector values = reply->result(); + qCDebug(dcCionModbusRtuConnection()) << "<-- Response from \"Maximum charging current\" register" << 121 << "size:" << 1 << values; + quint16 receivedStatusBits = ModbusDataUtils::convertToUInt16(values); + if (m_statusBits != receivedStatusBits) { + m_statusBits = receivedStatusBits; + emit statusBitsChanged(m_statusBits); + } + } + }); + + connect(reply, &ModbusRtuReply::errorOccurred, this, [reply] (ModbusRtuReply::Error error){ + qCWarning(dcCionModbusRtuConnection()) << "ModbusRtu reply error occurred while updating \"Maximum charging current\" registers" << error << reply->errorString(); + emit reply->finished(); + }); + } + } else { + qCWarning(dcCionModbusRtuConnection()) << "Error occurred while reading \"Maximum charging current\" registers"; + } +} + +void CionModbusRtuConnection::updateChargingDuration() +{ + // Update registers from Charging duration + qCDebug(dcCionModbusRtuConnection()) << "--> Read \"Charging duration\" register:" << 151 << "size:" << 2; + ModbusRtuReply *reply = readChargingDuration(); + if (reply) { + if (!reply->isFinished()) { + connect(reply, &ModbusRtuReply::finished, this, [this, reply](){ + if (reply->error() == ModbusRtuReply::NoError) { + QVector values = reply->result(); + qCDebug(dcCionModbusRtuConnection()) << "<-- Response from \"Charging duration\" register" << 151 << "size:" << 2 << values; + quint32 receivedChargingDuration = ModbusDataUtils::convertToUInt32(values, ModbusDataUtils::ByteOrderBigEndian); + if (m_chargingDuration != receivedChargingDuration) { + m_chargingDuration = receivedChargingDuration; + emit chargingDurationChanged(m_chargingDuration); + } + } + }); + + connect(reply, &ModbusRtuReply::errorOccurred, this, [reply] (ModbusRtuReply::Error error){ + qCWarning(dcCionModbusRtuConnection()) << "ModbusRtu reply error occurred while updating \"Charging duration\" registers" << error << reply->errorString(); + emit reply->finished(); + }); + } + } else { + qCWarning(dcCionModbusRtuConnection()) << "Error occurred while reading \"Charging duration\" registers"; + } +} + +void CionModbusRtuConnection::updatePluggedInDuration() +{ + // Update registers from Plugged in duration + qCDebug(dcCionModbusRtuConnection()) << "--> Read \"Plugged in duration\" register:" << 153 << "size:" << 2; + ModbusRtuReply *reply = readPluggedInDuration(); + if (reply) { + if (!reply->isFinished()) { + connect(reply, &ModbusRtuReply::finished, this, [this, reply](){ + if (reply->error() == ModbusRtuReply::NoError) { + QVector values = reply->result(); + qCDebug(dcCionModbusRtuConnection()) << "<-- Response from \"Plugged in duration\" register" << 153 << "size:" << 2 << values; + quint32 receivedPluggedInDuration = ModbusDataUtils::convertToUInt32(values, ModbusDataUtils::ByteOrderBigEndian); + if (m_pluggedInDuration != receivedPluggedInDuration) { + m_pluggedInDuration = receivedPluggedInDuration; + emit pluggedInDurationChanged(m_pluggedInDuration); + } + } + }); + + connect(reply, &ModbusRtuReply::errorOccurred, this, [reply] (ModbusRtuReply::Error error){ + qCWarning(dcCionModbusRtuConnection()) << "ModbusRtu reply error occurred while updating \"Plugged in duration\" registers" << error << reply->errorString(); + emit reply->finished(); + }); + } + } else { + qCWarning(dcCionModbusRtuConnection()) << "Error occurred while reading \"Plugged in duration\" registers"; + } +} + +void CionModbusRtuConnection::updateU1Voltage() +{ + // Update registers from U1 voltage + qCDebug(dcCionModbusRtuConnection()) << "--> Read \"U1 voltage\" register:" << 167 << "size:" << 1; + ModbusRtuReply *reply = readU1Voltage(); + if (reply) { + if (!reply->isFinished()) { + connect(reply, &ModbusRtuReply::finished, this, [this, reply](){ + if (reply->error() == ModbusRtuReply::NoError) { + QVector values = reply->result(); + qCDebug(dcCionModbusRtuConnection()) << "<-- Response from \"U1 voltage\" register" << 167 << "size:" << 1 << values; + quint16 receivedU1Voltage = ModbusDataUtils::convertToUInt16(values); + if (m_u1Voltage != receivedU1Voltage) { + m_u1Voltage = receivedU1Voltage; + emit u1VoltageChanged(m_u1Voltage); + } + } + }); + + connect(reply, &ModbusRtuReply::errorOccurred, this, [reply] (ModbusRtuReply::Error error){ + qCWarning(dcCionModbusRtuConnection()) << "ModbusRtu reply error occurred while updating \"U1 voltage\" registers" << error << reply->errorString(); + emit reply->finished(); + }); + } + } else { + qCWarning(dcCionModbusRtuConnection()) << "Error occurred while reading \"U1 voltage\" registers"; + } +} + +void CionModbusRtuConnection::updateGridVoltage() +{ + // Update registers from Voltage of the power supply grid + qCDebug(dcCionModbusRtuConnection()) << "--> Read \"Voltage of the power supply grid\" register:" << 302 << "size:" << 1; + ModbusRtuReply *reply = readGridVoltage(); + if (reply) { + if (!reply->isFinished()) { + connect(reply, &ModbusRtuReply::finished, this, [this, reply](){ + if (reply->error() == ModbusRtuReply::NoError) { + QVector values = reply->result(); + qCDebug(dcCionModbusRtuConnection()) << "<-- Response from \"Voltage of the power supply grid\" register" << 302 << "size:" << 1 << values; + quint16 receivedGridVoltage = ModbusDataUtils::convertToUInt16(values); + if (m_gridVoltage != receivedGridVoltage) { + m_gridVoltage = receivedGridVoltage; + emit gridVoltageChanged(m_gridVoltage); + } + } + }); + + connect(reply, &ModbusRtuReply::errorOccurred, this, [reply] (ModbusRtuReply::Error error){ + qCWarning(dcCionModbusRtuConnection()) << "ModbusRtu reply error occurred while updating \"Voltage of the power supply grid\" registers" << error << reply->errorString(); + emit reply->finished(); + }); + } + } else { + qCWarning(dcCionModbusRtuConnection()) << "Error occurred while reading \"Voltage of the power supply grid\" registers"; + } +} + +void CionModbusRtuConnection::updateMinChargingCurrent() +{ + // Update registers from Minimum charging current + qCDebug(dcCionModbusRtuConnection()) << "--> Read \"Minimum charging current\" register:" << 507 << "size:" << 1; + ModbusRtuReply *reply = readMinChargingCurrent(); + if (reply) { + if (!reply->isFinished()) { + connect(reply, &ModbusRtuReply::finished, this, [this, reply](){ + if (reply->error() == ModbusRtuReply::NoError) { + QVector values = reply->result(); + qCDebug(dcCionModbusRtuConnection()) << "<-- Response from \"Minimum charging current\" register" << 507 << "size:" << 1 << values; + quint16 receivedMinChargingCurrent = ModbusDataUtils::convertToUInt16(values); + if (m_minChargingCurrent != receivedMinChargingCurrent) { + m_minChargingCurrent = receivedMinChargingCurrent; + emit minChargingCurrentChanged(m_minChargingCurrent); + } + } + }); + + connect(reply, &ModbusRtuReply::errorOccurred, this, [reply] (ModbusRtuReply::Error error){ + qCWarning(dcCionModbusRtuConnection()) << "ModbusRtu reply error occurred while updating \"Minimum charging current\" registers" << error << reply->errorString(); + emit reply->finished(); + }); + } + } else { + qCWarning(dcCionModbusRtuConnection()) << "Error occurred while reading \"Minimum charging current\" registers"; + } +} + +void CionModbusRtuConnection::updateE3Block() +{ + // Update register block "e3" + qCDebug(dcCionModbusRtuConnection()) << "--> Read block \"e3\" registers from:" << 126 << "size:" << 2; + ModbusRtuReply *reply = m_modbusRtuMaster->readHoldingRegister(m_slaveId, 126, 2); + if (reply) { + if (!reply->isFinished()) { + connect(reply, &ModbusRtuReply::finished, this, [this, reply](){ + if (reply->error() == ModbusRtuReply::NoError) { + QVector blockValues = reply->result(); + QVector values; + qCDebug(dcCionModbusRtuConnection()) << "<-- Response from reading block \"e3\" register" << 126 << "size:" << 2 << blockValues; + values = blockValues.mid(0, 1); + quint16 receivedCurrentChargingCurrentE3 = ModbusDataUtils::convertToUInt16(values); + if (m_currentChargingCurrentE3 != receivedCurrentChargingCurrentE3) { + m_currentChargingCurrentE3 = receivedCurrentChargingCurrentE3; + emit currentChargingCurrentE3Changed(m_currentChargingCurrentE3); + } + + values = blockValues.mid(1, 1); + quint16 receivedMaxChargingCurrentE3 = ModbusDataUtils::convertToUInt16(values); + if (m_maxChargingCurrentE3 != receivedMaxChargingCurrentE3) { + m_maxChargingCurrentE3 = receivedMaxChargingCurrentE3; + emit maxChargingCurrentE3Changed(m_maxChargingCurrentE3); + } + + } + }); + + connect(reply, &ModbusRtuReply::errorOccurred, this, [reply] (ModbusRtuReply::Error error){ + qCWarning(dcCionModbusRtuConnection()) << "ModbusRtu reply error occurred while updating block \"e3\" registers" << error << reply->errorString(); + emit reply->finished(); + }); + } + } else { + qCWarning(dcCionModbusRtuConnection()) << "Error occurred while reading block \"e3\" registers"; + } +} + +ModbusRtuReply *CionModbusRtuConnection::readChargingEnabled() +{ + return m_modbusRtuMaster->readHoldingRegister(m_slaveId, 100, 1); +} + +ModbusRtuReply *CionModbusRtuConnection::readChargingCurrentSetpoint() +{ + return m_modbusRtuMaster->readHoldingRegister(m_slaveId, 101, 1); +} + +ModbusRtuReply *CionModbusRtuConnection::readStatusBits() +{ + return m_modbusRtuMaster->readHoldingRegister(m_slaveId, 121, 1); +} + +ModbusRtuReply *CionModbusRtuConnection::readChargingDuration() +{ + return m_modbusRtuMaster->readHoldingRegister(m_slaveId, 151, 2); +} + +ModbusRtuReply *CionModbusRtuConnection::readPluggedInDuration() +{ + return m_modbusRtuMaster->readHoldingRegister(m_slaveId, 153, 2); +} + +ModbusRtuReply *CionModbusRtuConnection::readU1Voltage() +{ + return m_modbusRtuMaster->readHoldingRegister(m_slaveId, 167, 1); +} + +ModbusRtuReply *CionModbusRtuConnection::readGridVoltage() +{ + return m_modbusRtuMaster->readHoldingRegister(m_slaveId, 302, 1); +} + +ModbusRtuReply *CionModbusRtuConnection::readMinChargingCurrent() +{ + return m_modbusRtuMaster->readHoldingRegister(m_slaveId, 507, 1); +} + +void CionModbusRtuConnection::verifyInitFinished() +{ + if (m_pendingInitReplies.isEmpty()) { + qCDebug(dcCionModbusRtuConnection()) << "Initialization finished of CionModbusRtuConnection"; + emit initializationFinished(); + } +} + +QDebug operator<<(QDebug debug, CionModbusRtuConnection *cionModbusRtuConnection) +{ + debug.nospace().noquote() << "CionModbusRtuConnection(" << cionModbusRtuConnection->modbusRtuMaster()->modbusUuid().toString() << ", " << cionModbusRtuConnection->modbusRtuMaster()->serialPort() << ", slave ID:" << cionModbusRtuConnection->slaveId() << ")" << "\n"; + debug.nospace().noquote() << " - Charging enabled:" << cionModbusRtuConnection->chargingEnabled() << "\n"; + debug.nospace().noquote() << " - Allowed charging current:" << cionModbusRtuConnection->chargingCurrentSetpoint() << " [A]" << "\n"; + debug.nospace().noquote() << " - Maximum charging current:" << cionModbusRtuConnection->statusBits() << "\n"; + debug.nospace().noquote() << " - Charging duration:" << cionModbusRtuConnection->chargingDuration() << " [ms]" << "\n"; + debug.nospace().noquote() << " - Plugged in duration:" << cionModbusRtuConnection->pluggedInDuration() << " [ms]" << "\n"; + debug.nospace().noquote() << " - U1 voltage:" << cionModbusRtuConnection->u1Voltage() << " [V]" << "\n"; + debug.nospace().noquote() << " - Voltage of the power supply grid:" << cionModbusRtuConnection->gridVoltage() << " [V]" << "\n"; + debug.nospace().noquote() << " - Minimum charging current:" << cionModbusRtuConnection->minChargingCurrent() << " [A]" << "\n"; + debug.nospace().noquote() << " - Current charging Ampere:" << cionModbusRtuConnection->currentChargingCurrentE3() << " [A]" << "\n"; + debug.nospace().noquote() << " - Maximum charging current of connected cable:" << cionModbusRtuConnection->maxChargingCurrentE3() << " [A]" << "\n"; + return debug.quote().space(); +} + diff --git a/schrack/cionmodbusrtuconnection.h b/schrack/cionmodbusrtuconnection.h new file mode 100644 index 0000000..5efab18 --- /dev/null +++ b/schrack/cionmodbusrtuconnection.h @@ -0,0 +1,160 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, nymea GmbH +* Contact: contact@nymea.io +* +* This fileDescriptor 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 . +* +* 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 CIONMODBUSRTUCONNECTION_H +#define CIONMODBUSRTUCONNECTION_H + +#include + +#include "../modbus/modbusdatautils.h" +#include + +class CionModbusRtuConnection : public QObject +{ + Q_OBJECT +public: + enum Registers { + RegisterChargingEnabled = 100, + RegisterChargingCurrentSetpoint = 101, + RegisterStatusBits = 121, + RegisterCurrentChargingCurrentE3 = 126, + RegisterMaxChargingCurrentE3 = 127, + RegisterChargingDuration = 151, + RegisterPluggedInDuration = 153, + RegisterU1Voltage = 167, + RegisterGridVoltage = 302, + RegisterMinChargingCurrent = 507 + }; + Q_ENUM(Registers) + + explicit CionModbusRtuConnection(ModbusRtuMaster *modbusRtuMaster, quint16 slaveId, QObject *parent = nullptr); + ~CionModbusRtuConnection() = default; + + ModbusRtuMaster *modbusRtuMaster() const; + quint16 slaveId() const; + + /* Charging enabled - Address: 100, Size: 1 */ + quint16 chargingEnabled() const; + ModbusRtuReply *setChargingEnabled(quint16 chargingEnabled); + + /* Allowed charging current [A] - Address: 101, Size: 1 */ + quint16 chargingCurrentSetpoint() const; + ModbusRtuReply *setChargingCurrentSetpoint(quint16 chargingCurrentSetpoint); + + /* Maximum charging current - Address: 121, Size: 1 */ + quint16 statusBits() const; + + /* Charging duration [ms] - Address: 151, Size: 2 */ + quint32 chargingDuration() const; + + /* Plugged in duration [ms] - Address: 153, Size: 2 */ + quint32 pluggedInDuration() const; + + /* U1 voltage [V] - Address: 167, Size: 1 */ + quint16 u1Voltage() const; + + /* Voltage of the power supply grid [V] - Address: 302, Size: 1 */ + quint16 gridVoltage() const; + + /* Minimum charging current [A] - Address: 507, Size: 1 */ + quint16 minChargingCurrent() const; + + /* Current charging Ampere [A] - Address: 126, Size: 1 */ + quint16 currentChargingCurrentE3() const; + + /* Maximum charging current of connected cable [A] - Address: 127, Size: 1 */ + quint16 maxChargingCurrentE3() const; + + /* Read block from start addess 126 with size of 2 registers containing following 2 properties: + - Current charging Ampere [A] - Address: 126, Size: 1 + - Maximum charging current of connected cable [A] - Address: 127, Size: 1 + */ + void updateE3Block(); + + void updateChargingEnabled(); + void updateChargingCurrentSetpoint(); + void updateStatusBits(); + void updateChargingDuration(); + void updatePluggedInDuration(); + void updateU1Voltage(); + void updateGridVoltage(); + void updateMinChargingCurrent(); + + virtual void initialize(); + virtual void update(); + +signals: + void initializationFinished(); + + void chargingEnabledChanged(quint16 chargingEnabled); + void chargingCurrentSetpointChanged(quint16 chargingCurrentSetpoint); + void statusBitsChanged(quint16 statusBits); + void chargingDurationChanged(quint32 chargingDuration); + void pluggedInDurationChanged(quint32 pluggedInDuration); + void u1VoltageChanged(quint16 u1Voltage); + void gridVoltageChanged(quint16 gridVoltage); + void minChargingCurrentChanged(quint16 minChargingCurrent); + void currentChargingCurrentE3Changed(quint16 currentChargingCurrentE3); + void maxChargingCurrentE3Changed(quint16 maxChargingCurrentE3); + +protected: + ModbusRtuReply *readChargingEnabled(); + ModbusRtuReply *readChargingCurrentSetpoint(); + ModbusRtuReply *readStatusBits(); + ModbusRtuReply *readChargingDuration(); + ModbusRtuReply *readPluggedInDuration(); + ModbusRtuReply *readU1Voltage(); + ModbusRtuReply *readGridVoltage(); + ModbusRtuReply *readMinChargingCurrent(); + + quint16 m_chargingEnabled = 0; + quint16 m_chargingCurrentSetpoint = 6; + quint16 m_statusBits = 0; + quint32 m_chargingDuration = 6; + quint32 m_pluggedInDuration = 6; + quint16 m_u1Voltage = 32; + quint16 m_gridVoltage = 0; + quint16 m_minChargingCurrent = 6; + quint16 m_currentChargingCurrentE3 = 6; + quint16 m_maxChargingCurrentE3 = 32; + +private: + ModbusRtuMaster *m_modbusRtuMaster = nullptr; + quint16 m_slaveId = 1; + QVector m_pendingInitReplies; + + void verifyInitFinished(); + + +}; + +QDebug operator<<(QDebug debug, CionModbusRtuConnection *cionModbusRtuConnection); + +#endif // CIONMODBUSRTUCONNECTION_H diff --git a/schrack/integrationpluginschrack.cpp b/schrack/integrationpluginschrack.cpp new file mode 100644 index 0000000..22096ad --- /dev/null +++ b/schrack/integrationpluginschrack.cpp @@ -0,0 +1,291 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2021, 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 . +* +* 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 "integrationpluginschrack.h" +#include "plugininfo.h" + +IntegrationPluginSchrack::IntegrationPluginSchrack() +{ +} + +void IntegrationPluginSchrack::init() +{ +// connect(hardwareManager()->modbusRtuResource(), &ModbusRtuHardwareResource::modbusRtuMasterRemoved, this, [=] (const QUuid &modbusUuid){ +// qCDebug(dcEnergyMeters()) << "Modbus RTU master has been removed" << modbusUuid.toString(); + +// foreach (Thing *thing, myThings()) { +// if (m_modbusUuidParamTypeIds.contains(thing->thingClassId())) { +// if (thing->paramValue(m_modbusUuidParamTypeIds.value(thing->thingClassId())) == modbusUuid) { +// qCWarning(dcEnergyMeters()) << "Modbus RTU hardware resource removed for" << thing << ". The thing will not be functional any more until a new resource has been configured for it."; +// thing->setStateValue(m_connectionStateTypeIds[thing->thingClassId()], false); + +// if (thing->thingClassId() == sdm630ThingClassId) { +// delete m_sdmConnections.take(thing); +// } else if (thing->thingClassId() == pro380ThingClassId) { +// delete m_ineproConnections.take(thing); +// } +// } +// } +// } +// }); +} + +void IntegrationPluginSchrack::discoverThings(ThingDiscoveryInfo *info) +{ + qCDebug(dcSchrack()) << "Discovering modbus RTU resources..."; + if (hardwareManager()->modbusRtuResource()->modbusRtuMasters().isEmpty()) { + info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("No Modbus RTU interface available. Please set up a Modbus RTU interface first.")); + return; + } + + uint slaveAddress = info->params().paramValue(cionDiscoverySlaveAddressParamTypeId).toUInt(); + if (slaveAddress > 254 || slaveAddress == 0) { + info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("The Modbus slave address must be a value between 1 and 254.")); + return; + } + + foreach (ModbusRtuMaster *modbusMaster, hardwareManager()->modbusRtuResource()->modbusRtuMasters()) { + qCDebug(dcSchrack()) << "Found RTU master resource" << modbusMaster << "connected" << modbusMaster->connected(); + if (!modbusMaster->connected()) + continue; + + ThingDescriptor descriptor(info->thingClassId(), "i-CHARGE CION", QString::number(slaveAddress) + " " + modbusMaster->serialPort()); + ParamList params; + params << Param(cionThingSlaveAddressParamTypeId, slaveAddress); + params << Param(cionThingModbusMasterUuidParamTypeId, modbusMaster->modbusUuid()); + descriptor.setParams(params); + info->addThingDescriptor(descriptor); + } + + info->finish(Thing::ThingErrorNoError); +} + +void IntegrationPluginSchrack::setupThing(ThingSetupInfo *info) +{ + Thing *thing = info->thing(); + qCDebug(dcSchrack()) << "Setup thing" << thing << thing->params(); + + uint address = thing->paramValue(cionThingSlaveAddressParamTypeId).toUInt(); + if (address > 254 || address == 0) { + qCWarning(dcSchrack()) << "Setup failed, slave address is not valid" << address; + info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("The Modbus address not valid. It must be a value between 1 and 254.")); + return; + } + + QUuid uuid = thing->paramValue(cionThingModbusMasterUuidParamTypeId).toUuid(); + if (!hardwareManager()->modbusRtuResource()->hasModbusRtuMaster(uuid)) { + qCWarning(dcSchrack()) << "Setup failed, hardware manager not available"; + info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("The Modbus RTU resource is not available.")); + return; + } + + if (m_cionConnections.contains(thing)) { + qCDebug(dcSchrack()) << "Already have a CION connection for this thing. Cleaning up old connection and initializing new one..."; + delete m_cionConnections.take(thing); + } + + CionModbusRtuConnection *cionConnection = new CionModbusRtuConnection(hardwareManager()->modbusRtuResource()->getModbusRtuMaster(uuid), address, this); + connect(cionConnection->modbusRtuMaster(), &ModbusRtuMaster::connectedChanged, thing, [=](bool connected){ + if (connected) { + qCDebug(dcSchrack()) << "Modbus RTU resource connected" << thing << cionConnection->modbusRtuMaster()->serialPort(); + } else { + qCWarning(dcSchrack()) << "Modbus RTU resource disconnected" << thing << cionConnection->modbusRtuMaster()->serialPort(); + } + }); + + connect(cionConnection, &CionModbusRtuConnection::chargingEnabledChanged, thing, [=](quint16 charging){ + qCDebug(dcSchrack()) << "Charging enabled changed:" << charging; + thing->setStateValue(cionPowerStateTypeId, charging == 1); + thing->setStateValue(cionConnectedStateTypeId, true); + finishAction(cionPowerStateTypeId); + }); + + // We can write chargingCurrentSetpoint to the preferred charging we want, and the wallbox will take it, + // however, it may not necessarily *do* it, but will give us the actual value it uses in currentChargingCurrentE3 + // We'll use that for setting our state, just monitoring this one on the logs + connect(cionConnection, &CionModbusRtuConnection::chargingCurrentSetpointChanged, thing, [=](quint16 chargingCurrentSetpoint){ + qCDebug(dcSchrack()) << "Charging current setpoint changed:" << chargingCurrentSetpoint; +// thing->setStateValue(cionMaxChargingCurrentStateTypeId, chargingCurrentSetpoint); +// thing->setStateValue(cionConnectedStateTypeId, true); +// finishAction(cionMaxChargingCurrentStateTypeId); + }); + + // + connect(cionConnection, &CionModbusRtuConnection::currentChargingCurrentE3Changed, thing, [=](quint16 currentChargingCurrentE3){ + qCDebug(dcSchrack()) << "Current charging current E3 current changed:" << currentChargingCurrentE3; + thing->setStateValue(cionMaxChargingCurrentStateTypeId, currentChargingCurrentE3); + thing->setStateValue(cionConnectedStateTypeId, true); + finishAction(cionMaxChargingCurrentStateTypeId); + }); + + // The maxChargingCurrentE3 takes into account the DIP switches and connected cable, so this is effectively + // our maximum. However, it will go to 0 when unplugged, which is odd, so we'll ignore 0 values. + connect(cionConnection, &CionModbusRtuConnection::maxChargingCurrentE3Changed, thing, [=](quint16 maxChargingCurrentE3){ + qCDebug(dcSchrack()) << "Maximum charging current E3 current changed:" << maxChargingCurrentE3; + if (maxChargingCurrentE3 != 0) { + thing->setStateMaxValue(cionMaxChargingCurrentStateTypeId, maxChargingCurrentE3); + thing->setStateValue(cionConnectedStateTypeId, true); + } + }); + + connect(cionConnection, &CionModbusRtuConnection::statusBitsChanged, thing, [=](quint16 statusBits){ + qCDebug(dcSchrack()) << "Status bits changed:" << statusBits; + thing->setStateValue(cionConnectedStateTypeId, true); + }); + + connect(cionConnection, &CionModbusRtuConnection::minChargingCurrentChanged, thing, [=](quint16 minChargingCurrent){ + qCDebug(dcSchrack()) << "Minimum charging current changed:" << minChargingCurrent; + thing->setStateMinValue(cionMaxChargingCurrentStateTypeId, minChargingCurrent); + thing->setStateValue(cionConnectedStateTypeId, true); + }); + + connect(cionConnection, &CionModbusRtuConnection::gridVoltageChanged, thing, [=](quint16 gridVoltage){ + qCDebug(dcSchrack()) << "Grid voltage changed:" << 1.0 * gridVoltage / 100; + thing->setStateValue(cionConnectedStateTypeId, true); +// updateCurrentPower(thing); + }); + connect(cionConnection, &CionModbusRtuConnection::u1VoltageChanged, thing, [=](quint16 gridVoltage){ + qCDebug(dcSchrack()) << "U1 voltage changed:" << 1.0 * gridVoltage / 100; + thing->setStateValue(cionConnectedStateTypeId, true); + updateCurrentPower(thing); + }); + connect(cionConnection, &CionModbusRtuConnection::pluggedInDurationChanged, thing, [=](quint32 pluggedInDuration){ + qCDebug(dcSchrack()) << "Plugged in duration changed:" << pluggedInDuration; + thing->setStateValue(cionPluggedInStateTypeId, pluggedInDuration > 0); + thing->setStateValue(cionConnectedStateTypeId, true); + }); + connect(cionConnection, &CionModbusRtuConnection::chargingDurationChanged, thing, [=](quint32 chargingDuration){ + qCDebug(dcSchrack()) << "Charging duration changed:" << chargingDuration; + thing->setStateValue(cionChargingStateTypeId, chargingDuration > 0); + thing->setStateValue(cionConnectedStateTypeId, true); + }); + + + cionConnection->update(); + + // Initialize min/max to their defaults. If both, nymea and the wallbox are restarted simultaneously, nymea would cache the min/max while + // the wallbox would revert to its defaults, and being the default, the modbusconnection also never emits "changed" signals for them. + // To prevent running out of sync we'll "uncache" min/max values too. + thing->setStateMinMaxValues(cionMaxChargingCurrentStateTypeId, 6, 32); + + m_cionConnections.insert(thing, cionConnection); + info->finish(Thing::ThingErrorNoError); +} + +void IntegrationPluginSchrack::postSetupThing(Thing *thing) +{ + qCDebug(dcSchrack()) << "Post setup thing" << thing->name(); + if (!m_refreshTimer) { + m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(2); + connect(m_refreshTimer, &PluginTimer::timeout, this, [this] { + foreach (Thing *thing, myThings()) { + + m_cionConnections.value(thing)->update(); + } + }); + + qCDebug(dcSchrack()) << "Starting refresh timer..."; + m_refreshTimer->start(); + } +} + +void IntegrationPluginSchrack::thingRemoved(Thing *thing) +{ + qCDebug(dcSchrack()) << "Thing removed" << thing->name(); + + if (m_cionConnections.contains(thing)) + m_cionConnections.take(thing)->deleteLater(); + + if (myThings().isEmpty() && m_refreshTimer) { + qCDebug(dcSchrack()) << "Stopping reconnect timer"; + hardwareManager()->pluginTimerManager()->unregisterTimer(m_refreshTimer); + m_refreshTimer = nullptr; + } +} + +void IntegrationPluginSchrack::executeAction(ThingActionInfo *info) +{ + CionModbusRtuConnection *cionConnection = m_cionConnections.value(info->thing()); + if (info->action().actionTypeId() == cionPowerActionTypeId) { + ModbusRtuReply *reply = cionConnection->setChargingEnabled(info->action().paramValue(cionPowerActionPowerParamTypeId).toBool() ? 1 : 0); + waitForActionFinish(info, reply, cionPowerStateTypeId); + } else if (info->action().actionTypeId() == cionMaxChargingCurrentActionTypeId) { + ModbusRtuReply *reply = cionConnection->setChargingCurrentSetpoint(info->action().paramValue(cionMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toUInt()); + waitForActionFinish(info, reply, cionMaxChargingCurrentStateTypeId); + } + + + Q_ASSERT_X(false, "IntegrationPluginSchrack::executeAction", QString("Unhandled action: %1").arg(info->action().actionTypeId().toString()).toLocal8Bit()); +} + +void IntegrationPluginSchrack::waitForActionFinish(ThingActionInfo *info, ModbusRtuReply *reply, const StateTypeId &stateTypeId) +{ + m_pendingActions.insert(info, stateTypeId); + connect(info, &ThingActionInfo::destroyed, this, [=](){ + m_pendingActions.remove(info); + }); + + connect(reply, &ModbusRtuReply::finished, info, [=](){ + if (reply->error() != ModbusRtuReply::NoError) { + info->finish(Thing::ThingErrorHardwareFailure); + } + }); +} + +void IntegrationPluginSchrack::finishAction(const StateTypeId &stateTypeId) +{ + foreach (ThingActionInfo *info, m_pendingActions.keys(stateTypeId)) { + info->finish(Thing::ThingErrorNoError); + } +} + +void IntegrationPluginSchrack::updateCurrentPower(Thing *thing) +{ + CionModbusRtuConnection *cionConnection = m_cionConnections.value(thing); + + QDateTime lastUpdate = thing->property("lastUpdate").toDateTime(); + QDateTime now = QDateTime::currentDateTime(); + if (lastUpdate.isValid()) { + double lastCurrentPower = thing->stateValue(cionCurrentPowerStateTypeId).toDouble(); + double lastTotal = thing->stateValue(cionTotalEnergyConsumedStateTypeId).toDouble(); + qlonglong msecsSinceLast = lastUpdate.msecsTo(now); + double wattMs = lastCurrentPower * msecsSinceLast; + double kWh = wattMs / 60 / 60; + thing->setStateValue(cionTotalEnergyConsumedStateTypeId, lastTotal + kWh); + } + thing->setProperty("lastUpdate", now); + + if (cionConnection->chargingDuration() > 0) { + thing->setStateValue(cionCurrentPowerStateTypeId, 1.0 * cionConnection->u1Voltage() / 100 * cionConnection->currentChargingCurrentE3()); + } else { + thing->setStateValue(cionCurrentPowerStateTypeId, 0); + } +} + diff --git a/schrack/integrationpluginschrack.h b/schrack/integrationpluginschrack.h new file mode 100644 index 0000000..75ace86 --- /dev/null +++ b/schrack/integrationpluginschrack.h @@ -0,0 +1,73 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2021, 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 . +* +* 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 INTEGRATIONPLUGINSCHRACK_H +#define INTEGRATIONPLUGINSCHRACK_H + +#include +#include +#include + +#include "cionmodbusrtuconnection.h" + +#include "extern-plugininfo.h" + +#include +#include + +class IntegrationPluginSchrack : public IntegrationPlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginschrack.json") + Q_INTERFACES(IntegrationPlugin) + +public: + explicit IntegrationPluginSchrack(); + void init() override; + void discoverThings(ThingDiscoveryInfo *info) override; + void setupThing(ThingSetupInfo *info) override; + void postSetupThing(Thing *thing) override; + void thingRemoved(Thing *thing) override; + void executeAction(ThingActionInfo *info) override; + +private: + void waitForActionFinish(ThingActionInfo *info, ModbusRtuReply *reply, const StateTypeId &stateTypeId); + void finishAction(const StateTypeId &stateTypeId); + + void updateCurrentPower(Thing *thing); +private: + PluginTimer *m_refreshTimer = nullptr; + + QHash m_cionConnections; + QHash m_pendingActions; +}; + +#endif // INTEGRATIONPLUGINSCHRACK_H diff --git a/schrack/integrationpluginschrack.json b/schrack/integrationpluginschrack.json new file mode 100644 index 0000000..055a322 --- /dev/null +++ b/schrack/integrationpluginschrack.json @@ -0,0 +1,116 @@ +{ + "name": "schrack", + "displayName": "Schrack", + "id": "600beeb5-5c34-49fc-b2af-8f83c1b11eab", + "paramTypes":[ ], + "vendors": [ + { + "name": "schrack", + "displayName": "Schrack GmbH", + "id": "cadbbbc5-216f-4d25-a8ad-ccf653d1518f", + "thingClasses": [ + { + "name": "cion", + "displayName": "i-CHARGE CION", + "id": "075d389d-3330-4d0b-9649-9f085120ca40", + "createMethods": ["discovery"], + "interfaces": ["evcharger", "connectable"], + "discoveryParamTypes": [ + { + "id": "8004705f-0e13-4713-b75e-49d115cd9517", + "name": "slaveAddress", + "displayName": "Slave address", + "type": "int", + "defaultValue": 1 + } + ], + "paramTypes": [ + { + "id": "dac10e08-734c-4e71-a5d6-0d2a1f416ca6", + "name": "slaveAddress", + "displayName": "Modbus slave address", + "type": "uint", + "defaultValue": 1 + }, + { + "id": "636241a9-4838-48ae-bcc8-3427ac3fc102", + "name": "modbusMasterUuid", + "displayName": "Modbus RTU master", + "type": "QUuid", + "defaultValue": "", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "b7aa8e49-a6c0-4b48-b65e-47259686185f", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "61aea53a-bf8d-4fe6-859a-f1687e15d190", + "name": "power", + "displayName": "Charging enabled", + "displayNameEvent": "Charging enabled or disabled", + "displayNameAction": "Enable or disable charging", + "type": "bool", + "defaultValue": false, + "writable": true + }, + { + "id": "221af869-a796-46c2-a5e0-27c8972a0bf2", + "name": "maxChargingCurrent", + "displayName": "Maximum charging current", + "displayNameEvent": "Maximum charging current changed", + "displayNameAction": "Set maximum charging current", + "type": "uint", + "unit": "Ampere", + "defaultValue": 6, + "minValue": 1, + "maxValue": 32, + "writable": true + }, + { + "id": "db5c951f-47e0-4cdc-8b8b-285f7ed95285", + "name": "currentPower", + "displayName": "Current power consumption", + "displayNameEvent": "Current power consumption changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "8390c005-a8ba-4db6-bb94-8f765ddcc784", + "name": "totalEnergyConsumed", + "displayName": "Total consumed energy", + "displayNameEvent": "Total consumed energy changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "13423618-4314-49be-b48c-42d9415199a8", + "name": "pluggedIn", + "displayName": "Plugged in", + "displayNameEvent": "Plugged or unplugged", + "type": "bool", + "defaultValue": false + }, + { + "id": "b59b4b4d-2cdb-4534-bf86-90123ae9bb1a", + "name": "charging", + "displayName": "Charging", + "displayNameEvent": "Charging started or stopped", + "type": "bool", + "defaultValue": false + } + ] + } + ] + } + ] +} diff --git a/schrack/meta.json b/schrack/meta.json new file mode 100644 index 0000000..b402a34 --- /dev/null +++ b/schrack/meta.json @@ -0,0 +1,13 @@ +{ + "title": "Schrack", + "tagline": "Integrate the Schrack i-CHARGE CION wallbox with nymea.", + "icon": "schrack.jpg", + "stability": "consumer", + "offline": true, + "technologies": [ + "modbus" + ], + "categories": [ + "energy" + ] +} diff --git a/schrack/schrack.jpg b/schrack/schrack.jpg new file mode 100644 index 0000000..0b6b4d0 Binary files /dev/null and b/schrack/schrack.jpg differ diff --git a/schrack/schrack.pro b/schrack/schrack.pro new file mode 100644 index 0000000..88565a9 --- /dev/null +++ b/schrack/schrack.pro @@ -0,0 +1,20 @@ +include(../plugins.pri) + +QT += serialport serialbus + +SOURCES += \ + integrationpluginschrack.cpp \ + cionmodbusrtuconnection.cpp \ + ../modbus/modbusdatautils.cpp + +HEADERS += \ + integrationpluginschrack.h \ + cionmodbusrtuconnection.h \ + ../modbus/modbusdatautils.h + +OTHER_FILES += cion-registers.json + +modbusconnection.commands = python $${top_srcdir}/modbus/tools/generate-connection.py -j $${_PRO_FILE_PWD_}/cion-registers.json -o $${_PRO_FILE_PWD_} -c CionModbusRtuConnection +QMAKE_EXTRA_TARGETS += modbusconnection + +#target.depends += modbusconnection diff --git a/schrack/translations/600beeb5-5c34-49fc-b2af-8f83c1b11eab-en_US.ts b/schrack/translations/600beeb5-5c34-49fc-b2af-8f83c1b11eab-en_US.ts new file mode 100644 index 0000000..da42dcd --- /dev/null +++ b/schrack/translations/600beeb5-5c34-49fc-b2af-8f83c1b11eab-en_US.ts @@ -0,0 +1,189 @@ + + + + + IntegrationPluginSchrack + + + No Modbus RTU interface available. Please set up a Modbus RTU interface first. + + + + + The Modbus slave address must be a value between 1 and 254. + + + + + The Modbus address not valid. It must be a value between 1 and 254. + + + + + The Modbus RTU resource is not available. + + + + + schrack + + + + Charging + The name of the ParamType (ThingClass: cion, EventType: charging, ID: {b59b4b4d-2cdb-4534-bf86-90123ae9bb1a}) +---------- +The name of the StateType ({b59b4b4d-2cdb-4534-bf86-90123ae9bb1a}) of ThingClass cion + + + + + + + Charging enabled + The name of the ParamType (ThingClass: cion, ActionType: power, ID: {61aea53a-bf8d-4fe6-859a-f1687e15d190}) +---------- +The name of the ParamType (ThingClass: cion, EventType: power, ID: {61aea53a-bf8d-4fe6-859a-f1687e15d190}) +---------- +The name of the StateType ({61aea53a-bf8d-4fe6-859a-f1687e15d190}) of ThingClass cion + + + + + Charging enabled or disabled + The name of the EventType ({61aea53a-bf8d-4fe6-859a-f1687e15d190}) of ThingClass cion + + + + + Charging started or stopped + The name of the EventType ({b59b4b4d-2cdb-4534-bf86-90123ae9bb1a}) of ThingClass cion + + + + + + Connected + The name of the ParamType (ThingClass: cion, EventType: connected, ID: {b7aa8e49-a6c0-4b48-b65e-47259686185f}) +---------- +The name of the StateType ({b7aa8e49-a6c0-4b48-b65e-47259686185f}) of ThingClass cion + + + + + Connected changed + The name of the EventType ({b7aa8e49-a6c0-4b48-b65e-47259686185f}) of ThingClass cion + + + + + + Current power consumption + The name of the ParamType (ThingClass: cion, EventType: currentPower, ID: {db5c951f-47e0-4cdc-8b8b-285f7ed95285}) +---------- +The name of the StateType ({db5c951f-47e0-4cdc-8b8b-285f7ed95285}) of ThingClass cion + + + + + Current power consumption changed + The name of the EventType ({db5c951f-47e0-4cdc-8b8b-285f7ed95285}) of ThingClass cion + + + + + Enable or disable charging + The name of the ActionType ({61aea53a-bf8d-4fe6-859a-f1687e15d190}) of ThingClass cion + + + + + + + Maximum charging current + The name of the ParamType (ThingClass: cion, ActionType: maxChargingCurrent, ID: {221af869-a796-46c2-a5e0-27c8972a0bf2}) +---------- +The name of the ParamType (ThingClass: cion, EventType: maxChargingCurrent, ID: {221af869-a796-46c2-a5e0-27c8972a0bf2}) +---------- +The name of the StateType ({221af869-a796-46c2-a5e0-27c8972a0bf2}) of ThingClass cion + + + + + Maximum charging current changed + The name of the EventType ({221af869-a796-46c2-a5e0-27c8972a0bf2}) of ThingClass cion + + + + + Modbus RTU master + The name of the ParamType (ThingClass: cion, Type: thing, ID: {636241a9-4838-48ae-bcc8-3427ac3fc102}) + + + + + Modbus slave address + The name of the ParamType (ThingClass: cion, Type: thing, ID: {dac10e08-734c-4e71-a5d6-0d2a1f416ca6}) + + + + + + Plugged in + The name of the ParamType (ThingClass: cion, EventType: pluggedIn, ID: {13423618-4314-49be-b48c-42d9415199a8}) +---------- +The name of the StateType ({13423618-4314-49be-b48c-42d9415199a8}) of ThingClass cion + + + + + Plugged or unplugged + The name of the EventType ({13423618-4314-49be-b48c-42d9415199a8}) of ThingClass cion + + + + + Schrack + The name of the plugin schrack ({600beeb5-5c34-49fc-b2af-8f83c1b11eab}) + + + + + Schrack GmbH + The name of the vendor ({cadbbbc5-216f-4d25-a8ad-ccf653d1518f}) + + + + + Set maximum charging current + The name of the ActionType ({221af869-a796-46c2-a5e0-27c8972a0bf2}) of ThingClass cion + + + + + Slave address + The name of the ParamType (ThingClass: cion, Type: discovery, ID: {8004705f-0e13-4713-b75e-49d115cd9517}) + + + + + + Total consumed energy + The name of the ParamType (ThingClass: cion, EventType: totalEnergyConsumed, ID: {8390c005-a8ba-4db6-bb94-8f765ddcc784}) +---------- +The name of the StateType ({8390c005-a8ba-4db6-bb94-8f765ddcc784}) of ThingClass cion + + + + + Total consumed energy changed + The name of the EventType ({8390c005-a8ba-4db6-bb94-8f765ddcc784}) of ThingClass cion + + + + + i-CHARGE CION + The name of the ThingClass ({075d389d-3330-4d0b-9649-9f085120ca40}) + + + +