Skip to content

Subexpressions info from adapter#26

Open
jgeudens wants to merge 8 commits intomasterfrom
dev/subexpressions
Open

Subexpressions info from adapter#26
jgeudens wants to merge 8 commits intomasterfrom
dev/subexpressions

Conversation

@jgeudens
Copy link
Copy Markdown
Member

@jgeudens jgeudens commented Apr 5, 2026

Summary by CodeRabbit

  • New Features

    • New adapter JSON-RPC methods expose data-point schemas, description, validation and expression-building; a central AdapterManager coordinates adapter lifecycle and exposes build-expression results.
  • Improvements

    • Add-register dialog rebuilt to drive address forms from adapter schemas, uses async expression construction, preselected data types and device-aware controls.
    • Expression parsing made more permissive; legacy expression generator removed.
  • Tests

    • Expanded unit tests 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 APIs and AdapterManager orchestration; stores schemas in SettingsModel/AdapterData; replaces ExpressionGenerator with schema-driven expression construction and updates UI/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() (no params → { addressSchema, dataTypes, defaultDataType }), adapter.describeDataPoint(expression), adapter.validateDataPoint(expression), and adapter.buildExpression(fields, dataType?, deviceId?) with described response/error behaviours.
Protocol adapter client
src/ProtocolAdapter/adapterclient.h, src/ProtocolAdapter/adapterclient.cpp
Added AdapterClient request methods: requestDataPointSchema(), describeDataPoint(), validateDataPoint(), buildExpression(...). Added corresponding result signals and lifecycle handling branches for the new JSON‑RPC methods; state guards applied for allowed client states.
Adapter manager
src/ProtocolAdapter/adaptermanager.h, src/ProtocolAdapter/adaptermanager.cpp
New AdapterManager class that encapsulates AdapterProcess/AdapterClient, relays signals, exposes init/start/stop/requestReadData, forwards buildExpression calls, persists describe/dataPointSchema results 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 an AdapterManager pointer; delegating adapter lifecycle and read/build calls to AdapterManager; removed local describe/diagnostic handlers and 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 a SchemaFormWidget from dataPointSchema, populates type/device combos, and performs async expression creation via AdapterManager::buildExpression; constructors and call sites updated (RegisterDialog/MainWindow).
Settings / Adapter data storage
src/models/adapterdata.h, src/models/adapterdata.cpp, src/models/settingsmodel.h, src/models/settingsmodel.cpp
Added storage API for adapter data‑point schema: AdapterData::setDataPointSchema() / dataPointSchema() and SettingsModel::setAdapterDataPointSchema() which ensures AdapterData entry and emits adapterDataChanged.
Expression handling refactor
src/importexport/mbcregisterdata.cpp, src/util/expressionregex.cpp
MbcRegisterData::toExpression() now builds register strings directly using ModbusDataType::typeString() and inline suffix logic; ExpressionRegex patterns relaxed to accept broader register tokens.
Removed expression helpers
src/util/expressiongenerator.h, src/util/expressiongenerator.cpp
Deleted ExpressionGenerator API and implementation (typeSuffix / constructRegisterString) and updated call sites to use string-based construction.
Tests: Protocol adapter client
tests/ProtocolAdapter/tst_adapterclient.h, tests/ProtocolAdapter/tst_adapterclient.cpp
Added lifecycle helpers and tests covering requestDataPointSchema, describeDataPoint, validateDataPoint, and buildExpression in permitted and wrong states; verify outgoing JSON‑RPC payloads and emitted result signals.
Tests: Adapter manager
tests/ProtocolAdapter/tst_adaptermanager.h, tests/ProtocolAdapter/tst_adaptermanager.cpp, tests/ProtocolAdapter/CMakeLists.txt
New AdapterManager tests (renamed from ModbusPoll tests) exercising 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 persistence via setAdapterDataPointSchema().
Build/test cleanup
tests/communication/CMakeLists.txt
Removed registration of the old tst_modbuspoll test target (now replaced by adapter manager tests).

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: JSON‑RPC request `adapter.buildExpression`
    Client->>Adapter: send JSON‑RPC
    Adapter-->>Client: response `{ expression }`
    Client-->>Manager: buildExpressionResult(expression)
    Manager-->>UI: buildExpressionResult(expression)
    UI->>UI: emit graphDataConfigured(expression)
Loading

Possibly related PRs

  • Support adapter #5 — Extends AdapterClient/adapter process plumbing this change builds upon (lifecycle, describe/configure flow).
  • Configuration (UI) #6 — Adds schema-driven UI components and adapter JSON usage (SchemaFormWidget), which this PR integrates into AddRegisterWidget and tests.
  • Restore MBC import functionality with strict isolation #16 — Touches MBC register expression construction and ExpressionGenerator usage; this PR removes ExpressionGenerator and modifies mbcregisterdata.cpp, so there is a direct code‑level overlap.
🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.39% 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 does not clearly communicate the main purpose of the changeset, which is to add JSON-RPC methods for data point schema, validation, and expression building to the adapter protocol. Revise the title to be more specific and descriptive, such as 'Add JSON-RPC methods for data point schema and expression building' or 'Implement adapter-driven data point expression workflow'.
✅ 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

Comment on lines +22 to +43
/*!
* \brief Returns the single-character address prefix for an object type string.
* \param objectType One of "coil", "discrete-input", "input-register", "holding-register".
* \return The corresponding prefix character ("c", "d", "i", or "h").
* Returns "h" for unknown values as a safe default.
*/
QString objectTypeToAddressPrefix(const QString& objectType)
{
if (objectType == QStringLiteral("coil"))
{
QString suffix = ExpressionGenerator::typeSuffix(type);
QString connStr = devId != Device::cFirstDeviceId ? QString("@%1").arg(devId) : QString();

return QString("${%1%2%3}").arg(registerAddress, connStr, suffix);
return QStringLiteral("c");
}
if (objectType == QStringLiteral("discrete-input"))
{
return QStringLiteral("d");
}
if (objectType == QStringLiteral("input-register"))
{
return QStringLiteral("i");
}
return QStringLiteral("h");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reject unknown object types instead of defaulting to h.

The fallback in objectTypeToAddressPrefix() silently turns typos or future adapter values into holding-register expressions. That polls the wrong register instead of failing fast.

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

In `@src/util/expressiongenerator.cpp` around lines 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".

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

Comment on lines +79 to +80
connect(_pUi->btnAdd, &QPushButton::clicked, this, &AddRegisterWidget::handleResultAccept);
connect(_pModbusPoll, &ModbusPoll::buildExpressionResult, this, &AddRegisterWidget::onBuildExpressionResult);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Correlate buildExpressionResult to the request that triggered it.

ModbusPoll::buildExpressionResult in src/communication/modbuspoll.h:35-50 only carries a QString, but every AddRegisterWidget instance connects to it here and onBuildExpressionResult() accepts any emission as its own reply. If two dialogs share the same ModbusPoll, or this signal is emitted before this widget clicks Add, this widget can emit graphDataConfigured with stale _pendingGraphData. Please route replies per request instead of through a shared broadcast signal, or at least ignore results when this widget has no in-flight build.

Also applies to: 94-115, 117-128

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

In `@src/dialogs/addregisterwidget.cpp` around lines 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.

Comment on lines +83 to +148
void AdapterClient::requestRegisterSchema()
{
if (_state != State::AWAITING_CONFIG)
{
qCWarning(scopeComm) << "AdapterClient: requestRegisterSchema called in unexpected state"
<< static_cast<int>(_state);
return;
}

_pProcess->sendRequest("adapter.registerSchema", QJsonObject());
}

void AdapterClient::describeRegister(const QString& expression)
{
if (_state != State::AWAITING_CONFIG && _state != State::ACTIVE)
{
qCWarning(scopeComm) << "AdapterClient: describeRegister called in unexpected state"
<< static_cast<int>(_state);
return;
}

QJsonObject params;
params["expression"] = expression;
_pProcess->sendRequest("adapter.describeRegister", params);
}

void AdapterClient::validateRegister(const QString& expression)
{
if (_state != State::AWAITING_CONFIG && _state != State::ACTIVE)
{
qCWarning(scopeComm) << "AdapterClient: validateRegister called in unexpected state"
<< static_cast<int>(_state);
return;
}

QJsonObject params;
params["expression"] = expression;
_pProcess->sendRequest("adapter.validateRegister", params);
}

/*!
* \brief Send an adapter.buildExpression request to construct a register expression string.
* \param addressFields Address field values as returned by the register schema form.
* \param dataType Data type identifier; omitted from params when empty.
* \param deviceId Device identifier; omitted from params when zero.
*/
void AdapterClient::buildExpression(const QJsonObject& addressFields, const QString& dataType, deviceId_t deviceId)
{
if (_state != State::AWAITING_CONFIG && _state != State::ACTIVE)
{
qCWarning(scopeComm) << "AdapterClient: buildExpression called in unexpected state" << static_cast<int>(_state);
return;
}

QJsonObject params;
params["fields"] = addressFields;
if (!dataType.isEmpty())
{
params["dataType"] = dataType;
}
if (deviceId != 0)
{
params["deviceId"] = static_cast<qint64>(deviceId);
}
_pProcess->sendRequest("adapter.buildExpression", params);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Track these auxiliary RPCs by request id.

requestRegisterSchema() is sent in AWAITING_CONFIG, but its reply is only accepted while _state is still AWAITING_CONFIG, so a quick provideConfig() can turn a valid schema reply into an “unexpected response”. The same method/state routing also makes describeRegister(), validateRegister(), and buildExpression() unsafe to issue back-to-back: onResponseReceived() discards the JSON-RPC id, so an older reply can arrive later and overwrite newer UI state. Please keep pending request ids (or an explicit caller token) for these non-lifecycle RPCs and ignore superseded responses; delayed-schema and out-of-order build-expression cases would be good regression tests.

Also applies to: 328-343

🧰 Tools
🪛 Cppcheck (2.20.0)

[error] 97-97: There is an unknown macro here somewhere. Configuration is required. If slots is a macro then please configure it.

(unknownMacro)

🤖 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 - 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).

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

Comment on lines +83 to +121
void AdapterClient::requestDataPointSchema()
{
if (_state != State::AWAITING_CONFIG)
{
qCWarning(scopeComm) << "AdapterClient: requestDataPointSchema called in unexpected state"
<< static_cast<int>(_state);
return;
}

_pProcess->sendRequest("adapter.dataPointSchema", QJsonObject());
}

void AdapterClient::describeDataPoint(const QString& expression)
{
if (_state != State::AWAITING_CONFIG && _state != State::ACTIVE)
{
qCWarning(scopeComm) << "AdapterClient: describeDataPoint called in unexpected state"
<< static_cast<int>(_state);
return;
}

QJsonObject params;
params["expression"] = expression;
_pProcess->sendRequest("adapter.describeDataPoint", params);
}

void AdapterClient::validateDataPoint(const QString& expression)
{
if (_state != State::AWAITING_CONFIG && _state != State::ACTIVE)
{
qCWarning(scopeComm) << "AdapterClient: validateDataPoint called in unexpected state"
<< static_cast<int>(_state);
return;
}

QJsonObject params;
params["expression"] = expression;
_pProcess->sendRequest("adapter.validateDataPoint", params);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add brief Doxygen comments for newly added public methods.

requestDataPointSchema, describeDataPoint, and validateDataPoint should be documented in the source file, consistent with buildExpression.

📝 Suggested doc additions
+/*! \brief Request the adapter's data point schema while awaiting configuration. */
 void AdapterClient::requestDataPointSchema()

+/*!
+ * \brief Request parsing/description metadata for a data point expression.
+ * \param expression Expression string to describe.
+ */
 void AdapterClient::describeDataPoint(const QString& expression)

+/*!
+ * \brief Validate a data point expression without starting polling.
+ * \param expression Expression string to validate.
+ */
 void AdapterClient::validateDataPoint(const QString& expression)
As per coding guidelines, "Document all public functions with brief Doxygen comments in the source file".
🧰 Tools
🪛 Cppcheck (2.20.0)

[error] 97-97: There is an unknown macro here somewhere. Configuration is required. If slots is a macro then please configure it.

(unknownMacro)

🤖 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 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.

Comment on lines +139 to +142
if (!dataType.isEmpty())
{
params["dataType"] = dataType;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Handle whitespace-only dataType as omitted.

Currently, " " is treated as non-empty and sent to the adapter. Trimming avoids sending effectively empty values.

🛠️ Suggested robustness fix
-    if (!dataType.isEmpty())
+    const QString normalisedDataType = dataType.trimmed();
+    if (!normalisedDataType.isEmpty())
     {
-        params["dataType"] = dataType;
+        params["dataType"] = normalisedDataType;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ProtocolAdapter/adapterclient.cpp` around lines 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.

Comment on lines +31 to +34
void AdapterManager::initAdapter()
{
const QString adapterPath = QCoreApplication::applicationDirPath() + "/modbusadapter";
_pAdapterClient->prepareAdapter(adapterPath);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Build the adapter path with Qt's executable suffix.

applicationDirPath() + "/modbusadapter" only resolves on Unix-like layouts. On Windows this will miss modbusadapter.exe, so prepareAdapter() cannot start the adapter.

Possible fix
+#include <QDir>
 ...
-    const QString adapterPath = QCoreApplication::applicationDirPath() + "/modbusadapter";
+    const QString adapterPath = QDir(QCoreApplication::applicationDirPath())
+                                    .filePath(QStringLiteral("modbusadapter") + QLatin1String(QT_EXECUTABLE_SUFFIX));
     _pAdapterClient->prepareAdapter(adapterPath);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ProtocolAdapter/adaptermanager.cpp` around lines 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.

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