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