fixed rebase

This commit is contained in:
bernhard.trinnes 2020-09-18 18:03:04 +02:00 committed by Boernsman
parent c1cdaac571
commit 5a2be02340
16 changed files with 2418 additions and 79 deletions

View File

@ -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 &paramTypeId, const QVariant &value)
@ -117,3 +298,137 @@ void IntegrationPluginSunSpec::onPluginConfigurationChanged(const ParamTypeId &p
}
}
}
void IntegrationPluginSunSpec::onInverterDataReceived(const SunSpecInverter::InverterData &inverterData)
{
SunSpecInverter *inverter = static_cast<SunSpecInverter *>(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<SunSpecStorage *>(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<SunSpecMeter *>(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<SunSpecTracker *>(sender());
Thing *thing = m_sunSpecTrackers.key(tracker);
if(!thing) {
return;
}
//thing->setStateValue(sunspecStorageStorageStateStateTypeId, storageData.chargingState);
}

View File

@ -36,6 +36,11 @@
#include "../modbus/modbustcpmaster.h"
#include "sunspecinverter.h"
#include "sunspecstorage.h"
#include "sunspecmeter.h"
#include "sunspectracker.h"
#include <QUuid>
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<Thing *, ModbusTCPMaster *> m_modbusTcpMasters;
QHash<QUuid, ThingActionInfo *> m_asyncActions;
QHash<Thing *, SunSpecInverter *> m_sunSpecInverters;
QHash<Thing *, SunSpecStorage *> m_sunSpecStorages;
QHash<Thing *, SunSpecMeter *> m_sunSpecMeters;
QHash<Thing *, SunSpecTracker *> 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<quint16> &values);
void onReceivedInputRegister(quint32 slaveAddress, quint32 modbusRegister, const QVector<quint16> &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

View File

@ -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
}
]
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "sunspec.h"
#include "extern-plugininfo.h"
#include <QtEndian>
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<BlockId> &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<quint16> &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<quint16> &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<qint16>(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<quint16>() << value);
}
QUuid SunSpec::writeHoldingRegisters(uint slaveAddress, uint registerAddress, const QVector<quint16> &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;
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef SUNSPEC_H
#define SUNSPEC_H
#include <QObject>
#include <QUuid>
#include <QHostAddress>
#include <QtSerialBus>
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<BlockId> &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<quint16> &modbusData, int offset, int size);
QUuid writeHoldingRegister(uint slaveAddress, uint registerAddress, quint16 value);
QUuid writeHoldingRegisters(uint slaveAddress, uint registerAddress, const QVector<quint16> &values);
signals:
void connectionStateChanged(bool status);
void requestExecuted(QUuid requetId, bool success);
void foundModbusMap(BlockId mapId, int modbusStartRegister);
void modbusMapSearchFinished(const QList<BlockId> &mapIds, uint modbusStartRegister, const QString &error);
void mapHeaderReceived(uint modbusAddress, BlockId mapId, uint mapLength);
void mapReceived(BlockId mapId, uint mapLength, QVector<quint16> 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<quint16> &values);
};
#endif // SUNSPEC_H

View File

@ -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 \

129
sunspec/sunspecinverter.cpp Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "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<BlockId> 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<quint16> 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;
}
}

118
sunspec/sunspecinverter.h Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef SUNSPECINVERTER_H
#define SUNSPECINVERTER_H
#include <QObject>
#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<quint16> data);
signals:
void initFinished();
void inverterDataReceived(InverterData data);
};
#endif // SUNSPECINVERTER_H

61
sunspec/sunspecmeter.cpp Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "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<BlockId> 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()
{
}

91
sunspec/sunspecmeter.h Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef SUNSPECMETER_H
#define SUNSPECMETER_H
#include <QObject>
#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<quint16> data);
signals:
void initFinished();
void meterDataReceived(const MeterData &data);
};
#endif // SUNSPECMETER_H

123
sunspec/sunspecstorage.cpp Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "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<BlockId> 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<quint16>(chargingEnabled) << StorageControlBitFieldCharge) |
(static_cast<quint16>(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<quint16> &data)
{
Q_UNUSED(mapLength)
switch (mapId) {
case BlockId::Storage: {
StorageData storageData;
storageData.chargingState = ChargingState(data[Model124::Model124ChargeStatus]);
emit storageDataReceived(storageData);
} break;
default:
break;
}
}

109
sunspec/sunspecstorage.h Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef SUNSPECSTORAGE_H
#define SUNSPECSTORAGE_H
#include <QObject>
#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<quint16> &data);
signals:
void initFinished();
void storageDataReceived(const StorageData &data);
};
#endif // SUNSPECSTORAGE_H

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "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<BlockId> 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<quint16> 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;
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef SUNSPECSTRINGCOMBINER_H
#define SUNSPECSTRINGCOMBINER_H
#include <QObject>
#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<quint16> data);
signals:
void initFinished();
void stringCombinerDataReceived(const StringCombinerData &data);
};
#endif // SUNSPECSTRINGCOMBINER_H

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "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);
}

78
sunspec/sunspectracker.h Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef SUNSPECTRACKER_H
#define SUNSPECTRACKER_H
#include <QObject>
#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<quint16> data);
signals:
void initFinished();
void trackerDataReceived(const TrackerData &data);
};
#endif // SUNSPECTRACKER_H