/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright 2013 - 2025, 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 "sunspecfreqwattparammodel.h" #include "sunspecconnection.h" SunSpecFreqWattParamModel::SunSpecFreqWattParamModel(SunSpecConnection *connection, quint16 modbusStartRegister, quint16 modelLength, SunSpecDataPoint::ByteOrder byteOrder, QObject *parent) : SunSpecModel(connection, modbusStartRegister, 127, modelLength, byteOrder, parent) { m_modelBlockType = SunSpecModel::ModelBlockTypeFixed; initDataPoints(); } SunSpecFreqWattParamModel::~SunSpecFreqWattParamModel() { } QString SunSpecFreqWattParamModel::name() const { return "freq_watt_param"; } QString SunSpecFreqWattParamModel::description() const { return "Parameterized Frequency-Watt "; } QString SunSpecFreqWattParamModel::label() const { return "Freq-Watt Param"; } float SunSpecFreqWattParamModel::wGra() const { return m_wGra; } QModbusReply *SunSpecFreqWattParamModel::setWGra(float wGra) { if (!m_initialized) return nullptr; SunSpecDataPoint dp = m_dataPoints.value("WGra"); QVector registers = SunSpecDataPoint::convertFromFloatWithSSF(wGra, m_wGraSf, dp.dataType()); QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, m_modbusStartRegister + dp.addressOffset(), registers.length()); request.setValues(registers); return m_connection->modbusTcpClient()->sendWriteRequest(request, m_connection->slaveId()); } float SunSpecFreqWattParamModel::hzStr() const { return m_hzStr; } QModbusReply *SunSpecFreqWattParamModel::setHzStr(float hzStr) { if (!m_initialized) return nullptr; SunSpecDataPoint dp = m_dataPoints.value("HzStr"); QVector registers = SunSpecDataPoint::convertFromFloatWithSSF(hzStr, m_hzStrStopSf, dp.dataType()); QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, m_modbusStartRegister + dp.addressOffset(), registers.length()); request.setValues(registers); return m_connection->modbusTcpClient()->sendWriteRequest(request, m_connection->slaveId()); } float SunSpecFreqWattParamModel::hzStop() const { return m_hzStop; } QModbusReply *SunSpecFreqWattParamModel::setHzStop(float hzStop) { if (!m_initialized) return nullptr; SunSpecDataPoint dp = m_dataPoints.value("HzStop"); QVector registers = SunSpecDataPoint::convertFromFloatWithSSF(hzStop, m_hzStrStopSf, dp.dataType()); QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, m_modbusStartRegister + dp.addressOffset(), registers.length()); request.setValues(registers); return m_connection->modbusTcpClient()->sendWriteRequest(request, m_connection->slaveId()); } SunSpecFreqWattParamModel::HysenaFlags SunSpecFreqWattParamModel::hysEna() const { return m_hysEna; } QModbusReply *SunSpecFreqWattParamModel::setHysEna(HysenaFlags hysEna) { if (!m_initialized) return nullptr; SunSpecDataPoint dp = m_dataPoints.value("HysEna"); QVector registers = SunSpecDataPoint::convertFromUInt16(static_cast(hysEna)); QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, m_modbusStartRegister + dp.addressOffset(), registers.length()); request.setValues(registers); return m_connection->modbusTcpClient()->sendWriteRequest(request, m_connection->slaveId()); } SunSpecFreqWattParamModel::ModenaFlags SunSpecFreqWattParamModel::modEna() const { return m_modEna; } QModbusReply *SunSpecFreqWattParamModel::setModEna(ModenaFlags modEna) { if (!m_initialized) return nullptr; SunSpecDataPoint dp = m_dataPoints.value("ModEna"); QVector registers = SunSpecDataPoint::convertFromUInt16(static_cast(modEna)); QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, m_modbusStartRegister + dp.addressOffset(), registers.length()); request.setValues(registers); return m_connection->modbusTcpClient()->sendWriteRequest(request, m_connection->slaveId()); } float SunSpecFreqWattParamModel::hzStopWGra() const { return m_hzStopWGra; } QModbusReply *SunSpecFreqWattParamModel::setHzStopWGra(float hzStopWGra) { if (!m_initialized) return nullptr; SunSpecDataPoint dp = m_dataPoints.value("HzStopWGra"); QVector registers = SunSpecDataPoint::convertFromFloatWithSSF(hzStopWGra, m_rmpIncDecSf, dp.dataType()); QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, m_modbusStartRegister + dp.addressOffset(), registers.length()); request.setValues(registers); return m_connection->modbusTcpClient()->sendWriteRequest(request, m_connection->slaveId()); } qint16 SunSpecFreqWattParamModel::wGraSf() const { return m_wGraSf; } qint16 SunSpecFreqWattParamModel::hzStrStopSf() const { return m_hzStrStopSf; } qint16 SunSpecFreqWattParamModel::rmpIncDecSf() const { return m_rmpIncDecSf; } quint16 SunSpecFreqWattParamModel::pad() const { return m_pad; } void SunSpecFreqWattParamModel::initDataPoints() { SunSpecDataPoint modelIdDataPoint; modelIdDataPoint.setName("ID"); modelIdDataPoint.setLabel("Model ID"); modelIdDataPoint.setDescription("Model identifier"); modelIdDataPoint.setMandatory(true); modelIdDataPoint.setSize(1); modelIdDataPoint.setAddressOffset(0); modelIdDataPoint.setSunSpecDataType("uint16"); modelIdDataPoint.setByteOrder(m_byteOrder); m_dataPoints.insert(modelIdDataPoint.name(), modelIdDataPoint); SunSpecDataPoint modelLengthDataPoint; modelLengthDataPoint.setName("L"); modelLengthDataPoint.setLabel("Model Length"); modelLengthDataPoint.setDescription("Model length"); modelLengthDataPoint.setMandatory(true); modelLengthDataPoint.setSize(1); modelLengthDataPoint.setAddressOffset(1); modelLengthDataPoint.setSunSpecDataType("uint16"); modelLengthDataPoint.setByteOrder(m_byteOrder); m_dataPoints.insert(modelLengthDataPoint.name(), modelLengthDataPoint); SunSpecDataPoint wGraDataPoint; wGraDataPoint.setName("WGra"); wGraDataPoint.setLabel("WGra"); wGraDataPoint.setDescription("The slope of the reduction in the maximum allowed watts output as a function of frequency."); wGraDataPoint.setUnits("% PM/Hz"); wGraDataPoint.setMandatory(true); wGraDataPoint.setSize(1); wGraDataPoint.setAddressOffset(2); wGraDataPoint.setBlockOffset(0); wGraDataPoint.setScaleFactorName("WGra_SF"); wGraDataPoint.setSunSpecDataType("uint16"); wGraDataPoint.setAccess(SunSpecDataPoint::AccessReadWrite); wGraDataPoint.setByteOrder(m_byteOrder); m_dataPoints.insert(wGraDataPoint.name(), wGraDataPoint); SunSpecDataPoint hzStrDataPoint; hzStrDataPoint.setName("HzStr"); hzStrDataPoint.setLabel("HzStr"); hzStrDataPoint.setDescription("The frequency deviation from nominal frequency (ECPNomHz) at which a snapshot of the instantaneous power output is taken to act as the CAPPED power level (PM) and above which reduction in power output occurs."); hzStrDataPoint.setUnits("Hz"); hzStrDataPoint.setMandatory(true); hzStrDataPoint.setSize(1); hzStrDataPoint.setAddressOffset(3); hzStrDataPoint.setBlockOffset(1); hzStrDataPoint.setScaleFactorName("HzStrStop_SF"); hzStrDataPoint.setSunSpecDataType("int16"); hzStrDataPoint.setAccess(SunSpecDataPoint::AccessReadWrite); hzStrDataPoint.setByteOrder(m_byteOrder); m_dataPoints.insert(hzStrDataPoint.name(), hzStrDataPoint); SunSpecDataPoint hzStopDataPoint; hzStopDataPoint.setName("HzStop"); hzStopDataPoint.setLabel("HzStop"); hzStopDataPoint.setDescription("The frequency deviation from nominal frequency (ECPNomHz) at which curtailed power output may return to normal and the cap on the power level value is removed."); hzStopDataPoint.setUnits("Hz"); hzStopDataPoint.setMandatory(true); hzStopDataPoint.setSize(1); hzStopDataPoint.setAddressOffset(4); hzStopDataPoint.setBlockOffset(2); hzStopDataPoint.setScaleFactorName("HzStrStop_SF"); hzStopDataPoint.setSunSpecDataType("int16"); hzStopDataPoint.setAccess(SunSpecDataPoint::AccessReadWrite); hzStopDataPoint.setByteOrder(m_byteOrder); m_dataPoints.insert(hzStopDataPoint.name(), hzStopDataPoint); SunSpecDataPoint hysEnaDataPoint; hysEnaDataPoint.setName("HysEna"); hysEnaDataPoint.setLabel("HysEna"); hysEnaDataPoint.setDescription("Enable hysteresis"); hysEnaDataPoint.setMandatory(true); hysEnaDataPoint.setSize(1); hysEnaDataPoint.setAddressOffset(5); hysEnaDataPoint.setBlockOffset(3); hysEnaDataPoint.setSunSpecDataType("bitfield16"); hysEnaDataPoint.setAccess(SunSpecDataPoint::AccessReadWrite); hysEnaDataPoint.setByteOrder(m_byteOrder); m_dataPoints.insert(hysEnaDataPoint.name(), hysEnaDataPoint); SunSpecDataPoint modEnaDataPoint; modEnaDataPoint.setName("ModEna"); modEnaDataPoint.setLabel("ModEna"); modEnaDataPoint.setDescription("Is Parameterized Frequency-Watt control active."); modEnaDataPoint.setMandatory(true); modEnaDataPoint.setSize(1); modEnaDataPoint.setAddressOffset(6); modEnaDataPoint.setBlockOffset(4); modEnaDataPoint.setSunSpecDataType("bitfield16"); modEnaDataPoint.setAccess(SunSpecDataPoint::AccessReadWrite); modEnaDataPoint.setByteOrder(m_byteOrder); m_dataPoints.insert(modEnaDataPoint.name(), modEnaDataPoint); SunSpecDataPoint hzStopWGraDataPoint; hzStopWGraDataPoint.setName("HzStopWGra"); hzStopWGraDataPoint.setLabel("HzStopWGra"); hzStopWGraDataPoint.setDescription("The maximum time-based rate of change at which power output returns to normal after having been capped by an over frequency event."); hzStopWGraDataPoint.setUnits("% WMax/min"); hzStopWGraDataPoint.setSize(1); hzStopWGraDataPoint.setAddressOffset(7); hzStopWGraDataPoint.setBlockOffset(5); hzStopWGraDataPoint.setScaleFactorName("RmpIncDec_SF"); hzStopWGraDataPoint.setSunSpecDataType("uint16"); hzStopWGraDataPoint.setAccess(SunSpecDataPoint::AccessReadWrite); hzStopWGraDataPoint.setByteOrder(m_byteOrder); m_dataPoints.insert(hzStopWGraDataPoint.name(), hzStopWGraDataPoint); SunSpecDataPoint wGraSfDataPoint; wGraSfDataPoint.setName("WGra_SF"); wGraSfDataPoint.setLabel("WGra_SF"); wGraSfDataPoint.setDescription("Scale factor for output gradient."); wGraSfDataPoint.setSize(1); wGraSfDataPoint.setAddressOffset(8); wGraSfDataPoint.setBlockOffset(6); wGraSfDataPoint.setSunSpecDataType("sunssf"); wGraSfDataPoint.setByteOrder(m_byteOrder); m_dataPoints.insert(wGraSfDataPoint.name(), wGraSfDataPoint); SunSpecDataPoint hzStrStopSfDataPoint; hzStrStopSfDataPoint.setName("HzStrStop_SF"); hzStrStopSfDataPoint.setLabel("HzStrStop_SF"); hzStrStopSfDataPoint.setDescription("Scale factor for frequency deviations."); hzStrStopSfDataPoint.setSize(1); hzStrStopSfDataPoint.setAddressOffset(9); hzStrStopSfDataPoint.setBlockOffset(7); hzStrStopSfDataPoint.setSunSpecDataType("sunssf"); hzStrStopSfDataPoint.setByteOrder(m_byteOrder); m_dataPoints.insert(hzStrStopSfDataPoint.name(), hzStrStopSfDataPoint); SunSpecDataPoint rmpIncDecSfDataPoint; rmpIncDecSfDataPoint.setName("RmpIncDec_SF"); rmpIncDecSfDataPoint.setLabel("RmpIncDec_SF"); rmpIncDecSfDataPoint.setDescription("Scale factor for increment and decrement ramps."); rmpIncDecSfDataPoint.setSize(1); rmpIncDecSfDataPoint.setAddressOffset(10); rmpIncDecSfDataPoint.setBlockOffset(8); rmpIncDecSfDataPoint.setSunSpecDataType("sunssf"); rmpIncDecSfDataPoint.setByteOrder(m_byteOrder); m_dataPoints.insert(rmpIncDecSfDataPoint.name(), rmpIncDecSfDataPoint); SunSpecDataPoint padDataPoint; padDataPoint.setName("Pad"); padDataPoint.setSize(1); padDataPoint.setAddressOffset(11); padDataPoint.setBlockOffset(9); padDataPoint.setSunSpecDataType("pad"); padDataPoint.setByteOrder(m_byteOrder); m_dataPoints.insert(padDataPoint.name(), padDataPoint); } void SunSpecFreqWattParamModel::processBlockData() { // Scale factors if (m_dataPoints.value("WGra_SF").isValid()) m_wGraSf = m_dataPoints.value("WGra_SF").toInt16(); if (m_dataPoints.value("HzStrStop_SF").isValid()) m_hzStrStopSf = m_dataPoints.value("HzStrStop_SF").toInt16(); if (m_dataPoints.value("RmpIncDec_SF").isValid()) m_rmpIncDecSf = m_dataPoints.value("RmpIncDec_SF").toInt16(); // Update properties according to the data point type if (m_dataPoints.value("WGra").isValid()) m_wGra = m_dataPoints.value("WGra").toFloatWithSSF(m_wGraSf); if (m_dataPoints.value("HzStr").isValid()) m_hzStr = m_dataPoints.value("HzStr").toFloatWithSSF(m_hzStrStopSf); if (m_dataPoints.value("HzStop").isValid()) m_hzStop = m_dataPoints.value("HzStop").toFloatWithSSF(m_hzStrStopSf); if (m_dataPoints.value("HysEna").isValid()) m_hysEna = static_cast(m_dataPoints.value("HysEna").toUInt16()); if (m_dataPoints.value("ModEna").isValid()) m_modEna = static_cast(m_dataPoints.value("ModEna").toUInt16()); if (m_dataPoints.value("HzStopWGra").isValid()) m_hzStopWGra = m_dataPoints.value("HzStopWGra").toFloatWithSSF(m_rmpIncDecSf); if (m_dataPoints.value("WGra_SF").isValid()) m_wGraSf = m_dataPoints.value("WGra_SF").toInt16(); if (m_dataPoints.value("HzStrStop_SF").isValid()) m_hzStrStopSf = m_dataPoints.value("HzStrStop_SF").toInt16(); if (m_dataPoints.value("RmpIncDec_SF").isValid()) m_rmpIncDecSf = m_dataPoints.value("RmpIncDec_SF").toInt16(); if (m_dataPoints.value("Pad").isValid()) m_pad = m_dataPoints.value("Pad").toUInt16(); qCDebug(dcSunSpecModelData()) << this; } QDebug operator<<(QDebug debug, SunSpecFreqWattParamModel *model) { debug.nospace().noquote() << "SunSpecFreqWattParamModel(Model: " << model->modelId() << ", Register: " << model->modbusStartRegister() << ", Length: " << model->modelLength() << ")\n"; debug.nospace().noquote() << " - " << model->dataPoints().value("WGra") << "-->"; if (model->dataPoints().value("WGra").isValid()) { debug.nospace().noquote() << model->wGra() << "\n"; } else { debug.nospace().noquote() << "NaN\n"; } debug.nospace().noquote() << " - " << model->dataPoints().value("HzStr") << "-->"; if (model->dataPoints().value("HzStr").isValid()) { debug.nospace().noquote() << model->hzStr() << "\n"; } else { debug.nospace().noquote() << "NaN\n"; } debug.nospace().noquote() << " - " << model->dataPoints().value("HzStop") << "-->"; if (model->dataPoints().value("HzStop").isValid()) { debug.nospace().noquote() << model->hzStop() << "\n"; } else { debug.nospace().noquote() << "NaN\n"; } debug.nospace().noquote() << " - " << model->dataPoints().value("HysEna") << "-->"; if (model->dataPoints().value("HysEna").isValid()) { debug.nospace().noquote() << model->hysEna() << "\n"; } else { debug.nospace().noquote() << "NaN\n"; } debug.nospace().noquote() << " - " << model->dataPoints().value("ModEna") << "-->"; if (model->dataPoints().value("ModEna").isValid()) { debug.nospace().noquote() << model->modEna() << "\n"; } else { debug.nospace().noquote() << "NaN\n"; } debug.nospace().noquote() << " - " << model->dataPoints().value("HzStopWGra") << "-->"; if (model->dataPoints().value("HzStopWGra").isValid()) { debug.nospace().noquote() << model->hzStopWGra() << "\n"; } else { debug.nospace().noquote() << "NaN\n"; } debug.nospace().noquote() << " - " << model->dataPoints().value("Pad") << "-->"; if (model->dataPoints().value("Pad").isValid()) { debug.nospace().noquote() << model->pad() << "\n"; } else { debug.nospace().noquote() << "NaN\n"; } return debug.space().quote(); }