Skip to content

feat(opcua): raise PLC_COMMS_LOST fault on connection loss#507

Open
mfaferek93 wants to merge 3 commits into
mainfrom
feat/opcua-comms-lost-fault
Open

feat(opcua): raise PLC_COMMS_LOST fault on connection loss#507
mfaferek93 wants to merge 3 commits into
mainfrom
feat/opcua-comms-lost-fault

Conversation

@mfaferek93

Copy link
Copy Markdown
Collaborator

Raises a single component-scoped PLC_COMMS_LOST fault (severity ERROR, configurable) when the OPC UA connection stays down, so a controller going dark surfaces on /faults instead of only the x-plc-status connected boolean. Brings the opcua plugin to parity with the modbus/s7/ads bridges.

  • Debounced: raised only after the connection is continuously down for comms_lost_debounce_ms (default 5000), so a brief blip during a normal reconnect does not flap a fault. Idempotent (raised once), cleared on the next successful reconnect.
  • Component-scoped to the node map's root component; message carries the endpoint.
  • Config knobs: comms_lost_fault_enabled (default true), comms_lost_debounce_ms (default 5000), comms_lost_severity (default ERROR); env OPCUA_COMMS_LOST_ENABLED / OPCUA_COMMS_LOST_DEBOUNCE_MS.
  • The debounce gate is a pure static helper with unit tests (CommsLostShouldRaise).

Closes #496

The poller raises a single component-scoped PLC_COMMS_LOST fault
(severity ERROR) once the OPC UA connection has been continuously down
for comms_lost_debounce_ms (default 5000, debouncing reconnect blips),
and clears it on the next successful reconnect. Knobs:
comms_lost_fault_enabled / comms_lost_debounce_ms / comms_lost_severity.
Copilot AI review requested due to automatic review settings July 5, 2026 19:49
@mfaferek93 mfaferek93 self-assigned this Jul 5, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds debounced “PLC communications lost” fault emission to the OPC-UA plugin so prolonged connection loss becomes visible via /faults, aligning behavior with other PLC bridges and issue #496’s expectations.

Changes:

  • Introduces a debounced comms-lost raise/clear path in the OPC-UA poller (PLC_COMMS_LOST, component-scoped).
  • Adds configuration knobs (YAML + env overrides) for enabling and tuning the comms-lost fault behavior and severity.
  • Adds unit tests for the pure debounce gate helper and updates plugin README parameter documentation.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/ros2_medkit_plugins/ros2_medkit_opcua/test/test_opcua_plugin.cpp Adds unit tests for the comms-lost debounce decision helper.
src/ros2_medkit_plugins/ros2_medkit_opcua/src/opcua_poller.cpp Raises/clears PLC_COMMS_LOST based on debounced connection-down duration.
src/ros2_medkit_plugins/ros2_medkit_opcua/src/opcua_plugin.cpp Parses new comms-lost config knobs (JSON + env overrides).
src/ros2_medkit_plugins/ros2_medkit_opcua/README.md Documents new comms-lost YAML parameters in the gateway parameter table.
src/ros2_medkit_plugins/ros2_medkit_opcua/include/ros2_medkit_opcua/opcua_poller.hpp Adds comms-lost config fields and poller state for debouncing/idempotence.

Comment thread src/ros2_medkit_plugins/ros2_medkit_opcua/src/opcua_plugin.cpp
Comment thread src/ros2_medkit_plugins/ros2_medkit_opcua/src/opcua_plugin.cpp
Comment thread src/ros2_medkit_plugins/ros2_medkit_opcua/README.md
Clamp comms_lost_debounce_ms to >= 0 and keep the existing value on a
non-numeric/negative OPCUA_COMMS_LOST_DEBOUNCE_MS (atoi parsed it as 0,
silently disabling the debounce). Validate comms_lost_severity against
the SOVD buckets and default to ERROR on a typo so the fault is not
downgraded to INFO. Document the comms-lost env overrides.

@bburda bburda left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Additional notes outside the diff:

  • ros2_medkit_opcua/design/index.rst documents the PLC condition/event to fault flow but not PLC_COMMS_LOST, which is different in kind (raised by the poll thread on connection loss, not sourced from a PLC condition node). A short note there would keep the design doc in step with the README.
  • Only the pure comms_lost_should_raise gate is unit-tested. The clear path (emit_comms_lost(false) plus resetting comms_lost_raised_ and comms_down_since_ on reconnect) has no coverage and is the more state-heavy half. Extracting a pure reconnect-transition helper would let it be tested the same way as the raise gate.

// does not silently disable the debounce (atoi would parse it as 0).
char * end = nullptr;
const long ms = std::strtol(env, &end, 10);
if (end != env && *end == '\0' && ms >= 0) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The strtol guard does not check errno == ERANGE, so an overflowing value (a long run of digits) returns LONG_MAX, still passes end != env && *end == '\0' && ms >= 0, and sets the debounce to about LONG_MAX ms. The gate then never fires, which silently disables the fault - the exact outcome the comment above says this guard prevents. Set errno = 0 before strtol and treat ERANGE as invalid. The YAML path just above (get<int>(), clamped only for negatives) has the same overflow gap and no upper bound, while poll_interval_ms clamps to [100, 60000]; matching that would make the two paths consistent.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 520e63a. Set errno = 0 before strtol and reject ERANGE, so an overflowing OPCUA_COMMS_LOST_DEBOUNCE_MS is now ignored (existing value kept + warning) instead of saturating to LONG_MAX and slipping past the >= 0 guard. Both the YAML get<> path and the env path go through one shared clamp to [0, 3600000] ms (1 h cap), documented in the README knob tables. Verified the parse edge cases (overflow / negative / non-numeric rejected; 7200000 -> 3600000) and test_opcua_plugin stays green (38/38).

if (comms_lost_should_raise(config_.comms_lost_fault_enabled, comms_lost_raised_, *comms_down_since_,
std::chrono::steady_clock::now(), config_.comms_lost_debounce)) {
emit_comms_lost(/*active=*/true);
comms_lost_raised_ = true;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

comms_lost_raised_ is set here regardless of whether the report was delivered. The report is fire-and-forget (async_send_request, no readiness check or retry) and nothing waits for the report service before the poller starts, so if /fault_manager/report_fault is not discovered yet when the raise fires, the report is dropped and the fault never re-fires until a full reconnect and re-down. Unlike native alarms, which re-fire on ConditionRefresh after reconnect, comms-lost is strictly one-shot. The 5s debounce makes this unlikely because the client is usually discovered by then, so it is low risk. Worth gating the flag on confirmed delivery, or accepting and noting it?

Reset errno and reject ERANGE so an overflowing OPCUA_COMMS_LOST_DEBOUNCE_MS
no longer saturates to LONG_MAX and silently disables the fault, and clamp
both the YAML and env paths to [0, 3600000] ms.
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.

opcua: raise a comms-lost fault when the OPC UA connection drops

3 participants