Skip to content

Commit 469a3a1

Browse files
committed
Datapoint info from adapter
1 parent a6172ee commit 469a3a1

23 files changed

Lines changed: 733 additions & 132 deletions

adapters/json-rpc-spec.md

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,133 @@ An empty `registers` array is valid and starts polling with no registers configu
277277

278278
---
279279

280+
### `adapter.registerSchema`
281+
282+
Returns the schema for register expressions — what fields make up a register address, how they should be rendered in the UI, and available data types. Call this after `adapter.describe` to discover how to build the register input UI.
283+
284+
**Params:** `{}` (none required)
285+
286+
**Result:**
287+
```json
288+
{
289+
"addressSchema": {
290+
"type": "object",
291+
"properties": {
292+
"objectType": {
293+
"type": "string",
294+
"title": "Object type",
295+
"enum": ["coil", "discrete-input", "input-register", "holding-register"],
296+
"x-enumLabels": ["Coil", "Discrete Input", "Input Register", "Holding Register"]
297+
},
298+
"address": {
299+
"type": "integer",
300+
"title": "Address",
301+
"minimum": 0,
302+
"maximum": 65535
303+
}
304+
},
305+
"required": ["objectType", "address"]
306+
},
307+
"dataTypes": [
308+
{ "id": "16b", "label": "Unsigned 16-bit" },
309+
{ "id": "s16b", "label": "Signed 16-bit" },
310+
{ "id": "32b", "label": "Unsigned 32-bit" },
311+
{ "id": "s32b", "label": "Signed 32-bit" },
312+
{ "id": "f32b", "label": "32-bit float" }
313+
],
314+
"defaultDataType": "16b"
315+
}
316+
```
317+
318+
| Field | Description |
319+
| --- | --- |
320+
| `addressSchema` | JSON Schema describing the address input fields. The core renders this with `SchemaFormWidget` |
321+
| `dataTypes` | Array of available data types. Each entry has `id` (used in expression strings) and `label` (UI display) |
322+
| `defaultDataType` | The `id` of the type to pre-select in the UI |
323+
324+
The `addressSchema` follows standard JSON Schema conventions. The core application uses it to dynamically generate the address input portion of the register dialog, so it must accurately describe all required fields and their constraints.
325+
326+
---
327+
328+
### `adapter.describeRegister`
329+
330+
Parses a register expression into structured fields and returns a human-readable description. Used by the core to display register details in tables and tooltips without understanding protocol-specific address formats.
331+
332+
**Params:**
333+
```json
334+
{
335+
"expression": "${40001: 16b}"
336+
}
337+
```
338+
339+
**Result (valid):**
340+
```json
341+
{
342+
"valid": true,
343+
"fields": {
344+
"objectType": "holding-register",
345+
"address": 0,
346+
"deviceId": 1,
347+
"dataType": "16b"
348+
},
349+
"description": "Holding register 0, device 1, unsigned 16-bit"
350+
}
351+
```
352+
353+
**Result (invalid):**
354+
```json
355+
{
356+
"valid": false,
357+
"error": "Unknown type 'xyz'"
358+
}
359+
```
360+
361+
| Field | Description |
362+
| --- | --- |
363+
| `valid` | Whether the expression is syntactically and semantically valid |
364+
| `fields` | Structured parsed fields — protocol-specific, but the core treats them as opaque display data |
365+
| `description` | Human-readable description for display in tables, tooltips, and logs |
366+
| `error` | Human-readable error message when `valid` is false |
367+
368+
**Errors:**
369+
- `-32602` — Missing `expression` field
370+
371+
---
372+
373+
### `adapter.validateRegister`
374+
375+
Validates a single register expression string without starting polling. Used for real-time validation feedback in the register input dialog.
376+
377+
**Params:**
378+
```json
379+
{
380+
"expression": "${40001: 16b}"
381+
}
382+
```
383+
384+
**Result (valid):**
385+
```json
386+
{ "valid": true }
387+
```
388+
389+
**Result (invalid):**
390+
```json
391+
{
392+
"valid": false,
393+
"error": "Unknown type 'xyz'"
394+
}
395+
```
396+
397+
| Field | Description |
398+
| --- | --- |
399+
| `valid` | Whether the expression is valid |
400+
| `error` | Human-readable error message when `valid` is false |
401+
402+
**Errors:**
403+
- `-32602` — Missing `expression` field
404+
405+
---
406+
280407
### `adapter.getStatus`
281408

