Skip to content

Commit a4147b1

Browse files
committed
Move adapter lifecycle management from ModbusPoll to AdapterManager
Introduces AdapterManager in src/ProtocolAdapter/ to own AdapterProcess and AdapterClient and handle the full adapter lifecycle: initialization, describe, schema retrieval, configuration, diagnostics, and expression building. ModbusPoll is reduced to polling-only responsibilities: timer management, read requests, and emitting registerDataReady. The diagnostic tests move from tst_modbuspoll to a new tst_adaptermanager in tests/ProtocolAdapter/. https://claude.ai/code/session_01AtVwF9u3BD3kqhLUfubEaX
1 parent 0aedcab commit a4147b1

8 files changed

Lines changed: 271 additions & 105 deletions

File tree

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
2+
#include "ProtocolAdapter/adaptermanager.h"
3+
4+
#include "ProtocolAdapter/adapterclient.h"
5+
#include "ProtocolAdapter/adapterprocess.h"
6+
#include "models/settingsmodel.h"
7+
#include "util/scopelogging.h"
8+
9+
#include <QCoreApplication>
10+
11+
AdapterManager::AdapterManager(SettingsModel* pSettingsModel, QObject* parent) : QObject(parent), _pSettingsModel(pSettingsModel)
12+
{
13+
_pAdapterProcess = new AdapterProcess(this);
14+
_pAdapterClient = new AdapterClient(_pAdapterProcess, this);
15+
16+
connect(_pAdapterClient, &AdapterClient::sessionStarted, this, &AdapterManager::sessionStarted);
17+
connect(_pAdapterClient, &AdapterClient::readDataResult, this, &AdapterManager::readDataResult);
18+
connect(_pAdapterClient, &AdapterClient::buildExpressionResult, this, &AdapterManager::buildExpressionResult);
19+
connect(_pAdapterClient, &AdapterClient::sessionStopped, this, &AdapterManager::sessionStopped);
20+
connect(_pAdapterClient, &AdapterClient::sessionError, this, &AdapterManager::sessionError);
21+
connect(_pAdapterClient, &AdapterClient::describeResult, this, &AdapterManager::onDescribeResult);
22+
connect(_pAdapterClient, &AdapterClient::registerSchemaResult, this, &AdapterManager::onRegisterSchemaResult);
23+
connect(_pAdapterClient, &AdapterClient::diagnosticReceived, this, &AdapterManager::onAdapterDiagnostic);
24+
}
25+
26+
/*! \brief Launch the adapter subprocess and start the initialization handshake.
27+
*
28+
* Resolves the adapter binary relative to the running executable so the path
29+
* is correct in the build tree, AppImage, and installed layouts alike.
30+
*/
31+
void AdapterManager::initAdapter()
32+
{
33+
const QString adapterPath = QCoreApplication::applicationDirPath() + "/modbusadapter";
34+
_pAdapterClient->prepareAdapter(adapterPath);
35+
}
36+
37+
/*! \brief Fetch the effective adapter config from the settings model and start the session.
38+
* \param registerExpressions Register expression strings to pass to the adapter.
39+
*/
40+
void AdapterManager::startSession(const QStringList& registerExpressions)
41+
{
42+
const AdapterData* data = _pSettingsModel->adapterData("modbus");
43+
QJsonObject config = data->effectiveConfig();
44+
_pAdapterClient->provideConfig(config, registerExpressions);
45+
}
46+
47+
/*! \brief Send adapter.shutdown and signal the adapter process to stop. */
48+
void AdapterManager::stopSession()
49+
{
50+
_pAdapterClient->stopSession();
51+
}
52+
53+
/*! \brief Send an adapter.readData request to the active adapter. */
54+
void AdapterManager::requestReadData()
55+
{
56+
_pAdapterClient->requestReadData();
57+
}
58+
59+
/*!
60+
* \brief Request the adapter to construct a register expression from its component parts.
61+
* \param addressFields Address field values from the register schema form.
62+
* \param dataType Data type identifier; empty string uses the adapter default.
63+
* \param deviceId Device identifier; 0 uses the adapter default.
64+
*/
65+
void AdapterManager::buildExpression(const QJsonObject& addressFields, const QString& dataType, deviceId_t deviceId)
66+
{
67+
_pAdapterClient->buildExpression(addressFields, dataType, deviceId);
68+
}
69+
70+
/*! \brief Receive the adapter.describe response, update the settings model, and request the register schema.
71+
* \param description The full describe result object from the adapter.
72+
*/
73+
void AdapterManager::onDescribeResult(const QJsonObject& description)
74+
{
75+
_pSettingsModel->updateAdapterFromDescribe("modbus", description);
76+
_pAdapterClient->requestRegisterSchema();
77+
}
78+
79+
/*! \brief Receive the adapter register schema and forward it to the settings model.
80+
* \param schema The register schema JSON object returned by adapter.registerSchema.
81+
*/
82+
void AdapterManager::onRegisterSchemaResult(const QJsonObject& schema)
83+
{
84+
_pSettingsModel->setAdapterRegisterSchema("modbus", schema);
85+
}
86+
87+
/*!
88+
* \brief Route an adapter.diagnostic notification to the diagnostics log.
89+
*
90+
* Maps the adapter's level string to the appropriate Qt logging severity so
91+
* the message flows through ScopeLogging into DiagnosticModel.
92+
*
93+
* \param level Severity string from the adapter: "debug", "info", "warning", or "error".
94+
* \param message The diagnostic message text.
95+
*/
96+
void AdapterManager::onAdapterDiagnostic(const QString& level, const QString& message)
97+
{
98+
if (level == QStringLiteral("debug"))
99+
{
100+
qCDebug(scopeAdapter) << message;
101+
}
102+
else if (level == QStringLiteral("info"))
103+
{
104+
qCInfo(scopeAdapter) << message;
105+
}
106+
else if (level == QStringLiteral("warning"))
107+
{
108+
qCWarning(scopeAdapter) << message;
109+
}
110+
else if (level == QStringLiteral("error"))
111+
{
112+
qCCritical(scopeAdapter) << message;
113+
}
114+
else
115+
{
116+
qCWarning(scopeAdapter) << "AdapterClient: unknown diagnostic level:" << level << "-" << message;
117+
}
118+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#ifndef ADAPTERMANAGER_H
2+
#define ADAPTERMANAGER_H
3+
4+
#include "models/device.h"
5+
#include "util/result.h"
6+
7+
#include <QJsonObject>
8+
#include <QObject>
9+
#include <QStringList>
10+
11+
class AdapterClient;
12+
class AdapterProcess;
13+
class SettingsModel;
14+
15+
/*!
16+
* \brief Owns the adapter subprocess and drives the full adapter session lifecycle.
17+
*
18+
* Creates and holds AdapterProcess and AdapterClient. Handles initialization,
19+
* describe, schema retrieval, configuration, and session management. Exposes
20+
* only the signals needed to coordinate data polling.
21+
*/
22+
class AdapterManager : public QObject
23+
{
24+
Q_OBJECT
25+
public:
26+
explicit AdapterManager(SettingsModel* pSettingsModel, QObject* parent = nullptr);
27+
28+
/*!
29+
* \brief Launch the adapter subprocess and begin the initialization handshake.
30+
*
31+
* Resolves the adapter binary path relative to the running executable and
32+
* calls AdapterClient::prepareAdapter().
33+
*/
34+
void initAdapter();
35+
36+
/*!
37+
* \brief Provide register expressions to the adapter and start the session.
38+
*
39+
* Fetches the effective adapter configuration from the settings model, then
40+
* calls AdapterClient::provideConfig() to configure and start the adapter.
41+
*
42+
* \param registerExpressions Register expression strings to pass to the adapter.
43+
*/
44+
void startSession(const QStringList& registerExpressions);
45+
46+
/*!
47+
* \brief Send adapter.shutdown and signal the adapter process to stop.
48+
*/
49+
void stopSession();
50+
51+
/*!
52+
* \brief Send an adapter.readData request to the active adapter.
53+
*/
54+
void requestReadData();
55+
56+
/*!
57+
* \brief Request the adapter to construct a register expression from its component parts.
58+
*
59+
* \param addressFields Address field values as returned by the register schema form.
60+
* \param dataType Data type identifier (e.g. "16b"). Pass empty string for the adapter default.
61+
* \param deviceId Device identifier. Pass 0 for the adapter default.
62+
*/
63+
void buildExpression(const QJsonObject& addressFields, const QString& dataType, deviceId_t deviceId);
64+
65+
/*!
66+
* \brief Route an adapter.diagnostic notification to the diagnostics log.
67+
*
68+
* Public for testability. Maps the adapter's level string to the appropriate
69+
* Qt logging severity.
70+
*
71+
* \param level Severity string from the adapter: "debug", "info", "warning", or "error".
72+
* \param message The diagnostic message text.
73+
*/
74+
void onAdapterDiagnostic(const QString& level, const QString& message);
75+
76+
signals:
77+
//! Emitted when the adapter has been initialized, described, configured, and started.
78+
void sessionStarted();
79+
80+
//! Emitted when the adapter process has been intentionally stopped and the client is IDLE.
81+
void sessionStopped();
82+
83+
/*!
84+
* \brief Emitted when an unrecoverable error occurs (process failure, RPC error).
85+
* \param message Human-readable error description.
86+
*/
87+
void sessionError(QString message);
88+
89+
/*!
90+
* \brief Emitted when an adapter.readData response has been received.
91+
* \param results One entry per register, in the same order as the expressions passed to startSession().
92+
*/
93+
void readDataResult(ResultDoubleList results);
94+
95+
/*!
96+
* \brief Emitted when an adapter.buildExpression response has been received.
97+
* \param expression The constructed register expression string (e.g. \c ${h0:f32b}).
98+
*/
99+
void buildExpressionResult(QString expression);
100+
101+
private slots:
102+
void onDescribeResult(const QJsonObject& description);
103+
void onRegisterSchemaResult(const QJsonObject& schema);
104+
105+
private:
106+
SettingsModel* _pSettingsModel;
107+
AdapterProcess* _pAdapterProcess;
108+
AdapterClient* _pAdapterClient;
109+
};
110+
111+
#endif // ADAPTERMANAGER_H

src/communication/modbuspoll.cpp

Lines changed: 15 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
#include "util/formatdatetime.h"
66
#include "util/scopelogging.h"
77

8-
#include <QCoreApplication>
98
#include <QJsonArray>
109

1110
ModbusPoll::ModbusPoll(SettingsModel* pSettingsModel, QObject* parent) : QObject(parent), _bPollActive(false)
@@ -17,21 +16,17 @@ ModbusPoll::ModbusPoll(SettingsModel* pSettingsModel, QObject* parent) : QObject
1716
_pSettingsModel = pSettingsModel;
1817
_lastPollStart = QDateTime::currentMSecsSinceEpoch();
1918

20-
_pAdapterProcess = new AdapterProcess(this);
21-
_pAdapterClient = new AdapterClient(_pAdapterProcess, this);
19+
_pAdapterManager = new AdapterManager(_pSettingsModel, this);
2220

23-
connect(_pAdapterClient, &AdapterClient::sessionStarted, this, &ModbusPoll::triggerRegisterRead);
24-
connect(_pAdapterClient, &AdapterClient::readDataResult, this, &ModbusPoll::onReadDataResult);
25-
connect(_pAdapterClient, &AdapterClient::describeResult, this, &ModbusPoll::onDescribeResult);
26-
connect(_pAdapterClient, &AdapterClient::registerSchemaResult, this, &ModbusPoll::onRegisterSchemaResult);
27-
connect(_pAdapterClient, &AdapterClient::buildExpressionResult, this, &ModbusPoll::buildExpressionResult);
28-
connect(_pAdapterClient, &AdapterClient::sessionError, this, [this](QString message) {
21+
connect(_pAdapterManager, &AdapterManager::sessionStarted, this, &ModbusPoll::triggerRegisterRead);
22+
connect(_pAdapterManager, &AdapterManager::readDataResult, this, &ModbusPoll::onReadDataResult);
23+
connect(_pAdapterManager, &AdapterManager::buildExpressionResult, this, &ModbusPoll::buildExpressionResult);
24+
connect(_pAdapterManager, &AdapterManager::sessionStopped, this, &ModbusPoll::initAdapter);
25+
connect(_pAdapterManager, &AdapterManager::sessionError, this, [this](QString message) {
2926
qCWarning(scopeComm) << "AdapterClient error:" << message;
3027
_bPollActive = false;
31-
disconnect(_pAdapterClient, &AdapterClient::sessionStopped, this, &ModbusPoll::initAdapter);
28+
disconnect(_pAdapterManager, &AdapterManager::sessionStopped, this, &ModbusPoll::initAdapter);
3229
});
33-
connect(_pAdapterClient, &AdapterClient::sessionStopped, this, &ModbusPoll::initAdapter);
34-
connect(_pAdapterClient, &AdapterClient::diagnosticReceived, this, &ModbusPoll::onAdapterDiagnostic);
3530
}
3631

3732
ModbusPoll::~ModbusPoll() = default;
@@ -40,13 +35,11 @@ ModbusPoll::~ModbusPoll() = default;
4035
*
4136
* Resolves the adapter binary relative to the running executable so the path
4237
* is correct in the build tree, AppImage, and installed layouts alike.
43-
* Calls prepareAdapter() on the client, which triggers the adapter.describe
44-
* handshake.
38+
* Delegates to AdapterManager::initAdapter().
4539
*/
4640
void ModbusPoll::initAdapter()
4741
{
48-
const QString adapterPath = QCoreApplication::applicationDirPath() + "/modbusadapter";
49-
_pAdapterClient->prepareAdapter(adapterPath);
42+
_pAdapterManager->initAdapter();
5043
}
5144

5245
void ModbusPoll::startCommunication(QList<DataPoint>& registerList)
@@ -55,19 +48,15 @@ void ModbusPoll::startCommunication(QList<DataPoint>& registerList)
5548
_bPollActive = true;
5649

5750
/* Re-establish auto-restart in case it was disconnected by a prior session error */
58-
disconnect(_pAdapterClient, &AdapterClient::sessionStopped, this, &ModbusPoll::initAdapter);
59-
connect(_pAdapterClient, &AdapterClient::sessionStopped, this, &ModbusPoll::initAdapter);
51+
disconnect(_pAdapterManager, &AdapterManager::sessionStopped, this, &ModbusPoll::initAdapter);
52+
connect(_pAdapterManager, &AdapterManager::sessionStopped, this, &ModbusPoll::initAdapter);
6053

6154
qCInfo(scopeComm) << QString("Start logging: %1").arg(FormatDateTime::currentDateTime());
6255

6356
resetCommunicationStats();
6457

6558
QStringList expressions = buildRegisterExpressions(_registerList);
66-
67-
const AdapterData* data = _pSettingsModel->adapterData("modbus");
68-
QJsonObject config = data->effectiveConfig();
69-
70-
_pAdapterClient->provideConfig(config, expressions);
59+
_pAdapterManager->startSession(expressions);
7160
}
7261

7362
void ModbusPoll::resetCommunicationStats()
@@ -79,7 +68,7 @@ void ModbusPoll::stopCommunication()
7968
{
8069
_bPollActive = false;
8170
_pPollTimer->stop();
82-
_pAdapterClient->stopSession();
71+
_pAdapterManager->stopSession();
8372

8473
qCInfo(scopeComm) << QString("Stop logging: %1").arg(FormatDateTime::currentDateTime());
8574
}
@@ -94,7 +83,7 @@ void ModbusPoll::triggerRegisterRead()
9483
if (_bPollActive)
9584
{
9685
_lastPollStart = QDateTime::currentMSecsSinceEpoch();
97-
_pAdapterClient->requestReadData();
86+
_pAdapterManager->requestReadData();
9887
}
9988
}
10089

