SunSpec: Add sunspec discovery
SunSpec: Make use network device monitor SunSpec: Reset energy live data on disconnected SunSpec: Remove devices if the model has been removed from the connectionpull/127/head
parent
c4e2178c3a
commit
2efabf95c8
File diff suppressed because it is too large
Load Diff
|
|
@ -1,6 +1,6 @@
|
|||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2021, nymea GmbH
|
||||
* Copyright 2013 - 2023, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
|
|
@ -32,12 +32,14 @@
|
|||
#define INTEGRATIONPLUGINSUNSPEC_H
|
||||
|
||||
#include <integrations/integrationplugin.h>
|
||||
#include <network/networkdevicemonitor.h>
|
||||
#include <plugintimer.h>
|
||||
|
||||
#include <sunspecconnection.h>
|
||||
#include <models/sunspecmodelfactory.h>
|
||||
|
||||
#include "sunspecthing.h"
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
#include <QUuid>
|
||||
|
||||
|
|
@ -74,6 +76,8 @@ private:
|
|||
|
||||
PluginTimer *m_refreshTimer = nullptr;
|
||||
|
||||
QHash<Thing *, NetworkDeviceMonitor *> m_monitors;
|
||||
|
||||
QHash<ThingId, SunSpecConnection *> m_sunSpecConnections;
|
||||
QHash<Thing *, SunSpecThing *> m_sunSpecThings;
|
||||
|
||||
|
|
@ -81,15 +85,13 @@ private:
|
|||
QHash<Thing *, SunSpecModel *> m_sunSpecMeters;
|
||||
QHash<Thing *, SunSpecModel *> m_sunSpecStorages;
|
||||
|
||||
|
||||
Thing *getThingForSunSpecModel(uint modelId, uint modbusAddress, const ThingId &parentId);
|
||||
bool sunspecThingAlreadyAdded(uint modelId, uint modbusAddress, const ThingId &parentId);
|
||||
void processDiscoveryResult(Thing *thing, SunSpecConnection *connection);
|
||||
void checkAutoSetupModels(Thing *connectionThing, QList<SunSpecModel *> models);
|
||||
|
||||
// SunSpec things
|
||||
void setupConnection(ThingSetupInfo *info);
|
||||
void setupInverter(ThingSetupInfo *info);
|
||||
void setupMeter(ThingSetupInfo *info);
|
||||
void setupStorage(ThingSetupInfo *info);
|
||||
SunSpecConnection *createConnection(Thing *thing);
|
||||
|
||||
// Custom types
|
||||
void setupSolarEdgeBattery(ThingSetupInfo *info);
|
||||
|
|
@ -107,6 +109,8 @@ private:
|
|||
QString getInverterErrorString(quint32 flag);
|
||||
|
||||
double fixValueSign(double targetValue, double powerValue);
|
||||
bool hasManufacturer(const QStringList &manufacturers, const QString &manufacturer);
|
||||
void markThingStatesDisconnected(Thing *thing);
|
||||
|
||||
private slots:
|
||||
void onRefreshTimer();
|
||||
|
|
|
|||
|
|
@ -39,17 +39,16 @@
|
|||
"name": "sunspecConnection",
|
||||
"displayName": "SunSpec Generic",
|
||||
"id": "f51853f3-8815-4cf3-b337-45cda1f3e6d5",
|
||||
"createMethods": [ "User", "Discovery" ],
|
||||
"discoveryType": "weak",
|
||||
"createMethods": [ "Discovery" ],
|
||||
"interfaces": ["gateway"],
|
||||
"providedInterfaces": [ "solarinverter", "energymeter", "energystorage"],
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "6be6abc4-e2b2-4687-9343-0e5164ed0ab2",
|
||||
"name":"ipAddress",
|
||||
"displayName": "IP address",
|
||||
"id": "f65d6c36-1672-44cb-b52a-62d71837ae67",
|
||||
"name":"macAddress",
|
||||
"displayName": "MAC address",
|
||||
"type": "QString",
|
||||
"defaultValue": "127.0.0.1"
|
||||
"defaultValue": "00:00:00:00:00:00"
|
||||
},
|
||||
{
|
||||
"id": "1fa4fc9c-f6be-47c7-928a-bcefc1142eec",
|
||||
|
|
@ -58,13 +57,6 @@
|
|||
"type": "int",
|
||||
"defaultValue": 502
|
||||
},
|
||||
{
|
||||
"id": "f65d6c36-1672-44cb-b52a-62d71837ae67",
|
||||
"name":"macAddress",
|
||||
"displayName": "MAC address",
|
||||
"type": "QString",
|
||||
"defaultValue": "00:00:00:00:00:00"
|
||||
},
|
||||
{
|
||||
"id": "953064e0-4675-4538-a9a2-fa22ce2f347c",
|
||||
"name":"slaveId",
|
||||
|
|
@ -1465,17 +1457,10 @@
|
|||
"name": "solarEdgeConnection",
|
||||
"displayName": "SolarEdge",
|
||||
"id": "7a92bf65-b443-4491-a012-2bec35eb5bf0",
|
||||
"createMethods": [ "User", "Discovery" ],
|
||||
"createMethods": [ "Discovery" ],
|
||||
"interfaces": ["gateway"],
|
||||
"providedInterfaces": [ "solarinverter", "energymeter", "energystorage" ],
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "94f6ba3c-6b8b-47a9-a5cb-73a091ae4cf7",
|
||||
"name":"ipAddress",
|
||||
"displayName": "IP address",
|
||||
"type": "QString",
|
||||
"defaultValue": "127.0.0.1"
|
||||
},
|
||||
{
|
||||
"id": "bb395c12-54d6-4139-b0a6-e31b28bc4d2e",
|
||||
"name":"macAddress",
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@ include(../sunspec.pri)
|
|||
SOURCES += \
|
||||
integrationpluginsunspec.cpp \
|
||||
solaredgebattery.cpp \
|
||||
sunspecdiscovery.cpp \
|
||||
sunspecthing.cpp
|
||||
|
||||
HEADERS += \
|
||||
integrationpluginsunspec.h \
|
||||
solaredgebattery.h \
|
||||
sunspecdiscovery.h \
|
||||
sunspecthing.h
|
||||
|
|
|
|||
|
|
@ -0,0 +1,169 @@
|
|||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2023, 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 "sunspecdiscovery.h"
|
||||
|
||||
#include <sunspecmodel.h>
|
||||
#include <models/sunspecmodelfactory.h>
|
||||
#include <models/sunspeccommonmodel.h>
|
||||
|
||||
SunSpecDiscovery::SunSpecDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, quint16 modbusAddress, QObject *parent)
|
||||
: QObject{parent},
|
||||
m_networkDeviceDiscovery{networkDeviceDiscovery},
|
||||
m_modbusAddress{modbusAddress}
|
||||
{
|
||||
m_scanPorts.append(502);
|
||||
m_scanPorts.append(1502);
|
||||
}
|
||||
|
||||
QList<SunSpecDiscovery::Result> SunSpecDiscovery::results() const
|
||||
{
|
||||
return m_results;
|
||||
}
|
||||
|
||||
void SunSpecDiscovery::addCustomDiscoveryPort(quint16 port)
|
||||
{
|
||||
if (m_scanPorts.contains(port))
|
||||
return;
|
||||
|
||||
m_scanPorts.append(port);
|
||||
}
|
||||
|
||||
void SunSpecDiscovery::startDiscovery()
|
||||
{
|
||||
qCInfo(dcSunSpec()) << "Discovery: Start searching for SunSpec devices in the network...";
|
||||
NetworkDeviceDiscoveryReply *discoveryReply = m_networkDeviceDiscovery->discover();
|
||||
|
||||
m_startDateTime = QDateTime::currentDateTime();
|
||||
|
||||
// Imedialty check any new device gets discovered
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, &SunSpecDiscovery::checkNetworkDevice);
|
||||
|
||||
// Check what might be left on finished
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, discoveryReply, &NetworkDeviceDiscoveryReply::deleteLater);
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){
|
||||
qCDebug(dcSunSpec()) << "Discovery: Network discovery finished. Give some time for pending discovery checks to finish...";
|
||||
|
||||
// Give the last connections added right before the network discovery finished a chance to check the device...
|
||||
QTimer::singleShot(3000, this, [this](){
|
||||
qCDebug(dcSunSpec()) << "Discovery: Grace period timer triggered";
|
||||
finishDiscovery();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void SunSpecDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo)
|
||||
{
|
||||
// Create a sunspec connection and try to initialize it (read models).
|
||||
if (m_verifiedNetworkDeviceInfos.contains(networkDeviceInfo))
|
||||
return;
|
||||
|
||||
// Check all ports for this host
|
||||
foreach (quint16 port, m_scanPorts) {
|
||||
SunSpecConnection *connection = new SunSpecConnection(networkDeviceInfo.address(), port, m_modbusAddress, this);
|
||||
m_connections.append(connection);
|
||||
m_verifiedNetworkDeviceInfos.append(networkDeviceInfo);
|
||||
|
||||
connect(connection, &SunSpecConnection::connectedChanged, this, [=](bool connected){
|
||||
if (!connected) {
|
||||
// Disconnected ... done with this connection
|
||||
cleanupConnection(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
// Modbus TCP connected, try to discovery sunspec models...
|
||||
connect(connection, &SunSpecConnection::discoveryFinished, this, [=](bool success){
|
||||
if (!success) {
|
||||
qCDebug(dcSunSpec()) << "Discovery: SunSpec discovery failed on" << networkDeviceInfo.address().toString() << "Continue...";;
|
||||
cleanupConnection(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
// Success, we found some sunspec models here, let's read some infomation from the models
|
||||
|
||||
Result result;
|
||||
result.networkDeviceInfo = networkDeviceInfo;
|
||||
result.port = connection->port();
|
||||
|
||||
qCDebug(dcSunSpec()) << "Discovery: --> Found SunSpec devices on" << result.networkDeviceInfo << "port" << result.port;
|
||||
foreach (SunSpecModel *model, connection->models()) {
|
||||
if (model->modelId() == SunSpecModelFactory::ModelIdCommon) {
|
||||
SunSpecCommonModel *commonModel = qobject_cast<SunSpecCommonModel *>(model);
|
||||
QString manufacturer = commonModel->manufacturer();
|
||||
if (!manufacturer.isEmpty() && !result.modelManufacturers.contains(manufacturer)) {
|
||||
result.modelManufacturers.append(manufacturer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_results.append(result);
|
||||
|
||||
// Done with this connection
|
||||
cleanupConnection(connection);
|
||||
});
|
||||
|
||||
// Run SunSPec discovery on connection...
|
||||
if (!connection->startDiscovery()) {
|
||||
qCDebug(dcSunSpec()) << "Discovery: Unable to discover SunSpec data on connection" << networkDeviceInfo.address().toString() << "Continue...";;
|
||||
cleanupConnection(connection);
|
||||
}
|
||||
});
|
||||
|
||||
// If we get any error...skip this host...
|
||||
connect(connection->modbusTcpClient(), &QModbusTcpClient::errorOccurred, this, [=](QModbusDevice::Error error){
|
||||
if (error != QModbusDevice::NoError) {
|
||||
qCDebug(dcSunSpec()) << "Discovery: Connection error on" << networkDeviceInfo.address().toString() << "Continue...";;
|
||||
cleanupConnection(connection);
|
||||
}
|
||||
});
|
||||
|
||||
// Try to connect, maybe it works, maybe not...
|
||||
connection->connectDevice();
|
||||
}
|
||||
}
|
||||
|
||||
void SunSpecDiscovery::cleanupConnection(SunSpecConnection *connection)
|
||||
{
|
||||
m_connections.removeAll(connection);
|
||||
connection->disconnectDevice();
|
||||
connection->deleteLater();
|
||||
}
|
||||
|
||||
void SunSpecDiscovery::finishDiscovery()
|
||||
{
|
||||
qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startDateTime.toMSecsSinceEpoch();
|
||||
|
||||
// Cleanup any leftovers...we don't care any more
|
||||
foreach (SunSpecConnection *connection, m_connections)
|
||||
cleanupConnection(connection);
|
||||
|
||||
qCInfo(dcSunSpec()) << "Discovery: Finished the discovery process. Found" << m_results.count() << "SunSpec devices in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz");
|
||||
emit discoveryFinished();
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2023, 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 SUNSPECDISCOVERY_H
|
||||
#define SUNSPECDISCOVERY_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QDateTime>
|
||||
|
||||
#include <sunspecconnection.h>
|
||||
#include <network/networkdevicediscovery.h>
|
||||
|
||||
class SunSpecDiscovery : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SunSpecDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, quint16 modbusAddress = 1, QObject *parent = nullptr);
|
||||
typedef struct Result {
|
||||
NetworkDeviceInfo networkDeviceInfo;
|
||||
quint16 port;
|
||||
QStringList modelManufacturers;
|
||||
} Result;
|
||||
|
||||
QList<Result> results() const;
|
||||
|
||||
void addCustomDiscoveryPort(quint16 port);
|
||||
void startDiscovery();
|
||||
|
||||
signals:
|
||||
void discoveryFinished();
|
||||
|
||||
private:
|
||||
NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr;
|
||||
quint16 m_modbusAddress;
|
||||
QList<quint16> m_scanPorts;
|
||||
|
||||
QDateTime m_startDateTime;
|
||||
|
||||
NetworkDeviceInfos m_verifiedNetworkDeviceInfos;
|
||||
|
||||
QList<SunSpecConnection *> m_connections;
|
||||
QList<Result> m_results;
|
||||
|
||||
void checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo);
|
||||
void cleanupConnection(SunSpecConnection *connection);
|
||||
|
||||
void finishDiscovery();
|
||||
};
|
||||
|
||||
#endif // SUNSPECDISCOVERY_H
|
||||
Loading…
Reference in New Issue