353 lines
12 KiB
C++
353 lines
12 KiB
C++
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
*
|
|
* Copyright 2013 - 2020, nymea GmbH
|
|
* Contact: contact@nymea.io
|
|
*
|
|
* This file is part of nymea.
|
|
* This project including source code and documentation is protected by
|
|
* copyright law, and remains the property of nymea GmbH. All rights, including
|
|
* reproduction, publication, editing and translation, are reserved. The use of
|
|
* this project is subject to the terms of a license agreement to be concluded
|
|
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
|
* under https://nymea.io/license
|
|
*
|
|
* GNU Lesser General Public License Usage
|
|
* Alternatively, this project may be redistributed and/or modified under the
|
|
* terms of the GNU Lesser General Public License as published by the Free
|
|
* Software Foundation; version 3. This project is distributed in the hope that
|
|
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
|
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this project. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
* For any further details and any questions please contact us under
|
|
* contact@nymea.io or see our FAQ/Licensing Information on
|
|
* https://nymea.io/license/faq
|
|
*
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
#include <QMetaObject>
|
|
#include <QMetaEnum>
|
|
|
|
#include "sunspecthing.h"
|
|
|
|
#include "extern-plugininfo.h"
|
|
|
|
SunspecThing::SunspecThing(Thing *thing, QObject *parent) :
|
|
QObject(parent),
|
|
m_thing(thing)
|
|
{
|
|
|
|
}
|
|
|
|
SunspecThing::~SunspecThing()
|
|
{
|
|
|
|
}
|
|
|
|
void SunspecThing::readCommonBlock()
|
|
{
|
|
if (!m_modbusTcpMaster)
|
|
return;
|
|
|
|
int address = 40001 - 1;
|
|
// Read common block
|
|
m_modbusTcpMaster->readHoldingRegister(m_slaveId, address, 70);
|
|
}
|
|
|
|
void SunspecThing::readStorageBlock()
|
|
{
|
|
if (!m_modbusTcpMaster)
|
|
return;
|
|
|
|
int address;
|
|
if (m_floatingPointRepresentation) {
|
|
address = 40313;
|
|
} else {
|
|
address = 40303;
|
|
}
|
|
/* Startadresse:
|
|
- bei Einstellung „float“: 40313
|
|
- bei Einstellung „int+SF“: 40303
|
|
*/
|
|
m_modbusTcpMaster->readHoldingRegister(m_slaveId, address, 26);
|
|
}
|
|
|
|
QByteArray SunspecThing::convertModbusRegister(const uint16_t &modbusData)
|
|
{
|
|
uint8_t data[2];
|
|
data[0] = modbusData >> 8;
|
|
data[1] = modbusData & 0xFF;
|
|
//qCDebug(dcFronius()) << (char)data[0] << (char)data[1];
|
|
return QByteArray().append((char)data[0]).append((char)data[1]);
|
|
}
|
|
|
|
QBitArray SunspecThing::convertModbusRegisterBits(const uint16_t &modbusData)
|
|
{
|
|
QByteArray data = convertModbusRegister(modbusData);
|
|
QBitArray bits(data.count() * 8);
|
|
|
|
// Convert from QByteArray to QBitArray
|
|
for(int i = 0; i < data.count(); ++i) {
|
|
for(int b = 0; b < 8; b++) {
|
|
bits.setBit(i * 8 + b, data.at(i) & (1 << ( 7 - b)));
|
|
}
|
|
}
|
|
return bits;
|
|
}
|
|
|
|
QByteArray SunspecThing::convertModbusRegisters(const QVector<quint16> &modbusData, int offset, int size)
|
|
{
|
|
QByteArray bytes;
|
|
for (int i = offset; i < offset + size; i++)
|
|
bytes.append(convertModbusRegister(modbusData[i]));
|
|
|
|
return bytes;
|
|
}
|
|
|
|
QString SunspecThing::storageStateToString(const SunspecThing::StorageState &state)
|
|
{
|
|
QMetaObject metaObject ;
|
|
metaObject= SunspecThing::staticMetaObject;
|
|
QMetaEnum metaEnum = metaObject.enumerator(metaObject.indexOfEnumerator("StorageState" ) );
|
|
return QString(metaEnum.valueToKey(state));
|
|
}
|
|
|
|
bool SunspecThing::connectModbus()
|
|
{
|
|
if (m_thing->paramValue(sunspecStorageThingModbusHostParamTypeId).toString().isEmpty()) {
|
|
qCWarning(dcFronius()) << "Empty ip address";
|
|
return false;
|
|
}
|
|
|
|
m_modbusTcpMaster = new ModbusTCPMaster(QHostAddress(m_thing->paramValue(sunspecStorageThingModbusHostParamTypeId).toString()), 502, this);
|
|
connect(m_modbusTcpMaster, &ModbusTCPMaster::connectionStateChanged, this, &SunspecThing::connectionStateChanged);
|
|
connect(m_modbusTcpMaster, &ModbusTCPMaster::writeRequestExecuted, this, &SunspecThing::requestExecuted);
|
|
connect(m_modbusTcpMaster, &ModbusTCPMaster::receivedHoldingRegister, this, &SunspecThing::onReceivedHoldingRegister);
|
|
|
|
m_modbusTcpMaster->setTimeout(3 * 1000); // 3 seconds
|
|
m_modbusTcpMaster->setNumberOfRetries(3);
|
|
|
|
qCDebug(dcFronius()) << "Connected successfully" << m_thing->paramValue(sunspecStorageThingModbusHostParamTypeId).toString();
|
|
|
|
readCommonBlock();
|
|
|
|
return true;
|
|
}
|
|
|
|
QUuid SunspecThing::setGridCharging(const bool &charging)
|
|
{
|
|
if (!m_modbusTcpMaster) {
|
|
return "";
|
|
}
|
|
|
|
/* Start address:
|
|
- for “float” setting: 40313
|
|
- for “int+SF” setting: 40303 */
|
|
|
|
// 40313 + Offset 18 - 1
|
|
// Name ChaGriSet
|
|
/* Setpoint to enable/dis-
|
|
able charging from grid
|
|
PV (charging from grid 0 disabled)
|
|
GRID (charging from 1 grid enabled*/
|
|
|
|
int registerAddress;
|
|
if (m_floatingPointRepresentation) {
|
|
registerAddress = 40313 + 18 - 1;
|
|
} else {
|
|
registerAddress = 40303 + 18 - 1;
|
|
}
|
|
|
|
quint16 value = charging;
|
|
return m_modbusTcpMaster->writeHoldingRegister(m_slaveId, registerAddress, value);
|
|
}
|
|
|
|
QUuid SunspecThing::setChargingRate(const int &charging)
|
|
{
|
|
// 40313 + Offset 14 - 1
|
|
//Register Name InWRte
|
|
/* Defines the maximum charge rate (charge limit). Default is 100% */
|
|
|
|
if (!m_modbusTcpMaster) {
|
|
return "";
|
|
}
|
|
|
|
int registerAddress;
|
|
if (m_floatingPointRepresentation) {
|
|
registerAddress = 40313 + 14 - 1;
|
|
} else {
|
|
registerAddress = 40303 + 14 - 1;
|
|
}
|
|
|
|
int16_t value = charging * 100;
|
|
return m_modbusTcpMaster->writeHoldingRegister(m_slaveId, registerAddress, value);
|
|
}
|
|
|
|
QUuid SunspecThing::setStorageControlMode(const int &charging)
|
|
{
|
|
// 40313 + Offset 6 - 1
|
|
// Set charge bit to enable charge limit, set discharge bit to enable discharge limit, set both bits to enable both limits
|
|
|
|
if (!m_modbusTcpMaster) {
|
|
return "";
|
|
}
|
|
|
|
int registerAddress;
|
|
if (m_floatingPointRepresentation) {
|
|
registerAddress = 40313 + 6 - 1;
|
|
} else {
|
|
registerAddress = 40303 + 6 - 1;
|
|
}
|
|
|
|
uint16_t value = charging;
|
|
return m_modbusTcpMaster->writeHoldingRegister(m_slaveId, registerAddress, value);
|
|
}
|
|
|
|
QUuid SunspecThing::setDischargingRate(const int &charging)
|
|
{
|
|
// 40313 + Offset 13 - 1
|
|
//Register Name OutWRte
|
|
/*Defines the maximum discharge rate (discharge limit). Default is 100% */
|
|
|
|
if (!m_modbusTcpMaster) {
|
|
return "";
|
|
}
|
|
|
|
int registerAddress;
|
|
if (m_floatingPointRepresentation) {
|
|
registerAddress = 40313 + 13 - 1;
|
|
} else {
|
|
registerAddress = 40303 + 13 - 1;
|
|
}
|
|
|
|
int16_t value = charging * 100;
|
|
return m_modbusTcpMaster->writeHoldingRegister(m_slaveId, registerAddress, value);
|
|
}
|
|
|
|
|
|
void SunspecThing::update()
|
|
{
|
|
if (!m_modbusTcpMaster) {
|
|
return;
|
|
}
|
|
|
|
readCommonBlock();
|
|
readStorageBlock();
|
|
}
|
|
|
|
void SunspecThing::onReceivedHoldingRegister(quint32 slaveAddress, quint32 modbusRegister, const QVector<quint16> &data)
|
|
{
|
|
if (!m_thing)
|
|
return;
|
|
|
|
switch (modbusRegister) {
|
|
case 40000: {// Common block, 70 registers long
|
|
|
|
qCDebug(dcFronius()) << "Sunspec Identification:" << convertModbusRegisters(data, 0, 2);
|
|
|
|
if (convertModbusRegisters(data, 0, 2) != "SunS") {
|
|
qCWarning(dcFronius()) << "Could not find SunS value at" << modbusRegister << "on" << slaveAddress;
|
|
return;
|
|
}
|
|
|
|
// ID: 40003
|
|
qCDebug(dcFronius()) << "Id:" << data[2];
|
|
|
|
// Manufacturer: 40005 | 16
|
|
qCDebug(dcFronius()) << "Manufacturer:" << QString::fromLatin1(convertModbusRegisters(data, 4, 16));
|
|
|
|
// Thing model: 40021 | 16
|
|
qCDebug(dcFronius()) << "Thing model:" << QString::fromLatin1(convertModbusRegisters(data, 20, 16));
|
|
|
|
// Data manager version: 40037 | 8
|
|
qCDebug(dcFronius()) << "Data manager version:" << QString::fromLatin1(convertModbusRegisters(data, 36, 8));
|
|
|
|
// Inverter Version: 40045 | 8
|
|
qCDebug(dcFronius()) << "Inverter version:" << QString::fromLatin1(convertModbusRegisters(data, 44, 8));
|
|
|
|
// Serial Number: 40053 | 16
|
|
qCDebug(dcFronius()) << "Serial number:" << QString::fromLatin1(convertModbusRegisters(data, 52, 16));
|
|
|
|
// Modbus thing address : 40069 | 1
|
|
qCDebug(dcFronius()) << "Thing modbus address:" << data[67];
|
|
|
|
/*Sunspec Model Type
|
|
zum Auswählen des Datentyps von Datenmodellen für Wechselrichter
|
|
(3d) float
|
|
Darstellung als Gleitkommazahlen
|
|
SunSpec Inverter Model I111, I112 oder I113
|
|
(3e) int+SF
|
|
Darstellung als ganze Zahlen mit Skalierungsfaktoren
|
|
SunSpec Inverter Model I101, I102 oder I103
|
|
WICHTIG! Da die verschiedenen Modelle über unterschiedliche Anzahlen an Re-
|
|
gistern verfügen, ändern sich durch den Wechsel des Datentyps auch die Regis-
|
|
teradressen aller nachfolgenden Modelle.*/
|
|
qCDebug(dcFronius()) << "SunSpec Inverter Modbus Map:" << data[69];
|
|
if (data[69] > 110){
|
|
m_floatingPointRepresentation = true;
|
|
} else {
|
|
m_floatingPointRepresentation = false;
|
|
}
|
|
} break;
|
|
case 40303:
|
|
case 40313: { //Storage Block
|
|
// ID
|
|
qCDebug(dcFronius()) << "Id:" << data[0];
|
|
|
|
if (data[0] != 124) {
|
|
qCWarning(dcFronius()) << "Invalid id in register address" << modbusRegister << ":" << data[0];
|
|
}
|
|
|
|
// L
|
|
qCDebug(dcFronius()) << "Register count:" << data[1];
|
|
|
|
// WchaMax
|
|
qCDebug(dcFronius()) << "Setpoint of maximum charge:" << data[2] << "W";
|
|
|
|
// WchaGra
|
|
qCDebug(dcFronius()) << "Setpoint for maximum charge:" << data[3] << "[s]";
|
|
|
|
// WdisChaGra
|
|
qCDebug(dcFronius()) << "Setpoint for maximum discharge rate:" << data[4] << "[s]";
|
|
|
|
// StorCtl_Mod: Activate hold/discharge/charge storage control mode. Bit0: charge, Bit 1: discharge
|
|
QBitArray storageControlBits = convertModbusRegisterBits(data[5]);
|
|
|
|
qCDebug(dcFronius()) << "Charging control mode:" << (storageControlBits.testBit(0) ? "On" : "Off");
|
|
m_thing->setStateValue(sunspecStorageEnableChargingLimitStateTypeId, storageControlBits.testBit(0));
|
|
|
|
qCDebug(dcFronius()) << "Discharging control mode:" << (storageControlBits.testBit(1) ? "On" : "Off");
|
|
m_thing->setStateValue(sunspecStorageEnableDischargingLimitStateTypeId, storageControlBits.testBit(1));
|
|
|
|
// MinRsvPct
|
|
qCDebug(dcFronius()) << "Setpoint for minimum reserve:" << data[7] << "[%]";
|
|
|
|
// ChaState
|
|
qCDebug(dcFronius()) << "Current energy:" << ((double)data[8] / 100.0) << "[%]";
|
|
m_thing->setStateValue(sunspecStorageEnergyStateTypeId, (double)data[8] / 100.0);
|
|
|
|
// ChaSt
|
|
//Charge status of storage thing. Enumerated
|
|
qCDebug(dcFronius()) << "Charge state" << storageStateToString(static_cast<StorageState>(data[11]));
|
|
m_thing->setStateValue(sunspecStorageStorageStateStateTypeId, storageStateToString(static_cast<StorageState>(data[11])));
|
|
|
|
// OutWRte
|
|
m_thing->setStateValue(sunspecStorageDischargingRateStateTypeId, (int16_t)data[12] / 100);
|
|
qCDebug(dcFronius()) << "Percent of max. discharge rate:" << ((int16_t)data[12] / 100) << "[%]";
|
|
|
|
// InWRte
|
|
m_thing->setStateValue(sunspecStorageChargingRateStateTypeId, (int16_t)data[13] / 100);
|
|
qCDebug(dcFronius()) << "Percent of max. charge rate:" << ((int16_t)data[13] / 100) << "[%]";
|
|
|
|
// ChaGriSet
|
|
m_thing->setStateValue(sunspecStorageGridChargingStateTypeId, data[18]);
|
|
qCDebug(dcFronius()) << "Charging from grid:" << (data[18] == 0 ? "disabled" : "enabled");
|
|
} break;
|
|
default:
|
|
break;
|
|
};
|
|
}
|