@@ -120,52 +109,6 @@ void ModbusPoll::onReadDataResult(ResultDoubleList results)
120109
}
121110
}
122111

123-
void ModbusPoll::onDescribeResult(const QJsonObject& description)
124-
{
125-
_pSettingsModel->updateAdapterFromDescribe("modbus", description);
126-
_pAdapterClient->requestRegisterSchema();
127-
}
128-
129-
/*! \brief Receive the adapter register schema and forward it to the settings model.
130-
* \param schema The register schema JSON object returned by adapter.registerSchema.
131-
*/
132-
void ModbusPoll::onRegisterSchemaResult(const QJsonObject& schema)
133-
{
134-
_pSettingsModel->setAdapterRegisterSchema("modbus", schema);
135-
}
136-
137-
/*! \brief Route an adapter.diagnostic notification to the diagnostics log.
138-
*
139-
* Maps the adapter's level string to the appropriate Qt logging severity so
140-
* the message flows through ScopeLogging into DiagnosticModel.
141-
*
142-
* \param level Severity string from the adapter: "debug", "info", "warning", or "error".
143-
* \param message The diagnostic message text.
144-
*/
145-
void ModbusPoll::onAdapterDiagnostic(const QString& level, const QString& message)
146-
{
147-
if (level == QStringLiteral("debug"))
148-
{
149-
qCDebug(scopeAdapter) << message;
150-
}
151-
else if (level == QStringLiteral("info"))
152-
{
153-
qCInfo(scopeAdapter) << message;
154-
}
155-
else if (level == QStringLiteral("warning"))
156-
{
157-
qCWarning(scopeAdapter) << message;
158-
}
159-
else if (level == QStringLiteral("error"))
160-
{
161-
qCCritical(scopeAdapter) << message;
162-
}
163-
else
164-
{
165-
qCWarning(scopeAdapter) << "AdapterClient: unknown diagnostic level:" << level << "-" << message;
166-
}
167-
}
168-
169112
/*!
170113
* \brief Request the adapter to construct a register expression from its component parts.
171114
* \param addressFields Address field values from the register schema form.
@@ -174,7 +117,7 @@ void ModbusPoll::onAdapterDiagnostic(const QString& level, const QString& messag
174117
*/
175118
void ModbusPoll::buildExpression(const QJsonObject& addressFields, const QString& dataType, deviceId_t deviceId)
176119
{
177-
_pAdapterClient->buildExpression(addressFields, dataType, deviceId);
120+
_pAdapterManager->buildExpression(addressFields, dataType, deviceId);
178121
}
179122

180123
QStringList ModbusPoll::buildRegisterExpressions(const QList<DataPoint>& registerList)

0 commit comments

Comments
 (0)