diff --git a/sunspec/integrationpluginsunspec.cpp b/sunspec/integrationpluginsunspec.cpp index 1e57caec..25ccd80f 100644 --- a/sunspec/integrationpluginsunspec.cpp +++ b/sunspec/integrationpluginsunspec.cpp @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2023, nymea GmbH +* Copyright 2013 - 2024, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -70,12 +70,18 @@ IntegrationPluginSunSpec::IntegrationPluginSunSpec() void IntegrationPluginSunSpec::init() { // SunSpec connection params - m_connectionPortParamTypeIds.insert(sunspecConnectionThingClassId, sunspecConnectionThingPortParamTypeId); - m_connectionPortParamTypeIds.insert(solarEdgeConnectionThingClassId, solarEdgeConnectionThingPortParamTypeId); - m_connectionMacAddressParamTypeIds.insert(sunspecConnectionThingClassId, sunspecConnectionThingMacAddressParamTypeId); m_connectionMacAddressParamTypeIds.insert(solarEdgeConnectionThingClassId, solarEdgeConnectionThingMacAddressParamTypeId); + m_connectionHostNameParamTypeIds.insert(sunspecConnectionThingClassId, sunspecConnectionThingHostNameParamTypeId); + m_connectionHostNameParamTypeIds.insert(solarEdgeConnectionThingClassId, solarEdgeConnectionThingHostNameParamTypeId); + + m_connectionAddressParamTypeIds.insert(sunspecConnectionThingClassId, sunspecConnectionThingAddressParamTypeId); + m_connectionAddressParamTypeIds.insert(solarEdgeConnectionThingClassId, solarEdgeConnectionThingAddressParamTypeId); + + m_connectionPortParamTypeIds.insert(sunspecConnectionThingClassId, sunspecConnectionThingPortParamTypeId); + m_connectionPortParamTypeIds.insert(solarEdgeConnectionThingClassId, solarEdgeConnectionThingPortParamTypeId); + m_connectionSlaveIdParamTypeIds.insert(sunspecConnectionThingClassId, sunspecConnectionThingSlaveIdParamTypeId); m_connectionSlaveIdParamTypeIds.insert(solarEdgeConnectionThingClassId, solarEdgeConnectionThingSlaveIdParamTypeId); @@ -141,7 +147,7 @@ void IntegrationPluginSunSpec::discoverThings(ThingDiscoveryInfo *info) SunSpecDiscovery *discovery = new SunSpecDiscovery(hardwareManager()->networkDeviceDiscovery(), slaveIds, byteOrder, info); // Note: we could add here more - connect(discovery, &SunSpecDiscovery::discoveryFinished, info, [=](){ + connect(discovery, &SunSpecDiscovery::discoveryFinished, info, [this, info, discovery](){ foreach (const SunSpecDiscovery::Result &result, discovery->results()) { // Extract the manufacturer: we pick the first manufacturer name of the first common model having a manufacturer name for now @@ -166,6 +172,7 @@ void IntegrationPluginSunSpec::discoverThings(ThingDiscoveryInfo *info) // Full support of meter, inverter and storage will be provided in the kostal plugin which makes // use of the native modbus communication from kostal. if (hasManufacturer(result.modelManufacturers, "kostal")) { + qCWarning(dcSunSpec()) << "Skipping Kostal manufacturer on SunSpec. Please use the native kostal integration plugin in order to add it to the system."; continue; } } @@ -177,26 +184,40 @@ void IntegrationPluginSunSpec::discoverThings(ThingDiscoveryInfo *info) title.append("SunSpec connection"); QString description; - if (result.networkDeviceInfo.macAddressManufacturer().isEmpty()) { - description = result.networkDeviceInfo.macAddress(); - } else { - description = result.networkDeviceInfo.macAddress() + " (" + result.networkDeviceInfo.macAddressManufacturer() + ")"; + MacAddressInfo macInfo; + switch (result.networkDeviceInfo.monitorMode()) { + case NetworkDeviceInfo::MonitorModeMac: + macInfo = result.networkDeviceInfo.macAddressInfos().constFirst(); + description = result.networkDeviceInfo.address().toString(); + if (!macInfo.vendorName().isEmpty()) + description += " - " + result.networkDeviceInfo.macAddressInfos().constFirst().vendorName(); + + break; + case NetworkDeviceInfo::MonitorModeHostName: + description = result.networkDeviceInfo.hostName(); + break; + case NetworkDeviceInfo::MonitorModeIp: + description = "Interface: " + result.networkDeviceInfo.networkInterface().name(); + break; } ThingDescriptor descriptor(info->thingClassId(), title, description); - // Check if we already have set up this device - Things existingThings = myThings().filterByParam(m_connectionMacAddressParamTypeIds.value(info->thingClassId()), result.networkDeviceInfo.macAddress()); - if (existingThings.count() == 1) { - qCDebug(dcSunSpec()) << "This thing already exists in the system." << existingThings.first() << result.networkDeviceInfo; - descriptor.setThingId(existingThings.first()->id()); - } - ParamList params; + params << Param(m_connectionMacAddressParamTypeIds.value(info->thingClassId()), result.networkDeviceInfo.thingParamValueMacAddress()); + params << Param(m_connectionHostNameParamTypeIds.value(info->thingClassId()), result.networkDeviceInfo.thingParamValueHostName()); + params << Param(m_connectionAddressParamTypeIds.value(info->thingClassId()), result.networkDeviceInfo.thingParamValueAddress()); params << Param(m_connectionPortParamTypeIds.value(info->thingClassId()), result.port); - params << Param(m_connectionMacAddressParamTypeIds.value(info->thingClassId()), result.networkDeviceInfo.macAddress()); params << Param(m_connectionSlaveIdParamTypeIds.value(info->thingClassId()), result.slaveId); descriptor.setParams(params); + + // Check if we already have set up this device + Thing *existingThing = myThings().findByParams(params); + if (existingThing) { + qCDebug(dcSunSpec()) << "This thing already exists in the system:" << result.networkDeviceInfo; + descriptor.setThingId(existingThing->id()); + } + info->addThingDescriptor(descriptor); } @@ -213,7 +234,8 @@ void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info) qCDebug(dcSunSpec()) << "Setup thing" << thing; qCDebug(dcSunSpec()) << thing->params(); - if (thing->thingClassId() == sunspecConnectionThingClassId || thing->thingClassId() == solarEdgeConnectionThingClassId) { + if (thing->thingClassId() == sunspecConnectionThingClassId || + thing->thingClassId() == solarEdgeConnectionThingClassId) { // Handle reconfigure if (m_sunSpecConnections.contains(thing->id())) { @@ -225,16 +247,17 @@ void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info) } } - MacAddress macAddress = MacAddress(thing->paramValue(m_connectionMacAddressParamTypeIds.value(thing->thingClassId())).toString()); - if (!macAddress.isValid()) { - qCWarning(dcSunSpec()) << "The configured mac address is not valid" << thing->params(); - info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("The MAC address is not known. Please reconfigure the connection.")); + + // Create the monitor + NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(thing); + if (!monitor) { + qCWarning(dcSunSpec()) << "Unable to register monitor with the given params" << thing->params(); + info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("Unable to set up the connection with this configuration, please reconfigure the connection.")); return; } - // Create the monitor - NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(macAddress); m_monitors.insert(thing, monitor); + QHostAddress address = monitor->networkDeviceInfo().address(); if (address.isNull() && info->isInitialSetup()) { qCWarning(dcSunSpec()) << "Cannot set up thing. The host address is not known and this is an initial setup"; @@ -305,9 +328,9 @@ void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info) } } - } else if (thing->thingClassId() == sunspecThreePhaseInverterThingClassId - || thing->thingClassId() == sunspecSplitPhaseInverterThingClassId - || thing->thingClassId() == sunspecSinglePhaseInverterThingClassId ) { + } else if (thing->thingClassId() == sunspecThreePhaseInverterThingClassId || + thing->thingClassId() == sunspecSplitPhaseInverterThingClassId || + thing->thingClassId() == sunspecSinglePhaseInverterThingClassId ) { Thing *thing = info->thing(); uint modelId = thing->paramValue(m_modelIdParamTypeIds.value(thing->thingClassId())).toInt(); @@ -420,9 +443,8 @@ void IntegrationPluginSunSpec::thingRemoved(Thing *thing) Q_ASSERT_X(false, "thingRemoved", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); } - if (m_monitors.contains(thing)) { + if (m_monitors.contains(thing)) hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); - } if (myThings().isEmpty()) { qCDebug(dcSunSpec()) << "Stopping refresh timer"; diff --git a/sunspec/integrationpluginsunspec.h b/sunspec/integrationpluginsunspec.h index c7a8b827..36ef6ca9 100644 --- a/sunspec/integrationpluginsunspec.h +++ b/sunspec/integrationpluginsunspec.h @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2023, nymea GmbH +* Copyright 2013 - 2024, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -62,9 +62,10 @@ class IntegrationPluginSunSpec: public IntegrationPlugin private: // SunSpec Connection params map - QHash m_connectionIpParamTypeIds; - QHash m_connectionPortParamTypeIds; QHash m_connectionMacAddressParamTypeIds; + QHash m_connectionAddressParamTypeIds; + QHash m_connectionHostNameParamTypeIds; + QHash m_connectionPortParamTypeIds; QHash m_connectionSlaveIdParamTypeIds; // SunSpec thing params map diff --git a/sunspec/integrationpluginsunspec.json b/sunspec/integrationpluginsunspec.json index 1f2ad4d1..fa3ad06b 100644 --- a/sunspec/integrationpluginsunspec.json +++ b/sunspec/integrationpluginsunspec.json @@ -40,15 +40,32 @@ "displayName": "SunSpec Generic", "id": "f51853f3-8815-4cf3-b337-45cda1f3e6d5", "createMethods": [ "Discovery" ], - "interfaces": ["gateway"], + "interfaces": [ "gateway", "networkdevice" ], "providedInterfaces": [ "solarinverter", "energymeter", "energystorage"], "paramTypes": [ + { + "id": "3567b389-9d42-48f9-a29b-d18388fb36a1", + "name": "address", + "displayName": "Host address", + "type": "QString", + "inputType": "IPv4Address", + "defaultValue": "" + }, + { + "id": "1667b0ed-9a2b-47c6-a01f-690caee55ffa", + "name": "hostName", + "displayName": "Host name", + "type": "QString", + "inputType": "TextLine", + "defaultValue": "" + }, { "id": "f65d6c36-1672-44cb-b52a-62d71837ae67", "name":"macAddress", "displayName": "MAC address", "type": "QString", - "defaultValue": "00:00:00:00:00:00" + "inputType": "MacAddress", + "defaultValue": "" }, { "id": "1fa4fc9c-f6be-47c7-928a-bcefc1142eec", @@ -1458,15 +1475,32 @@ "displayName": "SolarEdge", "id": "7a92bf65-b443-4491-a012-2bec35eb5bf0", "createMethods": [ "Discovery" ], - "interfaces": ["gateway"], + "interfaces": [ "gateway", "networkdevice" ], "providedInterfaces": [ "solarinverter", "energymeter", "energystorage" ], "paramTypes": [ + { + "id": "9c2bafd0-6d56-42e0-8ef3-c4940b4f18b5", + "name": "address", + "displayName": "Host address", + "type": "QString", + "inputType": "IPv4Address", + "defaultValue": "" + }, + { + "id": "2e22c369-8476-4908-9cdc-186472f4d472", + "name": "hostName", + "displayName": "Host name", + "type": "QString", + "inputType": "TextLine", + "defaultValue": "" + }, { "id": "bb395c12-54d6-4139-b0a6-e31b28bc4d2e", "name":"macAddress", "displayName": "MAC address", "type": "QString", - "defaultValue": "00:00:00:00:00:00" + "inputType": "MacAddress", + "defaultValue": "" }, { "id": "1bcede48-d167-4ced-8f1b-bea6302dd43f", diff --git a/sunspec/sunspecdiscovery.cpp b/sunspec/sunspecdiscovery.cpp index de761cf1..8286e5ff 100644 --- a/sunspec/sunspecdiscovery.cpp +++ b/sunspec/sunspecdiscovery.cpp @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2023, nymea GmbH +* Copyright 2013 - 2024, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -65,16 +65,16 @@ void SunSpecDiscovery::startDiscovery() m_startDateTime = QDateTime::currentDateTime(); // Imedialty check any new device gets discovered - connect(discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, &SunSpecDiscovery::checkNetworkDevice); + connect(discoveryReply, &NetworkDeviceDiscoveryReply::hostAddressDiscovered, this, &SunSpecDiscovery::checkNetworkDevice); // Check what might be left on finished connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, discoveryReply, &NetworkDeviceDiscoveryReply::deleteLater); - connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){ + connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [this, discoveryReply](){ qCDebug(dcSunSpec()) << "Discovery: Network discovery finished. Give some time for pending discovery checks to finish..."; + m_networkDeviceInfos = discoveryReply->networkDeviceInfos(); // 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(); }); }); @@ -121,7 +121,7 @@ void SunSpecDiscovery::testNextConnection(const QHostAddress &address) connectionTimer->start(); } -void SunSpecDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo) +void SunSpecDiscovery::checkNetworkDevice(const QHostAddress &address) { // Create a connection queue for this network device @@ -132,13 +132,13 @@ void SunSpecDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDevice foreach (quint16 slaveId, m_slaveIds) { - SunSpecConnection *connection = new SunSpecConnection(networkDeviceInfo.address(), port, slaveId, m_byteOrder, this); + SunSpecConnection *connection = new SunSpecConnection(address, port, slaveId, m_byteOrder, this); connection->setNumberOfRetries(1); connection->setTimeout(500); m_connections.append(connection); connectionQueue.enqueue(connection); - connect(connection, &SunSpecConnection::connectedChanged, this, [=](bool connected){ + connect(connection, &SunSpecConnection::connectedChanged, this, [this, connection, connectionQueue, address](bool connected){ if (!connected) { // Disconnected ... done with this connection cleanupConnection(connection); @@ -155,7 +155,9 @@ void SunSpecDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDevice // Modbus TCP connected, try to discovery sunspec models... connect(connection, &SunSpecConnection::discoveryFinished, this, [=](bool success){ if (!success) { - qCDebug(dcSunSpec()) << "Discovery: SunSpec discovery failed on" << QString("%1:%2").arg(networkDeviceInfo.address().toString()).arg(port) << "slave ID:" << slaveId << "Continue...";; + qCDebug(dcSunSpec()) << "Discovery: SunSpec discovery failed on" + << QString("%1:%2").arg(address.toString()).arg(connection->port()) + << "slave ID:" << connection->slaveId() << "Continue..."; cleanupConnection(connection); return; } @@ -163,7 +165,7 @@ void SunSpecDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDevice // Success, we found some sunspec models here, let's read some infomation from the models Result result; - result.networkDeviceInfo = networkDeviceInfo; + result.address = address; result.port = connection->port(); result.slaveId = connection->slaveId(); @@ -186,7 +188,9 @@ void SunSpecDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDevice // Run SunSpec discovery on connection... if (!connection->startDiscovery()) { - qCDebug(dcSunSpec()) << "Discovery: Unable to discover SunSpec data on connection" << QString("%1:%2").arg(networkDeviceInfo.address().toString()).arg(port) << "slave ID:" << slaveId << "Continue...";; + qCDebug(dcSunSpec()) << "Discovery: Unable to discover SunSpec data on connection" + << QString("%1:%2").arg(address.toString()).arg(connection->port()) + << "slave ID:" << connection->slaveId() << "Continue..."; cleanupConnection(connection); } }); @@ -194,15 +198,17 @@ void SunSpecDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDevice // 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" << QString("%1:%2").arg(networkDeviceInfo.address().toString()).arg(port) << "slave ID:" << slaveId << "Continue...";; + qCDebug(dcSunSpec()) << "Discovery: Connection error on" + << QString("%1:%2").arg(address.toString()).arg(connection->port()) + << "slave ID:" << connection->slaveId() << "Continue..."; cleanupConnection(connection); } }); } } - m_pendingConnectionAttempts[networkDeviceInfo.address()] = connectionQueue; - testNextConnection(networkDeviceInfo.address()); + m_pendingConnectionAttempts[address] = connectionQueue; + testNextConnection(address); } void SunSpecDiscovery::cleanupConnection(SunSpecConnection *connection) @@ -223,10 +229,15 @@ void SunSpecDiscovery::finishDiscovery() { qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startDateTime.toMSecsSinceEpoch(); + // Fill in all network device infos we have + for (int i = 0; i < m_results.count(); i++) + m_results[i].networkDeviceInfo = m_networkDeviceInfos.get(m_results.at(i).address); + // 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"); + qCInfo(dcSunSpec()) << "Discovery: Finished the discovery process. Found" << m_results.count() + << "SunSpec devices in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz"); emit discoveryFinished(); } diff --git a/sunspec/sunspecdiscovery.h b/sunspec/sunspecdiscovery.h index 2ea7a22f..eedeb54a 100644 --- a/sunspec/sunspecdiscovery.h +++ b/sunspec/sunspecdiscovery.h @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2023, nymea GmbH +* Copyright 2013 - 2024, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -44,6 +44,7 @@ class SunSpecDiscovery : public QObject public: explicit SunSpecDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, const QList &slaveIds, SunSpecDataPoint::ByteOrder byteOrder = SunSpecDataPoint::ByteOrderLittleEndian, QObject *parent = nullptr); typedef struct Result { + QHostAddress address; NetworkDeviceInfo networkDeviceInfo; quint16 port; quint16 slaveId; @@ -67,13 +68,13 @@ class SunSpecDiscovery : public QObject QDateTime m_startDateTime; QHash> m_pendingConnectionAttempts; QHash m_connectionTimers; - QList m_connections; + NetworkDeviceInfos m_networkDeviceInfos; QList m_results; void testNextConnection(const QHostAddress &address); - void checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo); + void checkNetworkDevice(const QHostAddress &address); void cleanupConnection(SunSpecConnection *connection); void finishDiscovery();