Finish basic inverter data parsing and implementation
This commit is contained in:
parent
8b9c38b04d
commit
c4ec1ee6c6
@ -118,7 +118,6 @@ isEmpty(WITH_PLUGINS) {
|
||||
}
|
||||
PLUGINS-=$${WITHOUT_PLUGINS}
|
||||
|
||||
# FIXME: PLUGINS=sma
|
||||
message("Building plugins:")
|
||||
for(plugin, PLUGINS) {
|
||||
exists($${plugin}) {
|
||||
|
||||
@ -212,12 +212,6 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info)
|
||||
connect(sunnyWebBox, &SunnyWebBox::connectedChanged, this, &IntegrationPluginSma::onConnectedChanged);
|
||||
connect(sunnyWebBox, &SunnyWebBox::plantOverviewReceived, this, &IntegrationPluginSma::onPlantOverviewReceived);
|
||||
m_sunnyWebBoxes.insert(info->thing(), sunnyWebBox);
|
||||
|
||||
if (!m_refreshTimer) {
|
||||
qCDebug(dcSma()) << "Starting refresh timer";
|
||||
m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(1);
|
||||
connect(m_refreshTimer, &PluginTimer::timeout, this, &IntegrationPluginSma::onRefreshTimer);
|
||||
}
|
||||
});
|
||||
} else if (thing->thingClassId() == speedwireMeterThingClassId) {
|
||||
QHostAddress address = QHostAddress(thing->paramValue(speedwireMeterThingHostParamTypeId).toString());
|
||||
@ -235,7 +229,9 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info)
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: reachable state
|
||||
connect(meter, &SpeedwireMeter::reachableChanged, this, [=](bool reachable){
|
||||
thing->setStateValue(speedwireMeterConnectedStateTypeId, reachable);
|
||||
});
|
||||
|
||||
connect(meter, &SpeedwireMeter::valuesUpdated, this, [=](){
|
||||
thing->setStateValue(speedwireMeterConnectedStateTypeId, true);
|
||||
@ -278,11 +274,36 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info)
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: reachable state
|
||||
qCDebug(dcSma()) << "Inverter: Interface initialized successfully.";
|
||||
|
||||
connect(inverter, &SpeedwireInverter::reachableChanged, this, [=](bool reachable){
|
||||
thing->setStateValue(speedwireInverterConnectedStateTypeId, reachable);
|
||||
});
|
||||
|
||||
connect(inverter, &SpeedwireInverter::valuesUpdated, this, [=](){
|
||||
thing->setStateValue(speedwireInverterTotalEnergyProducedStateTypeId, inverter->totalEnergyProduced());
|
||||
thing->setStateValue(speedwireInverterEnergyProducedTodayStateTypeId, inverter->todayEnergyProduced());
|
||||
thing->setStateValue(speedwireInverterCurrentPowerStateTypeId, -inverter->totalAcPower());
|
||||
thing->setStateValue(speedwireInverterFrequencyStateTypeId, inverter->gridFrequency());
|
||||
|
||||
thing->setStateValue(speedwireInverterVoltagePhaseAStateTypeId, inverter->voltageAcPhase1());
|
||||
thing->setStateValue(speedwireInverterVoltagePhaseBStateTypeId, inverter->voltageAcPhase2());
|
||||
thing->setStateValue(speedwireInverterVoltagePhaseCStateTypeId, inverter->voltageAcPhase3());
|
||||
|
||||
thing->setStateValue(speedwireInverterCurrentPhaseAStateTypeId, inverter->currentAcPhase1());
|
||||
thing->setStateValue(speedwireInverterCurrentPhaseBStateTypeId, inverter->currentAcPhase2());
|
||||
thing->setStateValue(speedwireInverterCurrentPhaseCStateTypeId, inverter->currentAcPhase3());
|
||||
|
||||
thing->setStateValue(speedwireInverterCurrentPowerMpp1StateTypeId, inverter->powerDcMpp1());
|
||||
thing->setStateValue(speedwireInverterCurrentPowerMpp2StateTypeId, inverter->powerDcMpp2());
|
||||
|
||||
});
|
||||
|
||||
m_speedwireInverters.insert(thing, inverter);
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
|
||||
inverter->sendLoginRequest();
|
||||
// Initial refresh data
|
||||
inverter->refresh();
|
||||
} else {
|
||||
Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8());
|
||||
}
|
||||
@ -295,8 +316,19 @@ void IntegrationPluginSma::postSetupThing(Thing *thing)
|
||||
SunnyWebBox *sunnyWebBox = m_sunnyWebBoxes.value(thing);
|
||||
if (!sunnyWebBox)
|
||||
return;
|
||||
|
||||
setupRefreshTimer();
|
||||
sunnyWebBox->getPlantOverview();
|
||||
thing->setStateValue(sunnyWebBoxConnectedStateTypeId, true);
|
||||
} else if (thing->thingClassId() == speedwireInverterThingClassId) {
|
||||
SpeedwireInverter *inverter = m_speedwireInverters.value(thing);
|
||||
if (inverter) {
|
||||
thing->setStateValue(speedwireInverterConnectedStateTypeId, inverter->reachable());
|
||||
} else {
|
||||
thing->setStateValue(speedwireInverterConnectedStateTypeId, false);
|
||||
}
|
||||
|
||||
setupRefreshTimer();
|
||||
}
|
||||
}
|
||||
|
||||
@ -306,6 +338,14 @@ void IntegrationPluginSma::thingRemoved(Thing *thing)
|
||||
m_sunnyWebBoxes.take(thing)->deleteLater();
|
||||
}
|
||||
|
||||
if (thing->thingClassId() == speedwireMeterThingClassId && m_speedwireMeters.contains(thing)) {
|
||||
m_speedwireMeters.take(thing)->deleteLater();
|
||||
}
|
||||
|
||||
if (thing->thingClassId() == speedwireInverterThingClassId && m_speedwireInverters.contains(thing)) {
|
||||
m_speedwireInverters.take(thing)->deleteLater();
|
||||
}
|
||||
|
||||
if (myThings().isEmpty()) {
|
||||
qCDebug(dcSma()) << "Stopping timer";
|
||||
hardwareManager()->pluginTimerManager()->unregisterTimer(m_refreshTimer);
|
||||
@ -313,14 +353,6 @@ void IntegrationPluginSma::thingRemoved(Thing *thing)
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginSma::onRefreshTimer()
|
||||
{
|
||||
foreach (Thing *thing, myThings().filterByThingClassId(sunnyWebBoxThingClassId)) {
|
||||
SunnyWebBox *sunnyWebBox = m_sunnyWebBoxes.value(thing);
|
||||
sunnyWebBox->getPlantOverview();
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginSma::onConnectedChanged(bool connected)
|
||||
{
|
||||
Thing *thing = m_sunnyWebBoxes.key(static_cast<SunnyWebBox *>(sender()));
|
||||
@ -347,3 +379,24 @@ void IntegrationPluginSma::onPlantOverviewReceived(const QString &messageId, Sun
|
||||
thing->setStateValue(sunnyWebBoxErrorStateTypeId, overview.error);
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginSma::setupRefreshTimer()
|
||||
{
|
||||
// If already set up...
|
||||
if (m_refreshTimer)
|
||||
return;
|
||||
|
||||
m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(2);
|
||||
connect(m_refreshTimer, &PluginTimer::timeout, this, [=](){
|
||||
foreach (Thing *thing, myThings().filterByThingClassId(sunnyWebBoxThingClassId)) {
|
||||
SunnyWebBox *sunnyWebBox = m_sunnyWebBoxes.value(thing);
|
||||
sunnyWebBox->getPlantOverview();
|
||||
}
|
||||
|
||||
foreach (SpeedwireInverter *inverter, m_speedwireInverters) {
|
||||
inverter->refresh();
|
||||
}
|
||||
});
|
||||
|
||||
m_refreshTimer->start();
|
||||
}
|
||||
|
||||
@ -53,13 +53,14 @@ public:
|
||||
void thingRemoved(Thing *thing) override;
|
||||
|
||||
private slots:
|
||||
void onRefreshTimer();
|
||||
|
||||
void onConnectedChanged(bool connected);
|
||||
void onPlantOverviewReceived(const QString &messageId, SunnyWebBox::Overview overview);
|
||||
|
||||
void setupRefreshTimer();
|
||||
|
||||
private:
|
||||
PluginTimer *m_refreshTimer = nullptr;
|
||||
|
||||
QHash<Thing *, SunnyWebBox *> m_sunnyWebBoxes;
|
||||
QHash<Thing *, SpeedwireMeter *> m_speedwireMeters;
|
||||
QHash<Thing *, SpeedwireInverter *> m_speedwireInverters;
|
||||
|
||||
@ -422,8 +422,8 @@
|
||||
{
|
||||
"id": "b366f680-6134-488b-8362-b1b824a8daca",
|
||||
"name": "currentPowerMpp1",
|
||||
"displayName": "Current power MPP1",
|
||||
"displayNameEvent": "Current power MPP1 changed",
|
||||
"displayName": "DC power MPP1",
|
||||
"displayNameEvent": "DC power MPP1 changed",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0
|
||||
@ -431,8 +431,8 @@
|
||||
{
|
||||
"id": "87d9b654-5558-47a3-9db9-ffd7c23b4774",
|
||||
"name": "currentPowerMpp2",
|
||||
"displayName": "Current power MPP2",
|
||||
"displayNameEvent": "Current power MPP2 changed",
|
||||
"displayName": "DC power MPP2",
|
||||
"displayNameEvent": "DC power MPP2 changed",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0
|
||||
@ -446,6 +446,24 @@
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0.00
|
||||
},
|
||||
{
|
||||
"id": "e8bc8f81-e5c5-4900-b429-93fcaa262fcb",
|
||||
"name": "energyProducedToday",
|
||||
"displayName": "Energy produced today",
|
||||
"displayNameEvent": "Energy produced today changed",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0.00
|
||||
},
|
||||
{
|
||||
"id": "fdccf5de-7413-4480-9ca0-1151665dede8",
|
||||
"name": "frequency",
|
||||
"displayName": "Frequency",
|
||||
"displayNameEvent": "Frequency changed",
|
||||
"type": "double",
|
||||
"unit": "Hertz",
|
||||
"defaultValue": 0.00
|
||||
},
|
||||
{
|
||||
"id": "6d76cc7b-9e00-4561-be7b-4e2a6b8f7b66",
|
||||
"name": "firmwareVersion",
|
||||
|
||||
@ -7,13 +7,18 @@ SOURCES += \
|
||||
speedwirediscovery.cpp \
|
||||
speedwireinterface.cpp \
|
||||
speedwireinverter.cpp \
|
||||
speedwireinverterreply.cpp \
|
||||
speedwireinverterrequest.cpp \
|
||||
speedwiremeter.cpp \
|
||||
sunnywebbox.cpp
|
||||
|
||||
HEADERS += \
|
||||
integrationpluginsma.h \
|
||||
speedwire.h \
|
||||
speedwirediscovery.h \
|
||||
speedwireinterface.h \
|
||||
speedwireinverter.h \
|
||||
speedwireinverterreply.h \
|
||||
speedwireinverterrequest.h \
|
||||
speedwiremeter.h \
|
||||
sunnywebbox.h
|
||||
|
||||
301
sma/speedwire.h
Normal file
301
sma/speedwire.h
Normal file
@ -0,0 +1,301 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2021, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU Lesser General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; version 3. This project is distributed in the hope that
|
||||
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this project. If not, see <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 SPEEDWIRE_H
|
||||
#define SPEEDWIRE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QDebug>
|
||||
#include <QDataStream>
|
||||
#include <QHostAddress>
|
||||
#include <QHash>
|
||||
|
||||
class Speedwire
|
||||
{
|
||||
Q_GADGET
|
||||
public:
|
||||
enum Command {
|
||||
CommandIdentify = 0x00000201,
|
||||
CommandQueryStatus = 0x51800200,
|
||||
CommandQueryAc = 0x51000200,
|
||||
CommandQueryDc = 0x53800200,
|
||||
CommandQueryEnergy = 0x54000200,
|
||||
CommandQueryDevice = 0x58000200,
|
||||
CommandQueryDeviceResponse = 58000201,
|
||||
CommandLogin = 0xfffd040c,
|
||||
CommandLogout = 0xfffd010e,
|
||||
CommandLoginResponse = 0x0ffdf40d
|
||||
};
|
||||
Q_ENUM(Command)
|
||||
|
||||
enum ProtocolId {
|
||||
ProtocolIdUnknown = 0x0000,
|
||||
ProtocolIdMeter = 0x6069,
|
||||
ProtocolIdInverter = 0x6065,
|
||||
ProtocolIdDiscoveryResponse = 0x0001,
|
||||
ProtocolIdDiscovery = 0xffff
|
||||
};
|
||||
Q_ENUM(ProtocolId)
|
||||
|
||||
enum DeviceClass {
|
||||
DeviceClassUnknown = 0x0000,
|
||||
DeviceClassAllDevices = 0x1f40,
|
||||
DeviceClassSolarInverter = 0x1f41,
|
||||
DeviceClassWindTurbine = 0x1f42,
|
||||
DeviceClassBatteryInverter = 0x1f47,
|
||||
DeviceClassConsumer = 0x1f61,
|
||||
DeviceClassSensorSystem = 0x1f80,
|
||||
DeviceClassElectricityMeter = 0x1f81,
|
||||
DeviceClassCommunicationProduct = 0x1fc0
|
||||
};
|
||||
Q_ENUM(DeviceClass)
|
||||
|
||||
class Header
|
||||
{
|
||||
public:
|
||||
Header() = default;
|
||||
quint32 smaSignature = 0;
|
||||
quint16 headerLength = 0;
|
||||
quint16 tagType = 0;
|
||||
quint16 tagVersion = 0;
|
||||
quint16 group = 0;
|
||||
quint16 payloadLength = 0;
|
||||
quint16 smaNet2Version = 0;
|
||||
ProtocolId protocolId = ProtocolIdUnknown;
|
||||
|
||||
inline bool isValid() const {
|
||||
return smaSignature == Speedwire::smaSignature() && protocolId != ProtocolIdUnknown;
|
||||
}
|
||||
};
|
||||
|
||||
typedef struct InverterPackage {
|
||||
quint8 wordCount = 0;
|
||||
quint8 control = 0;
|
||||
quint16 destinationModelId = 0;
|
||||
quint32 destinationSerialNumber = 0;
|
||||
quint16 destinationControl = 0;
|
||||
quint16 sourceModelId = 0;
|
||||
quint32 sourceSerialNumber = 0;
|
||||
quint16 sourceControl = 0;
|
||||
quint16 errorCode = 0;
|
||||
quint16 fragmentId = 0;
|
||||
quint16 packetId = 0;
|
||||
quint32 command = 0;
|
||||
} InverterPackage;
|
||||
|
||||
Speedwire() = default;
|
||||
|
||||
//static QHash<quint16, QString> deviceTypes = { {0x0000, "Unknwon"} };
|
||||
|
||||
static quint16 port() { return 9522; }
|
||||
static QHostAddress multicastAddress() { return QHostAddress("239.12.255.254"); }
|
||||
static quint32 smaSignature() { return 0x534d4100; }
|
||||
static quint16 tag0() { return 0x02a0; }
|
||||
static quint16 tagVersion() { return 0; }
|
||||
static quint16 smaNet2Version() { return 0x0010; }
|
||||
|
||||
static QString getModelName(quint16 modelIdentifier) {
|
||||
switch (modelIdentifier) {
|
||||
case 9015: return "SB 700";
|
||||
case 9016: return "SB 700U";
|
||||
case 9017: return "SB 1100";
|
||||
case 9018: return "SB 1100U";
|
||||
case 9019: return "SB 1100LV";
|
||||
case 9020: return "SB 1700";
|
||||
case 9021: return "SB 1900TLJ";
|
||||
case 9022: return "SB 2100TL";
|
||||
case 9023: return "SB 2500";
|
||||
case 9024: return "SB 2800";
|
||||
case 9025: return "SB 2800i";
|
||||
case 9026: return "SB 3000";
|
||||
case 9027: return "SB 3000US";
|
||||
case 9028: return "SB 3300";
|
||||
case 9029: return "SB 3300U";
|
||||
case 9030: return "SB 3300TL";
|
||||
case 9031: return "SB 3300TL HC";
|
||||
case 9032: return "SB 3800";
|
||||
case 9033: return "SB 3800U";
|
||||
case 9034: return "SB 4000US";
|
||||
case 9035: return "SB 4200TL";
|
||||
case 9036: return "SB 4200TL HC";
|
||||
case 9037: return "SB 5000TL";
|
||||
case 9038: return "SB 5000TLW";
|
||||
case 9039: return "SB 5000TL HC";
|
||||
case 9066: return "SB 1200";
|
||||
case 9067: return "STP 10000TL-10";
|
||||
case 9068: return "STP 12000TL-10";
|
||||
case 9069: return "STP 15000TL-10";
|
||||
case 9070: return "STP 17000TL-10";
|
||||
case 9084: return "WB 3600TL-20";
|
||||
case 9085: return "WB 5000TL-20";
|
||||
case 9086: return "SB 3800US-10";
|
||||
case 9098: return "STP 5000TL-20";
|
||||
case 9099: return "STP 6000TL-20";
|
||||
case 9100: return "STP 7000TL-20";
|
||||
case 9101: return "STP 8000TL-10";
|
||||
case 9102: return "STPcase 9000TL-20";
|
||||
case 9103: return "STP 8000TL-20";
|
||||
case 9104: return "SB 3000TL-JP-21";
|
||||
case 9105: return "SB 3500TL-JP-21";
|
||||
case 9106: return "SB 4000TL-JP-21";
|
||||
case 9107: return "SB 4500TL-JP-21";
|
||||
case 9108: return "SCSMC";
|
||||
case 9109: return "SB 1600TL-10";
|
||||
case 9131: return "STP 20000TL-10";
|
||||
case 9139: return "STP 20000TLHE-10";
|
||||
case 9140: return "STP 15000TLHE-10";
|
||||
case 9157: return "Sunny Island 2012";
|
||||
case 9158: return "Sunny Island 2224";
|
||||
case 9159: return "Sunny Island 5048";
|
||||
case 9160: return "SB 3600TL-20";
|
||||
case 9168: return "SC630HE-11";
|
||||
case 9169: return "SC500HE-11";
|
||||
case 9170: return "SC400HE-11";
|
||||
case 9171: return "WB 3000TL-21";
|
||||
case 9172: return "WB 3600TL-21";
|
||||
case 9173: return "WB 4000TL-21";
|
||||
case 9174: return "WB 5000TL-21";
|
||||
case 9175: return "SC 250";
|
||||
case 9176: return "SMA Meteo Station";
|
||||
case 9177: return "SB 240-10";
|
||||
case 9179: return "Multigate-10";
|
||||
case 9180: return "Multigate-US-10";
|
||||
case 9181: return "STP 20000TLEE-10";
|
||||
case 9182: return "STP 15000TLEE-10";
|
||||
case 9183: return "SB 2000TLST-21";
|
||||
case 9184: return "SB 2500TLST-21";
|
||||
case 9185: return "SB 3000TLST-21";
|
||||
case 9186: return "WB 2000TLST-21";
|
||||
case 9187: return "WB 2500TLST-21";
|
||||
case 9188: return "WB 3000TLST-21";
|
||||
case 9189: return "WTP 5000TL-20";
|
||||
case 9190: return "WTP 6000TL-20";
|
||||
case 9191: return "WTP 7000TL-20";
|
||||
case 9192: return "WTP 8000TL-20";
|
||||
case 9193: return "WTPcase 9000TL-20";
|
||||
case 9254: return "Sunny Island 3324";
|
||||
case 9255: return "Sunny Island 4.0M";
|
||||
case 9256: return "Sunny Island 4248";
|
||||
case 9257: return "Sunny Island 4248U";
|
||||
case 9258: return "Sunny Island 4500";
|
||||
case 9259: return "Sunny Island 4548U";
|
||||
case 9260: return "Sunny Island 5.4M";
|
||||
case 9261: return "Sunny Island 5048U";
|
||||
case 9262: return "Sunny Island 6048U";
|
||||
case 9278: return "Sunny Island 3.0M";
|
||||
case 9279: return "Sunny Island 4.4M";
|
||||
case 9281: return "STP 10000TL-20";
|
||||
case 9282: return "STP 11000TL-20";
|
||||
case 9283: return "STP 12000TL-20";
|
||||
case 9284: return "STP 20000TL-30";
|
||||
case 9285: return "STP 25000TL-30";
|
||||
case 9301: return "SB1.5-1VL-40";
|
||||
case 9302: return "SB2.5-1VL-40";
|
||||
case 9303: return "SB2.0-1VL-40";
|
||||
case 9304: return "SB5.0-1SP-US-40";
|
||||
case 9305: return "SB6.0-1SP-US-40";
|
||||
case 9306: return "SB8.0-1SP-US-40";
|
||||
case 9307: return "Energy Meter";
|
||||
default: return "Unknown";
|
||||
}
|
||||
};
|
||||
|
||||
// Multicast device discovery request packet, according to SMA documentation.
|
||||
// However, this does not seem to be supported anymore with version 3.x devices
|
||||
// 0x53, 0x4d, 0x41, 0x00, 0x00, 0x04, 0x02, 0xa0, // sma signature, tag0
|
||||
// 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x20, // 0xffffffff group, 0x0000 length, 0x0020 "SMA Net ?", Version ?
|
||||
// 0x00, 0x00, 0x00, 0x00 // 0x0000 protocol, 0x00 #long words, 0x00 ctrl
|
||||
|
||||
// Unicast device discovery request packet, according to SMA documentation
|
||||
// 0x53, 0x4d, 0x41, 0x00, 0x00, 0x04, 0x02, 0xa0, // sma signature, tag0
|
||||
// 0x00, 0x00, 0x00, 0x01, 0x00, 0x26, 0x00, 0x10, // 0x26 length, 0x0010 "SMA Net 2", Version 0
|
||||
// 0x60, 0x65, 0x09, 0xa0, 0xff, 0xff, 0xff, 0xff, // 0x6065 protocol, 0x09 #long words, 0xa0 ctrl, 0xffff dst susyID any, 0xffffffff dst serial any
|
||||
// 0xff, 0xff, 0x00, 0x00, 0x7d, 0x00, 0x52, 0xbe, // 0x0000 dst cntrl, 0x007d src susy id, 0x3a28be52 src serial
|
||||
// 0x28, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x0000 src cntrl, 0x0000 error code, 0x0000 fragment ID
|
||||
// 0x01, 0x80, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, // 0x8001 packet ID
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00
|
||||
|
||||
static QByteArray discoveryDatagramMulticast() { return QByteArray::fromHex("534d4100000402a0ffffffff0000002000000000"); }
|
||||
static QByteArray discoveryResponseDatagram() { return QByteArray::fromHex("534d4100000402A000000001000200000001"); }
|
||||
static QByteArray discoveryDatagramUnicast() { return QByteArray::fromHex("534d4100000402a00000000100260010606509a0ffffffffffff00007d0052be283a000000000000018000020000000000000000000000000000"); }
|
||||
|
||||
static Speedwire::Header parseHeader(QDataStream &stream) {
|
||||
stream.setByteOrder(QDataStream::BigEndian);
|
||||
Header header;
|
||||
quint16 protocolId;
|
||||
stream >> header.smaSignature >> header.headerLength;
|
||||
stream >> header.tagType >> header.tagVersion >> header.group;
|
||||
stream >> header.payloadLength >> header.smaNet2Version;
|
||||
stream >> protocolId;
|
||||
header.protocolId = static_cast<ProtocolId>(protocolId);
|
||||
return header;
|
||||
};
|
||||
|
||||
static Speedwire::InverterPackage parseInverterPackage(QDataStream &stream) {
|
||||
// Make sure the data stream is little endian
|
||||
stream.setByteOrder(QDataStream::LittleEndian);
|
||||
InverterPackage package;
|
||||
stream >> package.wordCount;
|
||||
stream >> package.control;
|
||||
stream >> package.destinationModelId;
|
||||
stream >> package.destinationSerialNumber;
|
||||
stream >> package.destinationControl;
|
||||
stream >> package.sourceModelId;
|
||||
stream >> package.sourceSerialNumber;
|
||||
stream >> package.sourceControl;
|
||||
stream >> package.errorCode;
|
||||
stream >> package.fragmentId;
|
||||
stream >> package.packetId;
|
||||
stream >> package.command;
|
||||
return package;
|
||||
};
|
||||
};
|
||||
|
||||
inline QDebug operator<<(QDebug debug, const Speedwire::Header &header)
|
||||
{
|
||||
debug.nospace() << "SpeedwireHeader(" << header.protocolId << ", payload size: " << header.payloadLength << ", group: " << header.payloadLength << ")";
|
||||
return debug.maybeSpace();
|
||||
}
|
||||
|
||||
inline QDebug operator<<(QDebug debug, const Speedwire::InverterPackage &package)
|
||||
{
|
||||
debug.nospace() << "InverterPackage(" << package.sourceSerialNumber;
|
||||
debug.nospace() << ", Model ID: " << package.sourceModelId;
|
||||
debug.nospace() << ", command: " << package.command;
|
||||
debug.nospace() << ", error: " << package.errorCode;
|
||||
debug.nospace() << ", fragment: " << package.fragmentId;
|
||||
debug.nospace() << ", package ID: " << package.fragmentId;
|
||||
debug.nospace() << ")";
|
||||
return debug.maybeSpace();
|
||||
}
|
||||
|
||||
|
||||
#endif // SPEEDWIRE_H
|
||||
@ -1,3 +1,33 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2021, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU Lesser General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; version 3. This project is distributed in the hope that
|
||||
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this project. If not, see <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 "speedwirediscovery.h"
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
@ -11,36 +41,10 @@ SpeedwireDiscovery::SpeedwireDiscovery(NetworkDeviceDiscovery *networkDeviceDisc
|
||||
// More details: https://github.com/RalfOGit/libspeedwire/
|
||||
|
||||
|
||||
// //! Multicast device discovery request packet, according to SMA documentation.
|
||||
// //! However, this does not seem to be supported anymore with version 3.x devices
|
||||
// const unsigned char multicast_request[] = {
|
||||
// 0x53, 0x4d, 0x41, 0x00, 0x00, 0x04, 0x02, 0xa0, // sma signature, tag0
|
||||
// 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x20, // 0xffffffff group, 0x0000 length, 0x0020 "SMA Net ?", Version ?
|
||||
// 0x00, 0x00, 0x00, 0x00 // 0x0000 protocol, 0x00 #long words, 0x00 ctrl
|
||||
// };
|
||||
|
||||
// //! Unicast device discovery request packet, according to SMA documentation
|
||||
// const unsigned char unicast_request[] = {
|
||||
// 0x53, 0x4d, 0x41, 0x00, 0x00, 0x04, 0x02, 0xa0, // sma signature, tag0
|
||||
// 0x00, 0x00, 0x00, 0x01, 0x00, 0x26, 0x00, 0x10, // 0x26 length, 0x0010 "SMA Net 2", Version 0
|
||||
// 0x60, 0x65, 0x09, 0xa0, 0xff, 0xff, 0xff, 0xff, // 0x6065 protocol, 0x09 #long words, 0xa0 ctrl, 0xffff dst susyID any, 0xffffffff dst serial any
|
||||
// 0xff, 0xff, 0x00, 0x00, 0x7d, 0x00, 0x52, 0xbe, // 0x0000 dst cntrl, 0x007d src susy id, 0x3a28be52 src serial
|
||||
// 0x28, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x0000 src cntrl, 0x0000 error code, 0x0000 fragment ID
|
||||
// 0x01, 0x80, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, // 0x8001 packet ID
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00
|
||||
// };
|
||||
|
||||
// // Request: 534d4100000402a00000000100260010 606509a0 ffffffffffff0000 7d0052be283a0000 000000000180 00020000 00000000 00000000 00000000 => command = 0x00000200, first = 0x00000000; last = 0x00000000; trailer = 0x00000000
|
||||
// // Response 534d4100000402a000000001004e0010 606513a0 7d0052be283a00c0 7a01842a71b30000 000000000180 01020000 00000000 00000000 00030000 00ff0000 00000000 01007a01 842a71b3 00000a00 0c000000 00000000 00000000 01010000 00000000
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// qCDebug(dcSma()) << "SpeedwireDiscovery: Create speed wire interface for multicast" << m_multicastAddress.toString() << "on port" << m_port;
|
||||
// QByteArray exampleData = QByteArray::fromHex("534d4100000402a000000001024400106069010e714369aee618a41600010400000000000001080000000021391229100002040000004415000208000000001575a137d800030400000000000003080000000003debed0e800040400000017c6000408000000001008c2070000090400000000000009080000000027c77bed20000a04000000481d000a08000000001722823410000d0400000003b00015040000000000001508000000000d1e1e0e3000160400000015120016080000000006c5a2d8b800170400000000000017080000000001bd6f680000180400000007990018080000000004def712b8001d040000000000001d08000000000eeefaafd0001e040000001666001e0800000000074b38bf88001f040000000a300020040000037bcb00210400000003ad0029040000000000002908000000000a9b1afec8002a040000001a81002a08000000000803e62b88002b040000000000002b080000000001511459b8002c0400000006d5002c0800000000052c8455b80031040000000000003108000000000cf83b37100032040000001b5f0032080000000008a6e257f80033040000000c3f003404000003747900350400000003c8003d040000000000003d08000000000a53d0ba08003e040000001482003e080000000007800fd188003f040000000000003f080000000001185820c8004004000000095800400800000000064563b1900045040000000000004508000000000d26d3eae0004604000000168900460800000000082b4fc5a80047040000000a440048040000037ed1004904000000038e900000000102085200000000");
|
||||
// processDatagram(QHostAddress("127.0.0.1"), m_port, exampleData);
|
||||
@ -160,7 +164,7 @@ void SpeedwireDiscovery::startMulticastDiscovery()
|
||||
|
||||
void SpeedwireDiscovery::sendUnicastDiscoveryRequest(const QHostAddress &targetHostAddress)
|
||||
{
|
||||
if (m_unicastSocket->writeDatagram(m_discoveryDatagramUnicast, targetHostAddress, m_port) < 0) {
|
||||
if (m_unicastSocket->writeDatagram(Speedwire::discoveryDatagramUnicast(), targetHostAddress, m_port) < 0) {
|
||||
qCWarning(dcSma()) << "SpeedwireDiscovery: Failed to send unicast discovery datagram to address" << targetHostAddress.toString();
|
||||
return;
|
||||
}
|
||||
@ -222,38 +226,25 @@ void SpeedwireDiscovery::processDatagram(const QHostAddress &senderAddress, quin
|
||||
}
|
||||
|
||||
// Ignore discovery requests
|
||||
if (datagram == m_discoveryDatagramMulticast || datagram == m_discoveryDatagramUnicast)
|
||||
if (datagram == Speedwire::discoveryDatagramMulticast() || datagram == Speedwire::discoveryDatagramUnicast())
|
||||
return;
|
||||
|
||||
QDataStream stream(datagram);
|
||||
stream.setByteOrder(QDataStream::BigEndian);
|
||||
|
||||
SpeedwireInterface::SpeedwireHeader header = SpeedwireInterface::parseHeader(stream);
|
||||
Speedwire::Header header = Speedwire::parseHeader(stream);
|
||||
if (!header.isValid()) {
|
||||
qCWarning(dcSma()) << "SpeedwireDiscovery: Datagram header is not valid. Ignoring data...";
|
||||
return;
|
||||
}
|
||||
// Example data:
|
||||
// 534d4100 0004 02a0 0000 0001 0244 0010 6069 010e 7143 69ae e618a416 00010400000000000001080000000021391229100002040000004415000208000000001575a137d800030400000000000003080000000003debed0e800040400000017c6000408000000001008c2070000090400000000000009080000000027c77bed20000a04000000481d000a08000000001722823410000d0400000003b00015040000000000001508000000000d1e1e0e3000160400000015120016080000000006c5a2d8b800170400000000000017080000000001bd6f680000180400000007990018080000000004def712b8001d040000000000001d08000000000eeefaafd0001e040000001666001e0800000000074b38bf88001f040000000a300020040000037bcb00210400000003ad0029040000000000002908000000000a9b1afec8002a040000001a81002a08000000000803e62b88002b040000000000002b080000000001511459b8002c0400000006d5002c0800000000052c8455b80031040000000000003108000000000cf83b37100032040000001b5f0032080000000008a6e257f80033040000000c3f003404000003747900350400000003c8003d040000000000003d08000000000a53d0ba08003e040000001482003e080000000007800fd188003f040000000000003f080000000001185820c8004004000000095800400800000000064563b1900045040000000000004508000000000d26d3eae0004604000000168900460800000000082b4fc5a80047040000000a440048040000037ed1004904000000038e900000000102085200000000
|
||||
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery:" << header;
|
||||
|
||||
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: ======================= Header";
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Length:" << header.headerLength;
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Tag0:" << header.tagType;
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Tag0 version:" << header.tagVersion;
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Group:" << header.group << (header.group == 1 ? "(default group)" : "");
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Data length:" << header.payloadLength << datagram.length();
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: SMA Net 2 Version" << header.smaNet2Version;
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Protocol ID" << header.protocolId;
|
||||
|
||||
if (header.protocolId == SpeedwireInterface::ProtocolIdDiscoveryResponse) {
|
||||
if (header.protocolId == Speedwire::ProtocolIdDiscoveryResponse) {
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Received discovery response from" << QString("%1:%2").arg(senderAddress.toString()).arg(senderPort);
|
||||
// Response: 534d4100 0004 02a0 0000 0001 0002 0000 0001
|
||||
// "192.168.178.25:9522" "534d4100 0004 02a0 0000 0001 0002 0000 0001 0004 0010 0001 0003 0004 0020 0000 0001 0004 0030 c0a8 b219 0004 0040 0000 0000 0002 0070 ef0c 00000000"
|
||||
// "192.168.178.22:9522" "534d4100 0004 02a0 0000 0001 0002 0000 0001 0004 0010 0001 0001 0004 0020 0000 0001 0004 0030 c0a8 b216 0004 0040 0000 0001 00000000"
|
||||
|
||||
if (!datagram.startsWith(m_discoveryResponseDatagram)) {
|
||||
// "534d4100 0004 02a0 0000 0001 0002 0000 0001 0004 0010 0001 0003 0004 0020 0000 0001 0004 0030 c0a8 b219 0004 0040 0000 0000 0002 0070 ef0c 00000000"
|
||||
// "534d4100 0004 02a0 0000 0001 0002 0000 0001 0004 0010 0001 0001 0004 0020 0000 0001 0004 0030 c0a8 b216 0004 0040 0000 0001 00000000"
|
||||
|
||||
if (!datagram.startsWith(Speedwire::discoveryResponseDatagram())) {
|
||||
qCWarning(dcSma()) << "SpeedwireDiscovery: Received discovery reply but the message start does not match the required schema. Ignoring data...";
|
||||
return;
|
||||
}
|
||||
@ -265,7 +256,6 @@ void SpeedwireDiscovery::processDatagram(const QHostAddress &senderAddress, quin
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
SpeedwireDiscoveryResult result;
|
||||
result.address = senderAddress;
|
||||
if (m_networkDeviceInfos.hasHostAddress(senderAddress)) {
|
||||
@ -283,14 +273,12 @@ void SpeedwireDiscovery::processDatagram(const QHostAddress &senderAddress, quin
|
||||
|
||||
// We received SMA data, let's parse depending on the protocol id
|
||||
|
||||
if (header.protocolId == SpeedwireInterface::ProtocolIdMeter) {
|
||||
if (header.protocolId == Speedwire::ProtocolIdMeter) {
|
||||
// Example: 010e 714369ae
|
||||
quint16 modelId;
|
||||
quint32 serialNumber;
|
||||
stream >> modelId >> serialNumber;
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: ======================= Meter identifier";
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Model ID:" << modelId;
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Serial number:" << serialNumber;
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Meter identifier: Model ID:" << modelId << "Serial number:" << serialNumber;
|
||||
|
||||
if (!m_results.contains(senderAddress)) {
|
||||
SpeedwireDiscoveryResult result;
|
||||
@ -305,13 +293,10 @@ void SpeedwireDiscovery::processDatagram(const QHostAddress &senderAddress, quin
|
||||
|
||||
m_results[senderAddress].modelId = modelId;
|
||||
m_results[senderAddress].serialNumber = serialNumber;
|
||||
} else if (header.protocolId == SpeedwireInterface::ProtocolIdInverter) {
|
||||
quint16 modelId;
|
||||
quint32 serialNumber;
|
||||
stream >> modelId >> serialNumber;
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: ======================= Inverter identifier";
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Model ID:" << modelId;
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Serial number:" << serialNumber;
|
||||
} else if (header.protocolId == Speedwire::ProtocolIdInverter) {
|
||||
Speedwire::InverterPackage inverterPackage = Speedwire::parseInverterPackage(stream);
|
||||
// Response from inverter 534d4100 0004 02a0 0000 0001 004e 0010 6065 1390 7d00 52be283a 0000 b500 c2c12e12 0000 0000 00000 1800102000000000000000000000003000000ff0000ecd5ff1f0100b500c2c12e1200000a000c00000000000000030000000101000000000000
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery:" << inverterPackage;
|
||||
|
||||
if (!m_results.contains(senderAddress)) {
|
||||
SpeedwireDiscoveryResult result;
|
||||
@ -324,18 +309,17 @@ void SpeedwireDiscovery::processDatagram(const QHostAddress &senderAddress, quin
|
||||
m_results[senderAddress].networkDeviceInfo = m_networkDeviceInfos.get(senderAddress);
|
||||
}
|
||||
|
||||
m_results[senderAddress].modelId = modelId;
|
||||
m_results[senderAddress].serialNumber = serialNumber;
|
||||
m_results[senderAddress].modelId = inverterPackage.sourceModelId;
|
||||
m_results[senderAddress].serialNumber = inverterPackage.sourceSerialNumber;
|
||||
} else {
|
||||
qCWarning(dcSma()) << "SpeedwireDiscovery: Unhandled data received" << datagram.toHex();
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SpeedwireDiscovery::sendDiscoveryRequest()
|
||||
{
|
||||
if (m_multicastSocket->writeDatagram(m_discoveryDatagramMulticast, m_multicastAddress, m_port) < 0) {
|
||||
if (m_multicastSocket->writeDatagram(Speedwire::discoveryDatagramMulticast(), m_multicastAddress, m_port) < 0) {
|
||||
qCWarning(dcSma()) << "SpeedwireDiscovery: Failed to send discovery datagram to multicast address" << m_multicastAddress.toString();
|
||||
return;
|
||||
}
|
||||
@ -360,6 +344,5 @@ void SpeedwireDiscovery::onDiscoveryProcessFinished()
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Serial number:" << result.serialNumber;
|
||||
}
|
||||
|
||||
|
||||
emit discoveryFinished();
|
||||
}
|
||||
|
||||
@ -1,3 +1,33 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2021, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU Lesser General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; version 3. This project is distributed in the hope that
|
||||
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this project. If not, see <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 SPEEDWIREDISCOVERY_H
|
||||
#define SPEEDWIREDISCOVERY_H
|
||||
|
||||
@ -7,6 +37,7 @@
|
||||
|
||||
#include <network/networkdevicediscovery.h>
|
||||
|
||||
#include "speedwire.h"
|
||||
#include "speedwireinterface.h"
|
||||
|
||||
class SpeedwireDiscovery : public QObject
|
||||
@ -39,8 +70,8 @@ private:
|
||||
NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr;
|
||||
QUdpSocket *m_multicastSocket = nullptr;
|
||||
QUdpSocket *m_unicastSocket = nullptr;
|
||||
QHostAddress m_multicastAddress = QHostAddress("239.12.255.254");
|
||||
quint16 m_port = 9522;
|
||||
QHostAddress m_multicastAddress = Speedwire::multicastAddress();
|
||||
quint16 m_port = Speedwire::port();
|
||||
bool m_initialized = false;
|
||||
|
||||
// Discovery
|
||||
@ -49,12 +80,6 @@ private:
|
||||
NetworkDeviceInfos m_networkDeviceInfos;
|
||||
QHash<QHostAddress, SpeedwireDiscoveryResult> m_results;
|
||||
|
||||
// Static discovery datagrams for speedwire
|
||||
QByteArray m_discoveryDatagramMulticast = QByteArray::fromHex("534d4100000402a0ffffffff0000002000000000");
|
||||
QByteArray m_discoveryResponseDatagram = QByteArray::fromHex("534d4100000402A000000001000200000001");
|
||||
|
||||
QByteArray m_discoveryDatagramUnicast = QByteArray::fromHex("534d4100000402a00000000100260010606509a0ffffffffffff00007d0052be283a000000000000018000020000000000000000000000000000");
|
||||
|
||||
void startMulticastDiscovery();
|
||||
void sendUnicastDiscoveryRequest(const QHostAddress &targetHostAddress);
|
||||
|
||||
|
||||
@ -1,3 +1,33 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2021, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU Lesser General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; version 3. This project is distributed in the hope that
|
||||
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this project. If not, see <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 "speedwireinterface.h"
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
@ -43,10 +73,10 @@ void SpeedwireInterface::deinitialize()
|
||||
if (!m_socket->leaveMulticastGroup(m_multicastAddress)) {
|
||||
qCWarning(dcSma()) << "SpeedwireInterface: Failed to leave multicast group" << m_multicastAddress.toString();
|
||||
}
|
||||
|
||||
m_socket->close();
|
||||
m_initialized = false;
|
||||
}
|
||||
|
||||
m_socket->close();
|
||||
m_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,22 +95,12 @@ quint32 SpeedwireInterface::sourceSerialNumber() const
|
||||
return m_sourceSerialNumber;
|
||||
}
|
||||
|
||||
SpeedwireInterface::SpeedwireHeader SpeedwireInterface::parseHeader(QDataStream &stream)
|
||||
{
|
||||
SpeedwireHeader header;
|
||||
quint16 protocolId;
|
||||
stream >> header.smaSignature >> header.headerLength;
|
||||
stream >> header.tagType >> header.tagVersion >> header.group;
|
||||
stream >> header.payloadLength >> header.smaNet2Version;
|
||||
stream >> protocolId;
|
||||
header.protocolId = static_cast<ProtocolId>(protocolId);
|
||||
return header;
|
||||
}
|
||||
|
||||
void SpeedwireInterface::sendData(const QByteArray &data)
|
||||
{
|
||||
//qCDebug(dcSma()) << "Send data:" << data.toHex();
|
||||
m_socket->writeDatagram(data, m_address, m_port);
|
||||
qCDebug(dcSma()) << "SpeedwireInterface: -->" << m_address.toString() << m_port << data.toHex();
|
||||
if (m_socket->writeDatagram(data, m_address, m_port) < 0) {
|
||||
qCWarning(dcSma()) << "SpeedwireInterface: failed to send data" << m_socket->errorString();
|
||||
}
|
||||
}
|
||||
|
||||
void SpeedwireInterface::readPendingDatagrams()
|
||||
|
||||
@ -1,3 +1,33 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2021, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU Lesser General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; version 3. This project is distributed in the hope that
|
||||
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this project. If not, see <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 SPEEDWIREINTERFACE_H
|
||||
#define SPEEDWIREINTERFACE_H
|
||||
|
||||
@ -5,19 +35,12 @@
|
||||
#include <QUdpSocket>
|
||||
#include <QDataStream>
|
||||
|
||||
#include "speedwire.h"
|
||||
|
||||
class SpeedwireInterface : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum ProtocolId {
|
||||
ProtocolIdUnknown = 0x0000,
|
||||
ProtocolIdMeter = 0x6069,
|
||||
ProtocolIdInverter = 0x6065,
|
||||
ProtocolIdDiscoveryResponse = 0x0001,
|
||||
ProtocolIdDiscovery = 0xffff
|
||||
};
|
||||
Q_ENUM(ProtocolId)
|
||||
|
||||
enum DeviceType {
|
||||
DeviceTypeUnknown,
|
||||
DeviceTypeMeter,
|
||||
@ -25,24 +48,6 @@ public:
|
||||
};
|
||||
Q_ENUM(DeviceType)
|
||||
|
||||
class SpeedwireHeader
|
||||
{
|
||||
public:
|
||||
SpeedwireHeader() = default;
|
||||
quint32 smaSignature = 0;
|
||||
quint16 headerLength = 0;
|
||||
quint16 tagType = 0;
|
||||
quint16 tagVersion = 0;
|
||||
quint16 group = 0;
|
||||
quint16 payloadLength = 0;
|
||||
quint16 smaNet2Version = 0;
|
||||
ProtocolId protocolId = ProtocolIdUnknown;
|
||||
|
||||
inline bool isValid() const {
|
||||
return smaSignature == 0x534d4100 && protocolId != ProtocolIdUnknown;
|
||||
}
|
||||
};
|
||||
|
||||
explicit SpeedwireInterface(const QHostAddress &address, bool multicast, QObject *parent = nullptr);
|
||||
~SpeedwireInterface();
|
||||
|
||||
@ -54,8 +59,6 @@ public:
|
||||
quint16 sourceModelId() const;
|
||||
quint32 sourceSerialNumber() const;
|
||||
|
||||
static SpeedwireInterface::SpeedwireHeader parseHeader(QDataStream &stream);
|
||||
|
||||
public slots:
|
||||
void sendData(const QByteArray &data);
|
||||
|
||||
@ -65,14 +68,14 @@ signals:
|
||||
private:
|
||||
QUdpSocket *m_socket = nullptr;
|
||||
QHostAddress m_address;
|
||||
quint16 m_port = 9522;
|
||||
QHostAddress m_multicastAddress = QHostAddress("239.12.255.254");
|
||||
quint16 m_port = Speedwire::port();
|
||||
QHostAddress m_multicastAddress = Speedwire::multicastAddress();
|
||||
bool m_multicast = false;
|
||||
bool m_initialized = false;
|
||||
|
||||
// Requester
|
||||
quint16 m_sourceModelId = 0x007d;
|
||||
quint32 m_sourceSerialNumber = 0x3a28be42;
|
||||
quint32 m_sourceSerialNumber = 0x3a28be52;
|
||||
|
||||
private slots:
|
||||
void readPendingDatagrams();
|
||||
@ -81,4 +84,5 @@ private slots:
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // SPEEDWIREINTERFACE_H
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,56 +1,114 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2021, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU Lesser General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; version 3. This project is distributed in the hope that
|
||||
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this project. If not, see <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 SPEEDWIREINVERTER_H
|
||||
#define SPEEDWIREINVERTER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QQueue>
|
||||
|
||||
#include "speedwire.h"
|
||||
#include "speedwireinterface.h"
|
||||
#include "speedwireinverterreply.h"
|
||||
#include "speedwireinverterrequest.h"
|
||||
|
||||
class SpeedwireInverter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Command {
|
||||
CommandQueryAc = 0x51000200,
|
||||
CommandQueryStatus = 0x51800200,
|
||||
CommandQueryDevice = 0x58000200,
|
||||
CommandQueryDc = 0x53800200,
|
||||
CommandQueryLogin = 0xfffd040c
|
||||
enum State {
|
||||
StateIdle,
|
||||
StateDisconnected,
|
||||
StateInitializing,
|
||||
StateLogin,
|
||||
StateGetInformation,
|
||||
StateQueryData
|
||||
};
|
||||
|
||||
|
||||
Q_ENUM(Command)
|
||||
|
||||
|
||||
class Request
|
||||
{
|
||||
public:
|
||||
Request();
|
||||
|
||||
SpeedwireInverter::Command command() const;
|
||||
|
||||
quint16 requestId() const;
|
||||
|
||||
private:
|
||||
SpeedwireInverter::Command m_command;
|
||||
quint16 m_requestId = 0;
|
||||
|
||||
};
|
||||
|
||||
Q_ENUM(State)
|
||||
|
||||
explicit SpeedwireInverter(const QHostAddress &address, quint16 modelId, quint32 serialNumber, QObject *parent = nullptr);
|
||||
|
||||
bool initialize();
|
||||
bool initialized() const;
|
||||
|
||||
double currentPower() const;
|
||||
State state() const;
|
||||
|
||||
bool reachable() const;
|
||||
|
||||
Speedwire::DeviceClass deviceClass() const;
|
||||
QString modelName() const;
|
||||
|
||||
double totalAcPower() const;
|
||||
|
||||
double gridFrequency() const;
|
||||
|
||||
double totalEnergyProduced() const;
|
||||
double todayEnergyProduced() const;
|
||||
|
||||
double voltageAcPhase1() const;
|
||||
double voltageAcPhase2() const;
|
||||
double voltageAcPhase3() const;
|
||||
|
||||
double currentAcPhase1() const;
|
||||
double currentAcPhase2() const;
|
||||
double currentAcPhase3() const;
|
||||
|
||||
double powerAcPhase1() const;
|
||||
double powerAcPhase2() const;
|
||||
double powerAcPhase3() const;
|
||||
|
||||
double powerDcMpp1() const;
|
||||
double powerDcMpp2() const;
|
||||
|
||||
double voltageDcMpp1() const;
|
||||
double voltageDcMpp2() const;
|
||||
|
||||
double currentDcMpp1() const;
|
||||
double currentDcMpp2() const;
|
||||
|
||||
// Query methods
|
||||
void sendLoginRequest(const QString &password = "0000", bool loginAsUser = true);
|
||||
void querySoftwareVersion();
|
||||
void queryDeviceType();
|
||||
SpeedwireInverterReply *sendIdentifyRequest();
|
||||
SpeedwireInverterReply *sendLoginRequest(const QString &password = "0000", bool loginAsUser = true);
|
||||
SpeedwireInverterReply *sendLogoutRequest();
|
||||
SpeedwireInverterReply *sendSoftwareVersionRequest();
|
||||
SpeedwireInverterReply *sendDeviceTypeRequest();
|
||||
|
||||
// Start connecting
|
||||
void startConnecting();
|
||||
|
||||
public slots:
|
||||
void refresh();
|
||||
|
||||
signals:
|
||||
void reachableChanged(bool reachable);
|
||||
void stateChanged(State state);
|
||||
void valuesUpdated();
|
||||
|
||||
private:
|
||||
@ -59,17 +117,83 @@ private:
|
||||
bool m_initialized = false;
|
||||
quint16 m_modelId = 0;
|
||||
quint32 m_serialNumber = 0;
|
||||
quint16 m_packetId = 0;
|
||||
|
||||
bool m_reachable = false;
|
||||
State m_state = StateDisconnected;
|
||||
quint16 m_packetId = 1;
|
||||
|
||||
bool deviceInformationFetched = false;
|
||||
|
||||
SpeedwireInverterReply *m_currentReply = nullptr;
|
||||
QQueue<SpeedwireInverterReply *> m_replyQueue;
|
||||
|
||||
// Properties
|
||||
double m_currentPower = 0;
|
||||
double m_totalEnergyProduced = 0;
|
||||
|
||||
Speedwire::DeviceClass m_deviceClass = Speedwire::DeviceClassUnknown;
|
||||
QString m_modelName;
|
||||
QString m_softwareVersion;
|
||||
|
||||
double m_totalAcPower = 0;
|
||||
double m_totalEnergyProduced = 0;
|
||||
double m_todayEnergyProduced = 0;
|
||||
|
||||
double m_gridFrequency = 0;
|
||||
|
||||
double m_voltageAcPhase1 = 0;
|
||||
double m_voltageAcPhase2 = 0;
|
||||
double m_voltageAcPhase3 = 0;
|
||||
|
||||
double m_currentAcPhase1 = 0;
|
||||
double m_currentAcPhase2 = 0;
|
||||
double m_currentAcPhase3 = 0;
|
||||
|
||||
double m_powerAcPhase1 = 0;
|
||||
double m_powerAcPhase2 = 0;
|
||||
double m_powerAcPhase3 = 0;
|
||||
|
||||
double m_powerDcMpp1 = 0;
|
||||
double m_powerDcMpp2 = 0;
|
||||
|
||||
double m_voltageDcMpp1 = 0;
|
||||
double m_voltageDcMpp2 = 0;
|
||||
|
||||
double m_currentDcMpp1 = 0;
|
||||
double m_currentDcMpp2 = 0;
|
||||
|
||||
void setState(State state);
|
||||
|
||||
void sendNextReply();
|
||||
SpeedwireInverterReply *createReply(const SpeedwireInverterRequest &request);
|
||||
|
||||
// Request builder function
|
||||
void buildDefaultHeader(QDataStream &stream, quint16 payloadSize = 38, quint8 control = 0xa0);
|
||||
void buildPackage(QDataStream &stream, quint32 command, quint16 packetId);
|
||||
|
||||
// Send generic request for internal use
|
||||
SpeedwireInverterReply *sendQueryRequest(Speedwire::Command command, quint32 firstWord, quint32 secondWord);
|
||||
|
||||
// Response process methods
|
||||
void processSoftwareVersionResponse(const QByteArray &response);
|
||||
void processDeviceTypeResponse(const QByteArray &response);
|
||||
void processAcPowerResponse(const QByteArray &response);
|
||||
void processAcVoltageCurrentResponse(const QByteArray &response);
|
||||
void processAcTotalPowerResponse(const QByteArray &response);
|
||||
void processDcPowerResponse(const QByteArray &response);
|
||||
void processDcVoltageCurrentResponse(const QByteArray &response);
|
||||
void processEnergyProductionResponse(const QByteArray &response);
|
||||
void processGridFrequencyResponse(const QByteArray &response);
|
||||
void processInverterStatusResponse(const QByteArray &response);
|
||||
|
||||
void readUntilEndOfMeasurement(QDataStream &stream);
|
||||
double readValue(quint32 value, double divisor);
|
||||
|
||||
void setReachable(bool reachable);
|
||||
|
||||
private slots:
|
||||
void processData(const QByteArray &data);
|
||||
|
||||
void onReplyTimeout();
|
||||
void onReplyFinished();
|
||||
|
||||
};
|
||||
|
||||
#endif // SPEEDWIREINVERTER_H
|
||||
|
||||
85
sma/speedwireinverterreply.cpp
Normal file
85
sma/speedwireinverterreply.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2021, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU Lesser General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; version 3. This project is distributed in the hope that
|
||||
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this project. If not, see <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 "speedwireinverterreply.h"
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
SpeedwireInverterReply::SpeedwireInverterReply(const SpeedwireInverterRequest &request, QObject *parent) :
|
||||
QObject(parent),
|
||||
m_request(request)
|
||||
{
|
||||
m_maxRetries = m_request.retries();
|
||||
|
||||
m_timer.setInterval(m_timeout);
|
||||
m_timer.setSingleShot(true);
|
||||
connect(&m_timer, &QTimer::timeout, this, &SpeedwireInverterReply::timeout);
|
||||
}
|
||||
|
||||
SpeedwireInverterRequest SpeedwireInverterReply::request() const
|
||||
{
|
||||
return m_request;
|
||||
}
|
||||
|
||||
SpeedwireInverterReply::Error SpeedwireInverterReply::error() const
|
||||
{
|
||||
return m_error;
|
||||
}
|
||||
|
||||
QByteArray SpeedwireInverterReply::responseData() const
|
||||
{
|
||||
return m_responseData;
|
||||
}
|
||||
|
||||
Speedwire::Header SpeedwireInverterReply::responseHeader() const
|
||||
{
|
||||
return m_responseHeader;
|
||||
}
|
||||
|
||||
Speedwire::InverterPackage SpeedwireInverterReply::responsePackage() const
|
||||
{
|
||||
return m_responsePackage;
|
||||
}
|
||||
|
||||
QByteArray SpeedwireInverterReply::responsePayload() const
|
||||
{
|
||||
return m_responsePayload;
|
||||
}
|
||||
|
||||
void SpeedwireInverterReply::startWaiting()
|
||||
{
|
||||
m_timer.start();
|
||||
}
|
||||
|
||||
void SpeedwireInverterReply::finishReply(Error error)
|
||||
{
|
||||
m_timer.stop();
|
||||
m_error = error;
|
||||
emit finished();
|
||||
}
|
||||
89
sma/speedwireinverterreply.h
Normal file
89
sma/speedwireinverterreply.h
Normal file
@ -0,0 +1,89 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2021, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU Lesser General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; version 3. This project is distributed in the hope that
|
||||
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this project. If not, see <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 SPEEDWIREINVERTERREPLY_H
|
||||
#define SPEEDWIREINVERTERREPLY_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
#include "speedwireinverterrequest.h"
|
||||
|
||||
class SpeedwireInverterReply : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
friend class SpeedwireInverter;
|
||||
|
||||
public:
|
||||
enum Error {
|
||||
ErrorNoError, // Response on, no error
|
||||
ErrorInverterError, // Inverter returned error
|
||||
ErrorTimeout // Request timeouted
|
||||
};
|
||||
Q_ENUM(Error)
|
||||
|
||||
// Request
|
||||
SpeedwireInverterRequest request() const;
|
||||
|
||||
Error error() const;
|
||||
|
||||
// Response
|
||||
QByteArray responseData() const;
|
||||
Speedwire::Header responseHeader() const;
|
||||
Speedwire::InverterPackage responsePackage() const;
|
||||
QByteArray responsePayload() const;
|
||||
|
||||
signals:
|
||||
void finished();
|
||||
void timeout();
|
||||
|
||||
private:
|
||||
explicit SpeedwireInverterReply(const SpeedwireInverterRequest &request, QObject *parent = nullptr);
|
||||
|
||||
QTimer m_timer;
|
||||
Error m_error = ErrorNoError;
|
||||
SpeedwireInverterRequest m_request;
|
||||
quint8 m_retries = 0;
|
||||
quint8 m_maxRetries = 3;
|
||||
int m_timeout = 3000;
|
||||
|
||||
QByteArray m_responseData;
|
||||
Speedwire::Header m_responseHeader;
|
||||
Speedwire::InverterPackage m_responsePackage;
|
||||
QByteArray m_responsePayload;
|
||||
|
||||
|
||||
void finishReply(Error error);
|
||||
void startWaiting();
|
||||
|
||||
};
|
||||
|
||||
#endif // SPEEDWIREINVERTERREPLY_H
|
||||
76
sma/speedwireinverterrequest.cpp
Normal file
76
sma/speedwireinverterrequest.cpp
Normal file
@ -0,0 +1,76 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2021, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU Lesser General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; version 3. This project is distributed in the hope that
|
||||
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this project. If not, see <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 "speedwireinverterrequest.h"
|
||||
|
||||
SpeedwireInverterRequest::SpeedwireInverterRequest()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Speedwire::Command SpeedwireInverterRequest::command() const
|
||||
{
|
||||
return m_command;
|
||||
}
|
||||
|
||||
void SpeedwireInverterRequest::setCommand(Speedwire::Command command)
|
||||
{
|
||||
m_command = command;
|
||||
}
|
||||
|
||||
quint16 SpeedwireInverterRequest::packetId() const
|
||||
{
|
||||
return m_packetId;
|
||||
}
|
||||
|
||||
void SpeedwireInverterRequest::setPacketId(quint16 packetId)
|
||||
{
|
||||
m_packetId = packetId;
|
||||
}
|
||||
|
||||
QByteArray SpeedwireInverterRequest::requestData() const
|
||||
{
|
||||
return m_requestData;
|
||||
}
|
||||
|
||||
void SpeedwireInverterRequest::setRequestData(const QByteArray &requestData)
|
||||
{
|
||||
m_requestData = requestData;
|
||||
}
|
||||
|
||||
quint8 SpeedwireInverterRequest::retries() const
|
||||
{
|
||||
return m_retries;
|
||||
}
|
||||
|
||||
void SpeedwireInverterRequest::setRetries(quint8 retries)
|
||||
{
|
||||
m_retries = retries;
|
||||
}
|
||||
62
sma/speedwireinverterrequest.h
Normal file
62
sma/speedwireinverterrequest.h
Normal file
@ -0,0 +1,62 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2021, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU Lesser General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; version 3. This project is distributed in the hope that
|
||||
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this project. If not, see <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 SPEEDWIREINVERTERREQUEST_H
|
||||
#define SPEEDWIREINVERTERREQUEST_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "speedwire.h"
|
||||
|
||||
class SpeedwireInverterRequest
|
||||
{
|
||||
public:
|
||||
explicit SpeedwireInverterRequest();
|
||||
|
||||
Speedwire::Command command() const;
|
||||
void setCommand(Speedwire::Command command);
|
||||
|
||||
quint16 packetId() const;
|
||||
void setPacketId(quint16 packetId);
|
||||
|
||||
QByteArray requestData() const;
|
||||
void setRequestData(const QByteArray &requestData);
|
||||
|
||||
quint8 retries() const;
|
||||
void setRetries(quint8 retries);
|
||||
|
||||
private:
|
||||
Speedwire::Command m_command;
|
||||
quint16 m_packetId = 0;
|
||||
QByteArray m_requestData;
|
||||
quint8 m_retries = 2; // Default try 2 times before timeout
|
||||
};
|
||||
|
||||
#endif // SPEEDWIREINVERTERREQUEST_H
|
||||
@ -1,3 +1,33 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2021, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU Lesser General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; version 3. This project is distributed in the hope that
|
||||
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this project. If not, see <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 "speedwiremeter.h"
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
@ -9,11 +39,20 @@ SpeedwireMeter::SpeedwireMeter(const QHostAddress &address, quint16 modelId, qui
|
||||
{
|
||||
m_interface = new SpeedwireInterface(m_address, true, this);
|
||||
connect(m_interface, &SpeedwireInterface::dataReceived, this, &SpeedwireMeter::processData);
|
||||
|
||||
// Reachable timestamp
|
||||
m_timer.setInterval(5000);
|
||||
m_timer.setSingleShot(false);
|
||||
connect(&m_timer, &QTimer::timeout, this, &SpeedwireMeter::evaluateReachable);
|
||||
}
|
||||
|
||||
bool SpeedwireMeter::initialize()
|
||||
{
|
||||
return m_interface->initialize();
|
||||
bool initSuccess = m_interface->initialize();
|
||||
if (initSuccess)
|
||||
m_timer.start();
|
||||
|
||||
return initSuccess;
|
||||
}
|
||||
|
||||
bool SpeedwireMeter::initialized() const
|
||||
@ -21,6 +60,11 @@ bool SpeedwireMeter::initialized() const
|
||||
return m_interface->initialized();
|
||||
}
|
||||
|
||||
bool SpeedwireMeter::reachable() const
|
||||
{
|
||||
return m_reachable;
|
||||
}
|
||||
|
||||
double SpeedwireMeter::currentPower() const
|
||||
{
|
||||
return m_currentPower;
|
||||
@ -116,19 +160,45 @@ QString SpeedwireMeter::softwareVersion() const
|
||||
return m_softwareVersion;
|
||||
}
|
||||
|
||||
void SpeedwireMeter::evaluateReachable()
|
||||
{
|
||||
// Note: the meter sends every second the data on the multicast
|
||||
qint64 currentTimestamp = QDateTime::currentDateTime().toMSecsSinceEpoch() / 1000;
|
||||
// If the meter has not sent data within the last 5 seconds it seems not to be reachable
|
||||
bool reachable = false;
|
||||
if (currentTimestamp - m_lastSeenTimestamp < 10) {
|
||||
reachable = true;
|
||||
}
|
||||
|
||||
if (m_reachable != reachable) {
|
||||
qCDebug(dcSma()) << "Meter: reachable changed to" << reachable;
|
||||
m_reachable = reachable;
|
||||
emit reachableChanged(m_reachable);
|
||||
}
|
||||
|
||||
// Restart the timer
|
||||
if (m_reachable) {
|
||||
m_timer.start();
|
||||
} else {
|
||||
// Reachable will be triggered automatically once data arrives
|
||||
// No need to run the timer all the time
|
||||
m_timer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
void SpeedwireMeter::processData(const QByteArray &data)
|
||||
{
|
||||
qCDebug(dcSma()) << "Meter: data received" << data.toHex();
|
||||
QDataStream stream(data);
|
||||
stream.setByteOrder(QDataStream::BigEndian);
|
||||
|
||||
SpeedwireInterface::SpeedwireHeader header = SpeedwireInterface::parseHeader(stream);
|
||||
Speedwire::Header header = Speedwire::parseHeader(stream);
|
||||
if (!header.isValid()) {
|
||||
qCDebug(dcSma()) << "Meter: Datagram header is not valid. Ignoring data...";
|
||||
return;
|
||||
}
|
||||
|
||||
if (header.protocolId != SpeedwireInterface::ProtocolIdMeter) {
|
||||
if (header.protocolId != Speedwire::ProtocolIdMeter) {
|
||||
qCDebug(dcSma()) << "Meter: received header protocol which is not from the meter protocol. Ignoring data...";
|
||||
return;
|
||||
}
|
||||
@ -266,5 +336,9 @@ void SpeedwireMeter::processData(const QByteArray &data)
|
||||
}
|
||||
}
|
||||
|
||||
// Save the current timestamp for reachable evaluation
|
||||
m_lastSeenTimestamp = QDateTime::currentDateTime().toMSecsSinceEpoch() / 1000;
|
||||
evaluateReachable();
|
||||
|
||||
emit valuesUpdated();
|
||||
}
|
||||
|
||||
@ -1,7 +1,39 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2021, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU Lesser General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; version 3. This project is distributed in the hope that
|
||||
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this project. If not, see <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 SPEEDWIREMETER_H
|
||||
#define SPEEDWIREMETER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QDateTime>
|
||||
#include <QTimer>
|
||||
|
||||
#include "speedwireinterface.h"
|
||||
|
||||
@ -14,6 +46,8 @@ public:
|
||||
bool initialize();
|
||||
bool initialized() const;
|
||||
|
||||
bool reachable() const;
|
||||
|
||||
double currentPower() const;
|
||||
double totalEnergyProduced() const;
|
||||
double totalEnergyConsumed() const;
|
||||
@ -42,6 +76,7 @@ public:
|
||||
|
||||
|
||||
signals:
|
||||
void reachableChanged(bool reachable);
|
||||
void valuesUpdated();
|
||||
|
||||
private:
|
||||
@ -51,6 +86,10 @@ private:
|
||||
quint16 m_modelId = 0;
|
||||
quint32 m_serialNumber = 0;
|
||||
|
||||
QTimer m_timer;
|
||||
bool m_reachable = false;
|
||||
qint64 m_lastSeenTimestamp = 0;
|
||||
|
||||
double m_currentPower = 0;
|
||||
double m_totalEnergyProduced = 0;
|
||||
double m_totalEnergyConsumed = 0;
|
||||
@ -77,7 +116,9 @@ private:
|
||||
|
||||
QString m_softwareVersion;
|
||||
|
||||
|
||||
private slots:
|
||||
void evaluateReachable();
|
||||
void processData(const QByteArray &data);
|
||||
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user