282409
Returns the current poll activity state.

src/ProtocolAdapter/adapterclient.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,46 @@ void AdapterClient::requestStatus()
8080
_pProcess->sendRequest("adapter.getStatus", QJsonObject());
8181
}
8282

83+
void AdapterClient::requestRegisterSchema()
84+
{
85+
if (_state != State::AWAITING_CONFIG)
86+
{
87+
qCWarning(scopeComm) << "AdapterClient: requestRegisterSchema called in unexpected state"
88+
<< static_cast<int>(_state);
89+
return;
90+
}
91+
92+
_pProcess->sendRequest("adapter.registerSchema", QJsonObject());
93+
}
94+
95+
void AdapterClient::describeRegister(const QString& expression)
96+
{
97+
if (_state != State::AWAITING_CONFIG && _state != State::ACTIVE)
98+
{
99+
qCWarning(scopeComm) << "AdapterClient: describeRegister called in unexpected state"
100+
<< static_cast<int>(_state);
101+
return;
102+
}
103+
104+
QJsonObject params;
105+
params["expression"] = expression;
106+
_pProcess->sendRequest("adapter.describeRegister", params);
107+
}
108+
109+
void AdapterClient::validateRegister(const QString& expression)
110+
{
111+
if (_state != State::AWAITING_CONFIG && _state != State::ACTIVE)
112+
{
113+
qCWarning(scopeComm) << "AdapterClient: validateRegister called in unexpected state"
114+
<< static_cast<int>(_state);
115+
return;
116+
}
117+
118+
QJsonObject params;
119+
params["expression"] = expression;
120+
_pProcess->sendRequest("adapter.validateRegister", params);
121+
}
122+
83123
void AdapterClient::stopSession()
84124
{
85125
if (_state == State::IDLE || _state == State::STOPPING)
@@ -258,6 +298,18 @@ void AdapterClient::handleLifecycleResponse(const QString& method, const QJsonOb
258298
_pProcess->stop();
259299
/* sessionStopped is emitted from onProcessFinished once the process exits */
260300
}
301+
else if (method == "adapter.registerSchema" && _state == State::AWAITING_CONFIG)
302+
{
303+
emit registerSchemaResult(result);
304+
}
305+
else if (method == "adapter.describeRegister" && (_state == State::AWAITING_CONFIG || _state == State::ACTIVE))
306+
{
307+
emit describeRegisterResult(result);
308+
}
309+
else if (method == "adapter.validateRegister" && (_state == State::AWAITING_CONFIG || _state == State::ACTIVE))
310+
{
311+
emit validateRegisterResult(result["valid"].toBool(), result["error"].toString());
312+
}
261313
else
262314
{
263315
qCWarning(scopeComm) << "AdapterClient: unexpected response for" << method << "in state"

src/ProtocolAdapter/adapterclient.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,34 @@ class AdapterClient : public QObject
7878
*/
7979
void stopSession();
8080

81+
/*!
82+
* \brief Send an adapter.registerSchema request to discover the register UI schema.
83+
*
84+
* Must only be called after describeResult() has been emitted (i.e., in the
85+
* AWAITING_CONFIG state). Emits registerSchemaResult() when the adapter responds.
86+
*/
87+
void requestRegisterSchema();
88+
89+
/*!
90+
* \brief Send an adapter.describeRegister request to parse a register expression.
91+
*
92+
* Can be called in AWAITING_CONFIG or ACTIVE state.
93+
* Emits describeRegisterResult() when the adapter responds.
94+
*
95+
* \param expression The register expression string to describe.
96+
*/
97+
void describeRegister(const QString& expression);
98+
99+
/*!
100+
* \brief Send an adapter.validateRegister request to validate a register expression.
101+
*
102+
* Can be called in AWAITING_CONFIG or ACTIVE state.
103+
* Emits validateRegisterResult() when the adapter responds.
104+
*
105+
* \param expression The register expression string to validate.
106+
*/
107+
void validateRegister(const QString& expression);
108+
81109
signals:
82110
/*!
83111
* \brief Emitted when the adapter has been initialized, described, configured, and started.
@@ -124,6 +152,25 @@ class AdapterClient : public QObject
124152
*/
125153
void diagnosticReceived(QString level, QString message);
126154

155+
/*!
156+
* \brief Emitted when an adapter.registerSchema response has been received.
157+
* \param schema The full register schema object (addressSchema, dataTypes, defaultDataType).
158+
*/
159+
void registerSchemaResult(QJsonObject schema);
160+
161+
/*!
162+
* \brief Emitted when an adapter.describeRegister response has been received.
163+
* \param result The full result object (valid, fields, description or error).
164+
*/
165+
void describeRegisterResult(QJsonObject result);
166+
167+
/*!
168+
* \brief Emitted when an adapter.validateRegister response has been received.
169+
* \param valid Whether the expression is valid.
170+
* \param error Human-readable error message when valid is false; empty otherwise.
171+
*/
172+
void validateRegisterResult(bool valid, QString error);
173+
127174
protected:
128175
enum class State
129176
{

src/communication/modbuspoll.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11

22
#include "communication/modbuspoll.h"
33

4-
#include "models/device.h"
54
#include "models/settingsmodel.h"
65
#include "util/formatdatetime.h"
7-
#include "util/modbusdatatype.h"
86
#include "util/scopelogging.h"
97

108
#include <QCoreApplication>
@@ -25,6 +23,7 @@ ModbusPoll::ModbusPoll(SettingsModel* pSettingsModel, QObject* parent) : QObject
2523
connect(_pAdapterClient, &AdapterClient::sessionStarted, this, &ModbusPoll::triggerRegisterRead);
2624
connect(_pAdapterClient, &AdapterClient::readDataResult, this, &ModbusPoll::onReadDataResult);
2725
connect(_pAdapterClient, &AdapterClient::describeResult, this, &ModbusPoll::onDescribeResult);
26+
connect(_pAdapterClient, &AdapterClient::registerSchemaResult, this, &ModbusPoll::onRegisterSchemaResult);
2827
connect(_pAdapterClient, &AdapterClient::sessionError, this, [this](QString message) {
2928
qCWarning(scopeComm) << "AdapterClient error:" << message;
3029
_bPollActive = false;
@@ -123,6 +122,12 @@ void ModbusPoll::onReadDataResult(ResultDoubleList results)
123122
void ModbusPoll::onDescribeResult(const QJsonObject& description)
124123
{
125124
_pSettingsModel->updateAdapterFromDescribe("modbus", description);
125+
_pAdapterClient->requestRegisterSchema();
126+
}
127+
128+
void ModbusPoll::onRegisterSchemaResult(const QJsonObject& schema)
129+
{
130+
_pSettingsModel->setAdapterRegisterSchema("modbus", schema);
126131
}
127132

128133
/*! \brief Route an adapter.diagnostic notification to the diagnostics log.

src/communication/modbuspoll.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ private slots:
3636
void triggerRegisterRead();
3737
void onReadDataResult(ResultDoubleList results);
3838
void onDescribeResult(const QJsonObject& description);
39+
void onRegisterSchemaResult(const QJsonObject& schema);
3940

4041
private:
4142
QStringList buildRegisterExpressions(const QList<DataPoint>& registerList);

0 commit comments

Comments
 (0)