feat(opcua): raise PLC_COMMS_LOST fault on connection loss#507
feat(opcua): raise PLC_COMMS_LOST fault on connection loss#507mfaferek93 wants to merge 3 commits into
Conversation
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.
There was a problem hiding this comment.
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. |
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
left a comment
There was a problem hiding this comment.
Additional notes outside the diff:
ros2_medkit_opcua/design/index.rstdocuments the PLC condition/event to fault flow but notPLC_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_raisegate is unit-tested. The clear path (emit_comms_lost(false)plus resettingcomms_lost_raised_andcomms_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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
Raises a single component-scoped
PLC_COMMS_LOSTfault (severityERROR, configurable) when the OPC UA connection stays down, so a controller going dark surfaces on/faultsinstead of only thex-plc-statusconnectedboolean. Brings the opcua plugin to parity with the modbus/s7/ads bridges.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.comms_lost_fault_enabled(default true),comms_lost_debounce_ms(default 5000),comms_lost_severity(default ERROR); envOPCUA_COMMS_LOST_ENABLED/OPCUA_COMMS_LOST_DEBOUNCE_MS.CommsLostShouldRaise).Closes #496