Skip to content

Subexpressions info from adapter#26

Merged
jgeudens merged 13 commits intomasterfrom
dev/subexpressions
Apr 10, 2026
Merged

Subexpressions info from adapter#26
jgeudens merged 13 commits intomasterfrom
dev/subexpressions

Conversation

@jgeudens
Copy link
Copy Markdown
Member

@jgeudens jgeudens commented Apr 5, 2026

Summary by CodeRabbit

  • New Features

    • New adapter RPCs expose data-point schemas and allow describing, validating and building expressions; a central adapter manager coordinates adapter sessions and returns build-expression results.
  • Improvements

    • Add-register dialog now drives address forms from adapter schemas, offers preselected data types, device-aware controls and async expression construction.
    • Expression parsing broadened; legacy expression string helper removed.
  • Tests

    • Expanded test coverage for schema, describe/validate, expression building, widget flows and adapter manager.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 5, 2026

Walkthrough

Adds JSON‑RPC methods for adapter data‑point schema discovery, description, validation and expression building; implements AdapterClient request APIs, introduces AdapterManager to coordinate adapter lifecycle and relay results, persists schemas in SettingsModel/AdapterData, refactors expression construction and updates UI and tests to use async buildExpression flow.

Changes

Cohort / File(s) Summary
Adapter protocol spec
adapters/json-rpc-spec.md
Documented new JSON‑RPC methods: adapter.dataPointSchema(), adapter.describeDataPoint(expression), adapter.validateDataPoint(expression), adapter.buildExpression(fields, dataType?, deviceId?) with specified result/error behaviours.
Protocol adapter client
src/ProtocolAdapter/adapterclient.h, src/ProtocolAdapter/adapterclient.cpp
Added request APIs: requestDataPointSchema(), describeDataPoint(), validateDataPoint(), buildExpression(...); added result signals; changed handleLifecycleResponse to accept id; track/clear _pendingAuxRequests; state guards for allowed client states.
Adapter manager
src/ProtocolAdapter/adaptermanager.h, src/ProtocolAdapter/adaptermanager.cpp
New AdapterManager owning AdapterProcess/AdapterClient; exposes init/start/stop/requestReadData/buildExpression; re‑emits signals, persists describe/dataPointSchema into SettingsModel, and maps adapter diagnostics to Qt logging.
Modbus integration / Polling
src/communication/modbuspoll.h, src/communication/modbuspoll.cpp
Replaced direct AdapterProcess/AdapterClient ownership with AdapterManager*; delegate lifecycle/read/build calls; removed local describe/diagnostic handlers; added adapterManager() accessor.
UI: Add register/data‑point flow
src/dialogs/addregisterwidget.h, src/dialogs/addregisterwidget.cpp, src/dialogs/addregisterwidget.ui, src/dialogs/registerdialog.h, src/dialogs/registerdialog.cpp, src/dialogs/mainwindow.cpp
AddRegisterWidget now accepts adapterId and AdapterManager*, builds SchemaFormWidget from dataPointSchema, populates type/device combos, and performs async expression creation via AdapterManager::buildExpression; constructors and call sites updated.
Settings / Adapter data storage
src/models/adapterdata.h, src/models/adapterdata.cpp, src/models/settingsmodel.h, src/models/settingsmodel.cpp
Added AdapterData::setDataPointSchema() / dataPointSchema() and SettingsModel::setAdapterDataPointSchema() to store and expose adapter data‑point schemas and emit adapterDataChanged.
Expression handling refactor
src/importexport/mbcregisterdata.cpp, src/util/expressionregex.cpp
MbcRegisterData::toExpression() now constructs register strings using ModbusDataType::typeString() and inline suffix logic; ExpressionRegex patterns broadened to accept more register token forms.
Removed expression helpers
src/util/expressiongenerator.h, src/util/expressiongenerator.cpp
Deleted ExpressionGenerator header and implementation (removed typeSuffix and constructRegisterString APIs) and updated callers.
Tests: Protocol adapter client
tests/ProtocolAdapter/tst_adapterclient.h, tests/ProtocolAdapter/tst_adapterclient.cpp
Added lifecycle helpers and tests for requestDataPointSchema, describeDataPoint, validateDataPoint, and buildExpression (permitted vs wrong states); verify outgoing JSON‑RPC payloads and emitted signals.
Tests: Adapter manager
tests/ProtocolAdapter/tst_adaptermanager.h, tests/ProtocolAdapter/tst_adaptermanager.cpp, tests/ProtocolAdapter/CMakeLists.txt
Replaced ModbusPoll tests with tst_adaptermanager fixture testing diagnostic mapping and lifecycle; added tst_adaptermanager test target.
Tests: AddRegisterWidget
tests/dialogs/tst_addregisterwidget.h, tests/dialogs/tst_addregisterwidget.cpp
Introduced MockAdapterManager to capture buildExpression calls and inject results; updated tests to use schema-driven SchemaFormWidget inputs, assert build call contents, handle empty responses and verify graphData emission and UI state.
Tests: Adapter data model
tests/models/tst_adapterdata.h, tests/models/tst_adapterdata.cpp
Added tests for default empty dataPointSchema, set/get roundtrip, and SettingsModel::setAdapterDataPointSchema() persistence.
Build/test cleanup
tests/communication/CMakeLists.txt
Removed registration of the old tst_modbuspoll test target.

Sequence Diagram(s)

