Skip to content

Commit 0aedcab

Browse files
committed
Use buildExpression command
1 parent 5bd8e85 commit 0aedcab

17 files changed

Lines changed: 344 additions & 175 deletions

adapters/json-rpc-spec.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ Returns the schema for register expressions — what fields make up a register a
292292
"objectType": {
293293
"type": "string",
294294
"title": "Object type",
295-
"enum": ["coil", "discrete-input", "input-register", "holding-register"],
295+
"enum": ["coil", "discrete input", "input register", "holding register"],
296296
"x-enumLabels": ["Coil", "Discrete Input", "Input Register", "Holding Register"]
297297
},
298298
"address": {
@@ -341,12 +341,12 @@ Parses a register expression into structured fields and returns a human-readable
341341
{
342342
"valid": true,
343343
"fields": {
344-
"objectType": "holding-register",
344+
"objectType": "holding register",
345345
"address": 0,
346346
"deviceId": 1,
347347
"dataType": "16b"
348348
},
349-
"description": "Holding register 0, device 1, unsigned 16-bit"
349+
"description": "holding register, 0, unsigned 16-bit, device id 1"
350350
}
351351
```
352352

@@ -404,6 +404,13 @@ Validates a single register expression string without starting polling. Used for
404404

405405
---
406406

407+
### `adapter.buildExpression`
408+
409+
Constructs a register expression string from its component parts. The core calls this after the user fills in the register address form and selects a data type and device, so expression syntax stays entirely within the adapter.
410+
411+
**Params:**
412+
---
413+
407414
### `adapter.getStatus`
408415

409416
Returns the current poll activity state.

src/ProtocolAdapter/adapterclient.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,33 @@ void AdapterClient::validateRegister(const QString& expression)
120120
_pProcess->sendRequest("adapter.validateRegister", params);
121121
}
122122

123+
/*!
124+
* \brief Send an adapter.buildExpression request to construct a register expression string.
125+
* \param addressFields Address field values as returned by the register schema form.
126+
* \param dataType Data type identifier; omitted from params when empty.
127+
* \param deviceId Device identifier; omitted from params when zero.
128+
*/
129+
void AdapterClient::buildExpression(const QJsonObject& addressFields, const QString& dataType, deviceId_t deviceId)
130+
{
131+
if (_state != State::AWAITING_CONFIG && _state != State::ACTIVE)
132+
{
133+
qCWarning(scopeComm) << "AdapterClient: buildExpression called in unexpected state" << static_cast<int>(_state);
134+
return;
135+
}
136+
137+
QJsonObject params;
138+
params["fields"] = addressFields;
139+
if (!dataType.isEmpty())
140+
{
141+
params["dataType"] = dataType;
142+
}
143+
if (deviceId != 0)
144+
{
145+
params["deviceId"] = static_cast<qint64>(deviceId);
146+
}
147+
_pProcess->sendRequest("adapter.buildExpression", params);
148+
}
149+
123150
void AdapterClient::stopSession()
124151
{
125152
if (_state == State::IDLE || _state == State::STOPPING)
@@ -310,6 +337,10 @@ void AdapterClient::handleLifecycleResponse(const QString& method, const QJsonOb
310337
{
311338
emit validateRegisterResult(result["valid"].toBool(), result["error"].toString());
312339
}
340+
else if (method == "adapter.buildExpression" && (_state == State::AWAITING_CONFIG || _state == State::ACTIVE))
341+
{
342+
emit buildExpressionResult(result["expression"].toString());
343+
}
313344
else
314345
{
315346
qCWarning(scopeComm) << "AdapterClient: unexpected response for" << method << "in state"

src/ProtocolAdapter/adapterclient.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define ADAPTERCLIENT_H
33

44
#include "ProtocolAdapter/adapterprocess.h"
5+
#include "models/device.h"
56
#include "util/result.h"
67

78
#include <QJsonObject>
@@ -106,6 +107,18 @@ class AdapterClient : public QObject
106107
*/
107108
void validateRegister(const QString& expression);
108109

110+
/*!
111+
* \brief Send an adapter.buildExpression request to construct a register expression string.
112+
*
113+
* Can be called in AWAITING_CONFIG or ACTIVE state.
114+
* Emits buildExpressionResult() when the adapter responds.
115+
*
116+
* \param addressFields Address field values as returned by the register schema form (e.g. objectType, address).
117+
* \param dataType Data type identifier (e.g. "16b"). Omitted from params when empty; adapter uses its default.
118+
* \param deviceId Device identifier. Omitted from params when zero; adapter uses its default.
119+
*/
120+
void buildExpression(const QJsonObject& addressFields, const QString& dataType, deviceId_t deviceId);
121+
109122
signals:
110123
/*!
111124
* \brief Emitted when the adapter has been initialized, described, configured, and started.
@@ -171,6 +184,12 @@ class AdapterClient : public QObject
171184
*/
172185
void validateRegisterResult(bool valid, QString error);
173186

187+
/*!
188+
* \brief Emitted when an adapter.buildExpression response has been received.
189+
* \param expression The constructed register expression string (e.g. \c ${h0:f32b}).
190+
*/
191+
void buildExpressionResult(QString expression);
192+
174193
protected:
175194
enum class State
176195
{

src/communication/modbuspoll.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ ModbusPoll::ModbusPoll(SettingsModel* pSettingsModel, QObject* parent) : QObject
2424
connect(_pAdapterClient, &AdapterClient::readDataResult, this, &ModbusPoll::onReadDataResult);
2525
connect(_pAdapterClient, &AdapterClient::describeResult, this, &ModbusPoll::onDescribeResult);
2626
connect(_pAdapterClient, &AdapterClient::registerSchemaResult, this, &ModbusPoll::onRegisterSchemaResult);
27+
connect(_pAdapterClient, &AdapterClient::buildExpressionResult, this, &ModbusPoll::buildExpressionResult);
2728
connect(_pAdapterClient, &AdapterClient::sessionError, this, [this](QString message) {
2829
qCWarning(scopeComm) << "AdapterClient error:" << message;
2930
_bPollActive = false;
@@ -165,6 +166,17 @@ void ModbusPoll::onAdapterDiagnostic(const QString& level, const QString& messag
165166
}
166167
}
167168

169+
/*!
170+
* \brief Request the adapter to construct a register expression from its component parts.
171+
* \param addressFields Address field values from the register schema form.
172+
* \param dataType Data type identifier; empty string uses the adapter default.
173+
* \param deviceId Device identifier; 0 uses the adapter default.
174+
*/
175+
void ModbusPoll::buildExpression(const QJsonObject& addressFields, const QString& dataType, deviceId_t deviceId)
176+
{
177+
_pAdapterClient->buildExpression(addressFields, dataType, deviceId);
178+
}
179+
168180
QStringList ModbusPoll::buildRegisterExpressions(const QList<DataPoint>& registerList)
169181
{
170182
QStringList expressions;

src/communication/modbuspoll.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,26 @@ class ModbusPoll : public QObject
2929

3030
void onAdapterDiagnostic(const QString& level, const QString& message);
3131

32+
/*!
33+
* \brief Request the adapter to construct a register expression string from its component parts.
34+
*
35+
* Delegates to AdapterClient::buildExpression(). Emits buildExpressionResult() on response.
36+
*
37+
* \param addressFields Address field values as returned by the register schema form.
38+
* \param dataType Data type identifier (e.g. "16b"). Pass empty string to use the adapter default.
39+
* \param deviceId Device identifier. Pass 0 to use the adapter default.
40+
*/
41+
virtual void buildExpression(const QJsonObject& addressFields, const QString& dataType, deviceId_t deviceId);
42+
3243
signals:
3344
void registerDataReady(ResultDoubleList registers);
3445

46+
/*!
47+
* \brief Emitted when an adapter.buildExpression response has been received.
48+
* \param expression The constructed register expression string (e.g. \c ${h0:f32b}).
49+
*/
50+
void buildExpressionResult(QString expression);
51+
3552
private slots:
3653
void triggerRegisterRead();
3754
void onReadDataResult(ResultDoubleList results);

src/dialogs/addregisterwidget.cpp

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
#include "addregisterwidget.h"
22
#include "ui_addregisterwidget.h"
33

4+
#include "communication/modbuspoll.h"
45
#include "customwidgets/schemaformwidget.h"
56
#include "models/adapterdata.h"
67
#include "models/device.h"
78
#include "models/settingsmodel.h"
8-
#include "util/expressiongenerator.h"
99

1010
#include <QJsonArray>
1111
#include <QVBoxLayout>
@@ -14,13 +14,18 @@
1414
* \brief Constructs the widget and populates it from the adapter's register schema.
1515
* \param pSettingsModel Pointer to the application settings model.
1616
* \param adapterId Identifier of the adapter whose register schema to use.
17+
* \param pModbusPoll Pointer to the Modbus poll instance used to build expression strings.
1718
* \param parent Optional parent widget.
1819
*/
19-
AddRegisterWidget::AddRegisterWidget(SettingsModel* pSettingsModel, const QString& adapterId, QWidget* parent)
20+
AddRegisterWidget::AddRegisterWidget(SettingsModel* pSettingsModel,
21+
const QString& adapterId,
22+
ModbusPoll* pModbusPoll,
23+
QWidget* parent)
2024
: QWidget(parent),
2125
_pUi(new Ui::AddRegisterWidget),
2226
_pAddressForm(new SchemaFormWidget(this)),
23-
_pSettingsModel(pSettingsModel)
27+
_pSettingsModel(pSettingsModel),
28+
_pModbusPoll(pModbusPoll)
2429
{
2530
_pUi->setupUi(this);
2631

@@ -72,6 +77,7 @@ AddRegisterWidget::AddRegisterWidget(SettingsModel* pSettingsModel, const QStrin
7277
}
7378

7479
connect(_pUi->btnAdd, &QPushButton::clicked, this, &AddRegisterWidget::handleResultAccept);
80+
connect(_pModbusPoll, &ModbusPoll::buildExpressionResult, this, &AddRegisterWidget::onBuildExpressionResult);
7581

7682
_axisGroup.setExclusive(true);
7783
_axisGroup.addButton(_pUi->radioPrimary);
@@ -87,28 +93,38 @@ AddRegisterWidget::~AddRegisterWidget()
8793

8894
void AddRegisterWidget::handleResultAccept()
8995
{
90-
const QString expression = generateExpression();
91-
if (expression.isEmpty())
96+
if (_pUi->cmbDevice->count() == 0)
9297
{
9398
return;
9499
}
95100

96-
GraphData graphData;
101+
collectPendingGraphData();
97102

98-
graphData.setLabel(_pUi->lineName->text());
103+
const QJsonObject addressValues = _pAddressForm->values();
104+
const QString typeId = _pUi->cmbType->currentData().toString();
99105

100-
if (_pUi->radioSecondary->isChecked())
106+
deviceId_t deviceId = Device::cFirstDeviceId;
107+
const QVariant devData = _pUi->cmbDevice->currentData();
108+
if (devData.canConvert<deviceId_t>())
101109
{
102-
graphData.setValueAxis(GraphData::VALUE_AXIS_SECONDARY);
110+
deviceId = devData.value<deviceId_t>();
103111
}
104-
else
112+
113+
_pUi->btnAdd->setEnabled(false);
114+
_pModbusPoll->buildExpression(addressValues, typeId, deviceId);
115+
}
116+
117+
void AddRegisterWidget::onBuildExpressionResult(const QString& expression)
118+
{
119+
_pUi->btnAdd->setEnabled(true);
120+
121+
if (expression.isEmpty())
105122
{
106-
graphData.setValueAxis(GraphData::VALUE_AXIS_PRIMARY);
123+
return;
107124
}
108125

109-
graphData.setExpression(expression);
110-
111-
emit graphDataConfigured(graphData);
126+
_pendingGraphData.setExpression(expression);
127+
emit graphDataConfigured(_pendingGraphData);
112128

113129
resetFields();
114130
}
@@ -122,29 +138,23 @@ void AddRegisterWidget::resetFields()
122138
_pAddressForm->setSchema(_addressSchema, QJsonObject());
123139
}
124140

125-
QString AddRegisterWidget::generateExpression()
141+
/*!
142+
* \brief Captures the current non-expression fields into \a _pendingGraphData.
143+
*
144+
* Called just before the async adapter.buildExpression request is sent, so that
145+
* the label and value axis are snapshotted at click time.
146+
*/
147+
void AddRegisterWidget::collectPendingGraphData()
126148
{
127-
if (_pUi->cmbDevice->count() == 0)
128-
{
129-
return QString();
130-
}
149+
_pendingGraphData = GraphData();
150+
_pendingGraphData.setLabel(_pUi->lineName->text());
131151

132-
const QJsonObject addressValues = _pAddressForm->values();
133-
if (!addressValues.contains("objectType") || !addressValues.contains("address"))
152+
if (_pUi->radioSecondary->isChecked())
134153
{
135-
return QString();
154+
_pendingGraphData.setValueAxis(GraphData::VALUE_AXIS_SECONDARY);
136155
}
137-
138-
const QString objectType = addressValues["objectType"].toString();
139-
const int address = addressValues["address"].toInt();
140-
const QString typeId = _pUi->cmbType->currentData().toString();
141-
142-
deviceId_t deviceId = Device::cFirstDeviceId;
143-
const QVariant devData = _pUi->cmbDevice->currentData();
144-
if (devData.canConvert<deviceId_t>())
156+
else
145157
{
146-
deviceId = devData.value<deviceId_t>();
158+
_pendingGraphData.setValueAxis(GraphData::VALUE_AXIS_PRIMARY);
147159
}
148-
149-
return ExpressionGenerator::constructRegisterString(objectType, address, typeId, deviceId);
150160
}

src/dialogs/addregisterwidget.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
#ifndef ADDREGISTERWIDGET_H
22
#define ADDREGISTERWIDGET_H
33

4+
#include "models/device.h"
45
#include "models/graphdata.h"
56

67
#include <QButtonGroup>
78
#include <QJsonObject>
89
#include <QWidget>
910

11+
class ModbusPoll;
1012
class SchemaFormWidget;
1113
class SettingsModel;
1214

@@ -21,27 +23,35 @@ class AddRegisterWidget : public QWidget
2123
friend class TestAddRegisterWidget;
2224

2325
public:
24-
explicit AddRegisterWidget(SettingsModel* pSettingsModel, const QString& adapterId, QWidget* parent = nullptr);
26+
explicit AddRegisterWidget(SettingsModel* pSettingsModel,
27+
const QString& adapterId,
28+
ModbusPoll* pModbusPoll,
29+
QWidget* parent = nullptr);
2530
~AddRegisterWidget();
2631

2732
signals:
2833
void graphDataConfigured(GraphData graphData);
2934

3035
private slots:
3136
void handleResultAccept();
37+
void onBuildExpressionResult(const QString& expression);
3238

3339
private:
3440
void resetFields();
35-
QString generateExpression();
41+
void collectPendingGraphData();
3642

3743
Ui::AddRegisterWidget* _pUi;
3844
SchemaFormWidget* _pAddressForm;
3945
QJsonObject _addressSchema;
4046
int _defaultTypeIndex{ 0 };
4147

4248
SettingsModel* _pSettingsModel;
49+
ModbusPoll* _pModbusPoll;
4350

4451
QButtonGroup _axisGroup;
52+
53+
/* Temporary storage while waiting for buildExpression response */
54+
GraphData _pendingGraphData;
4555
};
4656

4757
#endif // ADDREGISTERWIDGET_H

src/dialogs/mainwindow.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ void MainWindow::showRegisterDialog()
397397
_pGuiModel->setGuiState(GuiState::INIT);
398398
}
399399

400-
RegisterDialog registerDialog(_pGraphDataModel, _pSettingsModel, this);
400+
RegisterDialog registerDialog(_pGraphDataModel, _pSettingsModel, _pModbusPoll, this);
401401
registerDialog.exec();
402402
}
403403

0 commit comments

Comments
 (0)