From c2eccd96e5f199d88f3785fb54f453846119dcc4 Mon Sep 17 00:00:00 2001 From: Patrick Schurig Date: Sat, 1 Aug 2026 16:18:41 +0200 Subject: [PATCH] sunspec: add a failing test that sets up a Fronius connection The existing suite only exercises free functions and the plugin JSON, so it cannot see whether a thing class is dispatched at all. A thing class missing from the dispatch in setupThing() falls into the final Q_ASSERT_X: in a debug build the process dies, in a release build the assert is compiled out and setupThing() returns without ever calling info->finish(), leaving the pairing stuck with no message. Split the tests into two suites. The logic one keeps its former scope and its single dependency on libnymea-sunspec. The new one instantiates the plugin behind the real ThingManager of libnymea-core and a faked HardwareManager, so the dispatch of setupThing(), postSetupThing() and thingRemoved() is covered. No network access takes place: the faked monitor is never reachable, so the plugin never attempts a Modbus connection. The four new cases fail: froniusConnection is not dispatched yet. Co-Authored-By: Claude Opus 5 (1M context) --- sunspec/tests/logic/logic.pro | 29 ++ sunspec/tests/{ => logic}/tst_sunspec.cpp | 0 sunspec/tests/plugin/plugin.pro | 62 ++++ sunspec/tests/plugin/tst_sunspecplugin.cpp | 388 +++++++++++++++++++++ sunspec/tests/tests.pro | 36 +- 5 files changed, 487 insertions(+), 28 deletions(-) create mode 100644 sunspec/tests/logic/logic.pro rename sunspec/tests/{ => logic}/tst_sunspec.cpp (100%) create mode 100644 sunspec/tests/plugin/plugin.pro create mode 100644 sunspec/tests/plugin/tst_sunspecplugin.cpp diff --git a/sunspec/tests/logic/logic.pro b/sunspec/tests/logic/logic.pro new file mode 100644 index 0000000..153606b --- /dev/null +++ b/sunspec/tests/logic/logic.pro @@ -0,0 +1,29 @@ +include(../../../sunspec.pri) + +QT += testlib +QT -= gui +CONFIG += console testcase +CONFIG -= app_bundle +TEMPLATE = app +TARGET = tst_sunspec + +# Suite de logique pure : ce que le plugin *decide*. Elle ne charge ni nymea ni +# le plugin lui-meme, et ne demande donc que libnymea-sunspec. La suite qui +# instancie reellement le plugin vit dans ../plugin/ et porte ses propres +# dependances. Ce qui est emis sur le bus releve du banc (voir "Non verifiable +# par les tests unitaires" dans specs/fronius-sunspec.md). + +INCLUDEPATH += ../.. + +# Chemin du JSON de definition du plugin, pour les criteres qui portent sur les +# thingClasses et leurs plages de valeurs (AC-1, AC-7, AC-8, AC-10). +DEFINES += PLUGIN_JSON_PATH=\\\"$$PWD/../../integrationpluginsunspec.json\\\" + +SOURCES += \ + tst_sunspec.cpp \ + ../../sunspecstoragelogic.cpp \ + ../../froniusrules.cpp + +HEADERS += \ + ../../sunspecstoragelogic.h \ + ../../froniusrules.h diff --git a/sunspec/tests/tst_sunspec.cpp b/sunspec/tests/logic/tst_sunspec.cpp similarity index 100% rename from sunspec/tests/tst_sunspec.cpp rename to sunspec/tests/logic/tst_sunspec.cpp diff --git a/sunspec/tests/plugin/plugin.pro b/sunspec/tests/plugin/plugin.pro new file mode 100644 index 0000000..a80081b --- /dev/null +++ b/sunspec/tests/plugin/plugin.pro @@ -0,0 +1,62 @@ +include(../../../sunspec.pri) + +QT += testlib network serialbus +QT -= gui +CONFIG += console testcase c++17 +CONFIG -= app_bundle +TEMPLATE = app +TARGET = tst_sunspecplugin + +# Suite qui instancie reellement IntegrationPluginSunSpec, derriere un +# ThingManager reel (celui de libnymea-core) et un HardwareManager double. Elle +# couvre ce que la suite de logique pure ne peut pas voir : l'aiguillage de +# setupThing() / postSetupThing() / thingRemoved() sur la thingClass. +# +# Dependances supplementaires par rapport a ../logic : libnymea1-dev et +# libnymea-core (pkg-config nymea et nymea-core). + +CONFIG += link_pkgconfig +PKGCONFIG += nymea nymea-core + +PLUGIN_DIR = $$PWD/../.. +INCLUDEPATH += $$PLUGIN_DIR + +# Le plugin s'appuie sur les identifiants generes depuis son JSON. On les +# fabrique ici exactement comme le fait plugin.pri pour le plugin lui-meme : +# plugininfo.h (definitions, inclus par integrationpluginsunspec.cpp) et +# extern-plugininfo.h (declarations, inclus par les en-tetes et par le test). +JSONFILE = $$PLUGIN_DIR/integrationpluginsunspec.json +OTHER_FILES += $$JSONFILE + +plugininfo.input = JSONFILE +plugininfo.output = plugininfo.h +plugininfo.commands = nymea-plugininfocompiler ${QMAKE_FILE_NAME} --output ${QMAKE_FILE_OUT} +plugininfo.dependency_type = TYPE_C +plugininfo.CONFIG += no_link combine +QMAKE_EXTRA_COMPILERS += plugininfo + +extern_plugininfo.input = JSONFILE +extern_plugininfo.output = extern-plugininfo.h +extern_plugininfo.commands = nymea-plugininfocompiler ${QMAKE_FILE_NAME} --extern ${QMAKE_FILE_OUT} +extern_plugininfo.dependency_type = TYPE_C +extern_plugininfo.CONFIG += no_link combine +QMAKE_EXTRA_COMPILERS += extern_plugininfo + +DEFINES += PLUGIN_JSON_PATH=\\\"$$JSONFILE\\\" + +SOURCES += \ + tst_sunspecplugin.cpp \ + $$PLUGIN_DIR/integrationpluginsunspec.cpp \ + $$PLUGIN_DIR/froniusrules.cpp \ + $$PLUGIN_DIR/solaredgebattery.cpp \ + $$PLUGIN_DIR/sunspecdiscovery.cpp \ + $$PLUGIN_DIR/sunspecstoragelogic.cpp \ + $$PLUGIN_DIR/sunspecthing.cpp + +HEADERS += \ + $$PLUGIN_DIR/integrationpluginsunspec.h \ + $$PLUGIN_DIR/froniusrules.h \ + $$PLUGIN_DIR/solaredgebattery.h \ + $$PLUGIN_DIR/sunspecdiscovery.h \ + $$PLUGIN_DIR/sunspecstoragelogic.h \ + $$PLUGIN_DIR/sunspecthing.h diff --git a/sunspec/tests/plugin/tst_sunspecplugin.cpp b/sunspec/tests/plugin/tst_sunspecplugin.cpp new file mode 100644 index 0000000..79b74d0 --- /dev/null +++ b/sunspec/tests/plugin/tst_sunspecplugin.cpp @@ -0,0 +1,388 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright (C) 2026, ETM-PowerSync +* +* This file is part of nymea. +* +* nymea is free software: you can redistribute it and/or modify it under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, either version 3 of the License, or (at your option) any later +* version. +* +* nymea 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License along with +* nymea. If not, see . +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/* Suite qui instancie reellement le plugin. + * + * La suite de logique pure (../logic) ne peut voir que ce que le plugin + * *decide* : elle appelle des fonctions libres et lit le JSON. Elle ne peut pas + * voir l'aiguillage, c'est a dire le fait qu'une thingClass soit reconnue par + * setupThing(), postSetupThing() et thingRemoved(). Une thingClass oubliee dans + * l'un de ces trois aiguillages tombe dans un Q_ASSERT_X final : en debug le + * processus meurt, en release l'assert est compile hors du binaire et + * setupThing() rend la main sans jamais appeler info->finish(). L'appairage + * reste alors bloque indefiniment, sans le moindre message. + * + * C'est exactement ce qui est arrive a froniusConnection, et aucun test ne + * pouvait le voir. + * + * Le plugin est monte derriere le vrai ThingManager de libnymea-core - c'est + * lui qui construit les Thing et les ThingSetupInfo, et lui qui appelle + * postSetupThing() - et derriere un HardwareManager double. Aucun acces reseau + * n'a lieu : le moniteur double n'est jamais joignable, donc le plugin ne tente + * jamais de connexion Modbus. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "integrationpluginsunspec.h" + +/* -------------------------------------------------------------------------- + * Doublures du materiel + * + * Le moniteur ne connait aucune adresse et n'est jamais joignable : c'est l'etat + * dans lequel se trouve une connexion dont l'appareil n'a pas encore ete vu sur + * le reseau. Le plugin doit y repondre, pas s'y bloquer. + * -------------------------------------------------------------------------- */ + +class FakeNetworkDeviceMonitor : public NetworkDeviceMonitor +{ +public: + explicit FakeNetworkDeviceMonitor(QObject *parent = nullptr) : NetworkDeviceMonitor(parent) {} + + MacAddress macAddress() const override { return MacAddress(); } + QString hostName() const override { return QString(); } + QHostAddress address() const override { return QHostAddress(); } + NetworkDeviceInfo::MonitorMode monitorMode() const override { return NetworkDeviceInfo::MonitorModeIp; } + NetworkDeviceInfo networkDeviceInfo() const override { return NetworkDeviceInfo(); } + bool reachable() const override { return false; } + QDateTime lastSeen() const override { return QDateTime(); } + uint pingRetries() const override { return 0; } + void setPingRetries(uint) override {} +}; + +class FakeNetworkDeviceDiscovery : public NetworkDeviceDiscovery +{ +public: + explicit FakeNetworkDeviceDiscovery(QObject *parent = nullptr) : NetworkDeviceDiscovery(parent) {} + + /* registerMonitor() n'est appele que par la branche "connexion" de + * setupThing(). Compter les appels dit donc si le plugin a bien pris cette + * branche, et pas seulement s'il a fini par repondre quelque chose. */ + int registerMonitorCount = 0; + int unregisterMonitorCount = 0; + + NetworkDeviceDiscoveryReply *discover() override { return nullptr; } + bool running() const override { return false; } + + NetworkDeviceMonitor *registerMonitor(Thing *) override + { + registerMonitorCount++; + return new FakeNetworkDeviceMonitor(this); + } + + void unregisterMonitor(NetworkDeviceMonitor *monitor) override + { + unregisterMonitorCount++; + delete monitor; + } + + PingReply *ping(const QHostAddress &, uint) override { return nullptr; } + PingReply *ping(const QString &, uint) override { return nullptr; } + MacAddressDatabaseReply *lookupMacAddress(const QString &) override { return nullptr; } + MacAddressDatabaseReply *lookupMacAddress(const MacAddress &) override { return nullptr; } + bool sendArpRequest(const QHostAddress &) override { return false; } + NetworkDeviceInfos cache() const override { return NetworkDeviceInfos(); } + + bool available() const override { return true; } + bool enabled() const override { return true; } + +protected: + void setEnabled(bool) override {} +}; + +class FakePluginTimer : public PluginTimer +{ +public: + explicit FakePluginTimer(int seconds, QObject *parent = nullptr) + : PluginTimer(parent), m_interval(seconds) {} + + int interval() const override { return m_interval; } + int currentTick() const override { return 0; } + bool running() const override { return m_running; } + + void reset() override {} + void start() override { m_running = true; } + void stop() override { m_running = false; } + void pause() override {} + void resume() override {} + +private: + int m_interval = 0; + bool m_running = false; +}; + +class FakePluginTimerManager : public PluginTimerManager +{ +public: + explicit FakePluginTimerManager(QObject *parent = nullptr) : PluginTimerManager(parent) {} + + /* Le minuteur d'interrogation est arme par postSetupThing(). Le compteur dit + * donc si postSetupThing() a bien traite la connexion. */ + int registerTimerCount = 0; + + PluginTimer *registerTimer(int seconds) override + { + registerTimerCount++; + return new FakePluginTimer(seconds, this); + } + + void unregisterTimer(PluginTimer *timer) override { delete timer; } + + bool available() const override { return true; } + bool enabled() const override { return true; } + +protected: + void setEnabled(bool) override {} +}; + +class FakeHardwareManager : public HardwareManager +{ +public: + explicit FakeHardwareManager(QObject *parent = nullptr) + : HardwareManager(parent) + , m_pluginTimerManager(new FakePluginTimerManager(this)) + , m_networkDeviceDiscovery(new FakeNetworkDeviceDiscovery(this)) + {} + + Radio433 *radio433() override { return nullptr; } + PluginTimerManager *pluginTimerManager() override { return m_pluginTimerManager; } + NetworkAccessManager *networkManager() override { return nullptr; } + UpnpDiscovery *upnpDiscovery() override { return nullptr; } + PlatformZeroConfController *zeroConfController() override { return nullptr; } + BluetoothLowEnergyManager *bluetoothLowEnergyManager() override { return nullptr; } + MqttProvider *mqttProvider() override { return nullptr; } + I2CManager *i2cManager() override { return nullptr; } + ZigbeeHardwareResource *zigbeeResource() override { return nullptr; } + ZWaveHardwareResource *zwaveResource() override { return nullptr; } + ModbusRtuHardwareResource *modbusRtuResource() override { return nullptr; } + NetworkDeviceDiscovery *networkDeviceDiscovery() override { return m_networkDeviceDiscovery; } + + FakePluginTimerManager *timers() const { return m_pluginTimerManager; } + FakeNetworkDeviceDiscovery *monitors() const { return m_networkDeviceDiscovery; } + +private: + FakePluginTimerManager *m_pluginTimerManager = nullptr; + FakeNetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr; +}; + +/* setMetaData() est protege : le ThingManager le renseigne normalement depuis le + * QPluginLoader. Ici le plugin est instancie en dur, donc le test lui passe la + * meme metadonnee, lue dans le meme JSON. */ +class TestableSunSpecPlugin : public IntegrationPluginSunSpec +{ +public: + using IntegrationPlugin::setMetaData; +}; + +/* -------------------------------------------------------------------------- */ + +class SunSpecPluginTest : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + + void test_F1_stored_fronius_connection_completes_its_setup(); + void test_F1_stored_fronius_connection_is_removed_cleanly(); + void test_F1_added_fronius_connection_is_answered(); + void test_F1_added_fronius_connection_answers_like_the_generic_vendor(); + +private: + QTemporaryDir m_pluginSearchDir; + FakeHardwareManager *m_hardwareManager = nullptr; + ThingManagerImplementation *m_thingManager = nullptr; + + ThingSetupInfo *addConnection(const ThingClassId &thingClassId, const QString &name); +}; + +/* Thing deja configure, ecrit dans les reglages avant le demarrage du + * ThingManager. C'est le chemin emprunte a chaque demarrage de nymead pour une + * connexion deja appairee, et le seul qui aille jusqu'au bout du setup sans + * appareil joignable. */ +static const ThingId StoredFroniusThingId = ThingId("4e9b1c2d-6f31-4a58-91a7-2b8c0d5e7f14"); + +void SunSpecPluginTest::initTestCase() +{ + /* "nymea-test" deporte les reglages dans /tmp, hors de la configuration + * reelle de l'utilisateur. */ + QCoreApplication::setOrganizationName("nymea-test"); + + NymeaSettings thingSettings(NymeaSettings::SettingsRoleThings); + thingSettings.clear(); + NymeaSettings pluginSettings(NymeaSettings::SettingsRolePlugins); + pluginSettings.clear(); + + /* Repertoire de recherche vide : le seul plugin en jeu est celui que le test + * enregistre lui-meme, pas ceux installes sur la machine. */ + QVERIFY(m_pluginSearchDir.isValid()); + qputenv("NYMEA_PLUGINS_PATH", m_pluginSearchDir.path().toUtf8()); + + QFile jsonFile(PLUGIN_JSON_PATH); + QVERIFY2(jsonFile.open(QIODevice::ReadOnly), qPrintable(QString("Cannot open %1").arg(PLUGIN_JSON_PATH))); + QJsonParseError error; + const QJsonDocument document = QJsonDocument::fromJson(jsonFile.readAll(), &error); + QVERIFY2(error.error == QJsonParseError::NoError, qPrintable(error.errorString())); + + const PluginMetadata metadata(document.object()); + QVERIFY2(metadata.isValid(), "metadonnee du plugin invalide"); + + NymeaSettings settings(NymeaSettings::SettingsRoleThings); + settings.beginGroup("ThingConfig"); + settings.beginGroup(StoredFroniusThingId.toString()); + settings.setValue("thingName", "Fronius"); + settings.setValue("pluginid", metadata.pluginId().toString()); + settings.setValue("thingClassId", froniusConnectionThingClassId.toString()); + /* Le ThingManager est monte sans LogEngine - son en-tete ne fait pas partie + * de l'API publique de nymea-core - donc le thing ne doit journaliser aucun + * state, sans quoi le ThingManager enregistrerait un logger sur un moteur + * absent. Ces trois cles ecrasent les valeurs suggerees par la thingClass. */ + settings.setValue("loggedStateTypeIds", QStringList()); + settings.setValue("loggedEventTypeIds", QStringList()); + settings.setValue("loggedActionTypeIds", QStringList()); + settings.beginGroup("Params"); + settings.setValue(froniusConnectionThingAddressParamTypeId.toString(), "192.0.2.1"); + settings.setValue(froniusConnectionThingPortParamTypeId.toString(), 502); + settings.setValue(froniusConnectionThingSlaveIdParamTypeId.toString(), 1); + settings.endGroup(); + settings.endGroup(); + settings.endGroup(); + settings.sync(); + + m_hardwareManager = new FakeHardwareManager(this); + m_thingManager = new ThingManagerImplementation(m_hardwareManager, nullptr, QLocale(), this); + + TestableSunSpecPlugin *plugin = new TestableSunSpecPlugin(); + plugin->setMetaData(metadata); + m_thingManager->registerStaticPlugin(plugin); + + QVERIFY2(!m_thingManager->findThingClass(froniusConnectionThingClassId).id().isNull(), + "la thingClass froniusConnection n'est pas connue du ThingManager"); +} + +void SunSpecPluginTest::cleanupTestCase() +{ + delete m_thingManager; + m_thingManager = nullptr; +} + +ThingSetupInfo *SunSpecPluginTest::addConnection(const ThingClassId &thingClassId, const QString &name) +{ + const ThingClass thingClass = m_thingManager->findThingClass(thingClassId); + + ParamList params; + params.append(Param(thingClass.paramTypes().findByName("address").id(), QString("192.0.2.1"))); + + return m_thingManager->addConfiguredThing(thingClassId, params, name); +} + +/* Le chemin du demarrage de nymead : la connexion est deja dans les reglages, + * le setup n'est pas un setup initial et doit aboutir sans appareil joignable. + * C'est le seul cas ou l'on peut verifier que le succes est bien annonce. + * + * Avant correction, setupThing() ne reconnait pas froniusConnection, ne finit + * jamais, et le thing reste en ThingSetupStatusInProgress pour toujours. */ +void SunSpecPluginTest::test_F1_stored_fronius_connection_completes_its_setup() +{ + Thing *thing = nullptr; + QTRY_VERIFY_WITH_TIMEOUT((thing = m_thingManager->findConfiguredThing(StoredFroniusThingId)) != nullptr, 2000); + QCOMPARE(thing->thingClassId(), froniusConnectionThingClassId); + + QTRY_COMPARE_WITH_TIMEOUT(thing->setupStatus(), Thing::ThingSetupStatusComplete, 2000); + QCOMPARE(thing->setupError(), Thing::ThingErrorNoError); + + /* La branche connexion de setupThing() a bien ete prise... */ + QCOMPARE(m_hardwareManager->monitors()->registerMonitorCount, 1); + /* ...et postSetupThing() a arme le minuteur d'interrogation. */ + QCOMPARE(m_hardwareManager->timers()->registerTimerCount, 1); +} + +/* thingRemoved() est indexe sur m_sunSpecConnections, pas sur la thingClass : il + * n'a donc rien de specifique a faire pour Fronius. Verifie plutot que suppose. */ +void SunSpecPluginTest::test_F1_stored_fronius_connection_is_removed_cleanly() +{ + QVERIFY(m_thingManager->findConfiguredThing(StoredFroniusThingId)); + + const int unregisteredBefore = m_hardwareManager->monitors()->unregisterMonitorCount; + + QCOMPARE(m_thingManager->removeConfiguredThing(StoredFroniusThingId), Thing::ThingErrorNoError); + + QVERIFY(!m_thingManager->findConfiguredThing(StoredFroniusThingId)); + QCOMPARE(m_hardwareManager->monitors()->unregisterMonitorCount, unregisteredBefore + 1); +} + +/* Le chemin de l'appairage. Sans adresse connue, un setup initial se termine en + * ThingErrorHardwareFailure : c'est un refus explicite, pas un blocage. Avant + * correction, aucune reponse n'arrive jamais. */ +void SunSpecPluginTest::test_F1_added_fronius_connection_is_answered() +{ + const int monitorsBefore = m_hardwareManager->monitors()->registerMonitorCount; + + ThingSetupInfo *info = addConnection(froniusConnectionThingClassId, "Fronius appaire"); + QVERIFY(info); + + if (!info->isFinished()) { + QSignalSpy finished(info, &ThingSetupInfo::finished); + QVERIFY2(finished.wait(2000), "setupThing() n'a jamais appele info->finish()"); + } + + QCOMPARE(m_hardwareManager->monitors()->registerMonitorCount, monitorsBefore + 1); + QCOMPARE(info->status(), Thing::ThingErrorHardwareFailure); + QVERIFY(!info->displayMessage().isEmpty()); +} + +/* Le vendor generique passe par exactement le meme chemin. Si les deux reponses + * divergent, c'est que froniusConnection a ete branche autrement, pas comme les + * vendors deja fonctionnels. */ +void SunSpecPluginTest::test_F1_added_fronius_connection_answers_like_the_generic_vendor() +{ + ThingSetupInfo *generic = addConnection(sunspecConnectionThingClassId, "SunSpec appaire"); + QVERIFY(generic); + if (!generic->isFinished()) { + QSignalSpy finished(generic, &ThingSetupInfo::finished); + QVERIFY2(finished.wait(2000), "setupThing() n'a jamais appele info->finish() pour sunspecConnection"); + } + + ThingSetupInfo *fronius = addConnection(froniusConnectionThingClassId, "Fronius appaire 2"); + QVERIFY(fronius); + if (!fronius->isFinished()) { + QSignalSpy finished(fronius, &ThingSetupInfo::finished); + QVERIFY2(finished.wait(2000), "setupThing() n'a jamais appele info->finish() pour froniusConnection"); + } + + QCOMPARE(fronius->status(), generic->status()); + QCOMPARE(fronius->displayMessage(), generic->displayMessage()); +} + +QTEST_MAIN(SunSpecPluginTest) +#include "tst_sunspecplugin.moc" diff --git a/sunspec/tests/tests.pro b/sunspec/tests/tests.pro index a8981ef..c944294 100644 --- a/sunspec/tests/tests.pro +++ b/sunspec/tests/tests.pro @@ -1,30 +1,10 @@ -include(../../sunspec.pri) +TEMPLATE = subdirs -QT += testlib -QT -= gui -CONFIG += console testcase -CONFIG -= app_bundle -TEMPLATE = app -TARGET = tst_sunspec +# logic : logique pure du plugin, sans nymea. Ne demande que libnymea-sunspec. +# plugin : instancie reellement IntegrationPluginSunSpec derriere un +# ThingManager et un HardwareManager doubles. Demande en plus +# libnymea1-dev et libnymea-core (pkg-config nymea, nymea-core). -# Les tests pilotent la logique pure du plugin. Ils ne chargent pas le plugin -# nymea lui-meme : cela demanderait un ThingManager, un PluginTimer et un -# NetworkDeviceDiscovery, c'est-a-dire un nymead complet. Ce qui est teste ici, -# c'est ce que le plugin *decide* ; ce qu'il emet reellement sur le bus releve -# du banc (voir "Non verifiable par les tests unitaires" dans -# specs/fronius-sunspec.md). - -INCLUDEPATH += .. - -# Chemin du JSON de definition du plugin, pour les criteres qui portent sur les -# thingClasses et leurs plages de valeurs (AC-1, AC-7, AC-8, AC-10). -DEFINES += PLUGIN_JSON_PATH=\\\"$$PWD/../integrationpluginsunspec.json\\\" - -SOURCES += \ - tst_sunspec.cpp \ - ../sunspecstoragelogic.cpp \ - ../froniusrules.cpp - -HEADERS += \ - ../sunspecstoragelogic.h \ - ../froniusrules.h +SUBDIRS += \ + logic \ + plugin