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..779762d --- /dev/null +++ b/schrack/cion-registers.json @@ -0,0 +1,163 @@ +{ + "protocol": "RTU", + "endianness": "LittleEndian", + "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", + "unit": "A", + "defaultValue": 32, + "access": "R" + }, + { + "id": "maxChargingCurrentCableE3", + "address": 128, + "size": 1, + "type": "uint16", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "Maximum charging current of connected cable", + "unit": "A", + "defaultValue": 32, + "access": "R" + } + ] + }, + { + "id": "durations", + "readSchedule": "update", + "registers": [ + { + "id": "chargingDuration", + "address": 151, + "size": 2, + "type": "uint32", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "Charging duration", + "unit": "ms", + "defaultValue": 0, + "access": "R" + }, + { + "id": "pluggedInDuration", + "address": 153, + "size": 2, + "type": "uint32", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "Plugged in duration", + "unit": "ms", + "defaultValue": 0, + "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": "Charging current setpoint", + "unit": "A", + "defaultValue": 6, + "access": "RW" + }, + { + "id": "statusBits", + "address": 121, + "size": 1, + "type": "uint16", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "Mode3-State A, B, C, D, U", + "defaultValue": 85, + "access": "R" + }, + { + "id": "cpSignalState", + "address": 139, + "size": 1, + "type": "uint16", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "Status bits", + "defaultValue": 0, + "access": "R" + }, + { + "id": "u1Voltage", + "address": 167, + "size": 1, + "type": "uint16", + "registerType": "holdingRegister", + "readSchedule": "update", + "description": "U1 voltage", + "staticScaleFactor": -2, + "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", + "staticScaleFactor": -2, + "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": 13, + "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..7bb4349 --- /dev/null +++ b/schrack/cionmodbusrtuconnection.cpp @@ -0,0 +1,723 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* WARNING +* +* This file has been autogenerated. Any changes in this file may be overwritten. +* If you want to change something, update the register json or the tool. +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "cionmodbusrtuconnection.h" +#include "loggingcategories.h" +#include "math.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; +} +ModbusDataUtils::ByteOrder CionModbusRtuConnection::endianness() const +{ + return m_endianness; +} +void CionModbusRtuConnection::setEndianness(ModbusDataUtils::ByteOrder endianness) +{ + if (m_endianness == endianness) + return; + + m_endianness = endianness; + emit endiannessChanged(m_endianness); +} +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 \"Charging current setpoint\" register:" << 101 << "size:" << 1 << values; + return m_modbusRtuMaster->writeHoldingRegisters(m_slaveId, 101, values); +} + +quint16 CionModbusRtuConnection::statusBits() const +{ + return m_statusBits; +} + +quint16 CionModbusRtuConnection::cpSignalState() const +{ + return m_cpSignalState; +} + +float CionModbusRtuConnection::u1Voltage() const +{ + return m_u1Voltage; +} + +float 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; +} + +quint16 CionModbusRtuConnection::maxChargingCurrentCableE3() const +{ + return m_maxChargingCurrentCableE3; +} + +quint32 CionModbusRtuConnection::chargingDuration() const +{ + return m_chargingDuration; +} + +quint32 CionModbusRtuConnection::pluggedInDuration() const +{ + return m_pluggedInDuration; +} + +void CionModbusRtuConnection::initialize() +{ + // No init registers defined. Nothing to be done and we are finished. + emit initializationFinished(); +} + +void CionModbusRtuConnection::update() +{ + updateChargingEnabled(); + updateChargingCurrentSetpoint(); + updateStatusBits(); + updateCpSignalState(); + updateU1Voltage(); + updateGridVoltage(); + updateMinChargingCurrent(); + updateE3Block(); + updateDurationsBlock(); +} + +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; + processChargingEnabledRegisterValues(values); + } + }); + + 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 Charging current setpoint + qCDebug(dcCionModbusRtuConnection()) << "--> Read \"Charging current setpoint\" 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 \"Charging current setpoint\" register" << 101 << "size:" << 1 << values; + processChargingCurrentSetpointRegisterValues(values); + } + }); + + connect(reply, &ModbusRtuReply::errorOccurred, this, [reply] (ModbusRtuReply::Error error){ + qCWarning(dcCionModbusRtuConnection()) << "ModbusRtu reply error occurred while updating \"Charging current setpoint\" registers" << error << reply->errorString(); + emit reply->finished(); + }); + } + } else { + qCWarning(dcCionModbusRtuConnection()) << "Error occurred while reading \"Charging current setpoint\" registers"; + } +} + +void CionModbusRtuConnection::updateStatusBits() +{ + // Update registers from Mode3-State A, B, C, D, U + qCDebug(dcCionModbusRtuConnection()) << "--> Read \"Mode3-State A, B, C, D, U\" 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 \"Mode3-State A, B, C, D, U\" register" << 121 << "size:" << 1 << values; + processStatusBitsRegisterValues(values); + } + }); + + connect(reply, &ModbusRtuReply::errorOccurred, this, [reply] (ModbusRtuReply::Error error){ + qCWarning(dcCionModbusRtuConnection()) << "ModbusRtu reply error occurred while updating \"Mode3-State A, B, C, D, U\" registers" << error << reply->errorString(); + emit reply->finished(); + }); + } + } else { + qCWarning(dcCionModbusRtuConnection()) << "Error occurred while reading \"Mode3-State A, B, C, D, U\" registers"; + } +} + +void CionModbusRtuConnection::updateCpSignalState() +{ + // Update registers from Status bits + qCDebug(dcCionModbusRtuConnection()) << "--> Read \"Status bits\" register:" << 139 << "size:" << 1; + ModbusRtuReply *reply = readCpSignalState(); + 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 \"Status bits\" register" << 139 << "size:" << 1 << values; + processCpSignalStateRegisterValues(values); + } + }); + + connect(reply, &ModbusRtuReply::errorOccurred, this, [reply] (ModbusRtuReply::Error error){ + qCWarning(dcCionModbusRtuConnection()) << "ModbusRtu reply error occurred while updating \"Status bits\" registers" << error << reply->errorString(); + emit reply->finished(); + }); + } + } else { + qCWarning(dcCionModbusRtuConnection()) << "Error occurred while reading \"Status bits\" 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; + processU1VoltageRegisterValues(values); + } + }); + + 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; + processGridVoltageRegisterValues(values); + } + }); + + 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; + processMinChargingCurrentRegisterValues(values); + } + }); + + 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::updateCurrentChargingCurrentE3() +{ + // Update registers from Current charging Ampere + qCDebug(dcCionModbusRtuConnection()) << "--> Read \"Current charging Ampere\" register:" << 126 << "size:" << 1; + ModbusRtuReply *reply = readCurrentChargingCurrentE3(); + 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 \"Current charging Ampere\" register" << 126 << "size:" << 1 << values; + processCurrentChargingCurrentE3RegisterValues(values); + } + }); + + connect(reply, &ModbusRtuReply::errorOccurred, this, [reply] (ModbusRtuReply::Error error){ + qCWarning(dcCionModbusRtuConnection()) << "ModbusRtu reply error occurred while updating \"Current charging Ampere\" registers" << error << reply->errorString(); + emit reply->finished(); + }); + } + } else { + qCWarning(dcCionModbusRtuConnection()) << "Error occurred while reading \"Current charging Ampere\" registers"; + } +} + +void CionModbusRtuConnection::updateMaxChargingCurrentE3() +{ + // Update registers from Maximum charging current + qCDebug(dcCionModbusRtuConnection()) << "--> Read \"Maximum charging current\" register:" << 127 << "size:" << 1; + ModbusRtuReply *reply = readMaxChargingCurrentE3(); + 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" << 127 << "size:" << 1 << values; + processMaxChargingCurrentE3RegisterValues(values); + } + }); + + 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::updateMaxChargingCurrentCableE3() +{ + // Update registers from Maximum charging current of connected cable + qCDebug(dcCionModbusRtuConnection()) << "--> Read \"Maximum charging current of connected cable\" register:" << 128 << "size:" << 1; + ModbusRtuReply *reply = readMaxChargingCurrentCableE3(); + 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 of connected cable\" register" << 128 << "size:" << 1 << values; + processMaxChargingCurrentCableE3RegisterValues(values); + } + }); + + connect(reply, &ModbusRtuReply::errorOccurred, this, [reply] (ModbusRtuReply::Error error){ + qCWarning(dcCionModbusRtuConnection()) << "ModbusRtu reply error occurred while updating \"Maximum charging current of connected cable\" registers" << error << reply->errorString(); + emit reply->finished(); + }); + } + } else { + qCWarning(dcCionModbusRtuConnection()) << "Error occurred while reading \"Maximum charging current of connected cable\" 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; + processChargingDurationRegisterValues(values); + } + }); + + 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; + processPluggedInDurationRegisterValues(values); + } + }); + + 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::updateE3Block() +{ + // Update register block "e3" + qCDebug(dcCionModbusRtuConnection()) << "--> Read block \"e3\" registers from:" << 126 << "size:" << 3; + ModbusRtuReply *reply = m_modbusRtuMaster->readHoldingRegister(m_slaveId, 126, 3); + if (reply) { + if (!reply->isFinished()) { + connect(reply, &ModbusRtuReply::finished, this, [this, reply](){ + if (reply->error() == ModbusRtuReply::NoError) { + QVector blockValues = reply->result(); + qCDebug(dcCionModbusRtuConnection()) << "<-- Response from reading block \"e3\" register" << 126 << "size:" << 3 << blockValues; + processCurrentChargingCurrentE3RegisterValues(blockValues.mid(0, 1)); + processMaxChargingCurrentE3RegisterValues(blockValues.mid(1, 1)); + processMaxChargingCurrentCableE3RegisterValues(blockValues.mid(2, 1)); + } + }); + + 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"; + } +} + +void CionModbusRtuConnection::updateDurationsBlock() +{ + // Update register block "durations" + qCDebug(dcCionModbusRtuConnection()) << "--> Read block \"durations\" registers from:" << 151 << "size:" << 4; + ModbusRtuReply *reply = m_modbusRtuMaster->readHoldingRegister(m_slaveId, 151, 4); + if (reply) { + if (!reply->isFinished()) { + connect(reply, &ModbusRtuReply::finished, this, [this, reply](){ + if (reply->error() == ModbusRtuReply::NoError) { + QVector blockValues = reply->result(); + qCDebug(dcCionModbusRtuConnection()) << "<-- Response from reading block \"durations\" register" << 151 << "size:" << 4 << blockValues; + processChargingDurationRegisterValues(blockValues.mid(0, 2)); + processPluggedInDurationRegisterValues(blockValues.mid(2, 2)); + } + }); + + connect(reply, &ModbusRtuReply::errorOccurred, this, [reply] (ModbusRtuReply::Error error){ + qCWarning(dcCionModbusRtuConnection()) << "ModbusRtu reply error occurred while updating block \"durations\" registers" << error << reply->errorString(); + emit reply->finished(); + }); + } + } else { + qCWarning(dcCionModbusRtuConnection()) << "Error occurred while reading block \"durations\" 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::readCpSignalState() +{ + return m_modbusRtuMaster->readHoldingRegister(m_slaveId, 139, 1); +} + +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); +} + +ModbusRtuReply *CionModbusRtuConnection::readCurrentChargingCurrentE3() +{ + return m_modbusRtuMaster->readHoldingRegister(m_slaveId, 126, 1); +} + +ModbusRtuReply *CionModbusRtuConnection::readMaxChargingCurrentE3() +{ + return m_modbusRtuMaster->readHoldingRegister(m_slaveId, 127, 1); +} + +ModbusRtuReply *CionModbusRtuConnection::readMaxChargingCurrentCableE3() +{ + return m_modbusRtuMaster->readHoldingRegister(m_slaveId, 128, 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::readBlockE3() +{ + return m_modbusRtuMaster->readHoldingRegister(m_slaveId, 126, 3); +} + +ModbusRtuReply *CionModbusRtuConnection::readBlockDurations() +{ + return m_modbusRtuMaster->readHoldingRegister(m_slaveId, 151, 4); +} + +void CionModbusRtuConnection::processChargingEnabledRegisterValues(const QVector values) +{ + quint16 receivedChargingEnabled = ModbusDataUtils::convertToUInt16(values); + if (m_chargingEnabled != receivedChargingEnabled) { + m_chargingEnabled = receivedChargingEnabled; + emit chargingEnabledChanged(m_chargingEnabled); + } +} + +void CionModbusRtuConnection::processChargingCurrentSetpointRegisterValues(const QVector values) +{ + quint16 receivedChargingCurrentSetpoint = ModbusDataUtils::convertToUInt16(values); + if (m_chargingCurrentSetpoint != receivedChargingCurrentSetpoint) { + m_chargingCurrentSetpoint = receivedChargingCurrentSetpoint; + emit chargingCurrentSetpointChanged(m_chargingCurrentSetpoint); + } +} + +void CionModbusRtuConnection::processStatusBitsRegisterValues(const QVector values) +{ + quint16 receivedStatusBits = ModbusDataUtils::convertToUInt16(values); + if (m_statusBits != receivedStatusBits) { + m_statusBits = receivedStatusBits; + emit statusBitsChanged(m_statusBits); + } +} + +void CionModbusRtuConnection::processCpSignalStateRegisterValues(const QVector values) +{ + quint16 receivedCpSignalState = ModbusDataUtils::convertToUInt16(values); + if (m_cpSignalState != receivedCpSignalState) { + m_cpSignalState = receivedCpSignalState; + emit cpSignalStateChanged(m_cpSignalState); + } +} + +void CionModbusRtuConnection::processU1VoltageRegisterValues(const QVector values) +{ + float receivedU1Voltage = ModbusDataUtils::convertToUInt16(values) * 1.0 * pow(10, -2); + if (m_u1Voltage != receivedU1Voltage) { + m_u1Voltage = receivedU1Voltage; + emit u1VoltageChanged(m_u1Voltage); + } +} + +void CionModbusRtuConnection::processGridVoltageRegisterValues(const QVector values) +{ + float receivedGridVoltage = ModbusDataUtils::convertToUInt16(values) * 1.0 * pow(10, -2); + if (m_gridVoltage != receivedGridVoltage) { + m_gridVoltage = receivedGridVoltage; + emit gridVoltageChanged(m_gridVoltage); + } +} + +void CionModbusRtuConnection::processMinChargingCurrentRegisterValues(const QVector values) +{ + quint16 receivedMinChargingCurrent = ModbusDataUtils::convertToUInt16(values); + if (m_minChargingCurrent != receivedMinChargingCurrent) { + m_minChargingCurrent = receivedMinChargingCurrent; + emit minChargingCurrentChanged(m_minChargingCurrent); + } +} + +void CionModbusRtuConnection::processCurrentChargingCurrentE3RegisterValues(const QVector values) +{ + quint16 receivedCurrentChargingCurrentE3 = ModbusDataUtils::convertToUInt16(values); + if (m_currentChargingCurrentE3 != receivedCurrentChargingCurrentE3) { + m_currentChargingCurrentE3 = receivedCurrentChargingCurrentE3; + emit currentChargingCurrentE3Changed(m_currentChargingCurrentE3); + } +} + +void CionModbusRtuConnection::processMaxChargingCurrentE3RegisterValues(const QVector values) +{ + quint16 receivedMaxChargingCurrentE3 = ModbusDataUtils::convertToUInt16(values); + if (m_maxChargingCurrentE3 != receivedMaxChargingCurrentE3) { + m_maxChargingCurrentE3 = receivedMaxChargingCurrentE3; + emit maxChargingCurrentE3Changed(m_maxChargingCurrentE3); + } +} + +void CionModbusRtuConnection::processMaxChargingCurrentCableE3RegisterValues(const QVector values) +{ + quint16 receivedMaxChargingCurrentCableE3 = ModbusDataUtils::convertToUInt16(values); + if (m_maxChargingCurrentCableE3 != receivedMaxChargingCurrentCableE3) { + m_maxChargingCurrentCableE3 = receivedMaxChargingCurrentCableE3; + emit maxChargingCurrentCableE3Changed(m_maxChargingCurrentCableE3); + } +} + +void CionModbusRtuConnection::processChargingDurationRegisterValues(const QVector values) +{ + quint32 receivedChargingDuration = ModbusDataUtils::convertToUInt32(values, m_endianness); + if (m_chargingDuration != receivedChargingDuration) { + m_chargingDuration = receivedChargingDuration; + emit chargingDurationChanged(m_chargingDuration); + } +} + +void CionModbusRtuConnection::processPluggedInDurationRegisterValues(const QVector values) +{ + quint32 receivedPluggedInDuration = ModbusDataUtils::convertToUInt32(values, m_endianness); + if (m_pluggedInDuration != receivedPluggedInDuration) { + m_pluggedInDuration = receivedPluggedInDuration; + emit pluggedInDurationChanged(m_pluggedInDuration); + } +} + +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() << " - Charging current setpoint:" << cionModbusRtuConnection->chargingCurrentSetpoint() << " [A]" << "\n"; + debug.nospace().noquote() << " - Mode3-State A, B, C, D, U:" << cionModbusRtuConnection->statusBits() << "\n"; + debug.nospace().noquote() << " - Status bits:" << cionModbusRtuConnection->cpSignalState() << "\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:" << cionModbusRtuConnection->maxChargingCurrentE3() << " [A]" << "\n"; + debug.nospace().noquote() << " - Maximum charging current of connected cable:" << cionModbusRtuConnection->maxChargingCurrentCableE3() << " [A]" << "\n"; + debug.nospace().noquote() << " - Charging duration:" << cionModbusRtuConnection->chargingDuration() << " [ms]" << "\n"; + debug.nospace().noquote() << " - Plugged in duration:" << cionModbusRtuConnection->pluggedInDuration() << " [ms]" << "\n"; + return debug.quote().space(); +} + diff --git a/schrack/cionmodbusrtuconnection.h b/schrack/cionmodbusrtuconnection.h new file mode 100644 index 0000000..a175aea --- /dev/null +++ b/schrack/cionmodbusrtuconnection.h @@ -0,0 +1,231 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* WARNING +* +* This file has been autogenerated. Any changes in this file may be overwritten. +* If you want to change something, update the register json or the tool. +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#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, + RegisterMaxChargingCurrentCableE3 = 128, + RegisterCpSignalState = 139, + 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; + + ModbusDataUtils::ByteOrder endianness() const; + void setEndianness(ModbusDataUtils::ByteOrder endianness); + + /* Charging enabled - Address: 100, Size: 1 */ + quint16 chargingEnabled() const; + ModbusRtuReply *setChargingEnabled(quint16 chargingEnabled); + + /* Charging current setpoint [A] - Address: 101, Size: 1 */ + quint16 chargingCurrentSetpoint() const; + ModbusRtuReply *setChargingCurrentSetpoint(quint16 chargingCurrentSetpoint); + + /* Mode3-State A, B, C, D, U - Address: 121, Size: 1 */ + quint16 statusBits() const; + + /* Status bits - Address: 139, Size: 1 */ + quint16 cpSignalState() const; + + /* U1 voltage [V] - Address: 167, Size: 1 */ + float u1Voltage() const; + + /* Voltage of the power supply grid [V] - Address: 302, Size: 1 */ + float 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 [A] - Address: 127, Size: 1 */ + quint16 maxChargingCurrentE3() const; + + /* Maximum charging current of connected cable [A] - Address: 128, Size: 1 */ + quint16 maxChargingCurrentCableE3() const; + + /* Charging duration [ms] - Address: 151, Size: 2 */ + quint32 chargingDuration() const; + + /* Plugged in duration [ms] - Address: 153, Size: 2 */ + quint32 pluggedInDuration() const; + + /* Read block from start addess 126 with size of 3 registers containing following 3 properties: + - Current charging Ampere [A] - Address: 126, Size: 1 + - Maximum charging current [A] - Address: 127, Size: 1 + - Maximum charging current of connected cable [A] - Address: 128, Size: 1 + */ + void updateE3Block(); + + /* Read block from start addess 151 with size of 4 registers containing following 2 properties: + - Charging duration [ms] - Address: 151, Size: 2 + - Plugged in duration [ms] - Address: 153, Size: 2 + */ + void updateDurationsBlock(); + + virtual void initialize(); + virtual void update(); + + void updateChargingEnabled(); + void updateChargingCurrentSetpoint(); + void updateStatusBits(); + void updateCpSignalState(); + void updateU1Voltage(); + void updateGridVoltage(); + void updateMinChargingCurrent(); + + void updateCurrentChargingCurrentE3(); + void updateMaxChargingCurrentE3(); + void updateMaxChargingCurrentCableE3(); + void updateChargingDuration(); + void updatePluggedInDuration(); + + ModbusRtuReply *readChargingEnabled(); + ModbusRtuReply *readChargingCurrentSetpoint(); + ModbusRtuReply *readStatusBits(); + ModbusRtuReply *readCpSignalState(); + ModbusRtuReply *readU1Voltage(); + ModbusRtuReply *readGridVoltage(); + ModbusRtuReply *readMinChargingCurrent(); + ModbusRtuReply *readCurrentChargingCurrentE3(); + ModbusRtuReply *readMaxChargingCurrentE3(); + ModbusRtuReply *readMaxChargingCurrentCableE3(); + ModbusRtuReply *readChargingDuration(); + ModbusRtuReply *readPluggedInDuration(); + + /* Read block from start addess 126 with size of 3 registers containing following 3 properties: + - Current charging Ampere [A] - Address: 126, Size: 1 + - Maximum charging current [A] - Address: 127, Size: 1 + - Maximum charging current of connected cable [A] - Address: 128, Size: 1 + */ + ModbusRtuReply *readBlockE3(); + + /* Read block from start addess 151 with size of 4 registers containing following 2 properties: + - Charging duration [ms] - Address: 151, Size: 2 + - Plugged in duration [ms] - Address: 153, Size: 2 + */ + ModbusRtuReply *readBlockDurations(); + +signals: + void initializationFinished(); + void endiannessChanged(ModbusDataUtils::ByteOrder endianness); + + void chargingEnabledChanged(quint16 chargingEnabled); + void chargingCurrentSetpointChanged(quint16 chargingCurrentSetpoint); + void statusBitsChanged(quint16 statusBits); + void cpSignalStateChanged(quint16 cpSignalState); + void u1VoltageChanged(float u1Voltage); + void gridVoltageChanged(float gridVoltage); + void minChargingCurrentChanged(quint16 minChargingCurrent); + void currentChargingCurrentE3Changed(quint16 currentChargingCurrentE3); + void maxChargingCurrentE3Changed(quint16 maxChargingCurrentE3); + void maxChargingCurrentCableE3Changed(quint16 maxChargingCurrentCableE3); + void chargingDurationChanged(quint32 chargingDuration); + void pluggedInDurationChanged(quint32 pluggedInDuration); + +protected: + quint16 m_chargingEnabled = 0; + quint16 m_chargingCurrentSetpoint = 6; + quint16 m_statusBits = 85; + quint16 m_cpSignalState = 0; + float m_u1Voltage = 32; + float m_gridVoltage = 0; + quint16 m_minChargingCurrent = 13; + quint16 m_currentChargingCurrentE3 = 6; + quint16 m_maxChargingCurrentE3 = 32; + quint16 m_maxChargingCurrentCableE3 = 32; + quint32 m_chargingDuration = 0; + quint32 m_pluggedInDuration = 0; + + void processChargingEnabledRegisterValues(const QVector values); + void processChargingCurrentSetpointRegisterValues(const QVector values); + void processStatusBitsRegisterValues(const QVector values); + void processCpSignalStateRegisterValues(const QVector values); + void processU1VoltageRegisterValues(const QVector values); + void processGridVoltageRegisterValues(const QVector values); + void processMinChargingCurrentRegisterValues(const QVector values); + + void processCurrentChargingCurrentE3RegisterValues(const QVector values); + void processMaxChargingCurrentE3RegisterValues(const QVector values); + void processMaxChargingCurrentCableE3RegisterValues(const QVector values); + + void processChargingDurationRegisterValues(const QVector values); + void processPluggedInDurationRegisterValues(const QVector values); + + +private: + ModbusRtuMaster *m_modbusRtuMaster = nullptr; + quint16 m_slaveId = 1; + QVector m_pendingInitReplies; + ModbusDataUtils::ByteOrder m_endianness = ModbusDataUtils::ByteOrderBigEndian; + + 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..a7e99fa --- /dev/null +++ b/schrack/integrationpluginschrack.cpp @@ -0,0 +1,267 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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() +{ + +} + +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); + + // Note: This register really only tells us if we can control anything... i.e. if the wallbox is unlocked via RFID + connect(cionConnection, &CionModbusRtuConnection::chargingEnabledChanged, thing, [=](quint16 charging){ + qCDebug(dcSchrack()) << "Charging enabled changed:" << charging; + }); + + // 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 + // Setting this to 0 will pause charging, anything else will control the charging (and return the actual value in currentChargingCurrentE3) + connect(cionConnection, &CionModbusRtuConnection::chargingCurrentSetpointChanged, thing, [=](quint16 chargingCurrentSetpoint){ + qCDebug(dcSchrack()) << "Charging current setpoint changed:" << chargingCurrentSetpoint; + }); + + connect(cionConnection, &CionModbusRtuConnection::cpSignalStateChanged, thing, [=](quint16 cpSignalState){ + qCDebug(dcSchrack()) << "CP Signal state changed:" << (char)cpSignalState; + thing->setStateValue(cionPluggedInStateTypeId, cpSignalState >= 66); + }); + + connect(cionConnection, &CionModbusRtuConnection::currentChargingCurrentE3Changed, thing, [=](quint16 currentChargingCurrentE3){ + qCDebug(dcSchrack()) << "Current charging current E3 current changed:" << currentChargingCurrentE3; + if (cionConnection->chargingCurrentSetpoint() > 0) { + thing->setStateValue(cionMaxChargingCurrentStateTypeId, currentChargingCurrentE3); + } + }); + + // 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); + } + }); + + connect(cionConnection, &CionModbusRtuConnection::statusBitsChanged, thing, [=](quint16 /*statusBits*/){ + thing->setStateValue(cionConnectedStateTypeId, true); +// qCDebug(dcSchrack()) << "Status bits changed:" << statusBits; + }); + + connect(cionConnection, &CionModbusRtuConnection::minChargingCurrentChanged, thing, [=](quint16 minChargingCurrent){ + qCDebug(dcSchrack()) << "Minimum charging current changed:" << minChargingCurrent; + thing->setStateMinValue(cionMaxChargingCurrentStateTypeId, minChargingCurrent); + }); + + connect(cionConnection, &CionModbusRtuConnection::gridVoltageChanged, thing, [=](float /*gridVoltage*/){ +// qCDebug(dcSchrack()) << "Grid voltage changed:" << gridVoltage; + }); + + connect(cionConnection, &CionModbusRtuConnection::u1VoltageChanged, thing, [=](float /*u1Voltage*/){ +// qCDebug(dcSchrack()) << "U1 voltage changed:" << u1Voltage; + }); + + connect(cionConnection, &CionModbusRtuConnection::pluggedInDurationChanged, thing, [=](quint32 /*pluggedInDuration*/){ +// qCDebug(dcSchrack()) << "Plugged in duration changed:" << pluggedInDuration; + // Not reliable to determine if plugged in! +// thing->setStateValue(cionPluggedInStateTypeId, pluggedInDuration > 0); + }); + + connect(cionConnection, &CionModbusRtuConnection::chargingDurationChanged, thing, [=](quint32 chargingDuration){ +// qCDebug(dcSchrack()) << "Charging duration changed:" << chargingDuration; + thing->setStateValue(cionChargingStateTypeId, chargingDuration > 0); + }); + + 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()) { + + CionModbusRtuConnection *connection = m_cionConnections.value(thing); + connection->update(); + + // The only apparent way to know whether it is charging, is to compare if lastChargingDuration has changed + // If it didn't change in the last cycle + qulonglong lastChargingDuration = connection->property("lastChargingDuration").toULongLong(); + thing->setStateValue(cionChargingStateTypeId, connection->chargingDuration() != lastChargingDuration); + connection->setProperty("lastChargingDuration", connection->chargingDuration()); + + // Reading the CP signal state to know if the wallbox is reachable. + // Note that this is also an "update" register, hence being read twice. + // We'll not actually evaluate the actual results in here because + // this piece of code should be replaced with the modbus tool internal connected detection when it's ready + ModbusRtuReply *reply = connection->readCpSignalState(); + connect(reply, &ModbusRtuReply::finished, thing, [reply, thing, this](){ +// qCDebug(dcSchrack) << "CP signal state reply finished" << reply->error(); + thing->setStateValue(cionConnectedStateTypeId, reply->error() == ModbusRtuReply::NoError); + + // The Cion seems to crap out rather often and needs to be reconnected :/ + if (reply->error() == ModbusRtuReply::TimeoutError) { + QUuid uuid = thing->paramValue(cionThingModbusMasterUuidParamTypeId).toUuid(); + hardwareManager()->modbusRtuResource()->getModbusRtuMaster(uuid)->requestReconnect(); + } + }); + } + }); + + 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) { + qCDebug(dcSchrack()) << "Setting charging enabled:" << (info->action().paramValue(cionPowerActionPowerParamTypeId).toBool() ? 1 : 0); + int maxSetPoint = info->thing()->stateValue(cionMaxChargingCurrentStateTypeId).toUInt(); + // If user enables it, we'll write the maxChargingPower value + if (info->action().paramValue(cionPowerActionPowerParamTypeId).toBool()) { + ModbusRtuReply *reply = cionConnection->setChargingCurrentSetpoint(maxSetPoint); + waitForActionFinish(info, reply, cionPowerStateTypeId, true); + } else { // we'll write 0 to the max charging power + ModbusRtuReply *reply = cionConnection->setChargingCurrentSetpoint(0); + waitForActionFinish(info, reply, cionPowerStateTypeId, false); + } + + } else if (info->action().actionTypeId() == cionMaxChargingCurrentActionTypeId) { + // If charging is set to enabled, we'll write the value to the wallbox + if (info->thing()->stateValue(cionPowerStateTypeId).toBool()) { + int maxChargingCurrent = info->action().paramValue(cionMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toUInt(); + ModbusRtuReply *reply = cionConnection->setChargingCurrentSetpoint(maxChargingCurrent); + waitForActionFinish(info, reply, cionMaxChargingCurrentStateTypeId, maxChargingCurrent); + + } else { // we'll just memorize what the user wants in our state and write it when enabled is set to true + info->thing()->setStateValue(cionMaxChargingCurrentStateTypeId, info->action().paramValue(cionMaxChargingCurrentActionMaxChargingCurrentParamTypeId)); + info->finish(Thing::ThingErrorNoError); + } + } + + + 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, const QVariant &value) +{ + connect(reply, &ModbusRtuReply::finished, info, [=](){ + info->finish(reply->error() == ModbusRtuReply::NoError ? Thing::ThingErrorNoError : Thing::ThingErrorHardwareFailure); + if (reply->error() == ModbusRtuReply::NoError) { + info->thing()->setStateValue(stateTypeId, value); + } + }); +} diff --git a/schrack/integrationpluginschrack.h b/schrack/integrationpluginschrack.h new file mode 100644 index 0000000..0a5ba02 --- /dev/null +++ b/schrack/integrationpluginschrack.h @@ -0,0 +1,69 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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, const QVariant &value); +private: + PluginTimer *m_refreshTimer = nullptr; + + QHash m_cionConnections; +}; + +#endif // INTEGRATIONPLUGINSCHRACK_H diff --git a/schrack/integrationpluginschrack.json b/schrack/integrationpluginschrack.json new file mode 100644 index 0000000..9775477 --- /dev/null +++ b/schrack/integrationpluginschrack.json @@ -0,0 +1,98 @@ +{ + "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": "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}) + + + +