Skip to content

Commit f0f06d9

Browse files
committed
feat(opcua): implement DataProvider + OperationProvider + FaultProvider; harden plugin
Address all critical and important findings from maintainer review: Provider migration (#11 critical): - Implement DataProvider (list_data, read_data, write_data) so standard SOVD GET/PUT /{type}/{id}/data endpoints work for PLC entities - Implement OperationProvider (list_operations, execute_operation) for POST /{type}/{id}/operations/{name} - Implement FaultProvider (list_faults, get_fault, clear_fault) for GET/DELETE /{type}/{id}/faults - Export get_data_provider, get_operation_provider, get_fault_provider via extern "C" in opcua_plugin_exports.cpp - Keep all vendor x-plc-* routes for backward compat + rich PLC data - MCP tools (sovd_read_data etc.), Web UI, fleet gateway now work on PLC entities through standard SOVD paths Shutdown guard (#13 important): - Add std::atomic<bool> shutdown_requested_ with exchange(true) pattern - Destructor calls shutdown() (was = default) - Callbacks (publish_values, on_alarm_change) check flag before work - All 4 route handlers + 8 provider methods return 503 when shutdown Null checks (#14 important): - All route handlers and provider methods check ctx_ and poller_ at entry, returning 503 ERR_SERVICE_UNAVAILABLE if uninitialized Condition variable sleep (#15 important): - Replace integer-division sleep loops in opcua_poller with std::condition_variable::wait_for so stop() wakes the thread immediately and sub-100ms intervals no longer cause CPU spin - Replace assert(!running_) in set_poll_callback with std::logic_error throw that survives Release builds Small fixes (#21, #22, #25, #26): - SecurityPolicy=None warning now includes endpoint URL - FetchContent GIT_TAG pinned to full SHA (122ea4c8) instead of mutable v0.16.0 tag - package.xml: XML comment explaining open62541pp FetchContent + #366 - opcua-plugin.yml: add permissions: contents: read Rename private fault helpers (avoid name collision with FaultProvider): - report_fault -> send_report_fault - clear_fault(fault_code) -> send_clear_fault(fault_code)
1 parent 854e687 commit f0f06d9

8 files changed

Lines changed: 449 additions & 25 deletions

File tree

.github/workflows/opcua-plugin.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
name: OPC-UA Plugin Integration
22

3+
permissions:
4+
contents: read
5+
36
# Runs the OpenPLC-based Docker integration suite for ros2_medkit_opcua.
47
# Triggers only when the plugin itself, the gateway plugin/provider API, or
58
# ros2_medkit_msgs change. A nightly cron provides a safety net against silent

src/ros2_medkit_plugins/ros2_medkit_opcua/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ include(FetchContent)
4848
fetchcontent_declare(
4949
open62541pp
5050
GIT_REPOSITORY https://github.com/open62541pp/open62541pp.git
51-
GIT_TAG v0.16.0
51+
GIT_TAG 122ea4c842193918b97fd2f2def4fbecabb33ffe # v0.16.0 pinned by SHA
5252
GIT_SUBMODULES_RECURSE ON
5353
)
5454
# Build open62541 as part of open62541pp, with -fPIC for shared library

src/ros2_medkit_plugins/ros2_medkit_opcua/include/ros2_medkit_opcua/opcua_plugin.hpp

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@
2121
#include <ros2_medkit_gateway/plugins/gateway_plugin.hpp>
2222
#include <ros2_medkit_gateway/plugins/plugin_context.hpp>
2323
#include <ros2_medkit_gateway/plugins/plugin_http_types.hpp>
24+
#include <ros2_medkit_gateway/providers/data_provider.hpp>
25+
#include <ros2_medkit_gateway/providers/fault_provider.hpp>
2426
#include <ros2_medkit_gateway/providers/introspection_provider.hpp>
27+
#include <ros2_medkit_gateway/providers/operation_provider.hpp>
2528

29+
#include <atomic>
2630
#include <memory>
2731
#include <string>
2832
#include <unordered_map>
@@ -36,15 +40,32 @@ namespace ros2_medkit_gateway {
3640

3741
/// OPC-UA Gateway Plugin - bridges OPC-UA PLCs into the SOVD entity tree
3842
///
39-
/// Implements GatewayPlugin (lifecycle, routes) and IntrospectionProvider
40-
/// (entity discovery from OPC-UA address space).
43+
/// Implements GatewayPlugin (lifecycle, routes), IntrospectionProvider
44+
/// (entity discovery from OPC-UA address space), and the typed provider
45+
/// interfaces (DataProvider, OperationProvider, FaultProvider) so that
46+
/// standard SOVD endpoints (/data, /operations, /faults) work for PLC
47+
/// entities alongside the vendor x-plc-* extensions.
4148
///
42-
/// Vendor REST endpoints:
49+
/// Standard SOVD endpoints (via provider interfaces):
50+
/// GET /{type}/{id}/data - DataProvider::list_data
51+
/// GET /{type}/{id}/data/{name} - DataProvider::read_data
52+
/// PUT /{type}/{id}/data/{name} - DataProvider::write_data
53+
/// GET /{type}/{id}/operations - OperationProvider::list_operations
54+
/// POST /{type}/{id}/operations/{name} - OperationProvider::execute_operation
55+
/// GET /{type}/{id}/faults - FaultProvider::list_faults
56+
/// GET /{type}/{id}/faults/{code} - FaultProvider::get_fault
57+
/// DELETE /{type}/{id}/faults/{code} - FaultProvider::clear_fault
58+
///
59+
/// Vendor REST extensions (via get_routes):
4360
/// GET /apps/{id}/x-plc-data - All OPC-UA values for entity
4461
/// GET /apps/{id}/x-plc-data/{node} - Single node value
4562
/// POST /apps/{id}/x-plc-operations/{op} - Write value to PLC
4663
/// GET /components/{id}/x-plc-status - Connection state and stats
47-
class OpcuaPlugin : public ros2_medkit_gateway::GatewayPlugin, public ros2_medkit_gateway::IntrospectionProvider {
64+
class OpcuaPlugin : public ros2_medkit_gateway::GatewayPlugin,
65+
public ros2_medkit_gateway::IntrospectionProvider,
66+
public ros2_medkit_gateway::DataProvider,
67+
public ros2_medkit_gateway::OperationProvider,
68+
public ros2_medkit_gateway::FaultProvider {
4869
public:
4970
OpcuaPlugin();
5071
~OpcuaPlugin() override;
@@ -61,6 +82,26 @@ class OpcuaPlugin : public ros2_medkit_gateway::GatewayPlugin, public ros2_medki
6182
// -- IntrospectionProvider interface --
6283
ros2_medkit_gateway::IntrospectionResult introspect(const ros2_medkit_gateway::IntrospectionInput & input) override;
6384

85+
// -- DataProvider interface --
86+
tl::expected<nlohmann::json, DataProviderErrorInfo> list_data(const std::string & entity_id) override;
87+
tl::expected<nlohmann::json, DataProviderErrorInfo> read_data(const std::string & entity_id,
88+
const std::string & resource_name) override;
89+
tl::expected<nlohmann::json, DataProviderErrorInfo>
90+
write_data(const std::string & entity_id, const std::string & resource_name, const nlohmann::json & value) override;
91+
92+
// -- OperationProvider interface --
93+
tl::expected<nlohmann::json, OperationProviderErrorInfo> list_operations(const std::string & entity_id) override;
94+
tl::expected<nlohmann::json, OperationProviderErrorInfo>
95+
execute_operation(const std::string & entity_id, const std::string & operation_name,
96+
const nlohmann::json & parameters) override;
97+
98+
// -- FaultProvider interface --
99+
tl::expected<nlohmann::json, FaultProviderErrorInfo> list_faults(const std::string & entity_id) override;
100+
tl::expected<nlohmann::json, FaultProviderErrorInfo> get_fault(const std::string & entity_id,
101+
const std::string & fault_code) override;
102+
tl::expected<nlohmann::json, FaultProviderErrorInfo> clear_fault(const std::string & entity_id,
103+
const std::string & fault_code) override;
104+
64105
private:
65106
// Route handlers
66107
void handle_plc_data(const ros2_medkit_gateway::PluginRequest & req, ros2_medkit_gateway::PluginResponse & res);
@@ -72,17 +113,18 @@ class OpcuaPlugin : public ros2_medkit_gateway::GatewayPlugin, public ros2_medki
72113
// Alarm -> Fault bridge
73114
void on_alarm_change(const std::string & fault_code, const AlarmConfig & config, bool active);
74115

75-
// Report/clear fault via ROS 2 service
76-
void report_fault(const std::string & entity_id, const std::string & fault_code, const std::string & severity_str,
77-
const std::string & message);
78-
void clear_fault(const std::string & fault_code);
116+
// Report/clear fault via ROS 2 service (private helpers, not the FaultProvider overrides)
117+
void send_report_fault(const std::string & entity_id, const std::string & fault_code,
118+
const std::string & severity_str, const std::string & message);
119+
void send_clear_fault(const std::string & fault_code);
79120

80121
// Publish PLC values to ROS 2 topics (called after each poll)
81122
void publish_values(const PollSnapshot & snap);
82123

83124
// Build JSON response for data endpoint
84125
nlohmann::json build_data_response(const std::string & entity_id) const;
85126

127+
std::atomic<bool> shutdown_requested_{false};
86128
ros2_medkit_gateway::PluginContext * ctx_{nullptr};
87129
OpcuaClientConfig client_config_;
88130
PollerConfig poller_config_;

src/ros2_medkit_plugins/ros2_medkit_opcua/include/ros2_medkit_opcua/opcua_poller.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#include <atomic>
2121
#include <chrono>
22+
#include <condition_variable>
2223
#include <functional>
2324
#include <memory>
2425
#include <mutex>
@@ -100,6 +101,9 @@ class OpcuaPoller {
100101
std::atomic<bool> running_{false};
101102
std::atomic<bool> using_subscriptions_{false};
102103

104+
std::mutex stop_mutex_;
105+
std::condition_variable stop_cv_;
106+
103107
mutable std::mutex snapshot_mutex_;
104108
PollSnapshot snapshot_;
105109

src/ros2_medkit_plugins/ros2_medkit_opcua/package.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@
2222
<depend>yaml_cpp_vendor</depend>
2323
<depend>libssl-dev</depend>
2424

25+
<!-- open62541pp (OPC-UA C++ wrapper, MPLv2) is pulled via FetchContent at
26+
build time. Not declared as a rosdep dependency because it has no apt
27+
package. See https://github.com/selfpatch/ros2_medkit/issues/366 for
28+
vendoring follow-up before rosdistro release. -->
29+
2530
<test_depend>ament_lint_auto</test_depend>
2631
<test_depend>ament_lint_common</test_depend>
2732
<test_depend>ament_cmake_clang_format</test_depend>

0 commit comments

Comments
 (0)