sequenceDiagram
    participant UI as AddRegisterWidget
    participant Settings as SettingsModel
    participant Manager as AdapterManager
    participant Client as AdapterClient
    participant Adapter as AdapterProcess/Adapter

    UI->>Settings: request adapterData(adapterId).dataPointSchema()
    Settings-->>UI: addressSchema, dataTypes, defaultDataType
    UI->>UI: build SchemaFormWidget, populate type/device
    UI->>Manager: buildExpression(addressFields, dataType, deviceId)
    Manager->>Client: send JSON‑RPC `adapter.buildExpression` (request id)
    Client->>Adapter: transport JSON‑RPC
    Adapter-->>Client: response { expression } (id)
    Client-->>Manager: buildExpressionResult(expression) (matching id)
    Manager-->>UI: buildExpressionResult(expression)
    UI->>UI: emit graphDataConfigured(expression)
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Subexpressions info from adapter' is vague and generic, using a non-descriptive term ('Subexpressions info') that doesn't clearly convey the changeset's primary purpose. Consider a more specific title that describes the main change, such as 'Add adapter JSON-RPC methods for data point schema and expression building' or 'Implement expression building via adapter protocol'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev/subexpressions

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (5)
src/models/adapterdata.cpp (1)

46-49: Add Doxygen comments for the newly added public methods.

Line 46 and Line 91 introduce public API without the source-level brief docs used for the rest of this file.

📝 Proposed fix
+/*!
+ * \brief Stores the adapter-provided register schema JSON.
+ * \param schema Register schema object from adapter.registerSchema.
+ */
 void AdapterData::setRegisterSchema(const QJsonObject& schema)
 {
     _registerSchema = schema;
 }
@@
+/*!
+ * \brief Returns the stored adapter register schema JSON.
+ */
 QJsonObject AdapterData::registerSchema() const
 {
     return _registerSchema;
 }

As per coding guidelines, "Document all public functions with brief Doxygen comments in the source file".

Also applies to: 91-94

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/models/adapterdata.cpp` around lines 46 - 49, Add brief Doxygen comments
above the new public functions to match the file’s existing style: add a ///
`@brief` line (and `@param` / `@return` where applicable) immediately above
AdapterData::setRegisterSchema(const QJsonObject& schema) describing what the
setter does and its parameter, and do the same for the other newly added public
method(s) introduced at lines 91-94 (the register-schema-related getter),
including a short `@return` description; follow the same comment formatting used
by the other public functions in this source file.
tests/ProtocolAdapter/tst_adapterclient.cpp (1)

724-762: Consider adding validateRegisterInActiveState test.

describeRegister has tests for both AWAITING_CONFIG (line 666) and ACTIVE (line 691) states, but validateRegister only tests the AWAITING_CONFIG state. Since the implementation (context snippet 4) permits both states, a test for ACTIVE would ensure parity.

🧪 Proposed additional test case
void TestAdapterClient::validateRegisterInActiveState()
{
    auto* mock = new MockAdapterProcess();
    AdapterClient client(mock);

    QSignalSpy spyValidate(&client, &AdapterClient::validateRegisterResult);

    driveToActive(client, mock);

    client.validateRegister(QStringLiteral("${40001: 16b}"));

    QCOMPARE(mock->sentRequests.last().method, QStringLiteral("adapter.validateRegister"));

    mock->injectResponse(5, "adapter.validateRegister", QJsonObject{ { "valid", true } });

    QCOMPARE(spyValidate.count(), 1);
    QCOMPARE(spyValidate.at(0).at(0).toBool(), true);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/ProtocolAdapter/tst_adapterclient.cpp` around lines 724 - 762, Add a
new test that mirrors the existing validateRegister coverage for AWAITING_CONFIG
but runs in the ACTIVE state: create a
TestAdapterClient::validateRegisterInActiveState that constructs
MockAdapterProcess and AdapterClient, attaches a QSignalSpy to
AdapterClient::validateRegisterResult, calls driveToActive(client, mock),
invokes client.validateRegister(...) and asserts the last sent request method is
"adapter.validateRegister", then injects a successful response via
mock->injectResponse(...) and verifies the spy received the expected valid=true
result; reference validateRegister, validateRegisterResult, driveToActive,
MockAdapterProcess and AdapterClient when adding this test to ensure parity with
the describeRegister ACTIVE case.
src/communication/modbuspoll.cpp (1)

128-131: Add Doxygen comment for the new slot.

The new onRegisterSchemaResult slot is missing a brief Doxygen comment, unlike the other handlers in this file (e.g., onAdapterDiagnostic at line 133 has one). As per coding guidelines: "Document all public functions with brief Doxygen comments in the source file".

