Config-less discovery: device-derived identity, A&C faults, AccessLevel writable, runtime fault triggers#544
Open
mfaferek93 wants to merge 28 commits into
Open
Config-less discovery: device-derived identity, A&C faults, AccessLevel writable, runtime fault triggers#544mfaferek93 wants to merge 28 commits into
mfaferek93 wants to merge 28 commits into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR makes “config-less” PLC/OPC-UA discovery more production-ready and adds a new gateway-side runtime threshold (fault-trigger) engine. It also adjusts fault scoping and serialization so entity-scoped fault queries can include component sources and preserve occurrence metadata.
Changes:
- OPC-UA plugin: derive component identity from device fields, enable native A&C auto-alarms in config-less mode, infer writability from AccessLevel, and harden multi-plugin co-loading with hidden symbol visibility.
- Gateway: add
FaultTriggerEnginewith REST CRUD under/api/v1/apps/{app}/fault-triggers, persistence, and a dedicated evaluation loop. - Fault pipeline improvements: component IDs included as valid reporting sources in scoped queries; occurrence/confirmed timestamps added to storage and passed through where needed; plugin entity capabilities pruned when unsupported.
Reviewed changes
Copilot reviewed 31 out of 31 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| src/ros2_medkit_plugins/ros2_medkit_opcua/test/test_opcua_plugin.cpp | Extends plugin fault scoping tests to include reporting_sources and occurrence passthrough. |
| src/ros2_medkit_plugins/ros2_medkit_opcua/test/test_node_map.cpp | Updates default alarm-entity ID and tests identity override flowing into derived alarm entity IDs. |
| src/ros2_medkit_plugins/ros2_medkit_opcua/test/test_network_discovery.cpp | Updates tests to the renamed OpcuaDiscoveryConfig. |
| src/ros2_medkit_plugins/ros2_medkit_opcua/test/test_device_identity.cpp | Adds unit tests for config-less device-derived component identity derivation. |
| src/ros2_medkit_plugins/ros2_medkit_opcua/test/test_address_space_browser.cpp | Adds tests for infer_writable behavior and AccessLevel read failures. |
| src/ros2_medkit_plugins/ros2_medkit_opcua/src/opcua_plugin.cpp | Implements config-less identity override, auto-alarms enablement, writable logging, and fault list passthrough improvements. |
| src/ros2_medkit_plugins/ros2_medkit_opcua/src/opcua_client.cpp | Adds AccessLevel decoding and width-typed integer writes for PLC compatibility. |
| src/ros2_medkit_plugins/ros2_medkit_opcua/src/network_discovery.cpp | Renames discovery config type and updates parsing/constructor signatures. |
| src/ros2_medkit_plugins/ros2_medkit_opcua/src/device_identity.cpp | Adds slugification + endpoint host fallback and component identity derivation. |
| src/ros2_medkit_plugins/ros2_medkit_opcua/src/address_space_browser.cpp | Infers writability from server AccessLevel/UserAccessLevel during auto-browse. |
| src/ros2_medkit_plugins/ros2_medkit_opcua/include/ros2_medkit_opcua/opcua_plugin.hpp | Switches to OpcuaDiscoveryConfig and tracks whether auto-alarms were explicitly configured. |
| src/ros2_medkit_plugins/ros2_medkit_opcua/include/ros2_medkit_opcua/opcua_client.hpp | Adds AccessLevelInfo and read_access_level() API. |
| src/ros2_medkit_plugins/ros2_medkit_opcua/include/ros2_medkit_opcua/node_map.hpp | Adds infer_writable, runtime set_component_identity(), and updates neutral component defaults. |
| src/ros2_medkit_plugins/ros2_medkit_opcua/include/ros2_medkit_opcua/network_discovery.hpp | Renames DiscoveryConfig -> OpcuaDiscoveryConfig to avoid ODR collisions. |
| src/ros2_medkit_plugins/ros2_medkit_opcua/include/ros2_medkit_opcua/device_identity.hpp | Declares ComponentIdentity + derive_component_identity(). |
| src/ros2_medkit_plugins/ros2_medkit_opcua/include/ros2_medkit_opcua/address_space_browser.hpp | Extends browse source interface with read_writable(). |
| src/ros2_medkit_plugins/ros2_medkit_opcua/CMakeLists.txt | Enables hidden visibility to reduce plugin symbol interposition/ODR collisions. |
| src/ros2_medkit_gateway/test/test_handler_context.cpp | Updates fault-source resolution expectations to include component IDs as reporting sources. |
| src/ros2_medkit_gateway/test/test_fault_trigger_engine.cpp | Adds unit tests for fault-trigger CRUD, validation, edge behavior, and persistence. |
| src/ros2_medkit_gateway/src/main.cpp | Starts the fault-trigger engine after plugins are loaded. |
| src/ros2_medkit_gateway/src/http/rest_server.cpp | Adds REST CRUD routes for fault-triggers (direct httplib registration). |
| src/ros2_medkit_gateway/src/http/handlers/discovery_handlers.cpp | Prunes plugin entity capabilities that would dead-link to 404 when providers are absent. |
| src/ros2_medkit_gateway/src/gateway_node.cpp | Wires the fault-trigger engine: fetch via plugin route, report/clear via FaultManager, thread loop lifecycle. |
| src/ros2_medkit_gateway/src/core/faults/fault_scope.cpp | Treats components as valid reporting sources in scoped fault queries. |
| src/ros2_medkit_gateway/src/core/fault_trigger_engine.cpp | Implements the engine: JSON persistence, rule evaluation, report/clear behavior. |
| src/ros2_medkit_gateway/README.md | Documents the new /fault-triggers endpoint family and its relationship to /triggers. |
| src/ros2_medkit_gateway/include/ros2_medkit_gateway/gateway_node.hpp | Exposes init_fault_trigger_engine() and accessor for REST layer. |
| src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/fault_trigger_engine.hpp | Declares engine API and rule schema/validation helpers. |
| src/ros2_medkit_gateway/CMakeLists.txt | Adds the new unit test target for the engine (links gateway_core). |
| src/ros2_medkit_fault_manager/test/test_sqlite_storage.cpp | Adds tests for confirmed_at_ns recording and migration behavior. |
| src/ros2_medkit_fault_manager/src/sqlite_fault_storage.cpp | Adds confirmed_at_ns column, migration, and logic to record confirmation time. |
…lity hardening Zero-config discovery now names the SOVD component from the device's own read identity (DI nameplate manufacturer+model, else BuildInfo ProductName, else opcua-<host>) instead of a hardcoded "OpenPLC Runtime" default, and auto-enables native Alarms & Conditions (Server EventNotifier i=2253) when no node map and no explicit auto_alarms block are given, so discovered conditions surface on /api/v1/faults with a derived fault_code. Rename the plugin's DiscoveryConfig to OpcuaDiscoveryConfig and build the .so with -fvisibility=hidden -fvisibility-inlines-hidden so its internals cannot interpose another plugin's symbols when the gateway dlopens all plugins into one process (the extern "C" entry points stay exported).
Two lines in network_discovery.{hpp,cpp} failed clang-format-18
against the repo config. Formatting only, no behaviour change.
…ites auto_browse now reads each Variable's AccessLevel/UserAccessLevel and marks the point writable when the session's CurrentWrite bit is set (new auto_browse.infer_writable flag, default on; explicit node_map still wins). Integer writes probe the node's real DataType and write the matching scalar width, so narrow types like the S7 Int (Int16) no longer get BadTypeMismatch.
Runtime threshold rules on discovered data points: cross fires a SOVD fault,
un-cross auto-clears; rules persist to JSON and are edited over REST at
/api/v1/apps/{app_id}/fault-triggers. Kept as a sibling of /triggers, which
stays the SOVD notification-trigger CRUD - the two share no schema or store.
POST /fault-triggers accepted any data_name and listed the rule as active even when the app has no such point - a silently dead alarm. Validate against the app's discovered points (400 lists the valid names); unenumerable points (PLC offline) still skip the check.
Component-scoped fault queries collected only child-app FQNs, so faults reported under the component's own id (PLC_COMMS_LOST from bridge plugins) returned empty on /components/<id>/faults while the instance route listed them. Always include the component id.
list_faults dropped first/last_occurred, occurrence_count and severity_label and always served an empty source_id, so clients rendered 'time unknown'. Pass the fields through and derive source_id from reporting_sources.
Stamp the transition into CONFIRMED (report, reactivation and time-based paths) into a new confirmed_at_ns column; older DBs are migrated with 0 = unknown. Consumed by the compliance timeline, whose records carried confirmed_at_ns null.
Plugin-owned entities advertised data/operations hrefs that 404 by contract when the plugin registers no Data/OperationProvider - dead links every SOVD client follows. Prune them from the detail capability lists.
occurrence_count no longer increments on every FAILED re-report of an active fault - only on first raise and reactivation after CLEARED. Reactivation also resets first_occurred to the new cycle.
DataProvider/OperationProvider gain has_data/has_operations (default true); PluginManager stops routing entities the provider cannot serve, so capabilities stay honest. opcua: grouping component and the alarms fallback app no longer advertise data/operations/x-plc-data they 404.
Restore the #516 guardrail broken by the PLC_COMMS_LOST scope fix: an internal component must not claim faults reported under its bare id. The real gap was the opcua plugin never marking its component external; fix that instead of widening the scope grant to every component.
Re-reports of a still-active fault no longer bump occurrence_count; the reactivation-after-CLEARED case already asserted the edge semantics.
…ssed-latch docs fault_code is the fault store's primary key - two rules sharing it fight over one stored fault. The latch comment claimed edge-triggered reporting; evaluation is level-triggered with an edge-gated clear.
raw() escape hatch on the registry (documented JSON routes without a typed DTO) so the CRUD appears in the OpenAPI spec, Swagger UI and the endpoint list instead of being invisible to /api/v1/docs.
… registry The docs completeness gate pins both: tags must be globally defined and 4xx responses must be response-level GenericError refs - satisfied by not declaring 400/404 manually (the registry auto-adds them).
Publish responses are only processed inside runIterate; the poll thread's single iterate per cycle loses the client to sync API traffic long enough for every outstanding publish to expire in one burst, after which AlarmCondition events silently stop. A 20 ms iterate every 100 ms from its own thread keeps the channel serviced regardless of poll cadence or API contention. Seen live on the DiagBox bench vs a 1505SP.
Siemens A&C mints a fresh ConditionId per activation of the same Program_Alarm; the previous instance's heal / reconcile-clear then races the new instance's raise and - because fault_manager keys by fault_code - wipes the fault it just asserted (CONFIRMED then HEALED 170 us apart on a real 1505SP, alarm invisible). Heal/clear from an instance is now suppressed while another instance of the same fault_code is Confirmed.
…ctive Siemens sends secondary notifications of the same condition (ack-state transitions) without a readable ActiveState/Id; defaulting the missing field to false healed the fault the primary notification confirmed microseconds earlier. A notification that cannot state activity now inherits the tracked instance's previous activity; an explicit false still heals.
mfaferek93
force-pushed
the
feat/zeroconfig-discovery
branch
from
July 23, 2026 09:50
338e5a5 to
59945a2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Makes config-less PLC discovery production-usable and adds runtime threshold alarms.
opcua plugin
/faults, auto-clear on inactive.writableinferred from server AccessLevel (CurrentWrite); width-typed integer writes.OpcuaDiscoveryConfig) +-fvisibility=hiddenso multiple plugins co-load in one gateway safely.gateway core
FaultTriggerEngine: runtime threshold rules on discovered data points - CRUD at/api/v1/apps/{app}/fault-triggers, JSON persistence, fires through FaultManager (freeze-frame captured), auto-clears on un-cross. Rejects rules on nonexistent points. Sibling of the existing SOVD notification/triggers(documented).confirmed_at_ns(with migration); plugin entities advertise only capabilities they serve.Tests: full suite green (4797 gateway/fault_manager/opcua), new suites for the trigger engine, identity derivation, AccessLevel writable, confirmed_at. Verified live against an S7-1500: subnet scan -> auto-browse (57 nodes), write round-trip, trigger fault with freeze-frame.