Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
108 commits
Select commit Hold shift + click to select a range
8b39dcd
get_var_list available at runtime api arg
marconetsf Nov 20, 2025
73c80bc
adding get_var_list and var_size in python side
marconetsf Nov 21, 2025
772e82a
adding read and write function with typecheck
marconetsf Nov 21, 2025
51a11a7
Update core/src/drivers/plugins/python/shared/python_plugin_types.py
marconetsf Nov 21, 2025
339a743
Update core/src/drivers/plugins/python/shared/python_plugin_types.py
marconetsf Nov 21, 2025
4851b1a
adding get_var_count to runtime api for python
marconetsf Nov 21, 2025
31d8280
Update core/src/drivers/plugin_utils.c
marconetsf Nov 21, 2025
1d8a593
Update core/src/drivers/plugins/python/shared/python_plugin_types.py
marconetsf Nov 21, 2025
d52d462
Update core/src/drivers/plugins/python/shared/python_plugin_types.py
marconetsf Nov 21, 2025
6d4170b
refact safe buffer access
marconetsf Nov 24, 2025
ed76fbb
Merge branch 'RTOP-106-Create-multiple-addr-access-debug-function' of…
marconetsf Nov 24, 2025
f6b5104
Update core/src/drivers/plugins/python/shared/debug_utils.py
marconetsf Nov 24, 2025
227cd3b
adding initial json config and dataclass for interpretation
marconetsf Nov 25, 2025
7a439cb
[WIP] OPC ua plugin creating server
marconetsf Nov 26, 2025
d02b868
Rtop 106 create multiple addr access debug function (#37)
marconetsf Nov 26, 2025
3b56fb6
Remove redundant traceback and struct imports in opcua_plugin.py and …
marconetsf Nov 27, 2025
1ae69b8
adding support for complex structured data types
marconetsf Nov 27, 2025
b7a1b54
refactoring variable and funtion names
marconetsf Nov 28, 2025
d0a027b
Optimizing mutex taking with batch actions
marconetsf Nov 28, 2025
bba021f
Add OPC-UA plugin memory access utilities and refactor related compon…
marconetsf Nov 28, 2025
3118356
Remove unused methods for mapping and converting PLC types in OpcuaSe…
marconetsf Nov 28, 2025
6f1876c
[WIP] adding security
marconetsf Dec 1, 2025
0580980
adding routine to check and create cert
marconetsf Dec 3, 2025
babfd12
fix security policy method
marconetsf Dec 3, 2025
6998576
Refactor OPC-UA configuration and enhance security certificate valida…
marconetsf Dec 9, 2025
5c50404
Merge branch 'development' into RTOP-108-Init-and-Cleanup-functions
marconetsf Dec 9, 2025
63b1886
Enhance plugin stop and restart functions to skip disabled plugins
marconetsf Dec 9, 2025
08796e9
Fix unmapped functions from runtime api
marconetsf Dec 9, 2025
d4fa323
Implement logging functions and enhance synchronization between OPC-U…
marconetsf Dec 9, 2025
51c39a6
Fix support for security none
marconetsf Dec 9, 2025
ef51a47
Add OPC-UA plugin configuration and implementation
marconetsf Dec 10, 2025
a88807e
Add OPC-UA endpoint configuration and enhance server initialization
marconetsf Dec 10, 2025
0776e42
Enhance OPC-UA value conversion and error handling in plugin utilities
marconetsf Dec 10, 2025
11a067a
Implement role-based authorization and access control for OPC-UA plugin
marconetsf Dec 10, 2025
f82fc52
Refactor OPC-UA security management by introducing OpcuaSecurityManag…
marconetsf Dec 10, 2025
74716fb
Refactor logging in OPC-UA plugin to use centralized logging function…
marconetsf Dec 10, 2025
efe7d38
Enhance user authentication and security profile management in OPC-UA…
marconetsf Dec 10, 2025
fb3c207
Enhance security profile handling and certificate management in OPC-U…
marconetsf Dec 10, 2025
c75919e
Refactor permission management in OPC-UA plugin by removing custom ru…
marconetsf Dec 10, 2025
c85a925
Refactor read and write permission logging in OpcuaServer to improve …
marconetsf Dec 10, 2025
43d2f89
feat(opcua): Implement OPC UA server core components
marconetsf Dec 11, 2025
f49c8a4
Authentication method detection and fallback profile resolution
marconetsf Dec 12, 2025
931b3e4
session attributes and security policies
marconetsf Dec 15, 2025
e84b84e
feat(opcua): Enhance permission handling and improve type mapping for…
marconetsf Dec 15, 2025
d04a7fc
Refactor OPC UA Plugin: Modularize codebase and remove legacy configu…
marconetsf Dec 17, 2025
920c370
Merge branch 'development' into RTOP-100-OPC-UA
marconetsf Dec 18, 2025
2c44a4e
Merge branch 'RTOP-119-Access-Hierarchy' into RTOP-100-OPC-UA
marconetsf Dec 19, 2025
49f74aa
Enhance value conversion functions for OPC-UA compatibility using cty…
marconetsf Dec 19, 2025
37349d3
Merge branch 'development' into RTOP-100-OPC-UA
marconetsf Dec 22, 2025
c0053c0
Merge branch 'development' into RTOP-100-OPC-UA
marconetsf Jan 5, 2026
e1cab46
Refactor OPC-UA configuration and enhance callback handling
marconetsf Jan 7, 2026
7ace2ea
Ensure user roles are strings and implement unified bidirectional syn…
marconetsf Jan 7, 2026
98b9e63
Merge branch 'development' into RTOP-100-OPC-UA
marconetsf Jan 8, 2026
c733c69
Add proposal for robust C-Python runtime data sharing architecture
marconetsf Jan 8, 2026
34ba6be
Refactor OPC-UA plugin to modular architecture
marconetsf Jan 9, 2026
f3ca744
Fix insecure password fallback when bcrypt unavailable
marconetsf Jan 9, 2026
ee82ed6
Add comprehensive tests for OPC-UA plugin functionality
marconetsf Jan 12, 2026
faf10ab
Add OPC-UA data type test program and related files for comprehensive…
marconetsf Jan 12, 2026
c67b5f3
Enhance OPC-UA support for array handling and REAL data type conversions
marconetsf Jan 12, 2026
6d3282d
Merge branch 'RTOP-100-OPC-UA' into RTOP-124-All-Data-Types-tests
marconetsf Jan 12, 2026
4767351
Add OPC-UA Plugin Configuration Guide documentation
marconetsf Jan 12, 2026
0937eb5
Remove unused namespace_index from AddressSpace and update related co…
marconetsf Jan 13, 2026
8c4de4b
Remove unused namespace_index from address_space configuration and up…
marconetsf Jan 13, 2026
2116569
Add OPC-UA Subscription Support (#71)
marconetsf Jan 14, 2026
122862c
Merge branch 'development' into RTOP-100-OPC-UA
marconetsf Jan 14, 2026
601751b
Merge fix/plugin-stop-deadlock into RTOP-100-OPC-UA
thiagoralves Jan 14, 2026
d1b1268
fix: Release Python GIL when plugin loading fails at startup
thiagoralves Jan 15, 2026
d4f376e
feat: Add requirements.txt for OPC-UA plugin
thiagoralves Jan 15, 2026
e4aea0b
Update requirements.txt
marconetsf Jan 15, 2026
18b0475
fix: Prevent crash when accessing variables with no PLC program loaded
thiagoralves Jan 15, 2026
6503bad
fix: Handle no-PLC-loaded state gracefully in OPC-UA sync
thiagoralves Jan 15, 2026
3119882
moving configuration docs to propper folder
marconetsf Jan 16, 2026
1f85bf1
fix: Use actual network IP instead of localhost for OPC-UA endpoint n…
thiagoralves Jan 16, 2026
dd39f88
fix: Filter Docker internal networks from OPC-UA endpoint detection
thiagoralves Jan 16, 2026
06ec537
fix: Address OPC-UA plugin security and code quality issues (#78)
marconetsf Jan 16, 2026
f186790
fix: Simplify OPC-UA user authentication flow
marconetsf Jan 16, 2026
5db1846
feat: Add TIME type support for OPC-UA plugin
marconetsf Jan 19, 2026
0da9b99
feat: Add support for nested structures in OPC-UA address space
thiagoralves Jan 19, 2026
e0310fe
fix: Change log_warn to log_info for complex type fields without indices
thiagoralves Jan 20, 2026
ad6c8c2
Merge pull request #81 from Autonomy-Logic/feat/opcua-nested-structures
thiagoralves Jan 20, 2026
49bfbbb
test: Add unit tests for TIME type support
marconetsf Jan 20, 2026
2b42855
Merge remote-tracking branch 'origin/RTOP-100-OPC-UA' into feature/op…
marconetsf Jan 20, 2026
b48c16c
fix: Improve DATE and TOD type conversions for OPC-UA
marconetsf Jan 21, 2026
fb20d2e
Update core/src/drivers/plugins/python/opcua/opcua_config_template.json
marconetsf Jan 21, 2026
90acd95
Update tests/pytest/plugins/opcua/test_type_conversions.py
marconetsf Jan 21, 2026
a7d4199
feat: Add TIME type support for OPC-UA plugin (#82)
marconetsf Jan 21, 2026
64f2ca8
fix: Update function signatures to use tuple type hints for timespec …
marconetsf Jan 21, 2026
6b7ba70
feat: Add pull request review checklist to standardize review process
marconetsf Jan 21, 2026
7784b86
Merge branch 'feature/opcua-time-variable' into RTOP-100-OPC-UA
marconetsf Jan 21, 2026
090fbad
feat: Add brute-force protection and asyncua User class integration (…
marconetsf Jan 22, 2026
47ad51e
Merge branch 'development' into RTOP-100-OPC-UA
marconetsf Jan 22, 2026
783db65
fix: Make psutil optional on MSYS2/Cygwin for OPC-UA plugin
thiagoralves Jan 22, 2026
bf19704
fix: Support cryptography package on MSYS2 for OPC-UA plugin
thiagoralves Jan 22, 2026
df7b066
fix: Recreate plugin venvs on MSYS2 if missing system-site-packages
thiagoralves Jan 22, 2026
524aaab
Revert "fix: Recreate plugin venvs on MSYS2 if missing system-site-pa…
thiagoralves Jan 22, 2026
e7ec53a
fix: Add python-bcrypt to MSYS2 pacman dependencies
thiagoralves Jan 22, 2026
8dfded6
fix: Make bcrypt optional on MSYS2/Cygwin
thiagoralves Jan 22, 2026
65cbf6e
fix: Add mingw-w64-x86_64-python-bcrypt to MSYS2 dependencies
thiagoralves Jan 22, 2026
b1a4ef3
fix: Auto-detect local IPs for OPC-UA certificate to fix Docker hostn…
marconetsf Jan 22, 2026
7c8ee45
fix: Make bcrypt optional on plain MSYS (cygwin) due to build incompa…
thiagoralves Jan 22, 2026
d0647c7
Revert "fix: Make bcrypt optional on plain MSYS (cygwin) due to build…
thiagoralves Jan 22, 2026
9ab9a34
feat: Add PBKDF2 password hashing fallback for MSYS2/Cygwin
thiagoralves Jan 22, 2026
47733d2
Merge branch 'development' into RTOP-100-OPC-UA
thiagoralves Jan 23, 2026
749ee33
fix: Add recursive validation for nested OPC-UA structures
thiagoralves Jan 23, 2026
bfaf87d
fix: Address PR review comments for nested validation
thiagoralves Jan 23, 2026
e35eb04
Merge pull request #87 from Autonomy-Logic/fix/opcua-nested-structure…
thiagoralves Jan 23, 2026
afcce07
chore: Remove verbose debug messages from OPC-UA sync loop
thiagoralves Jan 23, 2026
a2fb2eb
fix: Address security issues in OPC-UA permission callbacks and memor…
thiagoralves Jan 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
587 changes: 587 additions & 0 deletions C_PYTHON_DATA_SHARING_PROPOSAL.md

Large diffs are not rendered by default.

511 changes: 511 additions & 0 deletions OPCUA_PLUGIN_FEATURES.md

Large diffs are not rendered by default.

886 changes: 886 additions & 0 deletions OPCUA_PLUGIN_REFACTORING.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions core/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ add_executable(plc_main
${CMAKE_SOURCE_DIR}/core/src/plc_app/scan_cycle_manager.c
${CMAKE_SOURCE_DIR}/core/src/drivers/plugin_driver.c
${CMAKE_SOURCE_DIR}/core/src/drivers/plugin_config.c
${CMAKE_SOURCE_DIR}/core/src/drivers/plugin_utils.c
${CMAKE_SOURCE_DIR}/core/src/plc_app/unix_socket.c
${CMAKE_SOURCE_DIR}/core/src/plc_app/debug_handler.c
)
Expand Down
25 changes: 18 additions & 7 deletions core/src/drivers/plugin_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "../plc_app/utils/log.h"
#include "plugin_config.h"
#include "plugin_driver.h"
#include "plugin_utils.h"
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
Expand Down Expand Up @@ -504,10 +505,19 @@ int plugin_driver_stop(plugin_driver_t *driver)
// Signal all plugins to stop
for (int i = 0; i < driver->plugin_count; i++)
{
plugin_instance_t *plugin = &driver->plugins[i];

// Skip disabled plugins
if (!plugin->config.enabled)
{
printf("[PLUGIN]: Skipping disabled plugin during stop: %s\n", plugin->config.name);
continue;
}

printf("[PLUGIN]: Stopping plugin %d/%d: %s\n", i + 1, driver->plugin_count,
driver->plugins[i].config.name);
if (driver->plugins[i].python_plugin && driver->plugins[i].python_plugin->pFuncStop &&
driver->plugins[i].running)
if (plugin->python_plugin && plugin->python_plugin->pFuncStop &&
plugin->running)
{
plugin_instance_t *plugin = &driver->plugins[i];
if (plugin->config.enabled == 0)
Expand All @@ -531,10 +541,9 @@ int plugin_driver_stop(plugin_driver_t *driver)
plugin->running = 0;
}

else if (driver->plugins[i].native_plugin && driver->plugins[i].native_plugin->stop &&
driver->plugins[i].running)
else if (plugin->native_plugin && plugin->native_plugin->stop &&
plugin->running)
{
plugin_instance_t *plugin = &driver->plugins[i];
plugin->native_plugin->stop();
printf("[PLUGIN]: Native plugin %s stopped successfully.\n", plugin->config.name);
plugin->running = 0;
Expand Down Expand Up @@ -733,6 +742,9 @@ void *generate_structured_args_with_driver(plugin_type_t type, plugin_driver_t *
// Initialize mutex functions
args->mutex_take = plugin_mutex_take;
args->mutex_give = plugin_mutex_give;
args->get_var_list = get_var_list;
args->get_var_size = get_var_size;
args->get_var_count = get_var_count;
// Set buffer mutex from driver
args->buffer_mutex = &driver->buffer_mutex;

Expand Down Expand Up @@ -1143,9 +1155,8 @@ void plugin_driver_cycle_end(plugin_driver_t *driver)
// Cleanup Python plugin
static void python_plugin_cleanup(plugin_instance_t *plugin)
{
(void)plugin; // Suppress unused parameter warning
// Cleanup Python resources
if (plugin && plugin->python_plugin)
if (plugin && plugin->python_plugin && plugin->config.enabled)
{
// Call cleanup function if available
if (plugin->python_plugin->pFuncCleanup)
Expand Down
6 changes: 6 additions & 0 deletions core/src/drivers/plugin_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include "../lib/iec_types.h"
#include <pthread.h>
#include <stdint.h>

/**
* @brief Logging function pointer types
Expand Down Expand Up @@ -87,6 +88,11 @@ typedef struct
int (*mutex_give)(pthread_mutex_t *mutex);
pthread_mutex_t *buffer_mutex;

/* Variable access functions */
void (*get_var_list)(size_t num_vars, size_t *indexes, void **result);
size_t (*get_var_size)(size_t idx);
uint16_t (*get_var_count)(void);

/* Plugin configuration */
char plugin_specific_config_file_path[256];

Expand Down
59 changes: 59 additions & 0 deletions core/src/drivers/plugin_utils.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#include "plugin_utils.h"
#include "../plc_app/image_tables.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Wrapper function to get list of variable addresses
// Returns NULL for all addresses if no PLC program is loaded
void get_var_list(size_t num_vars, size_t *indexes, void **result)
{
// Validate input parameters
if (!indexes || !result || num_vars == 0)
{
return;
}

// Check if PLC program is loaded (function pointers are set)
if (!ext_get_var_count || !ext_get_var_addr)
{
for (size_t i = 0; i < num_vars; i++)
{
result[i] = NULL;
}
return;
}

for (size_t i = 0; i < num_vars; i++)
{
size_t idx = indexes[i];
if (idx >= ext_get_var_count())
{
result[i] = NULL;
}
else
{
result[i] = ext_get_var_addr(idx);
}
}
}

// Returns 0 if no PLC program is loaded
size_t get_var_size(size_t idx)
{
if (!ext_get_var_size)
{
return 0;
}
return ext_get_var_size(idx);
}

// Returns 0 if no PLC program is loaded
uint16_t get_var_count(void)
{
if (!ext_get_var_count)
{
return 0;
}
return ext_get_var_count();
}
11 changes: 11 additions & 0 deletions core/src/drivers/plugin_utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#ifndef PLUGIN_UTILS_H
#define PLUGIN_UTILS_H

#include <stddef.h>
#include <stdint.h>

void get_var_list(size_t num_vars, size_t *indexes, void **result);
size_t get_var_size(size_t idx);
uint16_t get_var_count(void);

#endif // PLUGIN_UTILS_H
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# OPC-UA Subscription Implementation Plan

## Overview

This document outlines the implementation plan for adding OPC-UA subscription support to the OpenPLC OPC-UA plugin. Subscriptions enable push-based data updates, replacing inefficient polling with server-initiated notifications.

## Current State (UPDATED)

The plugin now supports subscriptions via asyncua's built-in subscription handling:
1. Reads PLC memory every `cycle_time_ms` (default 100ms)
2. Updates OPC-UA node values using `write_attribute_value()` with DataValue
3. Clients can create subscriptions and receive push notifications on value changes
4. Proper timestamps (SourceTimestamp, ServerTimestamp) are included

## Implementation Status

### Phase 1: Enable Native Subscription Support - COMPLETED
- [x] Verified asyncua server subscription handling works
- [x] Updated `_update_opcua_node()` to use `write_attribute_value()` with DataValue
- [x] Server reference passed to SynchronizationManager

### Phase 2: Optimize Value Updates - COMPLETED
- [x] Using `write_attribute_value()` with proper DataValue objects
- [x] SourceTimestamp set to PLC cycle time (when value was read)
- [x] ServerTimestamp set to processing time
- [x] StatusCode set to Good for valid values

### Phase 3: Subscription Configuration - PENDING
- [ ] Add subscription-related settings to config
- [ ] Configure default publishing intervals
- [ ] Set limits on max subscriptions/monitored items

### Phase 4: Advanced Features - PENDING
- [ ] Deadband filtering for analog values
- [ ] Queue size configuration
- [ ] Sampling interval limits

## Key asyncua APIs

### Server Value Updates (IMPLEMENTED)
```python
# Our implementation in synchronization.py:
from datetime import datetime, timezone
from asyncua import ua

# Create DataValue with timestamps
data_value = ua.DataValue(
Value=ua.Variant(opcua_value, expected_type),
StatusCode_=ua.StatusCode(ua.StatusCodes.Good),
SourceTimestamp=self._cycle_timestamp, # PLC cycle time
ServerTimestamp=datetime.now(timezone.utc)
)

# Use write_attribute_value for optimal subscription triggering
await self.server.write_attribute_value(
var_node.node.nodeid,
data_value
)
```

This approach:
- Triggers data change notifications for subscribed clients
- Is faster than `write_value()` (fewer validation checks)
- Includes proper timestamps for audit trail
- Bypasses PreWrite callbacks (server-internal operation)

### Subscription Parameters
- **PublishingInterval**: How often server sends notifications (ms)
- **LifetimeCount**: Number of publishing intervals before subscription expires
- **MaxKeepAliveCount**: Max intervals without notification before keep-alive
- **MaxNotificationsPerPublish**: Limit notifications per publish response
- **Priority**: Relative priority among subscriptions

## Testing Strategy

1. **Unit Tests**: Mock asyncua server, verify notification triggers
2. **Integration Tests**: Real server with Python client
3. **Manual Testing**: UAExpert, Prosys OPC UA Browser
4. **Performance Tests**: Compare bandwidth with polling vs subscriptions

## References

- [asyncua Documentation](https://opcua-asyncio.readthedocs.io/)
- [OPC UA Part 4: Services - Subscription Services](https://reference.opcfoundation.org/Core/Part4/)
- [OPC UA Part 5: Information Model - Subscription](https://reference.opcfoundation.org/Core/Part5/)
31 changes: 31 additions & 0 deletions core/src/drivers/plugins/python/opcua/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""
OpenPLC OPC UA Plugin.

This package implements an OPC UA server for the OpenPLC runtime,
providing industrial-grade connectivity using the asyncua library.

Architecture:
- plugin.py: Entry point with init/start_loop/stop_loop/cleanup
- config.py: Configuration loading and validation
- opcua_logging.py: Centralized logging singleton
- server.py: OpcuaServerManager (main orchestrator)
- address_space.py: AddressSpaceBuilder (node creation)
- synchronization.py: SynchronizationManager (bidirectional sync)
- user_manager.py: OpenPLCUserManager (authentication)
- callbacks.py: PermissionCallbackHandler (access control)
- opcua_types.py: Type definitions (VariableNode, VariableMetadata)
- opcua_utils.py: Utility functions (type mapping, conversion)
- opcua_security.py: OpcuaSecurityManager (certificates, policies)
- opcua_memory.py: Direct memory access utilities
- opcua_endpoints_config.py: Endpoint URL utilities

Usage:
The plugin is loaded by the OpenPLC runtime plugin system.
Configuration is provided via JSON file specified in plugins.conf.
"""

# Re-export plugin interface for runtime compatibility
from .plugin import init, start_loop, stop_loop, cleanup

__version__ = "2.0.0"
__all__ = ['init', 'start_loop', 'stop_loop', 'cleanup']
Loading
Loading