📝 Proposed Doxygen comment
+/*! \brief Store the adapter's register schema in settings.
+ *
+ * Called when the adapter responds to an adapter.registerSchema request.
+ * \param schema The full register schema object from the adapter.
+ */
 void ModbusPoll::onRegisterSchemaResult(const QJsonObject& schema)
 {
     _pSettingsModel->setAdapterRegisterSchema("modbus", schema);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/communication/modbuspoll.cpp` around lines 128 - 131, Add a brief Doxygen
comment above the ModbusPoll::onRegisterSchemaResult slot similar to other
handlers (e.g., onAdapterDiagnostic) describing that this slot receives the
adapter register schema and passes it to the settings model; place the short /**
... */ comment immediately above the onRegisterSchemaResult
declaration/definition, mention parameters (const QJsonObject& schema) and the
brief purpose (calls _pSettingsModel->setAdapterRegisterSchema("modbus",
schema)), and follow the project's one-line Doxygen style for public functions.
tests/models/tst_adapterdata.cpp (1)

134-137: Redundant null-pointer guard after QVERIFY.

QVERIFY(data != nullptr) at line 133 already aborts the test on failure, making the subsequent if (data == nullptr) return; unreachable. This pattern was likely added for static analysis suppression, but it adds confusion without benefit.

🔧 Option: remove the redundant check
     const AdapterData* data = model.adapterData("modbus");
     QVERIFY(data != nullptr);
-    if (data == nullptr)
-    {
-        return;
-    }
     QVERIFY(data->name().isEmpty());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/models/tst_adapterdata.cpp` around lines 134 - 137, Remove the
redundant null-pointer guard that follows the test assertion: the QVERIFY(data
!= nullptr) already aborts on failure, so delete the subsequent if (data ==
nullptr) { return; } block; locate the QVERIFY(data != nullptr) assertion and
remove the explicit post-check for the pointer named data (or replace it with an
appropriate static-analysis annotation only if required).
src/dialogs/addregisterwidget.cpp (1)

13-18: Add source Doxygen for the public constructor.

The constructor definition was changed here, but the source file still has no brief Doxygen block for it. As per coding guidelines, "Document all public functions with brief Doxygen comments in the source file".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/dialogs/addregisterwidget.cpp` around lines 13 - 18, Add a brief Doxygen
comment immediately above the AddRegisterWidget constructor definition
(AddRegisterWidget::AddRegisterWidget) in the source file: include a one-line
\brief describing the constructor and short `@param` tags for SettingsModel*
pSettingsModel, const QString& adapterId and QWidget* parent; use the project
Doxygen style (e.g. /*! \brief ... */ or ///) so the public constructor is
documented per guidelines.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/dialogs/addregisterwidget.cpp`:
- Around line 108-123: AddRegisterWidget::generateExpression currently assumes
SchemaFormWidget::values() contains "objectType" and "address" and
unconditionally calls ExpressionGenerator::constructRegisterString, which
hard-wires a Modbus schema; instead, check _pAddressForm->values() for the
presence and validity of "objectType" and "address" before using them (e.g. only
call constructRegisterString when both keys exist and are of expected types) or,
preferable, delegate expression construction to an adapter-facing API on the
address form (e.g. add a method on the form like
createRegisterExpression()/toExpression() that returns an optional string) and
call that from generateExpression so different adapters can supply their own
serialization; ensure you still handle deviceId/typeId extraction via
_pUi->cmbDevice and _pUi->cmbType but do not assume address keys exist when
delegating.
- Around line 54-60: The device combo population must handle an empty device
list: after calling _pSettingsModel->deviceListForAdapter(adapterId) check if
deviceList is empty and, if so, disable or hide the Add action (e.g.,
_pUi->btnAdd->setEnabled(false)) and disable the combo
(_pUi->cmbDevice->setEnabled(false)); also ensure the later code that currently
falls back to Device::cFirstDeviceId uses the combo count/currentIndex to detect
"no selection" and abort/return instead of emitting a register for a
non-existent device (update the selection logic that reads cmbDevice and the
fallback to Device::cFirstDeviceId).

In `@src/dialogs/registerdialog.cpp`:
- Around line 69-71: The code constructs AddRegisterWidget using adapterId even
when adapterIds() is empty (adapterId == QString()), which can leave the form
uninitialised; change the logic so you only create registerPopupMenu when a
valid adapter ID exists — e.g., compute ids via _pSettingsModel->adapterIds(),
find the first non-empty/valid adapter id (or test adapter validity via an
existing model method), and only call new AddRegisterWidget(_pSettingsModel,
adapterId, this) when adapterId is non-empty and valid; otherwise avoid creating
registerPopupMenu (or create a disabled/placeholder widget) and ensure any UI
actions that open it are disabled.

In `@src/util/expressiongenerator.cpp`:
- Around line 22-43: The function objectTypeToAddressPrefix currently defaults
unknown object types to "h"; change it to fail fast instead: validate the input
against the allowed values
("coil","discrete-input","input-register","holding-register") in
objectTypeToAddressPrefix and, on any other value, throw a descriptive exception
(e.g., std::invalid_argument with the offending objectType) or return an
explicit error indicator (empty QString) and log the error — choose the
project-consistent error strategy and update callers to handle the failure.
Ensure the error message includes the invalid objectType so typos/future values
are obvious, and remove the silent fallback to "h".

In `@tests/dialogs/tst_addregisterwidget.cpp`:
- Around line 63-68: The test fixture's shared _settingsModel is retaining state
across tests because registerDevice() adds a device; fix by resetting or
recreating the model for each test in TestAddRegisterWidget::init() (or fully
clearing it in cleanup()). Concretely, replace the current init() setup with
code that reinitializes _settingsModel (e.g. _settingsModel = SettingsModel();)
before calling _settingsModel.setAdapterRegisterSchema(...) and creating
AddRegisterWidget, or add an explicit clear/reset call (e.g.
_settingsModel.clearDevices() or similar API) in cleanup() so registerDevice()
side-effects do not persist between tests. Ensure you modify
TestAddRegisterWidget::init(), _settingsModel usage, and/or
TestAddRegisterWidget::cleanup() accordingly.

---

Nitpick comments:
In `@src/communication/modbuspoll.cpp`:
- Around line 128-131: Add a brief Doxygen comment above the
ModbusPoll::onRegisterSchemaResult slot similar to other handlers (e.g.,
onAdapterDiagnostic) describing that this slot receives the adapter register
schema and passes it to the settings model; place the short /** ... */ comment
immediately above the onRegisterSchemaResult declaration/definition, mention
parameters (const QJsonObject& schema) and the brief purpose (calls
_pSettingsModel->setAdapterRegisterSchema("modbus", schema)), and follow the
project's one-line Doxygen style for public functions.

In `@src/dialogs/addregisterwidget.cpp`:
- Around line 13-18: Add a brief Doxygen comment immediately above the
AddRegisterWidget constructor definition (AddRegisterWidget::AddRegisterWidget)
in the source file: include a one-line \brief describing the constructor and
short `@param` tags for SettingsModel* pSettingsModel, const QString& adapterId
and QWidget* parent; use the project Doxygen style (e.g. /*! \brief ... */ or
///) so the public constructor is documented per guidelines.

In `@src/models/adapterdata.cpp`:
- Around line 46-49: Add brief Doxygen comments above the new public functions
to match the file’s existing style: add a /// `@brief` line (and `@param` / `@return`
where applicable) immediately above AdapterData::setRegisterSchema(const
QJsonObject& schema) describing what the setter does and its parameter, and do
the same for the other newly added public method(s) introduced at lines 91-94
(the register-schema-related getter), including a short `@return` description;
follow the same comment formatting used by the other public functions in this
source file.

In `@tests/models/tst_adapterdata.cpp`:
- Around line 134-137: Remove the redundant null-pointer guard that follows the
test assertion: the QVERIFY(data != nullptr) already aborts on failure, so
delete the subsequent if (data == nullptr) { return; } block; locate the
QVERIFY(data != nullptr) assertion and remove the explicit post-check for the
pointer named data (or replace it with an appropriate static-analysis annotation
only if required).

In `@tests/ProtocolAdapter/tst_adapterclient.cpp`:
- Around line 724-762: Add a new test that mirrors the existing validateRegister
coverage for AWAITING_CONFIG but runs in the ACTIVE state: create a
TestAdapterClient::validateRegisterInActiveState that constructs
MockAdapterProcess and AdapterClient, attaches a QSignalSpy to
AdapterClient::validateRegisterResult, calls driveToActive(client, mock),
invokes client.validateRegister(...) and asserts the last sent request method is
"adapter.validateRegister", then injects a successful response via
mock->injectResponse(...) and verifies the spy received the expected valid=true
result; reference validateRegister, validateRegisterResult, driveToActive,
MockAdapterProcess and AdapterClient when adding this test to ensure parity with
the describeRegister ACTIVE case.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 43887b6d-34d2-446d-9755-490bdc7e915a

📥 Commits

Reviewing files that changed from the base of the PR and between a6172ee and 5f8a825.

📒 Files selected for processing (23)
  • adapters/json-rpc-spec.md
  • src/ProtocolAdapter/adapterclient.cpp
  • src/ProtocolAdapter/adapterclient.h
  • src/communication/modbuspoll.cpp
  • src/communication/modbuspoll.h
  • src/dialogs/addregisterwidget.cpp
  • src/dialogs/addregisterwidget.h
  • src/dialogs/addregisterwidget.ui
  • src/dialogs/registerdialog.cpp
  • src/importexport/mbcregisterdata.cpp
  • src/models/adapterdata.cpp
  • src/models/adapterdata.h
  • src/models/settingsmodel.cpp
  • src/models/settingsmodel.h
  • src/util/expressiongenerator.cpp
  • src/util/expressiongenerator.h
  • src/util/expressionregex.cpp
  • tests/ProtocolAdapter/tst_adapterclient.cpp
  • tests/ProtocolAdapter/tst_adapterclient.h
  • tests/dialogs/tst_addregisterwidget.cpp
  • tests/dialogs/tst_addregisterwidget.h
  • tests/models/tst_adapterdata.cpp
  • tests/models/tst_adapterdata.h

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
src/ProtocolAdapter/adapterclient.cpp (1)

83-121: Add source-file Doxygen for the remaining public methods.

requestRegisterSchema(), describeRegister(), and validateRegister() are new public entry points, but only buildExpression() has a source-file doc block here.

As per coding guidelines, **/*.{cpp,cxx}: Document all public functions with brief Doxygen comments in the source file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ProtocolAdapter/adapterclient.cpp` around lines 83 - 121, Add source-file
Doxygen comments for the three public methods requestRegisterSchema(),
describeRegister(const QString&), and validateRegister(const QString&) above
each function definition; each comment should be a brief Doxygen block
describing the purpose, parameters (e.g., `@param` expression for
describeRegister/validateRegister) and any relevant behavior (state
preconditions like State::AWAITING_CONFIG/State::ACTIVE) to match the existing
buildExpression() style and the project's documentation guideline.
tests/dialogs/tst_addregisterwidget.cpp (1)

138-154: Build the expected expression from devId2.

Line 151 and Line 153 still hard-code @2 even though the test already captures the real id from addNewDevice(). That makes this assertion brittle if device ids ever stop being sequential or start from a different base.

🔧 Suggested tweak
-    addRegister(graphData, QStringLiteral("${h0@2}"));
+    const QString expectedExpression = QStringLiteral("${h0@%1}").arg(devId2);
+    addRegister(graphData, expectedExpression);
 
-    QCOMPARE(graphData.expression(), QStringLiteral("${h0@2}"));
+    QCOMPARE(graphData.expression(), expectedExpression);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/dialogs/tst_addregisterwidget.cpp` around lines 138 - 154, The test
currently hard-codes "@2" in the expected expression making it brittle; change
the assertions to build the expected expression from the actual device id
returned by addNewDevice() (devId2) instead of literal "@2" — e.g. construct the
expected expression using devId2 when calling addRegister and when comparing
GraphData::expression(), and keep the existing assertion that
_pMockModbusPoll->buildCalls[0].deviceId equals devId2 so the test no longer
assumes sequential or fixed ids.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@adapters/json-rpc-spec.md`:
- Around line 407-412: The spec for adapter.buildExpression is incomplete: add a
formal request schema describing the JSON payload (include "fields" object for
register components, optional "dataType" string and optional "deviceId" number),
document omission rules (omit "dataType" when empty/blank and omit "deviceId"
when 0), and define the response contract as a JSON object with a single
"expression" string (e.g., { "expression": "..." }); update the
adapter.buildExpression section to explicitly state these input and output
shapes so implementers and tests use the same public protocol.

In `@src/dialogs/addregisterwidget.cpp`:
- Around line 79-80: The buildExpressionResult signal from ModbusPoll is a
broadcast (QString) and AddRegisterWidget's onBuildExpressionResult will treat
any emission as its own reply; update the flow so replies are correlated to the
originating request: either (preferred) extend ModbusPoll::buildExpressionResult
to include a request identifier (e.g., requestId or sender pointer) and emit
that id along with the QString, then have AddRegisterWidget call
handleResultAccept set a temporary requestId/_inFlight flag and only accept
results whose id matches in onBuildExpressionResult; or (quick) make
AddRegisterWidget create a scoped connection when sending the build request
(connect a lambda capturing a local inFlight token or the exact request
parameters) and disconnect it when complete, and in onBuildExpressionResult
guard against handling when _pendingGraphData is empty/not in-flight before
emitting graphDataConfigured. Ensure references to
ModbusPoll::buildExpressionResult, AddRegisterWidget::handleResultAccept,
AddRegisterWidget::onBuildExpressionResult, _pendingGraphData, and
graphDataConfigured are updated accordingly.

In `@src/ProtocolAdapter/adapterclient.cpp`:
- Around line 83-148: The RPCs requestRegisterSchema(), describeRegister(),
validateRegister(), and buildExpression() can race with state changes and
out-of-order replies because responses aren’t matched to the originating
request; add per-RPC pending request-id tracking and ignore superseded replies.
Store the id returned when calling _pProcess->sendRequest (or generate a unique
local id) into fields like _pendingRegisterSchemaRequestId,
_pendingDescribeRequestId, _pendingValidateRequestId,
_pendingBuildExpressionRequestId (or a QHash<QString,qint64> keyed by method
name), then update onResponseReceived() to compare the incoming JSON-RPC id
against the stored id for that method, discard responses that don't match, and
clear the stored id when the matching response is handled (also clear/replace
when a new request for the same RPC is sent).

---

Nitpick comments:
In `@src/ProtocolAdapter/adapterclient.cpp`:
- Around line 83-121: Add source-file Doxygen comments for the three public
methods requestRegisterSchema(), describeRegister(const QString&), and
validateRegister(const QString&) above each function definition; each comment
should be a brief Doxygen block describing the purpose, parameters (e.g., `@param`
expression for describeRegister/validateRegister) and any relevant behavior
(state preconditions like State::AWAITING_CONFIG/State::ACTIVE) to match the
existing buildExpression() style and the project's documentation guideline.

In `@tests/dialogs/tst_addregisterwidget.cpp`:
- Around line 138-154: The test currently hard-codes "@2" in the expected
expression making it brittle; change the assertions to build the expected
expression from the actual device id returned by addNewDevice() (devId2) instead
of literal "@2" — e.g. construct the expected expression using devId2 when
calling addRegister and when comparing GraphData::expression(), and keep the
existing assertion that _pMockModbusPoll->buildCalls[0].deviceId equals devId2
so the test no longer assumes sequential or fixed ids.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d6facb36-bf93-47e1-ab77-ea1c30f98fba

📥 Commits

Reviewing files that changed from the base of the PR and between 5f8a825 and 0aedcab.

📒 Files selected for processing (19)
  • adapters/json-rpc-spec.md
  • src/ProtocolAdapter/adapterclient.cpp
  • src/ProtocolAdapter/adapterclient.h
  • src/communication/modbuspoll.cpp
  • src/communication/modbuspoll.h
  • src/dialogs/addregisterwidget.cpp
  • src/dialogs/addregisterwidget.h
  • src/dialogs/mainwindow.cpp
  • src/dialogs/registerdialog.cpp
  • src/dialogs/registerdialog.h
  • src/importexport/mbcregisterdata.cpp
  • src/models/adapterdata.cpp
  • src/util/expressiongenerator.cpp
  • src/util/expressiongenerator.h
  • tests/ProtocolAdapter/tst_adapterclient.cpp
  • tests/ProtocolAdapter/tst_adapterclient.h
  • tests/dialogs/tst_addregisterwidget.cpp
  • tests/dialogs/tst_addregisterwidget.h
  • tests/models/tst_adapterdata.cpp
💤 Files with no reviewable changes (2)
  • src/util/expressiongenerator.h
  • src/util/expressiongenerator.cpp
✅ Files skipped from review due to trivial changes (1)
  • src/models/adapterdata.cpp
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/communication/modbuspoll.h
  • src/dialogs/addregisterwidget.h
  • src/importexport/mbcregisterdata.cpp
  • tests/ProtocolAdapter/tst_adapterclient.h
  • src/dialogs/registerdialog.cpp

claude and others added 3 commits April 8, 2026 19:43
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
AddRegisterWidget and RegisterDialog now reference AdapterManager
directly instead of going through ModbusPoll. ModbusPoll gains an
adapterManager() getter so MainWindow can pass the manager to
RegisterDialog without knowing its internals.

AdapterManager::buildExpression is marked virtual to allow test
subclassing. MockModbusPoll in tst_addregisterwidget is replaced by
MockAdapterManager, which overrides the virtual method and injects
results via the buildExpressionResult signal.

https://claude.ai/code/session_01AtVwF9u3BD3kqhLUfubEaX
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (2)
src/ProtocolAdapter/adapterclient.cpp (1)

328-343: ⚠️ Potential issue | 🟠 Major

Correlate auxiliary RPC responses by request id, not only method/state.

These branches still accept replies solely by method + current _state, so delayed/out-of-order responses can be misrouted or dropped (especially around state transitions). Please track pending request ids per auxiliary RPC and ignore stale/non-matching replies.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ProtocolAdapter/adapterclient.cpp` around lines 328 - 343, The current
branches emit auxiliary RPC results based only on method and _state; change them
to correlate replies by request id: when sending auxiliary RPCs
(adapter.dataPointSchema, adapter.describeDataPoint, adapter.validateDataPoint,
adapter.buildExpression) record the outgoing request id in a pending map keyed
by method (e.g., pendingRequests["adapter.describeDataPoint"] = id); in the
response-handling code check result["id"] (or the message id field) against the
pending id for that method and only emit dataPointSchemaResult,
describeDataPointResult, validateDataPointResult, or buildExpressionResult if
the ids match; remove the id from the pending map after handling and
ignore/stale responses when there is no match or the client has transitioned
state (clear relevant pending ids on state changes).
src/ProtocolAdapter/adaptermanager.h (1)

63-63: ⚠️ Potential issue | 🟠 Major

Correlate build-expression replies to the request that triggered them.

buildExpressionResult(QString) is a shared broadcast on AdapterManager, but the UI now attaches widget instances directly to it. If two dialogs are open, or one late reply arrives after another click, the wrong widget can accept the expression and emit graphDataConfigured with stale pending state. Carry a request token or requester context through both the request and the reply.

Possible API direction
+#include <QUuid>
 ...
-    virtual void buildExpression(const QJsonObject& addressFields, const QString& dataType, deviceId_t deviceId);
+    virtual QUuid buildExpression(const QJsonObject& addressFields, const QString& dataType, deviceId_t deviceId);
 ...
-    void buildExpressionResult(QString expression);
+    void buildExpressionResult(QUuid requestId, QString expression);

Also applies to: 95-99

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ProtocolAdapter/adaptermanager.h` at line 63, The buildExpression
request/reply path is using a shared broadcast (buildExpressionResult(QString))
so replies can be handled by the wrong UI when multiple dialogs are open; add a
request token or requester context to the API and carry it through the call and
the signal so replies can be correlated. Concretely, change
AdapterManager::buildExpression(...) to accept an extra
requestId/requesterContext (e.g. quint64 requestId or QObject* requester) and
update all other buildExpression overloads accordingly, then change the emitted
signal buildExpressionResult(QString) to include that same token (e.g.
buildExpressionResult(QString, quint64) or buildExpressionResult(QString,
QObject*)) and update all call sites and UI connections to filter by or match
the token before applying graphDataConfigured. Ensure the token is forwarded
from the caller that opens the dialog and that the dialog only reacts to replies
with its matching token.
🧹 Nitpick comments (2)
adapters/json-rpc-spec.md (1)

409-409: Optional wording tidy-up in method description.

“component parts” is slightly redundant; “components” reads tighter.

✏️ Suggested doc tweak
-Constructs a register expression string from its component parts.
+Constructs a register expression string from its components.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@adapters/json-rpc-spec.md` at line 409, Update the method description text
that currently reads "Constructs a register expression string from its component
parts." to use tighter wording: replace "component parts" with "components" so
it reads "Constructs a register expression string from its components." Locate
and edit this sentence in adapters/json-rpc-spec.md where the register
expression constructor is documented.
src/ProtocolAdapter/adaptermanager.cpp (1)

11-24: Add the missing source-file Doxygen for the constructor.

AdapterManager::AdapterManager(...) is public, but it is the only new public function here without a brief Doxygen block in the .cpp.

As per coding guidelines, "Document all public functions with brief Doxygen comments in the source file".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ProtocolAdapter/adaptermanager.cpp` around lines 11 - 24, Add a brief
Doxygen comment block above the AdapterManager::AdapterManager(SettingsModel*
pSettingsModel, QObject* parent) constructor in adaptermanager.cpp documenting
its purpose and parameters (e.g., brief description and `@param` tags for
pSettingsModel and parent), matching the project's source-file Doxygen style
used for other public functions so the constructor is documented consistently
with coding guidelines.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/ProtocolAdapter/adapterclient.cpp`:
- Around line 139-142: The check in adapterclient.cpp currently treats
whitespace-only strings like dataType = "   " as non-empty and includes them in
params; update the logic around dataType (the variable and the
params["dataType"] insertion) to trim whitespace (e.g., use QString::trimmed()
or equivalent) and only set params["dataType"] when trimmed dataType is not
empty, so that whitespace-only values are treated as omitted.
- Around line 83-121: Add brief Doxygen-style comments above the three new
public methods requestDataPointSchema(), describeDataPoint(const QString&), and
validateDataPoint(const QString&) in adapterclient.cpp (same style as the
existing buildExpression comment): include a one-line description, `@param` for
parameters where applicable, and `@see` or `@return` if relevant; place the comments
immediately above each method definition and keep wording consistent with
project style guidelines.

In `@src/ProtocolAdapter/adaptermanager.cpp`:
- Around line 31-34: AdapterManager::initAdapter builds the adapter path without
an executable suffix, which breaks on Windows; update initAdapter to derive the
platform-specific executable suffix from the running application (use
QFileInfo(QCoreApplication::applicationFilePath()).suffix()) and construct
adapterPath via QDir::filePath("modbusadapter" + (suffix.isEmpty() ? "" : "." +
suffix)) before calling _pAdapterClient->prepareAdapter(adapterPath). Ensure you
use QCoreApplication::applicationDirPath() as the base directory and
QDir::filePath to join segments so the resulting path points to the correct
executable on all platforms.

---

Duplicate comments:
In `@src/ProtocolAdapter/adapterclient.cpp`:
- Around line 328-343: The current branches emit auxiliary RPC results based
only on method and _state; change them to correlate replies by request id: when
sending auxiliary RPCs (adapter.dataPointSchema, adapter.describeDataPoint,
adapter.validateDataPoint, adapter.buildExpression) record the outgoing request
id in a pending map keyed by method (e.g.,
pendingRequests["adapter.describeDataPoint"] = id); in the response-handling
code check result["id"] (or the message id field) against the pending id for
that method and only emit dataPointSchemaResult, describeDataPointResult,
validateDataPointResult, or buildExpressionResult if the ids match; remove the
id from the pending map after handling and ignore/stale responses when there is
no match or the client has transitioned state (clear relevant pending ids on
state changes).

In `@src/ProtocolAdapter/adaptermanager.h`:
- Line 63: The buildExpression request/reply path is using a shared broadcast
(buildExpressionResult(QString)) so replies can be handled by the wrong UI when
multiple dialogs are open; add a request token or requester context to the API
and carry it through the call and the signal so replies can be correlated.
Concretely, change AdapterManager::buildExpression(...) to accept an extra
requestId/requesterContext (e.g. quint64 requestId or QObject* requester) and
update all other buildExpression overloads accordingly, then change the emitted
signal buildExpressionResult(QString) to include that same token (e.g.
buildExpressionResult(QString, quint64) or buildExpressionResult(QString,
QObject*)) and update all call sites and UI connections to filter by or match
the token before applying graphDataConfigured. Ensure the token is forwarded
from the caller that opens the dialog and that the dialog only reacts to replies
with its matching token.

---

Nitpick comments:
In `@adapters/json-rpc-spec.md`:
- Line 409: Update the method description text that currently reads "Constructs
a register expression string from its component parts." to use tighter wording:
replace "component parts" with "components" so it reads "Constructs a register
expression string from its components." Locate and edit this sentence in
adapters/json-rpc-spec.md where the register expression constructor is
documented.

In `@src/ProtocolAdapter/adaptermanager.cpp`:
- Around line 11-24: Add a brief Doxygen comment block above the
AdapterManager::AdapterManager(SettingsModel* pSettingsModel, QObject* parent)
constructor in adaptermanager.cpp documenting its purpose and parameters (e.g.,
brief description and `@param` tags for pSettingsModel and parent), matching the
project's source-file Doxygen style used for other public functions so the
constructor is documented consistently with coding guidelines.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ce6b1238-1891-4d06-b1e1-921d9995d447

📥 Commits

Reviewing files that changed from the base of the PR and between 0aedcab and 8de9368.

⛔ Files ignored due to path filters (2)
  • adapters/dummymodbusadapter.exe is excluded by !**/*.exe
  • adapters/modbusadapter.exe is excluded by !**/*.exe
📒 Files selected for processing (28)
  • adapters/dummymodbusadapter
  • adapters/json-rpc-spec.md
  • adapters/modbusadapter
  • src/ProtocolAdapter/adapterclient.cpp
  • src/ProtocolAdapter/adapterclient.h
  • src/ProtocolAdapter/adaptermanager.cpp
  • src/ProtocolAdapter/adaptermanager.h
  • src/communication/modbuspoll.cpp
  • src/communication/modbuspoll.h
  • src/dialogs/addregisterwidget.cpp
  • src/dialogs/addregisterwidget.h
  • src/dialogs/mainwindow.cpp
  • src/dialogs/registerdialog.cpp
  • src/dialogs/registerdialog.h
  • src/models/adapterdata.cpp
  • src/models/adapterdata.h
  • src/models/settingsmodel.cpp
  • src/models/settingsmodel.h
  • tests/ProtocolAdapter/CMakeLists.txt
  • tests/ProtocolAdapter/tst_adapterclient.cpp
  • tests/ProtocolAdapter/tst_adapterclient.h
  • tests/ProtocolAdapter/tst_adaptermanager.cpp
  • tests/ProtocolAdapter/tst_adaptermanager.h
  • tests/communication/CMakeLists.txt
  • tests/dialogs/tst_addregisterwidget.cpp
  • tests/dialogs/tst_addregisterwidget.h
  • tests/models/tst_adapterdata.cpp
  • tests/models/tst_adapterdata.h
💤 Files with no reviewable changes (1)
  • tests/communication/CMakeLists.txt
✅ Files skipped from review due to trivial changes (2)
  • tests/ProtocolAdapter/CMakeLists.txt
  • tests/models/tst_adapterdata.h
🚧 Files skipped from review as they are similar to previous changes (9)
  • src/models/settingsmodel.cpp
  • src/models/settingsmodel.h
  • tests/models/tst_adapterdata.cpp
  • tests/ProtocolAdapter/tst_adapterclient.cpp
  • src/dialogs/registerdialog.cpp
  • src/ProtocolAdapter/adapterclient.h
  • tests/ProtocolAdapter/tst_adapterclient.h
  • src/dialogs/mainwindow.cpp
  • src/communication/modbuspoll.cpp

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/ProtocolAdapter/adapterclient.h (1)

162-167: ⚠️ Potential issue | 🟡 Minor

Update diagnostic level documentation to include "error".

The Doxygen comment lists "debug", "info", or "warning" but the implementation in adaptermanager.cpp also handles "error" level, which is documented in the JSON-RPC spec.

📝 Suggested fix
     /*!
      * \brief Emitted when an adapter.diagnostic notification is received from the adapter.
-     * \param level Severity level string: "debug", "info", or "warning".
+     * \param level Severity level string: "debug", "info", "warning", or "error".
      * \param message The diagnostic message from the adapter.
      */
     void diagnosticReceived(QString level, QString message);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ProtocolAdapter/adapterclient.h` around lines 162 - 167, Update the
Doxygen for the signal diagnosticReceived(QString level, QString message) to
include "error" in the list of severity levels (e.g., "debug", "info",
"warning", or "error") so it matches the implementation in adaptermanager.cpp
and the JSON-RPC spec; edit the comment above diagnosticReceived to mention
"error" as a valid severity string and ensure wording remains consistent with
other doc comments.
🧹 Nitpick comments (1)
src/communication/modbuspoll.cpp (1)

23-27: Consider extracting the lambda to a named slot.

This lambda contains multiple statements. Per coding guidelines, lambdas with more than two captures or multiple statements should use named functions for clarity.

♻️ Proposed refactor

In the header, add a private slot:

private slots:
    void onAdapterSessionError(QString message);

In the source:

-    connect(_pAdapterManager, &AdapterManager::sessionError, this, [this](QString message) {
-        qCWarning(scopeComm) << "AdapterManager error:" << message;
-        _bPollActive = false;
-        disconnect(_pAdapterManager, &AdapterManager::sessionStopped, this, &ModbusPoll::initAdapter);
-    });
+    connect(_pAdapterManager, &AdapterManager::sessionError, this, &ModbusPoll::onAdapterSessionError);
void ModbusPoll::onAdapterSessionError(QString message)
{
    qCWarning(scopeComm) << "AdapterManager error:" << message;
    _bPollActive = false;
    disconnect(_pAdapterManager, &AdapterManager::sessionStopped, this, &ModbusPoll::initAdapter);
}

As per coding guidelines: "Avoid lambda expressions with more than 2 captures or multiple statements; use named functions instead for clarity".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/communication/modbuspoll.cpp` around lines 23 - 27, Extract the
multi-statement lambda connected to AdapterManager::sessionError into a named
private slot on ModbusPoll: declare a slot void onAdapterSessionError(QString
message) in the class, implement ModbusPoll::onAdapterSessionError to log the
message with qCWarning(scopeComm), set _bPollActive = false, and disconnect
_pAdapterManager's sessionStopped signal from ModbusPoll::initAdapter; then
replace the lambda connection with a direct connect(_pAdapterManager,
&AdapterManager::sessionError, this, &ModbusPoll::onAdapterSessionError).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/ProtocolAdapter/adapterclient.h`:
- Around line 162-167: Update the Doxygen for the signal
diagnosticReceived(QString level, QString message) to include "error" in the
list of severity levels (e.g., "debug", "info", "warning", or "error") so it
matches the implementation in adaptermanager.cpp and the JSON-RPC spec; edit the
comment above diagnosticReceived to mention "error" as a valid severity string
and ensure wording remains consistent with other doc comments.

---

Nitpick comments:
In `@src/communication/modbuspoll.cpp`:
- Around line 23-27: Extract the multi-statement lambda connected to
AdapterManager::sessionError into a named private slot on ModbusPoll: declare a
slot void onAdapterSessionError(QString message) in the class, implement
ModbusPoll::onAdapterSessionError to log the message with qCWarning(scopeComm),
set _bPollActive = false, and disconnect _pAdapterManager's sessionStopped
signal from ModbusPoll::initAdapter; then replace the lambda connection with a
direct connect(_pAdapterManager, &AdapterManager::sessionError, this,
&ModbusPoll::onAdapterSessionError).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 948ff9cb-32d1-42df-80ae-200ec8c3faad

📥 Commits

Reviewing files that changed from the base of the PR and between 8de9368 and e8063cc.

⛔ Files ignored due to path filters (2)
  • adapters/dummymodbusadapter.exe is excluded by !**/*.exe
  • adapters/modbusadapter.exe is excluded by !**/*.exe
📒 Files selected for processing (7)
  • adapters/dummymodbusadapter
  • adapters/json-rpc-spec.md
  • adapters/modbusadapter
  • src/ProtocolAdapter/adapterclient.cpp
  • src/ProtocolAdapter/adapterclient.h
  • src/ProtocolAdapter/adaptermanager.cpp
  • src/communication/modbuspoll.cpp

@jgeudens jgeudens merged commit 0b3d910 into master Apr 10, 2026
10 checks passed
@jgeudens jgeudens deleted the dev/subexpressions branch April 10, 2026 20:09
@jgeudens jgeudens restored the dev/subexpressions branch April 10, 2026 20:10
@jgeudens jgeudens deleted the dev/subexpressions branch April 10, 2026 20:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants