From 5a2be02340becd1c58c96ddbbbfe96e960789dc3 Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Fri, 18 Sep 2020 18:03:04 +0200 Subject: [PATCH] fixed rebase --- sunspec/integrationpluginsunspec.cpp | 335 +++++++++++++++- sunspec/integrationpluginsunspec.h | 20 +- sunspec/integrationpluginsunspec.json | 525 +++++++++++++++++++++++--- sunspec/sunspec.cpp | 413 ++++++++++++++++++++ sunspec/sunspec.h | 223 +++++++++++ sunspec/sunspec.pro | 12 +- sunspec/sunspecinverter.cpp | 129 +++++++ sunspec/sunspecinverter.h | 118 ++++++ sunspec/sunspecmeter.cpp | 61 +++ sunspec/sunspecmeter.h | 91 +++++ sunspec/sunspecstorage.cpp | 123 ++++++ sunspec/sunspecstorage.h | 109 ++++++ sunspec/sunspecstringcombiner.cpp | 89 +++++ sunspec/sunspecstringcombiner.h | 129 +++++++ sunspec/sunspectracker.cpp | 42 +++ sunspec/sunspectracker.h | 78 ++++ 16 files changed, 2418 insertions(+), 79 deletions(-) create mode 100644 sunspec/sunspecinverter.cpp create mode 100644 sunspec/sunspecinverter.h create mode 100644 sunspec/sunspecmeter.cpp create mode 100644 sunspec/sunspecmeter.h create mode 100644 sunspec/sunspecstorage.cpp create mode 100644 sunspec/sunspecstorage.h create mode 100644 sunspec/sunspecstringcombiner.cpp create mode 100644 sunspec/sunspecstringcombiner.h create mode 100644 sunspec/sunspectracker.cpp create mode 100644 sunspec/sunspectracker.h diff --git a/sunspec/integrationpluginsunspec.cpp b/sunspec/integrationpluginsunspec.cpp index 7974f77..378dbd3 100644 --- a/sunspec/integrationpluginsunspec.cpp +++ b/sunspec/integrationpluginsunspec.cpp @@ -35,25 +35,71 @@ IntegrationPluginSunSpec::IntegrationPluginSunSpec() { -} -void IntegrationPluginSunSpec::discoverThings(ThingDiscoveryInfo *info) -{ - // Discovery possible? - Q_ASSERT_X(false, "discoverThing", QString("Unhandled thingClassId: %1").arg(info->thingClassId().toString()).toUtf8()); } void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info) { if (info->thing()->thingClassId() == sunspecInverterThingClassId) { + QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecInverterThingIpAddressParamTypeId).toString()); + SunSpecInverter *sunSpecInverter = new SunSpecInverter(address, 502, this); + m_sunSpecInverters.insert(info->thing(), sunSpecInverter); + if (!sunSpecInverter->connectModbus()) { + QTimer::singleShot(2000, info, [this, info] { + setupThing(info); + }); + qCWarning(dcSunSpec()) << "Error connecting to SunSpec device"; + return; + } + connect(sunSpecInverter, &SunSpecInverter::initFinished, info, [info] { + qCDebug(dcSunSpec()) << "Modbus Inverter init finished"; + info->finish(Thing::ThingErrorNoError); + }); - } else if (info->thing()->thingClassId() == sunspecPvModuleThingClassId) { + connect(info, &ThingSetupInfo::aborted, sunSpecInverter, [info, this] { + m_sunSpecInverters.take(info->thing())->deleteLater(); + }); + connect(sunSpecInverter, &SunSpecInverter::inverterDataReceived, this, &IntegrationPluginSunSpec::onInverterDataReceived); } else if (info->thing()->thingClassId() == sunspecMeterThingClassId) { + QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecMeterThingIpAddressParamTypeId).toString()); + SunSpecMeter *sunSpecMeter = new SunSpecMeter(address, 502, this); + m_sunSpecMeters.insert(info->thing(), sunSpecMeter); + if (!sunSpecMeter->connectModbus()) { + QTimer::singleShot(2000, info, [this, info] { + setupThing(info); + }); + } + + connect(info, &ThingSetupInfo::aborted, sunSpecMeter, [info, this] { + m_sunSpecMeters.take(info->thing())->deleteLater(); + }); } else if (info->thing()->thingClassId() == sunspecTrackerThingClassId) { + QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecTrackerThingIpAddressParamTypeId).toString()); + SunSpecTracker *sunSpecTracker = new SunSpecTracker(address, 502, this); + m_sunSpecTrackers.insert(info->thing(), sunSpecTracker); + if (!sunSpecTracker->connectModbus()) { + QTimer::singleShot(2000, info, [this, info] { + setupThing(info); + }); + } + connect(info, &ThingSetupInfo::aborted, sunSpecTracker, [info, this] { + m_sunSpecTrackers.take(info->thing())->deleteLater(); + }); } else if (info->thing()->thingClassId() == sunspecStorageThingClassId) { + QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecStorageThingIpAddressParamTypeId).toString()); + SunSpecStorage *sunSpecStorage = new SunSpecStorage(address, 502, this); + m_sunSpecStorages.insert(info->thing(), sunSpecStorage); + if (!sunSpecStorage->connectModbus()) { + QTimer::singleShot(2000, info, [this, info] { + setupThing(info); + }); + } + connect(info, &ThingSetupInfo::aborted, sunSpecStorage, [info, this] { + m_sunSpecStorages.take(info->thing())->deleteLater(); + }); } else { Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(info->thing()->thingClassId().toString()).toUtf8()); @@ -69,12 +115,77 @@ void IntegrationPluginSunSpec::postSetupThing(Thing *thing) m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(refreshTime); connect(m_refreshTimer, &PluginTimer::timeout, this, &IntegrationPluginSunSpec::onRefreshTimer); } + + if (thing->thingClassId() == sunspecInverterThingClassId) { + SunSpecInverter *sunSpecInverter = m_sunSpecInverters.take(thing); + if (!sunSpecInverter) + return; + sunSpecInverter->getInverterMap(); + thing->setParamValue(sunspecInverterThingManufacturerParamTypeId, sunSpecInverter->manufacturer()); + thing->setParamValue(sunspecInverterThingSerialNumberParamTypeId, sunSpecInverter->serialNumber()); + thing->setParamValue(sunspecInverterThingDeviceModelParamTypeId, sunSpecInverter->deviceModel()); + + } else if (thing->thingClassId() == sunspecMeterThingClassId) { + SunSpecMeter *sunSpecMeter = m_sunSpecMeters.take(thing); + if (!sunSpecMeter) + return; + //TODO upate data + thing->setParamValue(sunspecMeterThingManufacturerParamTypeId, sunSpecMeter->manufacturer()); + thing->setParamValue(sunspecMeterThingSerialNumberParamTypeId, sunSpecMeter->serialNumber()); + thing->setParamValue(sunspecMeterThingDeviceModelParamTypeId, sunSpecMeter->deviceModel()); + + } else if (thing->thingClassId() == sunspecTrackerThingClassId) { + SunSpecTracker *sunSpecTracker = m_sunSpecTrackers.take(thing); + if (!sunSpecTracker) + return; + //TODO upate data + thing->setParamValue(sunspecTrackerThingManufacturerParamTypeId, sunSpecTracker->manufacturer()); + thing->setParamValue(sunspecTrackerThingSerialNumberParamTypeId, sunSpecTracker->serialNumber()); + thing->setParamValue(sunspecTrackerThingDeviceModelParamTypeId, sunSpecTracker->deviceModel()); + + } else if (thing->thingClassId() == sunspecStorageThingClassId) { + SunSpecStorage *sunSpecStorage = m_sunSpecStorages.take(thing); + if (!sunSpecStorage) + return; + //TODO upate data + thing->setParamValue(sunspecStorageThingManufacturerParamTypeId, sunSpecStorage->manufacturer()); + thing->setParamValue(sunspecStorageThingSerialNumberParamTypeId, sunSpecStorage->serialNumber()); + thing->setParamValue(sunspecStorageThingDeviceModelParamTypeId, sunSpecStorage->deviceModel()); + + } else { + Q_ASSERT_X(false, "postSetupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); + } + } void IntegrationPluginSunSpec::thingRemoved(Thing *thing) { Q_UNUSED(thing) + if (thing->thingClassId() == sunspecInverterThingClassId) { + SunSpecInverter *sunSpecInverter = m_sunSpecInverters.take(thing); + if (sunSpecInverter) + sunSpecInverter->deleteLater(); + + } else if (thing->thingClassId() == sunspecMeterThingClassId) { + SunSpecMeter *sunSpecMeter = m_sunSpecMeters.take(thing); + if (sunSpecMeter) + sunSpecMeter->deleteLater(); + + } else if (thing->thingClassId() == sunspecTrackerThingClassId) { + SunSpecTracker *sunSpecTracker = m_sunSpecTrackers.take(thing); + if (sunSpecTracker) + sunSpecTracker->deleteLater(); + + } else if (thing->thingClassId() == sunspecStorageThingClassId) { + SunSpecStorage *sunSpecStorage = m_sunSpecStorages.take(thing); + if (sunSpecStorage) + sunSpecStorage->deleteLater(); + + } else { + Q_ASSERT_X(false, "thingRemoved", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); + } + if (myThings().isEmpty()) { hardwareManager()->pluginTimerManager()->unregisterTimer(m_refreshTimer); m_refreshTimer = nullptr; @@ -84,18 +195,76 @@ void IntegrationPluginSunSpec::thingRemoved(Thing *thing) void IntegrationPluginSunSpec::executeAction(ThingActionInfo *info) { Thing *thing = info->thing(); - //Action action = info->action(); + Action action = info->action(); if (thing->thingClassId() == sunspecInverterThingClassId) { + SunSpecInverter *sunSpecInverter = m_sunSpecInverters.value(thing); + if (!sunSpecInverter) { + qWarning(dcSunSpec()) << "Could not find SunSpec inverter for thing" << thing->name(); + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } info->finish(Thing::ThingErrorActionTypeNotFound); + } else if (thing->thingClassId() == sunspecMeterThingClassId) { info->finish(Thing::ThingErrorActionTypeNotFound); + } else if (thing->thingClassId() == sunspecTrackerThingClassId) { info->finish(Thing::ThingErrorActionTypeNotFound); - } else if (thing->thingClassId() == sunspecPvModuleThingClassId) { - info->finish(Thing::ThingErrorActionTypeNotFound); + } else if (thing->thingClassId() == sunspecStorageThingClassId) { - info->finish(Thing::ThingErrorActionTypeNotFound); + SunSpecStorage *sunSpecStorage = m_sunSpecStorages.value(thing); + if (!sunSpecStorage) { + qWarning(dcSunSpec()) << "Could not find sunspec instance for thing"; + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } + + if (action.actionTypeId() == sunspecStorageGridChargingActionTypeId) { + QUuid requestId = sunSpecStorage->setGridCharging(action.param(sunspecStorageGridChargingActionGridChargingParamTypeId).value().toBool()); + if (requestId.isNull()) { + info->finish(Thing::ThingErrorHardwareFailure); + } else { + m_asyncActions.insert(requestId, info); + connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); + } + } else if (action.actionTypeId() == sunspecStorageEnableChargingLimitActionTypeId) { + int value = (action.param(sunspecStorageEnableChargingLimitActionEnableChargingLimitParamTypeId).value().toBool() << 1) | thing->stateValue(sunspecStorageEnableDischargingLimitStateTypeId).toBool(); + QUuid requestId = sunSpecStorage->setStorageControlMode(value); + if (requestId.isNull()) { + info->finish(Thing::ThingErrorHardwareFailure); + } else { + m_asyncActions.insert(requestId, info); + connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); + } + } else if (action.actionTypeId() == sunspecStorageChargingRateActionTypeId) { + QUuid requestId = sunSpecStorage->setChargingRate(action.param(sunspecStorageChargingRateActionChargingRateParamTypeId).value().toInt()); + if (requestId.isNull()) { + info->finish(Thing::ThingErrorHardwareFailure); + } else { + m_asyncActions.insert(requestId, info); + connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); + } + } else if (action.actionTypeId() == sunspecStorageEnableDischargingLimitActionTypeId) { + int value = (action.param(sunspecStorageEnableDischargingLimitActionEnableDischargingLimitParamTypeId).value().toBool() << 1) | thing->stateValue(sunspecStorageEnableChargingLimitStateTypeId).toBool(); + QUuid requestId = sunSpecStorage->setStorageControlMode(value); + if (requestId.isNull()) { + info->finish(Thing::ThingErrorHardwareFailure); + } else { + m_asyncActions.insert(requestId, info); + connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); + } + } else if (action.actionTypeId() == sunspecStorageDischargingRateActionTypeId) { + QUuid requestId = sunSpecStorage->setDischargingRate(action.param(sunspecStorageDischargingRateActionDischargingRateParamTypeId).value().toInt()); + if (requestId.isNull()) { + info->finish(Thing::ThingErrorHardwareFailure); + } else { + m_asyncActions.insert(requestId, info); + connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); + } + } else { + Q_ASSERT_X(false, "executeAction", QString("Unhandled action: %1").arg(action.actionTypeId().toString()).toUtf8()); + } } else { Q_ASSERT_X(false, "executeAction", QString("Unhandled thingClassId: %1").arg(info->thing()->thingClassId().toString()).toUtf8()); } @@ -104,6 +273,18 @@ void IntegrationPluginSunSpec::executeAction(ThingActionInfo *info) void IntegrationPluginSunSpec::onRefreshTimer() { //get data + foreach (SunSpecInverter *inverter, m_sunSpecInverters) { + inverter->getInverterMap(); + } + foreach (SunSpecMeter *meter, m_sunSpecMeters) { + Q_UNUSED(meter) //TODO + } + foreach (SunSpecTracker *tracker, m_sunSpecTrackers) { + Q_UNUSED(tracker) //TODO + } + foreach (SunSpecStorage *storage, m_sunSpecStorages) { + Q_UNUSED(storage) //TODO + } } void IntegrationPluginSunSpec::onPluginConfigurationChanged(const ParamTypeId ¶mTypeId, const QVariant &value) @@ -117,3 +298,137 @@ void IntegrationPluginSunSpec::onPluginConfigurationChanged(const ParamTypeId &p } } } + +void IntegrationPluginSunSpec::onInverterDataReceived(const SunSpecInverter::InverterData &inverterData) +{ + SunSpecInverter *inverter = static_cast(sender()); + Thing *thing = m_sunSpecInverters.key(inverter); + + if(!thing) { + return; + } + thing->setStateValue(sunspecInverterAcPowerStateTypeId, inverterData.acPower); + thing->setStateValue(sunspecInverterAcEnergyStateTypeId, inverterData.acEnergy/1000.00); + thing->setStateValue(sunspecInverterLineFrequencyStateTypeId, inverterData.lineFrequency); + thing->setStateValue(sunspecInverterTotalCurrentStateTypeId, inverterData.acCurrent); + thing->setStateValue(sunspecInverterCabinetTemperatureStateTypeId, inverterData.cabinetTemperature); + + thing->setStateValue(sunspecInverterPhaseACurrentStateTypeId, inverterData.phaseACurrent); + thing->setStateValue(sunspecInverterPhaseBCurrentStateTypeId, inverterData.phaseBCurrent); + thing->setStateValue(sunspecInverterPhaseCCurrentStateTypeId, inverterData.phaseCCurrent); + + thing->setStateValue(sunspecInverterPhaseANVoltageStateTypeId, inverterData.phaseVoltageAN); + thing->setStateValue(sunspecInverterPhaseBNVoltageStateTypeId, inverterData.phaseVoltageBN); + thing->setStateValue(sunspecInverterPhaseCNVoltageStateTypeId, inverterData.phaseVoltageCN); + + switch(inverterData.operatingState) { + case SunSpec::SunSpecOperatingState::Off: + thing->setStateValue(sunspecInverterOperatingStateStateTypeId, "Off"); + break; + case SunSpec::SunSpecOperatingState::MPPT: + thing->setStateValue(sunspecInverterOperatingStateStateTypeId, "MPPT"); + break; + case SunSpec::SunSpecOperatingState::Fault: + thing->setStateValue(sunspecInverterOperatingStateStateTypeId, "Fault"); + break; + case SunSpec::SunSpecOperatingState::Standby: + thing->setStateValue(sunspecInverterOperatingStateStateTypeId, "Standby"); + break; + case SunSpec::SunSpecOperatingState::Sleeping: + thing->setStateValue(sunspecInverterOperatingStateStateTypeId, "Sleeping"); + break; + case SunSpec::SunSpecOperatingState::Starting: + thing->setStateValue(sunspecInverterOperatingStateStateTypeId, "Starting"); + break; + case SunSpec::SunSpecOperatingState::Throttled: + thing->setStateValue(sunspecInverterOperatingStateStateTypeId, "Throttled"); + break; + case SunSpec::SunSpecOperatingState::ShuttingDown: + thing->setStateValue(sunspecInverterOperatingStateStateTypeId, "Shutting down"); + break; + } + + switch(inverterData.event) { + case SunSpec::SunSpecEvent1::OVER_TEMP: + thing->setStateValue(sunspecInverterErrorStateTypeId, "Over temperature"); + break; + case SunSpec::SunSpecEvent1::UNDER_TEMP: + thing->setStateValue(sunspecInverterErrorStateTypeId, "Under temperature"); + break; + case SunSpec::SunSpecEvent1::GroundFault: + thing->setStateValue(sunspecInverterErrorStateTypeId, "Ground fault"); + break; + case SunSpec::SunSpecEvent1::MEMORY_LOSS: + thing->setStateValue(sunspecInverterErrorStateTypeId, "Memory loss"); + break; + case SunSpec::SunSpecEvent1::AC_OVER_VOLT: + thing->setStateValue(sunspecInverterErrorStateTypeId, "AC voltage above limit"); + break; + case SunSpec::SunSpecEvent1::CABINET_OPEN: + thing->setStateValue(sunspecInverterErrorStateTypeId, "Cabinet open"); + break; + case SunSpec::SunSpecEvent1::AC_DISCONNECT: + thing->setStateValue(sunspecInverterErrorStateTypeId, "AC disconnect open"); + break; + case SunSpec::SunSpecEvent1::AC_UNDER_VOLT: + thing->setStateValue(sunspecInverterErrorStateTypeId, "AC voltage under limit"); + break; + case SunSpec::SunSpecEvent1::DC_DISCONNECT: + thing->setStateValue(sunspecInverterErrorStateTypeId, "DC disconnect open"); + break; + case SunSpec::SunSpecEvent1::DcOverVolatage: + thing->setStateValue(sunspecInverterErrorStateTypeId, "DC over voltage"); + break; + case SunSpec::SunSpecEvent1::OVER_FREQUENCY: + thing->setStateValue(sunspecInverterErrorStateTypeId, "Frequency above limit"); + break; + case SunSpec::SunSpecEvent1::GRID_DISCONNECT: + thing->setStateValue(sunspecInverterErrorStateTypeId, "Grid disconnect"); + break; + case SunSpec::SunSpecEvent1::HW_TEST_FAILURE: + thing->setStateValue(sunspecInverterErrorStateTypeId, "Hardware test failure"); + break; + case SunSpec::SunSpecEvent1::MANUAL_SHUTDOWN: + thing->setStateValue(sunspecInverterErrorStateTypeId, "Manual shutdown"); + break; + case SunSpec::SunSpecEvent1::UNDER_FREQUENCY: + thing->setStateValue(sunspecInverterErrorStateTypeId, "Frequency under limit"); + break; + case SunSpec::SunSpecEvent1::BLOWN_STRING_FUSE: + thing->setStateValue(sunspecInverterErrorStateTypeId, "Blown string fuse on input"); + break; + } +} + +void IntegrationPluginSunSpec::onStorageDataReceived(const SunSpecStorage::StorageData &storageData) +{ + SunSpecStorage *storage = static_cast(sender()); + Thing *thing = m_sunSpecStorages.key(storage); + + if(!thing) { + return; + } + thing->setStateValue(sunspecStorageStorageStateStateTypeId, storageData.chargingState); +} + +void IntegrationPluginSunSpec::onMeterDataReceived(const SunSpecMeter::MeterData &meterData) +{ + SunSpecMeter *meter = static_cast(sender()); + Thing *thing = m_sunSpecMeters.key(meter); + + if(!thing) { + return; + } + //thing->setStateValue(sunspecMeterStorageStateStateTypeId, meterData.event); +} + +void IntegrationPluginSunSpec::onTrackerDataReceived(const SunSpecTracker::TrackerData &trackerData) +{ + SunSpecTracker *tracker = static_cast(sender()); + Thing *thing = m_sunSpecTrackers.key(tracker); + + if(!thing) { + return; + } + //thing->setStateValue(sunspecStorageStorageStateStateTypeId, storageData.chargingState); +} diff --git a/sunspec/integrationpluginsunspec.h b/sunspec/integrationpluginsunspec.h index 85e62a9..48c393a 100644 --- a/sunspec/integrationpluginsunspec.h +++ b/sunspec/integrationpluginsunspec.h @@ -36,6 +36,11 @@ #include "../modbus/modbustcpmaster.h" +#include "sunspecinverter.h" +#include "sunspecstorage.h" +#include "sunspecmeter.h" +#include "sunspectracker.h" + #include class IntegrationPluginSunSpec: public IntegrationPlugin @@ -45,22 +50,21 @@ class IntegrationPluginSunSpec: public IntegrationPlugin Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginsunspec.json") Q_INTERFACES(IntegrationPlugin) - public: explicit IntegrationPluginSunSpec(); - - 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: - PluginTimer *m_refreshTimer = nullptr; - QHash m_modbusTcpMasters; QHash m_asyncActions; + QHash m_sunSpecInverters; + QHash m_sunSpecStorages; + QHash m_sunSpecMeters; + QHash m_sunSpecTrackers; void update(Thing *thing); private slots: @@ -72,8 +76,10 @@ private slots: void onWriteRequestExecuted(QUuid requestId, bool success); void onWriteRequestError(QUuid requestId, const QString &error); - void onReceivedHoldingRegister(quint32 slaveAddress, quint32 modbusRegister, const QVector &values); - void onReceivedInputRegister(quint32 slaveAddress, quint32 modbusRegister, const QVector &values); + void onInverterDataReceived(const SunSpecInverter::InverterData &inverterData); + void onStorageDataReceived(const SunSpecStorage::StorageData &storageData); + void onMeterDataReceived(const SunSpecMeter::MeterData &meterData); + void onTrackerDataReceived(const SunSpecTracker::TrackerData &trackerData); }; #endif // INTEGRATIONPLUGINSUNSPEC_H diff --git a/sunspec/integrationpluginsunspec.json b/sunspec/integrationpluginsunspec.json index 3a0ffbf..f394fda 100644 --- a/sunspec/integrationpluginsunspec.json +++ b/sunspec/integrationpluginsunspec.json @@ -32,10 +32,28 @@ "type": "QString" }, { - "id": "08bf6597-976d-4fb9-8fc2-4372c07961d4", + "id": "418d90eb-16e9-47e4-83e3-a116a8fa4de7", + "name":"manufacturer", + "displayName": "Manufacturer", + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" + }, + { + "id": "edf1bc0f-d143-4b75-a11f-635339fb30bf", + "name":"deviceModel", + "displayName": "Device model", + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" + }, + { + "id": "d5181377-c746-4352-8683-6d90a4a83e6e", "name":"serialNumber", "displayName": "Serial number", - "type": "QString" + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" } ], "stateTypes":[ @@ -57,48 +75,150 @@ "type": "bool", "defaultValue": 0, "writable": true - } - ] - }, - { - "name": "sunspecPvModule", - "displayName": "SunSpec PV Module", - "id": "fb7ff0df-a745-4313-83e4-1e5007b06fe2", - "createMethods": [ "User" ], - "interfaces": ["connectable"], - "paramTypes": [ - { - "id": "890e468b-fc76-4641-9569-93cd45e6bb8d", - "name":"ipAddress", - "displayName": "IP address", - "type": "QString" }, { - "id": "d5a9510d-720f-4a28-9d91-977eedd022bd", - "name":"serialNumber", - "displayName": "Serial number", - "type": "QString" - } - ], - "stateTypes":[ - { - "id": "edc032d2-022f-43f7-9131-831ad23ee62a", - "name": "connected", - "displayName": "Connected", - "displayNameEvent": "Connected changed", - "type": "bool", - "defaultValue": false, - "cached": false + "id": "26560dd8-6de4-445e-ba55-391d7241c370", + "name": "totalCurrent", + "displayName": "Total AC current", + "displayNameEvent": "Total AC current changed", + "type": "int", + "unit": "Ampere", + "defaultValue": 0 }, { - "id": "0b7485eb-3cf7-45b2-87cf-9ec85366b197", - "name": "power", - "displayName": "Power", - "displayNameEvent": "Power changed", - "displayNameAction": "Change power", - "type": "bool", - "defaultValue": 0, - "writable": true + "id": "3140ccd3-40cf-46c8-8bb2-8c3ea4582f84", + "name": "phaseACurrent", + "displayName": "Phase A current", + "displayNameEvent": "Phase A current changed", + "type": "int", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "7ea1a53a-6fd9-4914-8283-b57aa1aaaebf", + "name": "phaseBCurrent", + "displayName": "Phase B current", + "displayNameEvent": "Phase B current changed", + "type": "int", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "aa4b4cf5-43d0-4be5-9505-403918b5371d", + "name": "phaseCCurrent", + "displayName": "Phase C current", + "displayNameEvent": "Phase C current changed", + "type": "int", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "f08521aa-9c38-4c31-95e1-acb616f6e9c6", + "name": "phaseANVoltage", + "displayName": "Phase AN voltage", + "displayNameEvent": "Phase AN volatage changed", + "type": "int", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "739b8805-d522-4406-bede-d1e4200a3aa9", + "name": "phaseBNVoltage", + "displayName": "Phase BN voltage", + "displayNameEvent": "Phase BN voltage changed", + "type": "int", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "949b797d-5566-4667-8982-e430d23548e2", + "name": "phaseCNVoltage", + "displayName": "Phase CN voltage", + "displayNameEvent": "Phase CN voltage changed", + "type": "int", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "14036f44-25fd-4e93-8e8c-c677b06a2c34", + "name": "acPower", + "displayName": "AC power", + "displayNameEvent": "AC power changed", + "type": "int", + "unit": "KiloWatt", + "defaultValue": 0 + }, + { + "id": "faa45cae-ed28-4150-9036-fceddf9d6776", + "name": "lineFrequency", + "displayName": "Line frequency", + "displayNameEvent": "Line frequency changed", + "type": "int", + "unit": "Hertz", + "defaultValue": 0 + }, + { + "id": "d493880d-eb58-4530-8010-8ea4f6d63387", + "name": "acEnergy", + "displayName": "AC energy", + "displayNameEvent": "AC energy changed", + "type": "int", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "44b0320f-89a7-4248-bad4-288ef898a5cc", + "name": "cabinetTemperature", + "displayName": "Cabinet temperature", + "displayNameEvent": "Cabinet temperature changed", + "type": "int", + "unit": "DegreeCelsius", + "defaultValue": 0 + }, + { + "id": "cebdce98-42d1-4a28-8834-8960efc0e83f", + "name": "operatingState", + "displayName": "Operating state", + "displayNameEvent": "Operating state changed", + "type": "QString", + "possibleValues": [ + "Off", + "Sleeping", + "Starting", + "MPPT", + "Throttled", + "Shutting down", + "Fault", + "Standby" + ], + "defaultValue": "Off" + }, + { + "id": "4479af96-c499-4f15-abd6-4afdb18a9e09", + "name": "error", + "displayName": "Error", + "displayNameEvent": "Error changed", + "type": "QString", + "possibleValues": [ + "None", + "Ground fault", + "DC over voltage", + "AC disconnect open", + "DC disconnect open", + "Grid disconnect", + "Cabinet open", + "Manual shutdown", + "Over temperature", + "Frequency above limit", + "Frequency under limit", + "AC voltage above limit", + "AC voltage under limit", + "Blown string fuse on input", + "Under temperature", + "Communication error", + "Hardware test failure" + ], + "defaultValue": "None" } ] }, @@ -116,10 +236,28 @@ "type": "QString" }, { - "id": "89af6d27-4b81-42ef-9883-1e2ba2067884", + "id": "aa1ca51d-5e9c-4a1a-a4ec-e479d4f702d5", + "name":"manufacturer", + "displayName": "Manufacturer", + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" + }, + { + "id": "52da5388-28d7-48a2-8ad6-4c1e852a4348", + "name":"deviceModel", + "displayName": "Device model", + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" + }, + { + "id": "9fa7288c-e7a1-4163-baa1-6f484e1feb55", "name":"serialNumber", "displayName": "Serial number", - "type": "QString" + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" } ], "stateTypes":[ @@ -133,14 +271,113 @@ "cached": false }, { - "id": "36f18720-69ca-4021-9328-262d87397f1b", - "name": "power", - "displayName": "Power", - "displayNameEvent": "Power changed", - "displayNameAction": "Change power", - "type": "bool", - "defaultValue": 0, - "writable": true + "id": "7855d176-c6b8-439e-9f12-a71f01c1c7aa", + "name": "totalCurrent", + "displayName": "Total AC current", + "displayNameEvent": "Total AC current changed", + "type": "int", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "da494d99-5de3-4d03-b7dd-33a33db32164", + "name": "phaseACurrent", + "displayName": "Phase A current", + "displayNameEvent": "Phase A current changed", + "type": "int", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "023b6e5c-be3f-4d8c-adfd-e009c7bebffb", + "name": "phaseBCurrent", + "displayName": "Phase B current", + "displayNameEvent": "Phase B current changed", + "type": "int", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "2676ad36-3d97-4691-8334-e13934cc58d1", + "name": "phaseCCurrent", + "displayName": "Phase C current", + "displayNameEvent": "Phase C current changed", + "type": "int", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "bc54ca1e-1476-40eb-9974-9e3c2f893dd8", + "name": "lnACVoltage", + "displayName": "Line to Neutral AC Voltage", + "displayNameEvent": "Line to Neutral AC Voltage changed", + "type": "int", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "4bd32d91-877a-4821-9db9-5981de20d21d", + "name": "phaseANVoltage", + "displayName": "Phase AN voltage", + "displayNameEvent": "Phase AN volatage changed", + "type": "int", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "f090cb78-d7ed-44fd-a5ad-ea9016021c34", + "name": "phaseBNVoltage", + "displayName": "Phase BN voltage", + "displayNameEvent": "Phase BN voltage changed", + "type": "int", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "fa5aa6f5-e67d-485a-835f-24e49298856c", + "name": "phaseCNVoltage", + "displayName": "Phase CN voltage", + "displayNameEvent": "Phase CN voltage changed", + "type": "int", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "dfcf52f5-6279-4d25-b7c8-a93b92c39a0c", + "name": "frequency", + "displayName": "Frequency", + "displayNameEvent": "Frequency changed", + "type": "int", + "unit": "Hertz", + "defaultValue": 0 + }, + { + "id": "c28c642f-46da-44de-ba0d-c4cbfadbf2cd", + "name": "totalRealPower", + "displayName": "Total real power", + "displayNameEvent": "Total real power changed", + "type": "int", + "unit": "KiloWatt", + "defaultValue": 0 + }, + { + "id": "73ebf57f-1ad2-4d19-bfd9-9e0a514c1243 +", + "name": "energyExported", + "displayName": "Total real energy exported", + "displayNameEvent": "Total real energy exported changed", + "type": "int", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "63fa4721-1b0a-458c-b66c-17c161107f0d", + "name": "energyImported", + "displayName": "Total real energy imported", + "displayNameEvent": "Total real energy imported changed", + "type": "int", + "unit": "KiloWattHour", + "defaultValue": 0 } ] }, @@ -158,10 +395,28 @@ "type": "QString" }, { - "id": "67ce05ff-14bf-4d43-94ba-d5f290278514", + "id": "2795052c-5d63-423d-b44e-fdf02dfaac37", + "name":"manufacturer", + "displayName": "Manufacturer", + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" + }, + { + "id": "7528c840-da8c-40ba-ab82-914cfc686a49", + "name":"deviceModel", + "displayName": "Device model", + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" + }, + { + "id": "ce9cc598-947c-4efc-b04f-21e784c5d2df", "name":"serialNumber", "displayName": "Serial number", - "type": "QString" + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" } ], "stateTypes":[ @@ -200,10 +455,28 @@ "type": "QString" }, { - "id": "ae76db81-7969-440f-85f0-28ed46d772db", + "id": "1d8f1ef7-33ad-40ed-99dd-ec54f87c2f17", + "name":"manufacturer", + "displayName": "Manufacturer", + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" + }, + { + "id": "2a7f8464-6f03-4113-ac1d-d8ebd8705695", + "name":"deviceModel", + "displayName": "Device model", + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" + }, + { + "id": "2003acdb-38ad-4184-baf8-9ef8e67fc452", "name":"serialNumber", "displayName": "Serial number", - "type": "QString" + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" } ], "stateTypes":[ @@ -217,14 +490,146 @@ "cached": false }, { - "id": "9c0e0b08-4461-4e54-aaf3-cab9155028da", - "name": "power", - "displayName": "Power", - "displayNameEvent": "Power changed", - "displayNameAction": "Change power", + "id": "3171a6e0-43a7-4de8-8e20-f748e44af7ac", + "name": "batteryCritical", + "displayName": "Battery critical", + "displayNameEvent": "Battery critical changed", "type": "bool", - "defaultValue": 0, + "defaultValue": false + }, + { + "id": "0bf53f80-97f8-488b-b514-58f9fe08c183", + "name": "batteryLevel", + "displayName": "Battery level", + "displayNameEvent": "Battery level changed", + "type": "int", + "unit": "Percentage", + "minValue": 0, + "maxValue": 100, + "defaultValue": 0 + }, + { + "id": "da2b19c5-0f48-49d1-93f0-abdc0051407d", + "name": "storageState", + "displayName": "State", + "displayNameEvent": "State changed", + "type": "QString", + "possibleValues": [ + "Off", + "Empty", + "Discharging", + "Charging", + "Full", + "Holding", + "Testing" + ], + "defaultValue": "Off" + }, + { + "id": "221a2ef6-0a92-4ff0-87fe-7bd920dbec0b", + "name": "gridCharging", + "displayName": "Grid charging", + "displayNameEvent": "Grid charging changed", + "type": "bool", + "defaultValue": false, + "writable": true, + "displayNameAction": "Set grid charging" + }, + { + "id": "1f530f79-c0d2-466b-90e1-79149e34d92f", + "name": "enableChargingLimit", + "displayName": "Charging limit", + "displayNameEvent": "Charging limit changed", + "type": "bool", + "defaultValue": false, + "writable": true, + "displayNameAction": "Enable Charging Limit" + }, + { + "id": "7f469bbc-64a5-4045-8d5f-9a1a85039851", + "name": "chargingRate", + "displayName": "Charging rate", + "displayNameEvent": "Charging rate changed", + "type": "int", + "minValue": -100, + "maxValue": 100, + "unit": "Percentage", + "defaultValue": false, + "writable": true, + "displayNameAction": "Set charging rate" + }, + { + "id": "bc99a159-815a-40ab-a6e8-b46f315305f7", + "name": "enableDischargingLimit", + "displayName": "Discharging limit", + "displayNameEvent": "Discharging limit changed", + "displayNameAction": "Enable Discharging Limit", + "type": "bool", + "defaultValue": false, "writable": true + }, + { + "id": "6068f030-acce-44a2-b95f-bd00dd5ca760", + "name": "dischargingRate", + "displayName": "Discharging rate", + "displayNameEvent": "Discharging rate changed", + "type": "int", + "minValue": -100, + "maxValue": 100, + "unit": "Percentage", + "defaultValue": false, + "writable": true, + "displayNameAction": "Set discharging rate" + } + ] + }, + { + "name": "sunspecStringCombiner", + "displayName": "SunSpec String Combiner", + "id": "7787f238-f5a3-4ba5-b3ec-7d513b87bf71", + "createMethods": [ "User" ], + "interfaces": ["connectable"], + "paramTypes": [ + { + "id": "226697c3-37bd-49c7-8c0e-40537573c18e", + "name":"ipAddress", + "displayName": "IP address", + "type": "QString" + }, + { + "id": "3f126e65-05e2-46a3-aeeb-bfacc0bdd9b0", + "name":"manufacturer", + "displayName": "Manufacturer", + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" + }, + { + "id": "40666030-3609-467f-b26c-3a42d1c6dca1", + "name":"deviceModel", + "displayName": "Device model", + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" + }, + { + "id": "f5046138-9271-4a91-a3eb-c2ecfa755ef5", + "name":"serialNumber", + "displayName": "Serial number", + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" + } + ], + "stateTypes":[ + { + "id": "3f791612-50fd-4a3c-ac19-27e01dd8c63a", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false, + "cached": false } ] } diff --git a/sunspec/sunspec.cpp b/sunspec/sunspec.cpp index e69de29..37b22d0 100644 --- a/sunspec/sunspec.cpp +++ b/sunspec/sunspec.cpp @@ -0,0 +1,413 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 . +* +* 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 "sunspec.h" +#include "extern-plugininfo.h" +#include + +SunSpec::SunSpec(const QHostAddress &hostAddress, uint port, QObject *parent) : + QObject(parent), + m_hostAddress(hostAddress), + m_port(port) +{ + m_modbusTcpClient = new QModbusTcpClient(this); + m_modbusTcpClient->setConnectionParameter(QModbusDevice::NetworkPortParameter, m_port); + m_modbusTcpClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter, m_hostAddress.toString()); + m_modbusTcpClient->setTimeout(2000); + m_modbusTcpClient->setNumberOfRetries(3); + + connect(m_modbusTcpClient, &QModbusTcpClient::stateChanged, this, &SunSpec::onModbusStateChanged); +} + +SunSpec::~SunSpec() +{ + +} + +bool SunSpec::connectModbus() +{ + return m_modbusTcpClient->connectDevice();; +} + +void SunSpec::findBaseRegister() +{ + qCDebug(dcSunSpec()) << "find base register"; + + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, 40000, 2); + + if (QModbusReply *reply = m_modbusTcpClient->sendReadRequest(request, m_slaveId)) { + if (!reply->isFinished()) { + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [reply, this] { + + if (reply->error() == QModbusDevice::NoError) { + const QModbusDataUnit unit = reply->result(); + //uint modbusAddress = unit.startAddress(); TODO + if ((unit.value(0) << 16 | unit.value(1)) == 0x53756e53) { + //Well-known value. Uniquely identifies this as a SunSpec Modbus Map + qCDebug(dcSunSpec()) << "Found start of modbus map"; + //emit foundBaseRegister(modbusAddress); + } + } else { + qCWarning(dcSunSpec()) << "Read response error:" << reply->error(); + } + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error) { + qCWarning(dcSunSpec()) << "Modbus reply error:" << error; + reply->finished(); // To make sure it will be deleted + }); + } else { + qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + delete reply; // broadcast replies return immediately + return; + } + } else { + qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + return; + } +} + +void SunSpec::findModbusMap(const QList &ids, uint modbusAddressOffset) +{ + qCDebug(dcSunSpec()) << "Find modbus map. Start register" << m_baseRegister+modbusAddressOffset; + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, m_baseRegister+modbusAddressOffset, 2); + + if (QModbusReply *reply = m_modbusTcpClient->sendReadRequest(request, m_slaveId)) { + if (!reply->isFinished()) { + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [ids, reply, this] { + + if (reply->error() == QModbusDevice::NoError) { + const QModbusDataUnit unit = reply->result(); + uint modbusAddress = unit.startAddress(); + BlockId blockId = BlockId(unit.value(0)); + int blockLength = unit.value(1); + if (blockId > 800 || blockId == BlockId::End) { + qCDebug(dcSunSpec()) << "Block id not found, Id:" << ids; + modbusMapSearchFinished(ids, 0, "Ids not found"); + return; + } + if (ids.contains(blockId)) { + qCDebug(dcSunSpec()) << "Found block" << BlockId(blockId); + foundModbusMap(BlockId(blockId), modbusAddress); + } else { + //read next block header + qCDebug(dcSunSpec()) << "Found Block" << blockId << "with block length" << blockLength; + findModbusMap(ids, modbusAddress+2+blockLength-m_baseRegister); //read next block + } + } else { + qCWarning(dcSunSpec()) << "Read response error:" << reply->error(); + } + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error) { + qCWarning(dcSunSpec()) << "Modbus replay error:" << error; + reply->finished(); // To make sure it will be deleted + }); + } else { + delete reply; // broadcast replies return immediately + return; + } + } else { + qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + return; + } +} + +void SunSpec::readMapHeader(uint modbusAddress) +{ + qCDebug(dcSunSpec()) << "Read block header. Modbus Address:" << modbusAddress << "Slave ID" << m_slaveId; + if (modbusAddress == 40000) + modbusAddress += 2; + + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, modbusAddress, 2); + + if (QModbusReply *reply = m_modbusTcpClient->sendReadRequest(request, m_slaveId)) { + if (!reply->isFinished()) { + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [reply, this] { + + if (reply->error() == QModbusDevice::NoError) { + const QModbusDataUnit unit = reply->result(); + uint modbusAddress = unit.startAddress(); + BlockId mapId = BlockId(unit.value(0)); + int mapLength = unit.value(1); + qCDebug(dcSunSpec()) << "Received block header response. Map ID:" << mapId << "Map length" << mapLength; + mapHeaderReceived(modbusAddress, mapId, mapLength); + } else { + qCWarning(dcSunSpec()) << "Read response error:" << reply->error(); + } + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error) { + qCWarning(dcSunSpec()) << "Modbus reply error:" << error; + reply->finished(); // To make sure it will be deleted + }); + } else { + qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + delete reply; // broadcast replies return immediately + return; + } + } else { + qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + return; + } +} + +void SunSpec::readMap(uint modbusAddress, uint modelLength) +{ + qCDebug(dcSunSpec()) << "Read map. Modbus Address" << modbusAddress << ", Slave ID" << m_slaveId; + + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, modbusAddress, modelLength+2); + + if (QModbusReply *reply = m_modbusTcpClient->sendReadRequest(request, m_slaveId)) { + if (!reply->isFinished()) { + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [reply, this] { + + if (reply->error() == QModbusDevice::NoError) { + const QModbusDataUnit unit = reply->result(); + uint modbusAddress = unit.startAddress(); + BlockId mapId = BlockId(unit.value(0)); + uint mapLength = unit.value(1); + qCDebug(dcSunSpec()) << "Received map. Modbus address" << modbusAddress << "Map ID" << mapId << "Map Length" << mapLength; + emit mapReceived(mapId, mapLength, unit.values().mid(2)); + } else { + qCWarning(dcSunSpec()) << "Read response error:" << reply->error(); + } + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error) { + qCWarning(dcSunSpec()) << "Modbus reply error:" << error; + reply->finished(); // To make sure it will be deleted + }); + } else { + qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + delete reply; // broadcast replies return immediately + return; + } + } else { + qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + return; + } +} + +void SunSpec::readCommonMap() +{ + qCDebug(dcSunSpec()) << "Read common block"; + + // Base and Alternate Base Register Addresses + // DeviceModbus maps begin at one of three well-­known Modbus base addresses. + // Preferred Base Register: 40000 + // Alternate Base Register: 50000 + // Alternate Base Register: 00000 + + // Common Model model-length is 66 + // First two registers are the SunSpec Modbus Map identifier + + qCDebug(dcSunSpec()) << "Read common block header. Modbus Address" << m_baseRegister+2 << ", Slave ID" << m_slaveId; + + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, m_baseRegister+2, 66); + + if (QModbusReply *reply = m_modbusTcpClient->sendReadRequest(request, m_slaveId)) { + if (!reply->isFinished()) { + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [reply, this] { + + if (reply->error() == QModbusDevice::NoError) { + const QModbusDataUnit unit = reply->result(); + uint modbusAddress = unit.startAddress(); + BlockId mapId = BlockId(unit.value(0)); + int mapLength = unit.value(1); + m_manufacturer = convertModbusRegisters(unit.values(), MandatoryRegistersModel1::Manufacturer, 16); + m_manufacturer.remove('\x00'); + m_deviceModel = convertModbusRegisters(unit.values(), MandatoryRegistersModel1::Model, 16); + m_deviceModel.remove('\x00'); + m_serialNumber = convertModbusRegisters(unit.values(), MandatoryRegistersModel1::SerialNumber, 16); + m_serialNumber.remove('\x00'); + qCDebug(dcSunSpec()) << "Received common block response. Manufacturer" << m_manufacturer << "Model" << m_deviceModel << "Serial number" << m_serialNumber; + mapHeaderReceived(modbusAddress, mapId, mapLength); + } else { + qCWarning(dcSunSpec()) << "Read response error:" << reply->error(); + } + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error) { + qCWarning(dcSunSpec()) << "Modbus reply error:" << error; + reply->finished(); // To make sure it will be deleted + }); + } else { + qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + delete reply; // broadcast replies return immediately + return; + } + } else { + qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + return; + } +} + +void SunSpec::onReceivedHoldingRegister(quint32 slaveAddress, quint32 modbusRegister, const QVector &data) +{ + if (modbusRegister == 00000 || modbusRegister == 40000 || modbusRegister == 50000) { + // Common block, 66 registers long + 2 header registers + + qCDebug(dcSunSpec()) << "Sunspec Identification:" << convertModbusRegisters(data, 0, 2); + + if (convertModbusRegisters(data, 0, 2) != "SunS") { + qCWarning(dcSunSpec()) << "Could not find SunS value at" << modbusRegister << "at Slave" << slaveAddress; + return; + } + m_baseRegister = modbusRegister; + + //Mandatory SunSpec Registers + // ID: 40003 + qCDebug(dcSunSpec()) << "Id:" << data[MandatoryRegistersModel1::Manufacturer]; + + // Manufacturer: 40005 | 16 + qCDebug(dcSunSpec()) << "Manufacturer:" << QString::fromLatin1(convertModbusRegisters(data, MandatoryRegistersModel1::Manufacturer, 16)); + + // Thing model: 40021 | 16 + qCDebug(dcSunSpec()) << "Thing model:" << QString::fromLatin1(convertModbusRegisters(data, MandatoryRegistersModel1::Model, 16)); + + // Data manager version: 40037 | 8 + qCDebug(dcSunSpec()) << "Data manager version:" << QString::fromLatin1(convertModbusRegisters(data, 36, 8)); + + // Inverter Version: 40045 | 8 + qCDebug(dcSunSpec()) << "Inverter version:" << QString::fromLatin1(convertModbusRegisters(data, 44, 8)); + + // Serial Number: 40053 | 16 + qCDebug(dcSunSpec()) << "Serial number:" << QString::fromLatin1(convertModbusRegisters(data, MandatoryRegistersModel1::SerialNumber, 16)); + + // Modbus thing address : 40069 | 1 + qCDebug(dcSunSpec()) << "Thing modbus address:" << data[67]; + }; +} + + +QByteArray SunSpec::convertModbusRegister(const uint16_t &modbusData) +{ + uint8_t data[2]; + data[0] = modbusData >> 8; + data[1] = modbusData & 0xFF; + //qCDebug(dcSunSpec()) << (char)data[0] << (char)data[1]; + return QByteArray().append((char)data[0]).append((char)data[1]); +} + +QBitArray SunSpec::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 SunSpec::convertModbusRegisters(const QVector &modbusData, int offset, int size) +{ + QByteArray bytes; + for (int i = offset; i < offset + size; i++) + bytes.append(convertModbusRegister(modbusData[i])); + + return bytes; +} + +float SunSpec::convertValueWithSSF(quint16 rawValue, quint16 sunssf) +{ + float value; + value = rawValue * (10^static_cast(sunssf)); + return value; +} + +float SunSpec::convertFloatValues(quint16 rawValue0, quint16 rawValue1) +{ + float value; + uint32_t i = qFromLittleEndian(((uint32_t)rawValue0 << 16) + rawValue1); + memcpy(&value, &i, sizeof(float)); + return value; +} + +void SunSpec::onModbusStateChanged(QModbusDevice::State state) +{ + bool connected = (state != QModbusDevice::UnconnectedState); + if (!connected) { + //try to reconnect in 10 seconds + QTimer::singleShot(10000, m_modbusTcpClient, [this] { + if (m_modbusTcpClient->connectDevice()) { + qCDebug(dcSunSpec()) << "Could not reconnect"; + } + }); + } else { + readCommonMap(); + } + emit connectionStateChanged(connected); +} + +QUuid SunSpec::writeHoldingRegister(uint slaveAddress, uint registerAddress, quint16 value) +{ + return writeHoldingRegisters(slaveAddress, registerAddress, QVector() << value); +} + +QUuid SunSpec::writeHoldingRegisters(uint slaveAddress, uint registerAddress, const QVector &values) +{ + if (!m_modbusTcpClient) { + return ""; + } + QUuid requestId = QUuid::createUuid(); + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, registerAddress, values.length()); + request.setValues(values); + + if (QModbusReply *reply = m_modbusTcpClient->sendWriteRequest(request, slaveAddress)) { + if (!reply->isFinished()) { + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [reply, requestId, this] { + + if (reply->error() != QModbusDevice::NoError) { + qCWarning(dcSunSpec()) << "Read response error:" << reply->error(); + } + reply->deleteLater(); + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){ + + qCWarning(dcSunSpec()) << "Modbus reply error:" << error; + reply->finished(); // To make sure it will be deleted + }); + QTimer::singleShot(200, reply, &QModbusReply::deleteLater); + } else { + delete reply; // broadcast replies return immediately + return ""; + } + } else { + qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + return ""; + } + return requestId; +} diff --git a/sunspec/sunspec.h b/sunspec/sunspec.h index e69de29..878003b 100644 --- a/sunspec/sunspec.h +++ b/sunspec/sunspec.h @@ -0,0 +1,223 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 . +* +* 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 SUNSPEC_H +#define SUNSPEC_H + +#include +#include +#include +#include + +class SunSpec : public QObject +{ + Q_OBJECT + +public: + + enum MandatoryRegistersModel1 { + Manufacturer = 2, + Model = 18, + SerialNumber = 50 + }; + + enum OptionalRegistersModel1 { + Options = 34, + Version = 40, + DeviceAddress = 64, + ForceAlignment = 65 + }; + + enum SunSpecOperatingState { + Off = 1, + Sleeping, + Starting, + MPPT, + Throttled, + ShuttingDown, + Fault, + Standby + }; + Q_ENUM(SunSpecOperatingState) + + enum SunSpecEvent1 { + GroundFault = 0, + DcOverVolatage, + AC_DISCONNECT, + DC_DISCONNECT, + GRID_DISCONNECT, + CABINET_OPEN, + MANUAL_SHUTDOWN, + OVER_TEMP, + OVER_FREQUENCY, + UNDER_FREQUENCY, + AC_OVER_VOLT, + AC_UNDER_VOLT, + BLOWN_STRING_FUSE, + UNDER_TEMP, + MEMORY_LOSS, + HW_TEST_FAILURE + }; + Q_ENUM(SunSpecEvent1) + + enum BlockId { + Common = 1, + BasicAggregator = 2, + SecureDatasetReadRequest = 3, + SecureDatasetReadResponse = 4, + SecureWriteRequest = 5, + SecureWriteSequentialRequest = 6, + SecureWriteResponseModel = 7, + GetDeviceSecurityCertificate = 8, + SetOperatorSecurityCertificate = 9, + CommunicationInterfaceHeader = 10, + EthernetLinkLayer = 11, + IPv4 = 12, + IPv6 = 13, + ProxyServer = 14, + InterfaceCountersModel = 15, + SimpleIpNetwork = 16, + SerialInterface = 17, + CellularLink = 18, + PPPLink = 19, + InverterSinglePhase = 101, + InverterSplitPhase = 102, + InverterThreePhase = 103, + InverterSinglePhaseFloat = 111, + InverterSplitPhaseFloat = 112, + InverterThreePhaseFloat = 113, + Nameplate = 120, + BasicSettings = 121, + MeasurementsStatus = 122, + ImmediateControls = 123, + Storage = 124, + Pricing = 125, + StaticVoltVAR = 126, + FreqWattParam = 127, + DynamicReactiveCurrent = 128, + LVRTD = 129, + HVRTD = 130, + WattPF = 131, + VoltWatt = 132, + BasicScheduling = 133, + FreqWattCrv = 134, + LFRT = 135, + HFRT = 136, + LVRTC = 137, + HVRTC = 138, + MultipleMPPTInverterExtensionModel = 160, + SinglePhaseMeter = 201, + SplitSinglePhaseMeter = 202, + WyeConnectThreePhaseMeter = 203, + DeltaConnectThreePhaseMeter = 204, + SinglePhaseMeterFloat = 211, + SplitSinglePhaseMeterFloat = 212, + WyeConnectThreePhaseMeterFloat = 213, + DeltaConnectThreePhaseMeterFloat = 214, + SecureACMeterSelectedReadings = 220, + IrradianceModel = 302, + BackOfModuleTemperatureModel = 303, + InclinometerModel = 304, + GPS = 305, + ReferencePointModel = 306, + BaseMet = 307, + MiniMetModel = 308, + StringCombiner = 401, + StringCombinerAdvanced = 402, + StringCombinerCurrent = 403, + StringCombinerCurrentAdvanced = 404, + SolarModuleFloat = 501, + SolarModule = 502, + TrackerController = 601, + EnergyStorageBaseModel = 801, + BatteryBaseModel = 802, + LithiumIonBatteryModel = 803, + VerisStatusConfiguration = 64001, + MersenGreenString = 64020, + EltekInverterExtension = 64101, + OutBackAXSDevice = 64110, + BasicChargeController = 64111, + OutBackFMChargeController = 64112, + End = 65535 + }; + Q_ENUM(BlockId) + + explicit SunSpec(const QHostAddress &hostAddress, uint port = 502, QObject *parent = 0); + ~SunSpec(); + bool connectModbus(); + QString manufacturer(); + QString deviceModel(); + QString serialNumber(); + + QHostAddress m_hostAddress; + uint m_port; + QModbusTcpClient *m_modbusTcpClient = nullptr; + int m_slaveId = 1; + int m_baseRegister = 40000; + bool m_floatingPointRepresentation = false; + QString m_manufacturer = "unkown"; + QString m_deviceModel = "unknown"; + QString m_serialNumber = "unknown"; + + void findBaseRegister(); + void findModbusMap(const QList &mapIds, uint modbusAddressOffset = 69); + + void readMapHeader(uint modbusAddress); + void readMap(uint modbusAddress, uint modelLength); //modbusAddress = model start address, model length is without header + void readCommonMap(); + + float convertValueWithSSF(quint16 rawValue, quint16 sunssf); + float convertFloatValues(quint16 rawValue0, quint16 rawValue1); + QByteArray convertModbusRegister(const uint16_t &modbusData); + QBitArray convertModbusRegisterBits(const uint16_t &modbusData); + QByteArray convertModbusRegisters(const QVector &modbusData, int offset, int size); + + QUuid writeHoldingRegister(uint slaveAddress, uint registerAddress, quint16 value); + QUuid writeHoldingRegisters(uint slaveAddress, uint registerAddress, const QVector &values); + +signals: + void connectionStateChanged(bool status); + void requestExecuted(QUuid requetId, bool success); + + void foundModbusMap(BlockId mapId, int modbusStartRegister); + void modbusMapSearchFinished(const QList &mapIds, uint modbusStartRegister, const QString &error); + + void mapHeaderReceived(uint modbusAddress, BlockId mapId, uint mapLength); + void mapReceived(BlockId mapId, uint mapLength, QVector data); + +private slots: + void onModbusStateChanged(QModbusDevice::State state); + + void onRequestExecuted(QUuid requestId, bool success); + void onRequestError(QUuid requestId, const QString &error); + void onReceivedHoldingRegister(quint32 slaveAddress, quint32 modbusRegister, const QVector &values); +}; + +#endif // SUNSPEC_H diff --git a/sunspec/sunspec.pro b/sunspec/sunspec.pro index a9c3e6a..f005cb0 100644 --- a/sunspec/sunspec.pro +++ b/sunspec/sunspec.pro @@ -7,9 +7,17 @@ QT += \ SOURCES += \ integrationpluginsunspec.cpp \ sunspec.cpp \ - ../modbus/modbustcpmaster.cpp \ + sunspecinverter.cpp \ + sunspecmeter.cpp \ + sunspecstorage.cpp \ + sunspectracker.cpp \ + sunspecstringcombiner.cpp \ HEADERS += \ integrationpluginsunspec.h \ sunspec.h \ - ../modbus/modbustcpmaster.h \ + sunspecinverter.h \ + sunspecmeter.h \ + sunspecstorage.h \ + sunspectracker.h \ + sunspecstringcombiner.h \ diff --git a/sunspec/sunspecinverter.cpp b/sunspec/sunspecinverter.cpp new file mode 100644 index 0000000..69b9591 --- /dev/null +++ b/sunspec/sunspecinverter.cpp @@ -0,0 +1,129 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 . +* +* 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 "sunspecinverter.h" +#include "extern-plugininfo.h" + +SunSpecInverter::SunSpecInverter(const QHostAddress &hostAddress, uint port, QObject *parent) : SunSpec(hostAddress, port, parent) +{ + connect(m_modbusTcpClient, &QModbusClient::stateChanged, this, [this] (QModbusDevice::State state) { + if (state == QModbusDevice::ConnectedState) { + qCDebug(dcSunSpec()) << "Inverter connected successfully"; + QList mapIds; + mapIds.append(BlockId::InverterSinglePhase); + mapIds.append(BlockId::InverterSplitPhase); + mapIds.append(BlockId::InverterThreePhase); + mapIds.append(BlockId::InverterSinglePhaseFloat); + mapIds.append(BlockId::InverterSplitPhaseFloat); + mapIds.append(BlockId::InverterThreePhaseFloat); + findModbusMap(mapIds); + } + }); + connect(this, &SunSpec::foundModbusMap, this, [this] (BlockId mapId, uint modbusRegisterAddress) { + qCDebug(dcSunSpec()) << "Read map header for mapId" << mapId << "and modbus register" << modbusRegisterAddress; + readMapHeader(modbusRegisterAddress); + }); + + connect(this, &SunSpec::mapHeaderReceived, this, [this] (uint modbusAddress, BlockId mapId, uint mapLength) { + m_id = mapId; + m_mapLength = mapLength; + m_mapModbusStartRegister = modbusAddress; + readMap(modbusAddress, mapLength); + }); + + connect(this, &SunSpec::mapReceived, this, &SunSpecInverter::onModbusMapReceived); +} + +void SunSpecInverter::getInverterMap() +{ + readMap(m_mapModbusStartRegister, m_mapLength); +} + +void SunSpecInverter::readInverterBlockHeader() +{ + readMapHeader(m_mapModbusStartRegister); +} + +void SunSpecInverter::onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector data) +{ + Q_UNUSED(mapLength) + switch (mapId) { + case BlockId::InverterSinglePhase: + case BlockId::InverterSplitPhase: + case BlockId::InverterThreePhase: { + InverterData inverterData; + inverterData.acCurrent= convertValueWithSSF(data[Model10X::Model10XAcCurrent], data[Model10X::Model10XAmpereScaleFactor]); + inverterData.acPower = convertValueWithSSF(data[Model10X::Model10XACPower], data[Model10X::Model10XWattScaleFactor]); + inverterData.lineFrequency = convertValueWithSSF(data[Model10X::Model10XLineFrequency], data[Model10X::Model10XHerzScaleFactor]); + + inverterData.phaseACurrent = convertValueWithSSF(data[Model10X::Model10XPhaseACurrent], data[Model10X::Model10XAmpereScaleFactor]); + inverterData.phaseBCurrent = convertValueWithSSF(data[Model10X::Model10XPhaseBCurrent], data[Model10X::Model10XAmpereScaleFactor]); + inverterData.phaseCCurrent = convertValueWithSSF(data[Model10X::Model10XPhaseCCurrent], data[Model10X::Model10XAmpereScaleFactor]); + + inverterData.phaseVoltageAN = convertValueWithSSF(data[Model10X::Model10XPhaseVoltageAN], data[Model10X::Model10XVoltageScaleFactor]); + inverterData.phaseVoltageBN = convertValueWithSSF(data[Model10X::Model10XPhaseVoltageBN], data[Model10X::Model10XVoltageScaleFactor]); + inverterData.phaseVoltageCN = convertValueWithSSF(data[Model10X::Model10XPhaseVoltageCN], data[Model10X::Model10XVoltageScaleFactor]); + + inverterData.acEnergy = convertValueWithSSF(data[Model10X::Model10XAcEnergy], data[Model10X::Model10XWattHoursScaleFactor]); + + inverterData.cabinetTemperature = convertValueWithSSF(data[Model10X::Model10XCabinetTemperature], data[Model10X::Model10XTemperatureScaleFactor]); + inverterData.event = SunSpecEvent1(data[Model10X::Model10XEvent1]); + inverterData.operatingState = SunSpecOperatingState(data[Model10X::Model10XOperatingState]); + emit inverterDataReceived(inverterData); + + } break; + case BlockId::InverterSinglePhaseFloat: + case BlockId::InverterSplitPhaseFloat: + case BlockId::InverterThreePhaseFloat: { + InverterData inverterData; + inverterData.acCurrent = convertFloatValues(data[Model11X::Model11XAcCurrent], data[Model11X::Model11XAcCurrent+1]); + + inverterData.phaseACurrent = convertFloatValues(data[Model11X::Model11XPhaseACurrent], data[Model11X::Model11XPhaseACurrent+1]); + inverterData.phaseBCurrent = convertFloatValues(data[Model11X::Model11XPhaseBCurrent], data[Model11X::Model11XPhaseBCurrent+1]); + inverterData.phaseCCurrent = convertFloatValues(data[Model11X::Model11XPhaseCCurrent], data[Model11X::Model11XPhaseCCurrent+1]); + + inverterData.phaseVoltageAN = convertFloatValues(data[Model11X::Model11XPhaseVoltageAN], data[Model11X::Model11XPhaseVoltageAN+1]); + inverterData.phaseVoltageBN = convertFloatValues(data[Model11X::Model11XPhaseVoltageBN], data[Model11X::Model11XPhaseVoltageBN+1]); + inverterData.phaseVoltageCN = convertFloatValues(data[Model11X::Model11XPhaseVoltageCN], data[Model11X::Model11XPhaseVoltageCN+1]); + + inverterData.acPower = convertFloatValues(data[Model11X::Model11XACPower], data[Model11X::Model11XACPower+1]); + inverterData.lineFrequency = convertFloatValues(data[Model11X::Model11XLineFrequency], data[Model11X::Model11XLineFrequency+1]); + + inverterData.acEnergy = convertFloatValues(data[Model11X::Model11XAcEnergy], data[Model11X::Model11XAcEnergy+1]); + inverterData.cabinetTemperature = convertFloatValues(data[Model11X::Model11XCabinetTemperature], data[Model11X::Model11XCabinetTemperature+1]); + inverterData.event = SunSpecEvent1(data[Model11X::Model11XEvent1]); + inverterData.operatingState = SunSpecOperatingState(data[Model11X::Model11XOperatingState]); + emit inverterDataReceived(inverterData); + } break; + default: + //ignore + break; + } +} diff --git a/sunspec/sunspecinverter.h b/sunspec/sunspecinverter.h new file mode 100644 index 0000000..7243c17 --- /dev/null +++ b/sunspec/sunspecinverter.h @@ -0,0 +1,118 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 . +* +* 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 SUNSPECINVERTER_H +#define SUNSPECINVERTER_H + +#include +#include "sunspec.h" + +class SunSpecInverter : public SunSpec +{ + Q_OBJECT +public: + SunSpecInverter(const QHostAddress &hostAddress, uint port = 502, QObject *parent = 0); + + enum Model10X { // Mandatory register + Model10XAcCurrent = 2, + Model10XPhaseACurrent = 3, + Model10XPhaseBCurrent = 4, + Model10XPhaseCCurrent = 5, + Model10XAmpereScaleFactor = 6, + Model10XPhaseVoltageAN = 10, + Model10XPhaseVoltageBN = 11, + Model10XPhaseVoltageCN = 12, + Model10XVoltageScaleFactor = 13, + Model10XACPower = 14, + Model10XWattScaleFactor = 15, + Model10XLineFrequency = 16, + Model10XHerzScaleFactor = 17, + Model10XAcEnergy = 24, + Model10XWattHoursScaleFactor = 25, + Model10XCabinetTemperature = 33, + Model10XTemperatureScaleFactor = 37, + Model10XOperatingState = 38, + Model10XEvent1 = 40 + }; + + enum Model11X { // Mandatory register + Model11XAcCurrent = 0, + Model11XPhaseACurrent = 2, + Model11XPhaseBCurrent = 4, + Model11XPhaseCCurrent = 6, + Model11XPhaseVoltageAN = 14, + Model11XPhaseVoltageBN = 16, + Model11XPhaseVoltageCN = 18, + Model11XACPower = 20, + Model11XLineFrequency = 22, + Model11XAcEnergy = 30, + Model11XCabinetTemperature = 38, + Model11XOperatingState = 46, + Model11XEvent1 = 48 + }; + + struct InverterData { + float acCurrent; //in ampere + float phaseACurrent; + float phaseBCurrent; + float phaseCCurrent; + float phaseVoltageAB; + float phaseVoltageBC; + float phaseVoltageCA; + float phaseVoltageAN; + float phaseVoltageBN; + float phaseVoltageCN; + float acPower; + float lineFrequency; + float acEnergy; + float cabinetTemperature; // in degree Celsius + SunSpecEvent1 event; + SunSpecOperatingState operatingState; + }; + + void getInverterMap(); + +private: + BlockId m_id = BlockId::InverterThreePhase; //e.g. 103 for three phase inverter, 113 for three phase inverter with floating point representation + uint m_mapLength = 0; + uint m_mapModbusStartRegister = 40000; + + void readInverterBlockHeader(); + +private slots: + void onConnectionStateChanged(); + void onModbusMapReceived(BlockId mapId, uint mapLength, QVector data); + +signals: + void initFinished(); + void inverterDataReceived(InverterData data); +}; + +#endif // SUNSPECINVERTER_H diff --git a/sunspec/sunspecmeter.cpp b/sunspec/sunspecmeter.cpp new file mode 100644 index 0000000..d9b1763 --- /dev/null +++ b/sunspec/sunspecmeter.cpp @@ -0,0 +1,61 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 . +* +* 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 "sunspecmeter.h" +#include "extern-plugininfo.h" + +SunSpecMeter::SunSpecMeter(const QHostAddress &hostAddress, uint port, QObject *parent) : SunSpec(hostAddress, port, parent) +{ + connect(m_modbusTcpClient, &QModbusClient::stateChanged, this, [this] (QModbusDevice::State state) { + if (state == QModbusDevice::ConnectedState) { + qCDebug(dcSunSpec()) << "Meter connected successfully"; + QList mapIds; + mapIds.append(BlockId::SinglePhaseMeter); + mapIds.append(BlockId::SplitSinglePhaseMeter); + mapIds.append(BlockId::WyeConnectThreePhaseMeter); + mapIds.append(BlockId::DeltaConnectThreePhaseMeter); + mapIds.append(BlockId::SinglePhaseMeterFloat); + mapIds.append(BlockId::SplitSinglePhaseMeterFloat); + mapIds.append(BlockId::WyeConnectThreePhaseMeterFloat); + mapIds.append(BlockId::DeltaConnectThreePhaseMeterFloat); + findModbusMap(mapIds); + } + }); +} + +void SunSpecMeter::geMeterMap() +{ + readMap(m_mapModbusStartRegister, m_mapLength); +} + +void SunSpecMeter::readMeterBlockHeader() +{ + +} diff --git a/sunspec/sunspecmeter.h b/sunspec/sunspecmeter.h new file mode 100644 index 0000000..a05967e --- /dev/null +++ b/sunspec/sunspecmeter.h @@ -0,0 +1,91 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 . +* +* 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 SUNSPECMETER_H +#define SUNSPECMETER_H + +#include +#include "sunspec.h" + +class SunSpecMeter : public SunSpec +{ + Q_OBJECT +public: + //Model 203 = Three phase meter + enum MandatoryRegistersModel20X { + TotalAcCurrent = 2, + PhaseACurrent = 3, + PhaseBCurrent = 4, + PhaseCCurrent = 5, + CurrentScaleFactor = 6, + VoltageLN = 7, + PhaseVoltageAN = 8, + PhaseVoltageBN = 9, + PhaseVoltageCN = 10, + VoltageLL = 11, + PhaseVoltageAB = 12, + PhaseVoltageBC = 13, + PhaseVoltageCA = 14, + VoltageScaleFactor = 15, + Frequency = 16, + TotalRealPower = 18, + RealPowerScaleFactor = 22, + TotalRealEnergyExported = 38, + TotalRealEnergyImported = 46, + RealEnergyScaleFactor = 54, + MeterEventFlags = 105 + }; + + struct MeterData { + SunSpecEvent1 event; + SunSpecOperatingState operatingState; + }; + + SunSpecMeter(const QHostAddress &hostAddress, uint port = 502, QObject *parent = 0); + + void geMeterMap(); + +private: + BlockId m_id = BlockId::DeltaConnectThreePhaseMeter; + uint m_mapLength = 0; + uint m_mapModbusStartRegister = 40000; + + void readMeterBlockHeader(); + +private slots: + void onConnectionStateChanged(); + void onModbusMapReceived(BlockId mapId, uint mapLength, QVector data); + +signals: + void initFinished(); + void meterDataReceived(const MeterData &data); +}; + +#endif // SUNSPECMETER_H diff --git a/sunspec/sunspecstorage.cpp b/sunspec/sunspecstorage.cpp new file mode 100644 index 0000000..516cba9 --- /dev/null +++ b/sunspec/sunspecstorage.cpp @@ -0,0 +1,123 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 . +* +* 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 "sunspecstorage.h" +#include "extern-plugininfo.h" + +SunSpecStorage::SunSpecStorage(const QHostAddress &hostAddress, uint port, QObject *parent) : SunSpec(hostAddress, port, parent) +{ + connect(m_modbusTcpClient, &QModbusClient::stateChanged, this, [this] (QModbusDevice::State state) { + if (state == QModbusDevice::ConnectedState) { + qCDebug(dcSunSpec()) << "Inverter connected successfully"; + QList mapIds; + mapIds.append(BlockId::Storage); + findModbusMap(mapIds); + } + }); + connect(this, &SunSpec::foundModbusMap, this, [this] (BlockId mapId, uint modbusRegisterAddress) { + qCDebug(dcSunSpec()) << "Read map header for mapId" << mapId << "and modbus register" << modbusRegisterAddress; + readMapHeader(modbusRegisterAddress); + }); + + connect(this, &SunSpec::mapHeaderReceived, this, [this] (uint modbusAddress, BlockId mapId, uint mapLength) { + m_id = mapId; + m_mapLength = mapLength; + m_mapModbusStartRegister = modbusAddress; + readMap(modbusAddress, mapLength); + }); + + connect(this, &SunSpec::mapReceived, this, &SunSpecStorage::onModbusMapReceived); +} + +void SunSpecStorage::getStorageMap() +{ + readMap(m_mapModbusStartRegister, m_mapLength); +} + +void SunSpecStorage::readStorageBlockHeader() +{ + readMapHeader(m_mapModbusStartRegister); +} + +QUuid SunSpecStorage::setGridCharging(bool enabled) +{ + // Name ChaGriSet + /* Setpoint to enable/dis- + able charging from grid + PV (charging from grid 0 disabled) + GRID (charging from 1 grid enabled*/ + + uint registerAddress = m_mapModbusStartRegister + Model124::Model124ChaGriSet; + quint16 value = enabled; + return writeHoldingRegister(m_slaveId, registerAddress, value); +} + +QUuid SunSpecStorage::setStorageControlMode(bool chargingEnabled, bool dischargingEnabled) +{ + // Set charge bit to enable charge limit, set discharge bit to enable discharge limit, set both bits to enable both limits + quint16 value = ((static_cast(chargingEnabled) << StorageControlBitFieldCharge) | + (static_cast(dischargingEnabled) << StorageControlBitFieldDischarge)) ; + + uint modbusRegister = m_mapModbusStartRegister + Model124::Model124ActivateStorageControlMode; + return writeHoldingRegister(m_slaveId, modbusRegister, value); +} + +QUuid SunSpecStorage::setChargingRate(int rate) +{ + //Register Name InWRte + /* Defines the maximum charge rate (charge limit). Default is 100% */ + + uint modbusRegister = m_mapModbusStartRegister + Model124::Model124SetpointMaximumChargingRate; + int16_t value = rate * 100; + return writeHoldingRegister(m_slaveId, modbusRegister, value); +} + +QUuid SunSpecStorage::setDischargingRate(int charging) +{ + //Register Name OutWRte + /* Defines the maximum discharge rate (discharge limit). Default is 100% */ + uint modbusRegister = m_mapModbusStartRegister + Model124::Model124SetpointMaximumDischargeRate; + quint16 value = charging * 100; + return writeHoldingRegister(m_slaveId, modbusRegister, value); +} + +void SunSpecStorage::onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength, const QVector &data) +{ + Q_UNUSED(mapLength) + switch (mapId) { + case BlockId::Storage: { + StorageData storageData; + storageData.chargingState = ChargingState(data[Model124::Model124ChargeStatus]); + emit storageDataReceived(storageData); + } break; + default: + break; + } +} diff --git a/sunspec/sunspecstorage.h b/sunspec/sunspecstorage.h new file mode 100644 index 0000000..36cdbef --- /dev/null +++ b/sunspec/sunspecstorage.h @@ -0,0 +1,109 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 . +* +* 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 SUNSPECSTORAGE_H +#define SUNSPECSTORAGE_H + +#include +#include "sunspec.h" + +class SunSpecStorage : public SunSpec +{ + Q_OBJECT +public: + SunSpecStorage(const QHostAddress &hostAddress, uint port = 502, QObject *parent = 0); + + QUuid setGridCharging(bool enabled); + QUuid setDischargingRate(int rate); + QUuid setChargingRate(int rate); + QUuid setStorageControlMode(bool chargingEnabled, bool dischargingEnabled); + + + enum StorageControlBitField { + StorageControlBitFieldCharge = 0, + StorageControlBitFieldDischarge = 1 + }; + Q_ENUM(StorageControlBitField) + + enum GridCharge { + PV = 0, + Grid = 1 + }; + Q_ENUM(GridCharge) + + enum ChargingState { + ChargingStateOff, + ChargingStateEmpty, + ChargingStateDischarging, + ChargingStateCharging, + ChargingStateFull, + ChargingStateHolding, + ChargingStateTesting + }; + Q_ENUM(ChargingState) + + enum Model124 { // Mandatory register + Model124SetpointMaximumCharge = 0, + Model124SetpointMaximumChargingRate = 1, + Model124SetpointMaximumDischargeRate = 2, + Model124ActivateStorageControlMode = 3, + Model124CurrentlyAvailableEnergy = 6, + Model124ChargeStatus = 9, + Model124ChaGriSet = 15, + Model124ScaleFactorMaximumCharge = 16, + Model124ScaleFactorMaximumChargeDischargeRate = 17, + Model124ScaleFactorAvailableEnergyPercent = 20 + }; + Q_ENUM(Model124) + + struct StorageData { + ChargingState chargingState; + bool gridChargingEnabled; + }; + + void getStorageMap(); + +private: + BlockId m_id = BlockId::EnergyStorageBaseModel; + uint m_mapLength = 0; + uint m_mapModbusStartRegister = 40000; + + void readStorageBlockHeader(); + +private slots: + void onConnectionStateChanged(); + void onModbusMapReceived(BlockId mapId, uint mapLength, const QVector &data); + +signals: + void initFinished(); + void storageDataReceived(const StorageData &data); +}; + +#endif // SUNSPECSTORAGE_H diff --git a/sunspec/sunspecstringcombiner.cpp b/sunspec/sunspecstringcombiner.cpp new file mode 100644 index 0000000..dc36460 --- /dev/null +++ b/sunspec/sunspecstringcombiner.cpp @@ -0,0 +1,89 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 . +* +* 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 "sunspecstringcombiner.h" +#include "extern-plugininfo.h" + +SunSpecStringCombiner::SunSpecStringCombiner(const QHostAddress &hostAddress, uint port, QObject *parent) : SunSpec(hostAddress, port, parent) +{ + connect(m_modbusTcpClient, &QModbusClient::stateChanged, this, [this] (QModbusDevice::State state) { + if (state == QModbusDevice::ConnectedState) { + qCDebug(dcSunSpec()) << "String combiner connected successfully"; + QList mapIds; + mapIds.append(BlockId::StringCombiner); + mapIds.append(BlockId::StringCombinerCurrent); + mapIds.append(BlockId::StringCombinerAdvanced); + mapIds.append(BlockId::StringCombinerCurrentAdvanced); + findModbusMap(mapIds); + } + }); + connect(this, &SunSpec::foundModbusMap, this, [this] (BlockId mapId, uint modbusRegisterAddress) { + qCDebug(dcSunSpec()) << "Read map header for mapId" << mapId << "and modbus register" << modbusRegisterAddress; + readMapHeader(modbusRegisterAddress); + }); + + connect(this, &SunSpec::mapHeaderReceived, this, [this] (uint modbusAddress, BlockId mapId, uint mapLength) { + m_id = mapId; + m_mapLength = mapLength; + m_mapModbusStartRegister = modbusAddress; + readMap(modbusAddress, mapLength); + }); + + connect(this, &SunSpec::mapReceived, this, &SunSpecStringCombiner::onModbusMapReceived); +} + +void SunSpecStringCombiner::getStringCombinerMap() +{ + +} + +void SunSpecStringCombiner::readStringCombinerMapHeader() +{ + readMap(m_mapModbusStartRegister, m_mapLength); +} + +void SunSpecStringCombiner::onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector data) +{ + + switch (mapId) { + case BlockId::StringCombiner: { + int rbCount = (mapLength-14)/8; + qCDebug(dcSunSpec()) << "Map" << mapId << "Repeating Block Count" << rbCount; + } break; + case BlockId::StringCombinerCurrent: + case BlockId::StringCombinerAdvanced: + case BlockId::StringCombinerCurrentAdvanced: { + //StringCombinerData stringCombinerData; + //stringCombinerData.acCurrent= convertValueWithSSF(data[Model10X::Model10XAcCurrent], data[Model10X::Model10XAmpereScaleFactor]); + } break; + default: + break; + } +} diff --git a/sunspec/sunspecstringcombiner.h b/sunspec/sunspecstringcombiner.h new file mode 100644 index 0000000..b4645fd --- /dev/null +++ b/sunspec/sunspecstringcombiner.h @@ -0,0 +1,129 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 . +* +* 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 SUNSPECSTRINGCOMBINER_H +#define SUNSPECSTRINGCOMBINER_H + +#include +#include "sunspec.h" + +class SunSpecStringCombiner : public SunSpec +{ + Q_OBJECT +public: + + //Map401 length: 14 + (RB Count * 8) + //Map403 length: 16 + (RB Count * 8) + enum Map401 { + CurrentScaleFactor = 0, + AmpHourScaleFactor = 1, + VoltageScaleFactor = 2, + MaximumDCCurrentRating = 3, + NumberOfInputs = 4, + Events = 5, + VendorDefniedEvents = 7, + TotalMeasuredCurrent = 9, + TotalMeteredAmpHours = 10, + OutputVoltage = 12, + InternalOperatingTemperature = 13 + }; + + enum Map402 { + CurrentScaleFactor, + AmpHourScaleFactor, + VoltageScaleFactor + PowerScale factor + EnergyScale factor + Maximum DC Current Rating + Number of Inputs + Bitmask value. Events + Bitmask value. Vendor defnied events + Total measured current + Total metered Amp-hours + OutputVoltage + }; + + enum Map401RB { //Repeating block + ID = 0, + Event = 1, + VendorEvent = 3, + Amps = 5, + AmpHours = 6 + }; + + enum StringCombinerEvent { + LowVoltage = 0, + LowPower, + LowEfficiency, + Current, + Voltage, + Power, + Pr, + Disconnected, + FuseFault, + CombinerFuseFault, + CombinerCabinetOpen, + Temp, + Groundfault, + ReversedPolarity, + Incompatible, + CommunicationError, + InternalError, + Theft, + ArcDetected + }; + Q_ENUM(StringCombinerEvent) + + struct StringCombinerData { + SunSpecEvent1 event; + SunSpecOperatingState operatingState; + }; + + SunSpecStringCombiner(const QHostAddress &hostAddress, uint port = 502, QObject *parent = 0); + + void getStringCombinerMap(); + +private: + BlockId m_id = BlockId::StringCombiner; + uint m_mapLength = 0; + uint m_mapModbusStartRegister = 40000; + + void readStringCombinerMapHeader(); + +private slots: + void onConnectionStateChanged(); + void onModbusMapReceived(BlockId mapId, uint mapLength, QVector data); + +signals: + void initFinished(); + void stringCombinerDataReceived(const StringCombinerData &data); +}; + +#endif // SUNSPECSTRINGCOMBINER_H diff --git a/sunspec/sunspectracker.cpp b/sunspec/sunspectracker.cpp new file mode 100644 index 0000000..8d99fbf --- /dev/null +++ b/sunspec/sunspectracker.cpp @@ -0,0 +1,42 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 . +* +* 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 "sunspectracker.h" +#include "extern-plugininfo.h" + +SunSpecTracker::SunSpecTracker(const QHostAddress &hostAddress, uint port, QObject *parent) : SunSpec(hostAddress, port, parent) +{ + +} + +void SunSpecTracker::readTrackerBlockHeader() +{ + readMap(m_mapModbusStartRegister, m_mapLength); +} diff --git a/sunspec/sunspectracker.h b/sunspec/sunspectracker.h new file mode 100644 index 0000000..8549e46 --- /dev/null +++ b/sunspec/sunspectracker.h @@ -0,0 +1,78 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 . +* +* 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 SUNSPECTRACKER_H +#define SUNSPECTRACKER_H + +#include +#include "sunspec.h" + +class SunSpecTracker : public SunSpec +{ + Q_OBJECT +public: + + enum TrackerType { + Unknown = 0, + Fixed = 1, + Horizontal = 2, + Tilted = 3, + Azimuth = 4, + Dual = 5, + Other = 99 + }; + Q_ENUM(TrackerType) + + struct TrackerData { + SunSpecEvent1 event; + SunSpecOperatingState operatingState; + }; + + SunSpecTracker(const QHostAddress &hostAddress, uint port = 502, QObject *parent = 0); + + void getTrackerMap(); + +private: + BlockId m_id = BlockId::TrackerController; + uint m_mapLength = 0; + uint m_mapModbusStartRegister = 40000; + + void readTrackerBlockHeader(); + +private slots: + void onConnectionStateChanged(); + void onModbusMapReceived(BlockId mapId, uint mapLength, QVector data); + +signals: + void initFinished(); + void trackerDataReceived(const TrackerData &data); +}; + +#endif // SUNSPECTRACKER_H